Flutter学习笔记

Flutter 喜欢按钮动画

2020-04-27  本文已影响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,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

上一篇下一篇

猜你喜欢

热点阅读