The CustomPainter class, as described in the previous section, is in the Bezier curve, but it is not difficult, its main function is to allow users to draw a variety of controls. This paper mainly records and explains the effect realization of wechat photo button, and its button effect is generally as follows:
rendering
Realization observation
From the renderings, the effect of this button is divided into two stages.
-
Stage 1: There are two circles, which I call the button circle (foreground circle) and the background circle. When the button is pressed long: the background circle becomes larger and the button circle becomes smaller. Here I set the radius change to 1.5 times, controlled by animation.
-
Stage 2: After the radius change is completed, draw a circular progress bar on the background circle, and the progress bar is controlled by animation.
Variable definition and initialization
We need three brushes to draw the background circle, the button circle, the circular progress bar, and the animation value to control the radius change and the animation value to control the progress bar.
- define
final double firstProgress; // first animation control value, value range [0,1]
final double secondProgress; // second animation control value, value range [0,1]
// The color of the main button
final Color buttonColor = Colors.white;
// Progress bar parameters
final double progressWidth = 5; // Progress bar width
final Color progressColor = Colors.green; // Progress bar color
// The color behind the main button is also the background color for progress drawing
Color progressBackgroundColor;
// Background round brush
Paint backGroundPaint;
// Main button brush
Paint btnPaint;
// Progress bar brushes
Paint progressPaint;
Copy the code
- Initialize the
WeChatShotVideoBtn(this.firstProgress, this.secondProgress) {
progressBackgroundColor = buttonColor.withOpacity(0.7);
// Initialize the brushbackGroundPaint = Paint() .. style = PaintingStyle.fill .. color = progressBackgroundColor; btnPaint = Paint() .. style = PaintingStyle.fill .. color = buttonColor; progressPaint = Paint() .. style = PaintingStyle.stroke .. color = progressColor .. strokeWidth = progressWidth; }Copy the code
draw
To draw a circle, we need to know the center of the circle and the radius. The center of the three circles is the same. We can draw a circle according to the previous analysis idea. The details are drawn as follows, with full comments.
@override
void paint(Canvas canvas, Size size) {
// The initial radius of the circle is the radius of the circle before the animation starts
final double initRadius = size.width * 0.5;
// The largest circle at the bottom
final double center = size.width * 0.5;
/ / circle
final Offset circleCenter = Offset(center, center);
// Set the radius of the background circle, so that the radius of the background circle changes with the animation control value, which becomes 1.5 times the radius of the button circle
final double backGroundRadius = initRadius * (1 + (firstProgress / 2));
// Draw the background circle
canvas.drawCircle(circleCenter, backGroundRadius, backGroundPaint);
// Button circle, the initial radius of the button circle should be subtracted from the width of the progress bar at the beginning, the radius of the button circle becomes smaller in the long time
final double initBtnCircleRadius = initRadius - progressWidth;
// In a long time, the radius of the button circle becomes 1/2 times of the original button circle according to the animation
final double circleRadius = initBtnCircleRadius * (1 / (1 + firstProgress));
// Draw the button circle
canvas.drawCircle(circleCenter, circleRadius, btnPaint);
// In the second stage, the progress bar is drawn, indicating that the second stage animation starts
if (secondProgress > 0) {
// The secondProgress value is converted to degrees
final double angle = 360.0 * secondProgress;
// Angle is converted to radians
final double sweepAngle = deg2Rad(angle);
final double progressCircleRadius = backGroundRadius - progressWidth;
final Rect arcRect =
Rect.fromCircle(center: circleCenter, radius: progressCircleRadius);
// The default starting point for radians is 3 o 'clock
// So the starting Angle here is adjusted 90 degrees forward so that it begins to draw an arc at 12 o 'clock
canvas.drawArc(arcRect, back90, sweepAngle, false, progressPaint); }}Copy the code
Page control
It is important to note that we are a basic animation controller at ordinary times, so in class is SingleTickerProviderStateMixin, but there are two animation controller, so in class should be: TickerProviderStateMixin.
The next thing we need to do is control the animation controller and start the animation controller with a long press:
_animationController2.forward();
Copy the code
Reset and restore the animation controller when unpressing:
_animationController2.reverse();
_animationController3.value = 0;
_animationController3.stop();
Copy the code
To trigger these controls, we introduce gesture controls:
GestureDetector
Copy the code
The complete code
import 'dart:ui';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class PainterPageFirst extends StatefulWidget {
@override
_PainterPageFirstState createState() => _PainterPageFirstState();
}
class _PainterPageFirstState extends State<PainterPageFirst>
with TickerProviderStateMixin {
AnimationController _animationController1;
AnimationController _animationController2;
AnimationController _animationController3;
@override
void initState() {
// TODO: implement initState
super.initState();
_animationController1 =
AnimationController(duration: Duration(seconds: 2), vsync: this)
..addListener(() {
setState(() {});
})
..repeat();
_animationController2 =
AnimationController(duration: Duration(milliseconds: 500), vsync: this)
..addListener(() {
setState(() {});
})
..addStatusListener((status) {
if (status == AnimationStatus.completed) {
// Start the progress bar animation for recording video after the button transition animation is complete_animationController3.forward(); }});// The second controller
_animationController3 =
AnimationController(duration: Duration(seconds: 8), vsync: this)
..addListener(() {
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Painter&Animation"),
),
body: Container(
margin: EdgeInsets.only(top: 10),
alignment: Alignment.center,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 200,
height: 200,
color: Colors.red,
alignment: Alignment.center,
child: CustomPaint(
painter: CirclePainter1(progress: _animationController1.value),
size: Size(150.150),
),
),
Container(
margin: EdgeInsets.only(top: 20),
width: 200,
height: 200,
color: Colors.black,
alignment: Alignment.center,
child: GestureDetector(
onLongPress: () {
_animationController2.forward();
},
onLongPressUp: () {
_animationController2.reverse();
_animationController3.value = 0;
_animationController3.stop();
},
child: CustomPaint(
painter: WeChatShotVideoBtn(
_animationController2.value, _animationController3.value),
size: Size(100.100(() (() [() (() [() (() [() (() }@override
void dispose() {
// TODO: implement dispose_animationController1? .dispose(); _animationController3? .dispose(); _animationController2? .dispose();super.dispose(); }}class CirclePainter1 extends CustomPainter { Paint _paint = Paint() .. style = PaintingStyle.fill .. color = Colors.greenAccent;final double progress;
CirclePainter1({this.progress});
@override
void paint(Canvas canvas, Size size) {
// TODO: implement paint
final double center = size.width * 0.5;
final double radius = size.width * 0.5;
// The center of the circle
final Offset centerOffset = Offset(center, center);
final Rect rect = Rect.fromCircle(center: centerOffset, radius: radius);
final double startAngle = 0;
final double angle = 360.0 * progress;
final double sweepAngle = (angle * (math.pi / 180.0));
Draw the arc according to the Angle. If you look at the illustration later, you will find that the starting point is 0 and the starting point is 3 o 'clock
canvas.drawArc(rect, startAngle, sweepAngle, true, _paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
// TODO: implement shouldRepaint
return true; }}class WeChatShotVideoBtn extends CustomPainter {
final double firstProgress; // first animation control value, value range [0,1]
final double secondProgress; // second animation control value, value range [0,1]
// The color of the main button
final Color buttonColor = Colors.white;
// Progress bar parameters
final double progressWidth = 5; // Progress bar width
final Color progressColor = Colors.green; // Progress bar color
final back90 = deg2Rad(90.0); // Push forward 90 degrees from 12 o 'clock
// The color behind the main button is also the background color for progress drawing
Color progressBackgroundColor;
// Background round brush
Paint backGroundPaint;
// Main button brush
Paint btnPaint;
// Progress bar brushes
Paint progressPaint;
WeChatShotVideoBtn(this.firstProgress, this.secondProgress) {
progressBackgroundColor = buttonColor.withOpacity(0.7);
// Initialize the brushbackGroundPaint = Paint() .. style = PaintingStyle.fill .. color = progressBackgroundColor; btnPaint = Paint() .. style = PaintingStyle.fill .. color = buttonColor; progressPaint = Paint() .. style = PaintingStyle.stroke .. color = progressColor .. strokeWidth = progressWidth; }@override
void paint(Canvas canvas, Size size) {
// The initial radius of the circle is the radius of the circle before the animation starts
final double initRadius = size.width * 0.5;
// The largest circle at the bottom
final double center = size.width * 0.5;
/ / circle
final Offset circleCenter = Offset(center, center);
// Set the radius of the background circle, so that the radius of the background circle changes with the animation control value, which becomes 1.5 times the radius of the button circle
final double backGroundRadius = initRadius * (1 + (firstProgress / 2));
// Draw the background circle
canvas.drawCircle(circleCenter, backGroundRadius, backGroundPaint);
// Button circle, the initial radius of the button circle should be subtracted from the width of the progress bar at the beginning, the radius of the button circle becomes smaller in the long time
final double initBtnCircleRadius = initRadius - progressWidth;
// In a long time, the radius of the button circle becomes 1/2 times of the original button circle according to the animation
final double circleRadius = initBtnCircleRadius * (1 / (1 + firstProgress));
// Draw the button circle
canvas.drawCircle(circleCenter, circleRadius, btnPaint);
// In the second stage, the progress bar is drawn, indicating that the second stage animation starts
if (secondProgress > 0) {
// The secondProgress value is converted to degrees
final double angle = 360.0 * secondProgress;
// Angle is converted to radians
final double sweepAngle = deg2Rad(angle);
final double progressCircleRadius = backGroundRadius - progressWidth;
final Rect arcRect =
Rect.fromCircle(center: circleCenter, radius: progressCircleRadius);
// The default starting point for radians is 3 o 'clock
// So the starting Angle here is adjusted 90 degrees forward so that it begins to draw an arc at 12 o 'clock
canvas.drawArc(arcRect, back90, sweepAngle, false, progressPaint); }}@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true; }}// The Angle turns radians
num deg2Rad(num deg) => deg * (math.pi / 180.0);
Copy the code