【中秋征文活动】月亮阴晴圆缺动画Flutter版本来啦~
先来介绍一下关于月相的知识
每当月球运行到太阳与地球之间,被太阳照亮的半球背对着地球时,人们在地球上就看不到月球,这一天称为“新月”,也叫“朔”,这时是农历初一。 过了新月,月球顺着地球自转方向运行,亮区逐渐转向地球,在地球上就可看到露出一丝纤细银钩似的月球,出现在西方天空,弓背朝向夕阳,这一月相叫“娥眉月”,这时是农历初三、四。 随后,月球在天空里逐日远离太阳,到了农历初七、八,半个亮区对着地球,人们可以看到半个月亮(凸面向西),这一月相叫“上弦月”。 上弦月过后,在农历初九左右-农历十四左右,便是“凸月”。我们能看到月球的大半部分。 当月球运行到地球的背日方向,即农历十五、十六,月球的亮区全部对着地球,我们能看到一轮圆月,这一月相称为“满月”,也叫“望”。 满月过后,亮区西侧开始亏缺,到农历二十二、二十三,又能看到半个月亮(凸面向东),这一月相叫做“下弦月”。在这一期间月球日渐向太阳靠拢,半夜时分才能从东方升起。 再过四五天,月球又变成一个娥眉形月牙,弓背朝向旭日,这一月相叫“残月”。 当月球再次运行到日地之间,月亮又回到“新月”
代码实现效果
思路
- 首先,根据月相的变化图, 我们可以看出,在新月到峨眉月再到上弦月的过程中,可以看做是右半月被一个椭圆遮住,这个椭圆越来越窄,直到消失的过程;
- 在上弦月到凸月再到满月的过程中,可以看做是一个右半月,左边有一个同色的椭圆越来越宽,直到成圆的过程; 知道这个,我们接下来就要动手编写代码了 示例如下图:
代码逻辑
此处关于画布 画笔的知识不在做过多介绍,有兴趣的同学可以参考
1.我们首先使用画圆弧的方法,先画出一个半圆
double r = 100;
double centerX = size.width / 2;
double centerY = size.height / 2;
Rect rect2 = Rect.fromCircle(center: Offset(centerX, centerY), radius: r);
canvas.drawArc(
rect2,
math.pi * 3 / 2,
math.pi,
true,
_paint
..color = Colors.yellow
..style = PaintingStyle.fill);
void drawArc(Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint)
- 需要指定圆弧的位置rect(圆心,半径)
- 设置startAngle,sweepAngle,这里是采用的弧度制,想必你可能还有些印象,见下图;
- 还有一个useCenter的属性,如果“useCenter”为true,则弧为封闭回到中心,形成一个圆形扇区。否则,圆弧不会闭合,形成一个圆段。
- 指定_paint
2.接下来,我们需要画椭圆了
我们知道,画椭圆是需要指定一个矩形区域的,椭圆是在这个矩形之中内切的,我们需要使用左上和右下角坐标来确定矩形的大小和位置. 分析我们想要实现的效果,可以看出,我们想要实现的椭圆,其上下顶点与圆的上下顶点是重合的,想要实现椭圆高度不变,宽度变窄的过程,其实就是外切矩形的左上角及右下角点在如图横线方向变化
Rect rect1 = Rect.fromPoints(Offset(centerX - changeR, centerY - r),
Offset(centerX + changeR, centerY + r));
_paint.color = Colors.black;
canvas.drawOval(rect1, _paint);
3.接下来,加入动画
加入一个Tween动画,改变椭圆的外切矩形大小,从而实现椭圆+半圆的月相变化 这样,我们就实现了一个月盈的过程;同理,我们也可以实现月亏的过程了.
完整代码:
动画及逻辑
import 'dart:ui';
import 'package:flutter/material.dart';
import 'dart:math' as math;
class MoonPage extends StatefulWidget {
MoonPage({Key? key}) : super(key: key);
@override
_MoonPageState createState() => _MoonPageState();
}
class _MoonPageState extends State<MoonPage>
with SingleTickerProviderStateMixin {
AnimationController? _controller; // 动画 controller
Animation<double>? _animation; // 动画
double changeR = 100.0;
bool bigToSmall = true;
int times = 0;
double _screenWidth = 0, _screenHeight = 0;
@override
void initState() {
super.initState();
_controller =
AnimationController(duration: Duration(seconds: 4), vsync: this);
_animation = Tween(begin: 100.0, end: 0.0).animate(_controller!)
..addListener(() {
//times = 1; 月盈--月半 100->0
//times = 2; 月盈--满月 0->100
//times = 3; 月亏--月半 100->0
//times = 4; 月亏--无月 0->100
if (_animation?.status == AnimationStatus.completed) {
times = times + 1;
_controller?.reverse();
} else if (_animation?.status == AnimationStatus.dismissed) {
times = times + 1;
_controller?.forward();
if (times == 4) {
times = 0;
}
}
if ((_animation?.value ?? 0.0) > changeR) {
bigToSmall = false;
} else {
bigToSmall = true;
}
changeR = _animation?.value ?? 0.0;
setState(() {});
});
// 显示的时候动画就开始
_controller?.forward();
}
@override
Widget build(BuildContext context) {
_screenHeight = MediaQuery.of(context).size.height;
_screenWidth = MediaQuery.of(context).size.width;
return Scaffold(
appBar: AppBar(
title: Text("月亮"),
),
body: Container(
color: Colors.black,
width: _screenWidth,
height: _screenHeight,
child: Stack(children: [
Offstage(
offstage: !(times < 2),
child: CustomPaint(
painter: MoonFullShapedPainter(
_screenWidth, _screenWidth, changeR, bigToSmall),
size: Size(_screenWidth, _screenWidth),
),
),
Offstage(
offstage: (times < 2),
child: CustomPaint(
painter: MoonLossShapedPainter(
_screenWidth, _screenWidth, changeR, bigToSmall),
size: Size(_screenWidth, _screenWidth),
),
),
Positioned(top:30,left:20,child: Text("人有悲欢离合,月有阴晴圆缺",style: TextStyle(color: Colors.yellow,fontSize: 18),))
]),
),
);
}
@override
void dispose() {
_animation?.isDismissed;
_controller?.dispose();
super.dispose();
}
}
月盈:
class MoonFullShapedPainter extends CustomPainter {
double width;
double height;
double changeR;
bool bigToSmall;
MoonFullShapedPainter(this.width, this.height, this.changeR, this.bigToSmall);
Paint _paint = Paint()..strokeWidth = 2.0;
@override
void paint(Canvas canvas, Size size) {
double r = 100;
double centerX = size.width / 2;
double centerY = size.height / 2;
Rect rect2 = Rect.fromCircle(center: Offset(centerX, centerY), radius: r);
// 还需要开始的弧度、结束的弧度(0-2*pi)、是否使用中心点绘制、以及paint弧度
canvas.drawArc(
rect2,
math.pi * 3 / 2,
math.pi,
true,
_paint
..color = Colors.yellow
..style = PaintingStyle.fill);
/// 椭圆
//使用左上和右下角坐标来确定矩形的大小和位置,椭圆是在这个矩形之中内切的
Rect rect1 = Rect.fromPoints(Offset(centerX - changeR, centerY - r),
Offset(centerX + changeR, centerY + r));
if (bigToSmall) {
//右边黄色半圆+白色椭圆100->0
_paint.color = Colors.black;
canvas.drawOval(rect1, _paint);
} else {
//右边黄色半圆,黄色椭圆0->100
_paint.color = Colors.yellow;
canvas.drawOval(rect1, _paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
月亏:
class MoonLossShapedPainter extends CustomPainter {
double width;
double height;
double changeR;
bool bigToSmall;
MoonLossShapedPainter(this.width, this.height, this.changeR, this.bigToSmall);
Paint _paint = Paint()..strokeWidth = 2.0;
@override
void paint(Canvas canvas, Size size) {
double r = 100;
double centerX = size.width / 2;
double centerY = size.height / 2;
Rect rect2 = Rect.fromCircle(center: Offset(centerX, centerY), radius: r);
// 还需要开始的弧度、结束的弧度(0-2*pi)、是否使用中心点绘制、以及paint弧度
canvas.drawArc(
rect2,
math.pi / 2,
math.pi,
true,
_paint
..color = Colors.yellow
..style = PaintingStyle.fill);
/// 椭圆
//使用左上和右下角坐标来确定矩形的大小和位置,椭圆是在这个矩形之中内切的
Rect rect1 = Rect.fromPoints(Offset(centerX - changeR, centerY - r),
Offset(centerX + changeR, centerY + r));
if (bigToSmall) {
//左边边黄色半圆+白色椭圆100->0
_paint.color = Colors.yellow;
canvas.drawOval(rect1, _paint);
} else {
//右边黄色半圆,黄色椭圆0->100
_paint.color = Colors.black;
canvas.drawOval(rect1, _paint);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
转载自:https://juejin.cn/post/7007625387152769061