Flutter 喜欢按钮动画
2020-04-27 本文已影响0人
王俏
喜欢按钮的动画
- 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)';
}
}
- 公用函数 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);
}
- 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;
}
}
- 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;
}
}
- 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,
),
),
);
}
}
- 使用
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,
),
),
],
),
),
);
}
}