一、简介
现在有一个需求是要实现一个纵向的操作步聚进度指示功能,我想使用时间轴来实现,在pub.dev上Flutter的时间轴package有很多,本篇介绍使用timelines这个package制作如图所示的纵向嵌套步聚效果。
此示例是从官方Package delivery tracking示例改造而来,官方示例如下图所示:
Github链接:timelines/example at main · chulwoo-park/timelines (github.com)
二、静态实现
先说一下静态效果如何实现,安装timelines很简单,执行flutter pub add timelines即可,下面讲主要代码:
- 主步聚核心代码
FixedTimeline.tileBuilder(
theme: TimelineThemeData(
nodePosition: 0,
color: Theme.of(context).primaryColor,
indicatorTheme: const IndicatorThemeData(
size: 15,
),
connectorTheme: const ConnectorThemeData(
thickness: 1,
),
),
builder: TimelineTileBuilder.connected(
itemCount: 4,
contentsAlign: ContentsAlign.basic,
indicatorBuilder: (_, index) {
return DotIndicator(
color: Theme.of(context).primaryColor,
position: index == 2 ? 0.13 : null, // 根据索引是否为2,设置偏移
);
},
connectorBuilder: (_, index, ___) {
return SolidLineConnector(
color: Theme.of(context).dividerColor,
);
},
contentsBuilder: (context, index) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 20, 0, 20),
child: Text(
'主步聚$index',
style: DefaultTextStyle.of(context)
.style
.copyWith(fontSize: 14),
),
),
_InnerStep(
index: index,
),
],
);
},
),
)
上述代码是使用FixedTimeline构建了一个不可滑动的时间轴,并采用只在右侧显示内容的方式,其中有一句话要注意,position: index == 2 ? 0.13 : null,,该值不是像素值,而一个0~1的小数,表示百分比,代码片段:
DotIndicator(
color: Theme.of(context).primaryColor,
position: index == 2 ? 0.13 : null, // 根据索引是否为2,设置偏移
);
如果不加此参数,则左侧的绿点就会默认居中,就像下图这样:
但是如果像官方例子中,直接在indicatorTheme中加上position: 0
indicatorTheme: const IndicatorThemeData(
size: 15,
position: 0, // 这里直接加,位置太靠上
),
则绿点的位置又太靠上了,成了这样:
而我只需要在有子步聚的时候,才调整绿点的偏移位置,所以加了此行代码。
- 子步聚
class _InnerStep extends StatelessWidget {
final int index;
const _InnerStep({Key? key, required this.index}) : super(key: key);
@override
Widget build(BuildContext context) {
if (index != 2) {
return const SizedBox.shrink();
}
return FixedTimeline.tileBuilder(
theme: TimelineTheme.of(context).copyWith(
nodePosition: 0,
color: Theme.of(context).primaryColor,
indicatorTheme: const IndicatorThemeData(
position: 0,
size: 10,
),
),
builder: TimelineTileBuilder(
contentsAlign: ContentsAlign.basic,
contentsBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.all(10),
child: Text(
'子步聚$index',
style: DefaultTextStyle.of(context).style.copyWith(fontSize: 14),
),
);
},
indicatorBuilder: (_, index) {
return Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: DotIndicator(
color: Theme.of(context).primaryColor,
),
);
},
itemCount: 3,
),
);
}
}
我这里的子步聚不需要前后的连接线,所以比较简单。官方需要连接线的话,可参考官方示例。
三、切换进度
现在要将整个进度封装成一个TaskProgress组件,并实现外部传入一个值,就自动切换前面圆点的颜色,我是这么实现的:
- 在该
TaskProgress组件中定义一个step参数,这一步相信大家都会。 - 因为几个步骤是固定,所以在组件内部使用枚举实现。
enum ProgressStep {
stepItem0(0.0, '主步骤0'),
stepItem1(1.0, '主步骤1'),
stepItem20(2.0, '主步骤2', '子步骤0'),
stepItem21(2.1, '主步骤2', '子步骤1'),
stepItem22(2.2, '主步骤2', '子步骤2'),
stepItem3(3.0, '主步骤3'),
stepItemNull(-1.0, '无效状态'); // 注意这里是分号
final double stepNum; // 步骤数,整数表示主步骤,小数表示子步骤
final String stepName; // 步骤名称
final String stepSubName; // 子步骤名称
const ProgressStep(this.stepNum, this.stepName, [this.stepSubName = '']);
static ProgressStep parse({required int mainNum, int subNum = 0}) {
return values.firstWhere(
(element) => element.stepNum == (mainNum + subNum / 10),
orElse: () => ProgressStep.stepItemNull,
);
}
}
定义了一个枚举,并将每一个枚举元素上都加入了步骤数和步骤名称,步骤名称又分为主名称和子名称。
- 遍历展示时,增加步骤数的判断,并根据步骤数显示名称。
- 圆点状态处理
主步骤显示圆点的DotIndicator组件改为:
DotIndicator(
color: step.stepNum.toInt() >= index
? Theme.of(context).primaryColor
: Theme.of(context).disabledColor,
position: step.stepNum.toInt() >= 2 && index == 2
? 0.13
: null, // 根据索引是否为2,设置指示器偏移
)
子步骤显示圆点的DotIndicator组件改为:
DotIndicator(
color: step.stepNum.toInt() > 2 ||
(step.stepNum.toInt() == 2 &&
((step.stepNum % 1) * 10).toInt() >= index)
? Theme.of(context).primaryColor
: Theme.of(context).disabledColor,
)
- 显示步骤名称
主步骤显示名称的Text组件改为:
Text(
ProgressStep.parse(mainNum: index).stepName,
style: DefaultTextStyle.of(context)
.style
.copyWith(fontSize: 14),
)
子步骤显示名称的Text组件改为:
Text(
ProgressStep.parse(
mainNum: 2,
subNum: index,
).stepSubName,
style: DefaultTextStyle.of(context).style.copyWith(fontSize: 14),
)
- 控制子步骤显示
因为我这里只有当状态进入主步骤2时,才会显示子步骤,所以在子步骤的build方法最开始加入了如下代码:,
Widget build(BuildContext context) {
if (step.stepNum.toInt() != 2 || parentIndex != 2) {
return const SizedBox.shrink();
}
//...
完成后的效果
以上就是我自己使用timelines实现一个纵向时间轴的过程,若有错误之处,还望指出。