美文网首页Flutter学习笔记
Flutter 喜欢按钮动画

Flutter 喜欢按钮动画

作者: 王俏 | 来源:发表于2020-04-27 10:29 被阅读0次

    喜欢按钮的动画

    1. DotColor模型 model.dart
    import 'package:flutter/material.dart';
    
    class DotColor {
      final Color dotPrimaryColor;
      final Color dotSecondaryColor;
      final Color dotThirdColor;
      final Color dotLastColor;
    
      const DotColor({
        @required this.dotPrimaryColor,
        @required this.dotSecondaryColor,
        this.dotThirdColor,
        this.dotLastColor,
      });
    
      Color get dotThirdColorReal =>
          dotThirdColor == null ? dotPrimaryColor : dotThirdColor;
    
      Color get dotLastColorReal =>
          dotLastColor == null ? dotSecondaryColor : dotLastColor;
    }
    
    class LikeIcon extends Icon {
      final Color iconColor;
    
      const LikeIcon(
        IconData icon, {
        this.iconColor,
      }) : super(icon);
    
      @override
      Color get color => this.iconColor;
    }
    
    class OvershootCurve extends Curve {
      const OvershootCurve([this.period = 2.5]);
    
      final double period;
    
      @override
      double transform(double t) {
        assert(t >= 0.0 && t <= 1.0);
        t -= 1.0;
        return t * t * ((period + 1) * t + period) + 1.0;
      }
    
      @override
      String toString() {
        return '$runtimeType($period)';
      }
    }
    
    
    1. 公用函数 like_button_util.dart
    import 'dart:math' as math;
    
    num degToRad(num deg) => deg * (math.pi / 180.0);
    
    num radToDeg(num rad) => rad * (180.0 / math.pi);
    
    double mapValueFromRangeToRange(double value, double fromLow, double fromHigh, double toLow, double toHigh) {
      return toLow + ((value - fromLow) / (fromHigh - fromLow) * (toHigh - toLow));
    }
    
    double clamp(double value, double low, double high) {
      return math.min(math.max(value, low), high);
    }
    
    1. DotPainter
    import 'package:flutter/material.dart';
    import 'dart:math' as math;
    import 'like_button_util.dart';
    
    class DotPainter extends CustomPainter {
      final int dotCount;
      double outerDotsPositionAngle = 51.42;
    
      final Color color1;
      final Color color2;
      final Color color3;
      final Color color4;
    
      double centerX = 0.0;
      double centerY = 0.0;
    
      final List<Paint> circlePaints = List(4);
    
      double maxOuterDotsRadius = 0.0;
      double maxInnerDotsRadius = 0.0;
      double maxDotSize;
    
      final currentProgress;
    
      double currentRadius1 = 0.0;
      double currentDotSize1 = 0.0;
      double currentDotSize2 = 0.0;
      double currentRadius2 = 0.0;
    
      bool isFirst = true;
    
      DotPainter({
        @required this.currentProgress,
        this.dotCount = 7,
        this.color1 = const Color(0xFFFFC107),
        this.color2 = const Color(0xFFFF9800),
        this.color3 = const Color(0xFFFF5722),
        this.color4 = const Color(0xFFF44336),
      }) {
        outerDotsPositionAngle = 360.0 / dotCount;
        for (int i = 0; i < circlePaints.length; i++) {
          circlePaints[i] = new Paint()..style = PaintingStyle.fill;
        }
      }
    
      @override
      void paint(Canvas canvas, Size size) {
        if (isFirst) {
          centerX = size.width * 0.5;
          centerY = size.height * 0.5;
          maxDotSize = size.width * 0.05;
          maxOuterDotsRadius = size.width * 0.5 - maxDotSize * 2;
          maxInnerDotsRadius = 0.8 * maxOuterDotsRadius;
          isFirst = false;
        }
        _updateOuterDotsPosition();
        _updateInnerDotsPosition();
        _updateDotsPaints();
        _drawOuterDotsFrame(canvas);
        _drawInnerDotsFrame(canvas);
      }
    
      void _drawOuterDotsFrame(Canvas canvas) {
        for (int i = 0; i < dotCount; i++) {
          double cX = centerX +
              currentRadius1 * math.cos(i * degToRad(outerDotsPositionAngle));
          double cY = centerY +
              currentRadius1 * math.sin(i * degToRad(outerDotsPositionAngle));
          canvas.drawCircle(Offset(cX, cY), currentDotSize1,
              circlePaints[i % circlePaints.length]);
        }
      }
    
      void _drawInnerDotsFrame(Canvas canvas) {
        for (int i = 0; i < dotCount; i++) {
          double cX = centerX +
              currentRadius2 *
                  math.cos((i * degToRad(outerDotsPositionAngle - 10)));
          double cY = centerY +
              currentRadius2 *
                  math.sin((i * degToRad(outerDotsPositionAngle - 10)));
          canvas.drawCircle(Offset(cX, cY), currentDotSize2,
              circlePaints[(i + 1) % circlePaints.length]);
        }
      }
    
      void _updateOuterDotsPosition() {
        if (currentProgress < 0.3) {
          currentRadius1 = mapValueFromRangeToRange(
              currentProgress, 0.0, 0.3, 0.0, maxOuterDotsRadius * 0.8);
        } else {
          currentRadius1 = mapValueFromRangeToRange(currentProgress, 0.3, 1.0,
              0.8 * maxOuterDotsRadius, maxOuterDotsRadius);
        }
        if (currentProgress == 0) {
          currentDotSize1 = 0;
        } else if (currentProgress < 0.7) {
          currentDotSize1 = maxDotSize;
        } else {
          currentDotSize1 =
              mapValueFromRangeToRange(currentProgress, 0.7, 1.0, maxDotSize, 0.0);
        }
      }
    
      void _updateInnerDotsPosition() {
        if (currentProgress < 0.3) {
          currentRadius2 = mapValueFromRangeToRange(
              currentProgress, 0.0, 0.3, 0.0, maxInnerDotsRadius);
        } else {
          currentRadius2 = maxInnerDotsRadius;
        }
        if (currentProgress == 0) {
          currentDotSize2 = 0;
        } else if (currentProgress < 0.2) {
          currentDotSize2 = maxDotSize;
        } else if (currentProgress < 0.5) {
          currentDotSize2 = mapValueFromRangeToRange(
              currentProgress, 0.2, 0.5, maxDotSize, 0.3 * maxDotSize);
        } else {
          currentDotSize2 = mapValueFromRangeToRange(
              currentProgress, 0.5, 1.0, maxDotSize * 0.3, 0.0);
        }
      }
    
      void _updateDotsPaints() {
        double progress = clamp(currentProgress, 0.6, 1.0);
        int alpha =
            mapValueFromRangeToRange(progress, 0.6, 1.0, 255.0, 0.0).toInt();
        if (currentProgress < 0.5) {
          double progress =
              mapValueFromRangeToRange(currentProgress, 0.0, 0.5, 0.0, 1.0);
          circlePaints[0]
            ..color = Color.lerp(color1, color2, progress).withAlpha(alpha);
          circlePaints[1]
            ..color = Color.lerp(color2, color3, progress).withAlpha(alpha);
          circlePaints[2]
            ..color = Color.lerp(color3, color4, progress).withAlpha(alpha);
          circlePaints[3]
            ..color = Color.lerp(color4, color1, progress).withAlpha(alpha);
        } else {
          double progress =
              mapValueFromRangeToRange(currentProgress, 0.5, 1.0, 0.0, 1.0);
          circlePaints[0]
            ..color = Color.lerp(color2, color3, progress).withAlpha(alpha);
          circlePaints[1]
            ..color = Color.lerp(color3, color4, progress).withAlpha(alpha);
          circlePaints[2]
            ..color = Color.lerp(color4, color1, progress).withAlpha(alpha);
          circlePaints[3]
            ..color = Color.lerp(color1, color2, progress).withAlpha(alpha);
        }
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return true;
      }
    }
    
    
    1. CirclePainter circle_painter.dart
    import 'package:flutter/material.dart';
    import 'like_button_util.dart';
    
    class CirclePainter extends CustomPainter {
      Paint circlePaint = new Paint();
      Paint maskPaint = new Paint();
    
      final double outerCircleRadiusProgress;
      final double innerCircleRadiusProgress;
      final Color startColor;
      final Color endColor;
    
      CirclePainter({
        @required this.outerCircleRadiusProgress,
        @required this.innerCircleRadiusProgress,
        this.startColor = const Color(0xFFFF5722),
        this.endColor = const Color(0xFFFFC107),
      }) {
        circlePaint..style = PaintingStyle.fill;
        maskPaint..blendMode = BlendMode.clear;
      }
    
      @override
      void paint(Canvas canvas, Size size) {
        double center = size.width * 0.5;
        _updateCircleColor();
        canvas.saveLayer(Offset.zero & size, Paint());
        canvas.drawCircle(Offset(center, center),
            outerCircleRadiusProgress * center, circlePaint);
        canvas.drawCircle(Offset(center, center),
            innerCircleRadiusProgress * center + 1, maskPaint);
        canvas.restore();
      }
    
      void _updateCircleColor() {
        double colorProgress = clamp(outerCircleRadiusProgress, 0.5, 1.0);
        colorProgress = mapValueFromRangeToRange(colorProgress, 0.5, 1.0, 0.0, 1.0);
        circlePaint..color = Color.lerp(startColor, endColor, colorProgress);
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return true;
      }
    }
    
    
    1. LikeButtom like_button.dart
    import 'package:flutter/material.dart';
    import 'model.dart';
    import 'dot_painter.dart';
    import 'circle_painter.dart';
    
    typedef LikeCallback = void Function(bool isLike);
    
    class LikeButton extends StatefulWidget {
      final double width;
      final LikeIcon icon;
      final Duration duration;
      final DotColor dotColor;
      final Color circleStartColor;
      final Color circleEndColor;
      final LikeCallback onIconClicked;
    
      const LikeButton({
        Key key,
        @required this.width,
        this.icon = const LikeIcon(
          Icons.favorite,
          iconColor: Colors.pinkAccent,
        ),
        this.duration = const Duration(milliseconds: 5000),
        this.dotColor = const DotColor(
          dotPrimaryColor: const Color(0xFFFFC107),
          dotSecondaryColor: const Color(0xFFFF9800),
          dotThirdColor: const Color(0xFFFF5722),
          dotLastColor: const Color(0xFFF44336),
        ),
        this.circleStartColor = const Color(0xFFFF5722),
        this.circleEndColor = const Color(0xFFFFC107),
        this.onIconClicked,
      }) : super(key: key);
    
      @override
      State<StatefulWidget> createState() => _LikeButtonState();
    }
    
    class _LikeButtonState extends State<LikeButton> with TickerProviderStateMixin {
      AnimationController _controller;
      Animation<double> outerCircle;
      Animation<double> innerCircle;
      Animation<double> scale;
      Animation<double> dots;
    
      bool isLiked = false;
    
      @override
      void initState() {
        super.initState();
        _controller =
            new AnimationController(duration: widget.duration, vsync: this)
              ..addListener(() {
                setState(() {});
              });
        _initAllAmimations();
      }
    
      @override
      Widget build(BuildContext context) {
        return Stack(
          alignment: Alignment.center,
          children: <Widget>[
            CustomPaint(
              size: Size(widget.width, widget.width),
              painter: DotPainter(
                currentProgress: dots.value,
                color1: widget.dotColor.dotPrimaryColor,
                color2: widget.dotColor.dotSecondaryColor,
                color3: widget.dotColor.dotThirdColorReal,
                color4: widget.dotColor.dotLastColorReal,
              ),
            ),
            CustomPaint(
              size: Size(widget.width * 0.35, widget.width * 0.35),
              painter: CirclePainter(
                  innerCircleRadiusProgress: innerCircle.value,
                  outerCircleRadiusProgress: outerCircle.value,
                  startColor: widget.circleStartColor,
                  endColor: widget.circleEndColor),
            ),
            Container(
              width: widget.width,
              height: widget.width,
              alignment: Alignment.center,
              child: Transform.scale(
                scale: isLiked ? scale.value : 1.0,
                child: GestureDetector(
                  child: Icon(
                    widget.icon.icon,
                    color: isLiked ? widget.icon.color : Colors.grey,
                    size: widget.width * 0.4,
                  ),
                  onTap: _onTap,
                ),
              ),
            ),
          ],
        );
      }
    
      void _onTap() {
        if (_controller.isAnimating) return;
        isLiked = !isLiked;
        if (isLiked) {
          _controller.reset();
          _controller.forward();
        } else {
          setState(() {});
        }
        if (widget.onIconClicked != null) widget.onIconClicked(isLiked);
      }
    
      void _initAllAmimations() {
        outerCircle = new Tween<double>(
          begin: 0.1,
          end: 1.0,
        ).animate(
          new CurvedAnimation(
            parent: _controller,
            curve: new Interval(
              0.0,
              0.3,
              curve: Curves.ease,
            ),
          ),
        );
        innerCircle = new Tween<double>(
          begin: 0.2,
          end: 1.0,
        ).animate(
          new CurvedAnimation(
            parent: _controller,
            curve: new Interval(
              0.2,
              0.5,
              curve: Curves.ease,
            ),
          ),
        );
        scale = new Tween<double>(
          begin: 0.2,
          end: 1.0,
        ).animate(
          new CurvedAnimation(
            parent: _controller,
            curve: new Interval(
              0.35,
              0.7,
              curve: OvershootCurve(),
            ),
          ),
        );
        dots = new Tween<double>(
          begin: 0.0,
          end: 1.0,
        ).animate(
          new CurvedAnimation(
            parent: _controller,
            curve: new Interval(
              0.1,
              1.0,
              curve: Curves.decelerate,
            ),
          ),
        );
      }
    }
    
    
    
    1. 使用
    import 'package:flutter/material.dart';
    import 'like_button.dart';
    import 'model.dart';
    
    class LikeButtonPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('LikeButton'),
          ),
          body: Container(
            alignment: Alignment.center,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                LikeButton(
                  width: 80.0,
                ),
                LikeButton(
                  width: 80.0,
                  circleStartColor: Color(0xff00ddff),
                  circleEndColor: Color(0xff0099cc),
                  dotColor: DotColor(
                    dotPrimaryColor: Color(0xff33b5e5),
                    dotSecondaryColor: Color(0xff0099cc),
                  ),
                  icon: LikeIcon(
                    Icons.home,
                    iconColor: Colors.deepPurpleAccent,
                  ),
                ),
                LikeButton(
                  width: 80.0,
                  circleStartColor: Color(0xff669900),
                  circleEndColor: Color(0xff669900),
                  dotColor: DotColor(
                    dotPrimaryColor: Color(0xff669900),
                    dotSecondaryColor: Color(0xff99cc00),
                  ),
                  icon: LikeIcon(
                    Icons.adb,
                    iconColor: Colors.green,
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    

    相关文章

      网友评论

        本文标题:Flutter 喜欢按钮动画

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