上回说到,我用Jetpack Compose
写了一个“一键三连”按钮。虽然不是同一时间,但是是同一地点,今天再次挑战用Flutter写一个一键三连,看完的朋友点个赞支持一下,干了兄弟们!
同样的,我们还是分三步走:
- 布局
- 交互
- 动画
我们的布局相对简单,只需要用一个横向的微件包裹三个竖向的微件,而图标与文字在Column
默认状态下会自动对齐,因此我们可以:
本文所示代码均为简化的伪代码,仅为讲解重点,无法直接使用
Row(children: [
// 点赞微件
Column(children: [
Icon(),
Text()
])
// 投币微件
Column(children: [
Icon(),
Text()
])
// 收藏微件
Column(children: [
Icon(),
Text()
])
])
要实现点击图标后变红并且数字+1,我们可以用GestureDetector
微件包裹图标,它提供了onTap()
点击事件回调, 因此我们可以:
// 点赞状态,默认没点
bool _thumbed = false;
// 点赞数
int _thumbCounter = 0;
GestureDetector(
onTap: () => {
// 点击后状态改为true,点赞数+1,并刷新界面 (有发现这里与Compose的不同点吗? 这里状态是一个基本数据类型,数据改变后,需要手动刷新UI)
setState(() {
_thumbed = true;
_thumbCounter += 1;
}
}),
child: Column(children: [
// 点过赞图标为粉色,否则灰色
Icon(color: _thumbed ? Color(0xfffe669b) : Color(0xff60676a)),
Text(_thumbCounter.toString())
])
}
Flutter的动画系统与Compose有较大差异,个人感觉不如compose来的方便,它主要分四步来集成:
- 将
SingleTickerProviderStateMixin
混入State
类中,用以提供vsync
对象 - 初始化一个动画控制器
AnimationController
, 并提供动画所需的合适的插值器Animatable
(此处使用线性的Tween
) - 用
AnimatedBuilder
包裹目标微件,并将步骤2
的动画设置给它 - 播放动画
// ... initState中初始化动画控制器
_controller = AnimationController(duration: const Duration(milliseconds: 1500), vsync: this);
// 动画插值器的起止范围,从0-2π
_animation = Tween<double>(begin: 0, end: 2 * math.pi).animate(_controller)
// ... build()中构建UI
AnimatedBuilder(
animation: _animation,
builder: (BuildContext context, Widget? child) {
return Column( children: [
CustomPaint(
painter: _ArcPainter(-_animation.value),
child: Icon(),
),
Text(_starCounter.toString())
],
);
}
)
// ... 自定义画笔绘制一键三连的圆弧
class _ArcPainter extends CustomPainter {
// 圆弧扫过的角度,由外部传入
double sweepAngle;
...
@override
void paint(Canvas canvas, Size size) {
// 绘制圆弧,从-π/2为圆弧起始端,结束端为sweepAngle
canvas.drawArc(
-math.pi / 2,
sweepAngle
);
}
}
// ... 在前面的GestureDetector中增加触摸事件
GestureDetector(
onLongPressStart: (LongPressStartDetails detail) => {
// 长按开始时播放动画, 圆弧慢慢变长
_controller.forward()
}
onLongPressUp: () => {
// 长按结束时反向播放动画,表现为圆弧慢慢缩短
_controller.reverse()
}
}
通过以上的例子,我们可以发现:
- Flutter采用类的方式来组织UI代码,状态被包含在其所属的
State
子类中, 类似于React
中的类组件
- 而Compose采用函数的方式来组织UI代码,状态直接属于函数内部,类似于
React
中的函数组件
- 仅就此案例来说,同样为声明式UI,Flutter代码对比Compose代码稍显啰嗦,对动画的集成也需要更多的步骤来完成
- 但其中的编程**是类似的:UI由各个组件相互包裹嵌套来描述,并在底层转化为DOM树,UI的变化依赖于状态,状态改变UI随之改变,并在变化时由系统自动计算其Diff以减少重复渲染的成本
flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.3.5, on macOS 13.0.1 22A400 darwin-arm, locale zh-Hans-CN)
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 14.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2022.1)
[✓] IntelliJ IDEA Ultimate Edition (version 2022.1.4)
[✓] VS Code (version 1.73.0)
[✓] Proxy Configuration
[✓] Connected device (2 available)
[✓] HTTP Host Availability