美文网首页FlutterFlutter圈子All in Flutter
CustomPainter——微信拍视频按钮效果实现

CustomPainter——微信拍视频按钮效果实现

作者: 向日花开 | 来源:发表于2020-04-18 19:44 被阅读0次

    CustomPainter 这个类前文讲过,就在贝塞尔曲线那块,不了解的可以爬楼,但这个类不难,其主要功能就是提供用户绘制各种各样的控件。
    本文主要记录讲解微信拍照按钮的效果实现,其按钮效果大体如下:

    效果图

    实现思路观察

    由效果图可知,这个按钮效果分两个阶段。

    • 第一阶段:有两个圆,我称之为按钮圆(前景圆),背景圆。当长按按钮时:背景圆变大,按钮圆变小。此处我设置半径变化倍率为 1.5 倍,由动画控制。

    • 第二阶段:当半径变化完成之后,在背景圆上画圆形进度条,进度条进度通过动画控制。

    变量定义与初始化

    我们需要三只画笔,分别画背景圆,按钮圆,圆形进度条,以及控制半径变化的动画值以及控制进度条的动画值。

    • 定义
    
      final double firstProgress; //第一段动画控制值,值范围[0,1]
    
      final double secondProgress; //第二段动画控制值,值范围[0,1]
    
      //主按钮的颜色
      final Color buttonColor = Colors.white;
      //进度条相关参数
      final double progressWidth = 5; //进度条 宽度
      final Color progressColor = Colors.green; //进度条颜色
    
    
      //主按钮背后一层的颜色,也是progress绘制时的背景色
      Color progressBackgroundColor;
    
      //背景圆的画笔
      Paint backGroundPaint;
    
      //主按钮画笔
      Paint btnPaint;
    
      //进度条画笔
      Paint progressPaint;
    
    
    • 初始化
      WeChatShotVideoBtn(this.firstProgress, this.secondProgress) {
        progressBackgroundColor = buttonColor.withOpacity(0.7);
    
        //初始化画笔
        backGroundPaint = 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) {
        //初始化的圆半径,就是在动画开始前的圆半径
        final double initRadius = size.width * 0.5;
    
        // 底部最大的圆
        final double center = size.width * 0.5;
        //圆心
        final Offset circleCenter = Offset(center, center);
    
        //设置背景圆半径,让背景圆的半径随着动画控制值的变化,此处变为按钮圆半径的1.5倍
        final double backGroundRadius = initRadius * (1 + (firstProgress / 2));
        //画背景圆
        canvas.drawCircle(circleCenter, backGroundRadius, backGroundPaint);
    
        // 按钮圆,按钮圆初始半径刚开始时应减去 进度条的宽度,在长按时按钮圆半径变小
        final double initBtnCircleRadius = initRadius - progressWidth;
        //长按时,按钮圆半径根据动画变为初始按钮圆的1/2倍
        final double circleRadius = initBtnCircleRadius * (1 / (1 + firstProgress));
        //画按钮圆
        canvas.drawCircle(circleCenter, circleRadius, btnPaint);
    
        // 第二阶段,进度条的绘制,表示第二阶段动画启动
        if (secondProgress > 0) {
          //secondProgress 值转化为度数
          final double angle = 360.0 * secondProgress;
          //角度转化为弧度
          final double sweepAngle = deg2Rad(angle);
    
          final double progressCircleRadius = backGroundRadius - progressWidth;
          final Rect arcRect =
              Rect.fromCircle(center: circleCenter, radius: progressCircleRadius);
          //这里画弧度的时候它默认起点是从3点钟方向开始
          // 所以这里的开始角度向前调整90度让它从12点钟方向开始画弧
          canvas.drawArc(arcRect, back90, sweepAngle, false, progressPaint);
        }
      }
    
    
    

    页面控制

    值得注意的是,平时我们基本都是一个动画控制器,所以混入的类是 SingleTickerProviderStateMixin,但这里有两个动画控制器,所以混入的类应该是:TickerProviderStateMixin。

    接下来我们要做的就是控制动画控制器,在长按的时候启动动画控制器:

     _animationController2.forward();
    

    在取消长按的时候重置以及还原动画控制器:

       _animationController2.reverse();
       _animationController3.value = 0;
       _animationController3.stop();
    

    为触发这些控制,我们引入手势控件:

    GestureDetector
    

    插波广告,有兴趣的小伙伴关注公众号,和我一起学习,本文代码在文末。


    完整代码

    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) {
                  //按钮过渡动画完成后启动录制视频的进度条动画
                  _animationController3.forward();
                }
              });
    
        //第二个控制器
        _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;
        // 圆的中心点位置
        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));
        // 画圆弧 按照角度来画圆弧,后面看效果图会发现起点从0开始画的时候是3点钟方向开始的
        canvas.drawArc(rect, startAngle, sweepAngle, true, _paint);
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        // TODO: implement shouldRepaint
        return true;
      }
    }
    
    class WeChatShotVideoBtn extends CustomPainter {
      final double firstProgress; //第一段动画控制值,值范围[0,1]
    
      final double secondProgress; //第二段动画控制值,值范围[0,1]
    
      //主按钮的颜色
      final Color buttonColor = Colors.white;
    
      //进度条相关参数
      final double progressWidth = 5; //进度条 宽度
      final Color progressColor = Colors.green; //进度条颜色
      final back90 = deg2Rad(-90.0); //往前推90度 从12点钟方向开始
    
      //主按钮背后一层的颜色,也是progress绘制时的背景色
      Color progressBackgroundColor;
    
      //背景圆的画笔
      Paint backGroundPaint;
    
      //主按钮画笔
      Paint btnPaint;
    
      //进度条画笔
      Paint progressPaint;
    
      WeChatShotVideoBtn(this.firstProgress, this.secondProgress) {
        progressBackgroundColor = buttonColor.withOpacity(0.7);
    
        //初始化画笔
        backGroundPaint = 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) {
        //初始化的圆半径,就是在动画开始前的圆半径
        final double initRadius = size.width * 0.5;
    
        // 底部最大的圆
        final double center = size.width * 0.5;
        //圆心
        final Offset circleCenter = Offset(center, center);
    
        //设置背景圆半径,让背景圆的半径随着动画控制值的变化,此处变为按钮圆半径的1.5倍
        final double backGroundRadius = initRadius * (1 + (firstProgress / 2));
        //画背景圆
        canvas.drawCircle(circleCenter, backGroundRadius, backGroundPaint);
    
        // 按钮圆,按钮圆初始半径刚开始时应减去 进度条的宽度,在长按时按钮圆半径变小
        final double initBtnCircleRadius = initRadius - progressWidth;
        //长按时,按钮圆半径根据动画变为初始按钮圆的1/2倍
        final double circleRadius = initBtnCircleRadius * (1 / (1 + firstProgress));
        //画按钮圆
        canvas.drawCircle(circleCenter, circleRadius, btnPaint);
    
        // 第二阶段,进度条的绘制,表示第二阶段动画启动
        if (secondProgress > 0) {
          //secondProgress 值转化为度数
          final double angle = 360.0 * secondProgress;
          //角度转化为弧度
          final double sweepAngle = deg2Rad(angle);
    
          final double progressCircleRadius = backGroundRadius - progressWidth;
          final Rect arcRect =
              Rect.fromCircle(center: circleCenter, radius: progressCircleRadius);
          //这里画弧度的时候它默认起点是从3点钟方向开始
          // 所以这里的开始角度向前调整90度让它从12点钟方向开始画弧
          canvas.drawArc(arcRect, back90, sweepAngle, false, progressPaint);
        }
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return true;
      }
    }
    
    //角度转弧度
    num deg2Rad(num deg) => deg * (math.pi / 180.0);
    
    

    相关文章

      网友评论

        本文标题:CustomPainter——微信拍视频按钮效果实现

        本文链接:https://www.haomeiwen.com/subject/dfjdvhtx.html