Flutter 实现商品交易水平进度效果
Flutter 是一种流行的移动应用程序开发框架,它提供了许多有用的工具和库来简化应用程序的开发过程。在 Flutter 中,可以轻松地实现商品交易的水平进度,让应用程序更具吸引力和用户体验。本文将介绍如何使用 Flutter 实现商品交易的水平进度条,支持自定义样式以及动态进度。
效果如下:
废话不多说,直接上代码,如下:
HorizontalNodeProgressView(
bars: progressBars, /// 动态节点数据
isComplete: false, /// 是否完成节点 (完成节点 整体置灰色)
);
自定义水平进度节点(HorizontalNodeProgressView),代码如下:
import 'package:flutter/material.dart';
import "package:jm_foundation/jm_foundation.dart";
import 'package:jm_order/app/modules/order_detail/model/order_model.dart';
class HorizontalNodeProgressView extends StatefulWidget {
const HorizontalNodeProgressView({Key? key, required this.bars, required this.isComplete})
: super(key: key);
final List<ProgressBars> bars;
final bool isComplete;
State<HorizontalNodeProgressView> createState() => _HorizontalNodeProgressViewState();
}
class _HorizontalNodeProgressViewState extends State<HorizontalNodeProgressView> {
/// 左边 已完成节点颜色
final Color leftColor = Colors.blue;
final Color rightColor = Colors.grey;
/// 当前完成节点
late int finishIndex;
bool isComplete = false;
List<bool> values = [];
double progressTitleSize = 11;
Widget build(BuildContext context) {
for (int i = 0; i < widget.bars.length; i++) {
values.add(widget.bars[i].bools!);
if (widget.bars[i].bools == false) {
finishIndex = i;
break;
} else {
finishIndex = widget.bars.length;
}
}
if (finishIndex == 0 && !widget.isComplete) {
finishIndex += 1;
}
if (!values.contains(false)) {
isComplete = true;
}
/// 用于计算完成节点数
int computeFinishIndex = finishIndex;
if (computeFinishIndex >= widget.bars.length) {
/// 完成节点数不能超过实际节点数
computeFinishIndex = widget.bars.length - 1;
}
return SizedBox(
height: 50,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
double availableWidth = constraints.maxWidth;
List<double> nodeTitleWidths = [];
double nodeTitleTotalWidth = 0;
double nodeTitleEventWidth = 0;
/// 进度线列表
List<Widget> lineList = [];
/// 节点列表
List<Widget> nodeList = [];
for (int index = 0; index < widget.bars.length; index++) {
if (index < widget.bars.length) {
String title = widget.bars[index].name!;
double titleWidth = title
.computeParagraphSize(
textStyle: TextStyle(fontSize: progressTitleSize),
)
.width;
nodeTitleWidths.add(titleWidth);
nodeTitleTotalWidth += titleWidth;
nodeTitleEventWidth = nodeTitleTotalWidth / (index + 1);
nodeList.add(
_nodeView(title, index),
);
} else {
nodeTitleWidths.add(nodeTitleEventWidth);
nodeTitleTotalWidth += nodeTitleEventWidth;
nodeList.add(SizedBox(
width: nodeTitleEventWidth,
));
}
}
/// 每个节点间隔
double side = ((availableWidth - nodeTitleTotalWidth) ~/
(widget.bars.length - 1))
.toDouble();
for (int index = 0; index < widget.bars.length; index++) {
if (index < (widget.bars.length - 1)) {
lineList.add(_lineView(
index,
((nodeTitleWidths[index + 1] / 2) +
(nodeTitleWidths[index] / 2) +
side)));
}
}
return Stack(
children: [
Positioned(
left: nodeTitleWidths.first / 2,
right: nodeTitleWidths.last / 2,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: lineList,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: nodeList,
)
],
);
},
),
);
}
Widget _lineView(int index, double width) {
Color lineColor = Colors.transparent;
double leftPadding = 0;
double rightPadding = 0;
if (index < (widget.bars.length - 1)) {
if (index < finishIndex) {
lineColor = leftColor;
leftPadding = 4;
if (index < (finishIndex - 1)) {
rightPadding = 4;
}
} else {
lineColor = rightColor;
}
}
return SizedBox(
width: width,
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.fromLTRB(leftPadding, 11.65, rightPadding, 0),
child: Container(
height: 0.5,
color: lineColor,
),
),
),
);
}
Widget _nodeView(String element, int index) {
Color titleColor;
FontWeight weight;
if (index < finishIndex) {
titleColor = Colors.grey;
weight = FontWeight.w400;
} else if (index == finishIndex) {
titleColor = leftColor;
weight = FontWeight.w500;
} else {
titleColor = rightColor;
weight = FontWeight.w400;
}
return Column(
children: [
SizedBox(
width: 24,
height: 24,
child: Center(
child: (index < finishIndex)
? Icon(
Icons.check_circle,
size: 16,
color: leftColor,
)
: Container(
width: 8,
height: 8,
decoration: BoxDecoration(
color: widget.isComplete
? rightColor
: (index > finishIndex ? rightColor : leftColor),
borderRadius: BorderRadius.circular(4),
),
),
),
),
Expanded(
child: Text(
element,
textAlign: TextAlign.center,
style: TextStyle(
color: widget.isComplete
? rightColor
: ((index == widget.bars.length - 1 && isComplete)
? leftColor
: titleColor),
fontSize: progressTitleSize,
fontWeight: widget.isComplete
? FontWeight.w400
: ((index == widget.bars.length - 1 && isComplete)
? FontWeight.w400
: weight)),
),
),
],
);
}
}
重点代码如下,是通过动态的节点计算每个节点的宽度
for (int index = 0; index < widget.bars.length; index++) {
if (index < widget.bars.length) {
String title = widget.bars[index].name!;
double titleWidth = title
.computeParagraphSize(
textStyle: TextStyle(fontSize: progressTitleSize),
)
.width;
nodeTitleWidths.add(titleWidth);
nodeTitleTotalWidth += titleWidth;
nodeTitleEventWidth = nodeTitleTotalWidth / (index + 1);
nodeList.add(
_nodeView(title, index),
);
} else {
nodeTitleWidths.add(nodeTitleEventWidth);
nodeTitleTotalWidth += nodeTitleEventWidth;
nodeList.add(SizedBox(
width: nodeTitleEventWidth,
));
}
}