learn-tech/专栏/Flutter入门教程/16用滑动列表展示记录.md
2024-10-16 00:01:16 +08:00

224 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

因收到Google相关通知网站将会择期关闭。相关通知内容
16 用滑动列表展示记录
1. 功德记录的功能需求
现在想要记录每次点击时的功德数据,以便让用户了解功德增加的历史。对于一个需求而言,最重要的是分析其中需要的数据,以及如何维护这些数据。
效果如下,点击左上角按钮,跳转到功德记录的面板。功德记录界面是一个可以滑动的列表,每个条目展示当前功德的 木鱼图片、音效名称、时间、功德数 信息:
标题
根据界面中的数据,可以封装一个类来维护,如下 MeritRecord 类。其中 id 是一条记录的身份标识,就像人的身份证编号,就可以确保他在社会中的唯一性。
class MeritRecord {
final String id; // 记录的唯一标识
final int timestamp; // 记录的时间戳
final int value; // 功德数
final String image; // 图片资源
final String audio; // 音效名称
MeritRecord(this.id, this.timestamp, this.value, this.image, this.audio);
}
对于功德记录的需求而言,需要在 _MuyuPageState 中新加的数据也很明确: 对 MeritRecord 列表的维护。接下来的任务就是,在点击时,为 _records 列表添加 MeritRecord 类型的元素。
---->[_MuyuPageState]----
List<MeritRecord> _records = [];
2. 列表数据的维护
对于生成唯一 id 这里使用 uuid 库,目前最新版本 3.0.7。记得在 pubspec.yaml 的依赖节点配置:
dependencies:
...
uuid: ^3.0.7
如下代码是 _onKnock 函数处理点击时的逻辑,其中创建 MeritRecord 对象,构造函数中的相关数据,在状态类中都可以轻松的到。
---->[_MuyuPageState]----
final Uuid uuid = Uuid();
void _onKnock() {
pool?.start();
setState(() {
_cruValue = knockValue;
_counter += _cruValue;
// 添加功德记录
String id = uuid.v4();
_records.add(MeritRecord(
id,
DateTime.now().millisecondsSinceEpoch,
_cruValue,
activeImage,
audioOptions[_activeAudioIndex].name,
));
});
}
这里来分析一下上一篇中切换木鱼样式,会导致动画触发的问题。原因非常简单,因为动画控制器会在 didUpdateWidget 中启动。而切换木鱼样式时,通过触发了 setState 更新主界面的木鱼图片。于是会连带触发动画器的启动,解决方案有很多:
解决方案 1 : 将动画控制器交由上层维护,仅在点击时启动动画。
解决方案 2 :为 didUpdateWidget 回调中动画启动添加限制条件。
解决方案 3 :切换木鱼样式时,使用局部更新图片,不重新构建整个 _MuyuPageState 。
上面的三个方案中各有优劣,综合来看,方案 1 是最好的;方案 2 次之;方案 3 虽然可以解决当前问题,但治标不治本。就当前代码而言,使用 方案 2 处理最简单,那么动画启动的限制条件是什么呢?
当功德发生变化时,才需要启动动画。
而现在每个功德对应一个 MeritRecord 对象,且 id 可以作为功德的身份标识。功德发生变化 ,也就意味着新旧功德 id 的不同。那么具体的方案就呼之欲出了,如下所示:让 AnimateText 持有 MeritRecord 数据
在状态类 didUpdateWidget 回调中,可以访问到旧的组件,通过对比新旧 AnimateText 组件持有的 record 记录 id ,当不同时才启动动画。这样就避免了在功德未变化的情况下,启动动画的场景。通过个问题的解决,想必大家对状态类的 didUpdateWidget 方法,有更深一点的了解。
@override
void didUpdateWidget(covariant AnimateText oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.record.id != widget.record.id) {
controller.forward(from: 0);
}
}
小练习: 自己尝试使用解决方案 1 ,解决动画误启动问题。
3. ListView 的简单使用
我们已经知道,通过 Column 组件可以实现若干个组件的竖向排列。但当组件数量众多时,超越 Column 组件尺寸范围后,就无法显示;并且在 Debug 模式中会出现越界的异常(下左图)。很多场景中,需要支持视图的滑动,可以使用 ListView 组件来实现 (下右图)
Column
ListView
这里历史记录界面通过 RecordHistory 组件构建,构造函数中传入 MeritRecord 列表。主体内容通过 ListView.builder 构建滑动列表,通过 _buildItem 方法,根据索引值返回条目组件。
class RecordHistory extends StatelessWidget {
final List<MeritRecord> records;
const RecordHistory({Key? key, required this.records}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar:_buildAppBar(),
body: ListView.builder(
itemBuilder: _buildItem,
itemCount: records.length,
),
);
}
PreferredSizeWidget _buildAppBar() =>
AppBar(
iconTheme: const IconThemeData(color: Colors.black),
centerTitle: true,
title: const Text(
'功德记录', style: TextStyle(color: Colors.black, fontSize: 16),),
elevation: 0,
backgroundColor: Colors.white,
);
在 _buildItem 方法中,使用 ListTile 组件构建条目视图;其中:
leading 表示左侧组件,使用 CircleAvatar 展示圆形图形;
title 表示标题组件,展示功德数;
subtitle 表示副标题组件,展示音效名称;
trailing 表示尾部组件,展示日期。
注: 这里使用 DateFormat 来格式化时间,需要在依赖中添加 intl: ^0.18.1 包。
DateFormat format = DateFormat('yyyy年MM月dd日 HH:mm:ss');
Widget? _buildItem(BuildContext context, int index) {
MeritRecord merit = records[index];
String date = format.format(DateTime.fromMillisecondsSinceEpoch(merit.timestamp));
return ListTile(
leading: CircleAvatar(
backgroundColor: Colors.blue,
backgroundImage: AssetImage(merit.image),
),
title: Text('功德 +${merit.value}'),
subtitle: Text(merit.audio),
trailing: Text(
date, style: const TextStyle(fontSize: 12, color: Colors.grey),),
);
}
}
最后在 _MuyuPageState 中处理 _toHistory 点击事件,通过 Navigator 跳转到 RecordHistory 。
---->[_MuyuPageState]----
void _toHistory() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => RecordHistory( records: _records.reversed.toList()),
),
);
}
4.本章小结
本章主要介绍了如何使用可滑动列表展示非常多的内容ListView 组件可以很便捷地帮我们让记录列表支持滑动。滑动视图使用起来并不是很复杂,但真的能用好,理解其内部的原理,还有很长的路要走。对于新手而言,目前简单能用就行了,以后可以基于小册来深入研究。
到这里,电子木鱼的基本功能完成了,当前代码位置 muyu 。不过现在的数据都是存储在内存中的,应用退出之后无论是选项,还是功德记录都会重置。想要数据持久化存储,在后面的 数据的持久化存储 一章中再继续完善,木鱼项目先告一段落。