Flutter与OpenHarmony打卡数据图表组件
本文介绍了在Flutter和OpenHarmony平台上实现打卡数据图表组件的技术方案。主要内容包括: 数据模型设计 定义ChartData类,包含标签、数值和高亮标识 支持周视图和月视图两种展示模式 Flutter实现 使用AnimationController实现柱状图生长动画 通过Row布局和BoxDecoration构建柱状图样式 折线图采用CustomPaint自定义绘制 OpenHar

前言
数据图表是打卡工具类应用中帮助用户分析打卡习惯的重要功能。通过可视化的图表展示,用户可以直观地了解自己的打卡趋势、周期规律和长期变化。本文将详细介绍如何在Flutter和OpenHarmony平台上实现功能丰富的打卡数据图表组件,包括柱状图、折线图等常见图表类型。
数据图表的设计需要在信息准确性和视觉美观性之间取得平衡。图表应该能够准确反映数据,同时通过合理的颜色、动画和交互设计,让用户愿意查看和分析自己的打卡数据。我们将实现周视图和月视图两种展示方式,满足不同的分析需求。
Flutter柱状图实现
首先定义图表数据模型:
class ChartData {
final String label;
final double value;
final bool isHighlighted;
const ChartData({
required this.label,
required this.value,
this.isHighlighted = false,
});
}
class WeeklyChartWidget extends StatefulWidget {
final List<ChartData> data;
final double maxValue;
const WeeklyChartWidget({
Key? key,
required this.data,
this.maxValue = 1.0,
}) : super(key: key);
State<WeeklyChartWidget> createState() => _WeeklyChartWidgetState();
}
ChartData模型包含标签、数值和高亮标识三个属性。label用于显示X轴标签(如星期几),value是0到1之间的归一化数值,isHighlighted用于标识当天或特殊日期。WeeklyChartWidget接收数据列表和最大值参数,maxValue用于计算柱状图的高度比例。
实现柱状图动画:
class _WeeklyChartWidgetState extends State<WeeklyChartWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 800),
vsync: this,
);
_animation = CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic);
_controller.forward();
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) => _buildChart(),
);
}
}
柱状图使用动画让数据"生长"出来,从0逐渐增长到目标高度。AnimationController控制动画时长为800毫秒,easeOutCubic缓动曲线让动画先快后慢,在接近目标值时减速,视觉效果更加自然。AnimatedBuilder在动画值变化时重建图表,实现平滑的动画效果。
构建柱状图布局:
Widget _buildChart() {
return Container(
height: 200,
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.end,
children: widget.data.map((item) => _buildBar(item)).toList(),
),
);
}
Widget _buildBar(ChartData item) {
final barHeight = 150 * (item.value / widget.maxValue) * _animation.value;
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
width: 32,
height: barHeight,
decoration: BoxDecoration(
color: item.isHighlighted ? Colors.orange : Colors.blue.shade400,
borderRadius: const BorderRadius.vertical(top: Radius.circular(4)),
),
),
const SizedBox(height: 8),
Text(item.label, style: const TextStyle(fontSize: 12)),
],
);
}
图表使用Row布局水平排列所有柱状图,spaceEvenly让柱子均匀分布。每个柱子的高度通过数据值、最大值和动画进度计算得出。高亮的柱子使用橙色,普通柱子使用蓝色,这种颜色区分帮助用户快速定位当天的数据。柱子顶部使用圆角,底部与X轴对齐,视觉效果更加美观。
OpenHarmony柱状图实现
在鸿蒙系统中定义图表组件:
interface ChartData {
label: string
value: number
isHighlighted: boolean
}
@Component
struct WeeklyChartWidget {
@Prop data: ChartData[] = []
@Prop maxValue: number = 1.0
@State animationProgress: number = 0
}
鸿蒙的图表组件使用相同的数据结构。@State装饰的animationProgress用于控制动画进度,从0逐渐变化到1,驱动柱状图的高度变化。这种状态驱动的动画方式与Flutter的AnimationController有异曲同工之妙。
启动图表动画:
aboutToAppear() {
animateTo({
duration: 800,
curve: Curve.EaseOut
}, () => {
this.animationProgress = 1
})
}
aboutToAppear生命周期方法在组件即将显示时调用,我们在这里启动动画。animateTo函数将animationProgress从0变化到1,duration设为800毫秒与Flutter保持一致。当animationProgress变化时,由于它是@State状态,会自动触发UI更新,柱状图会平滑地"生长"出来。
构建柱状图:
build() {
Column() {
Row() {
ForEach(this.data, (item: ChartData) => {
this.BarItem(item)
})
}
.width('100%')
.height(200)
.justifyContent(FlexAlign.SpaceEvenly)
.alignItems(VerticalAlign.Bottom)
}
.padding(16)
}
图表容器使用Column包裹,内部Row实现水平布局。justifyContent设为SpaceEvenly让柱子均匀分布,alignItems设为Bottom让所有柱子底部对齐。ForEach遍历数据数组,为每个数据点生成一个柱子。这种布局方式简洁高效,代码可读性强。
实现单个柱子:
@Builder
BarItem(item: ChartData) {
Column() {
Column()
.width(32)
.height(150 * (item.value / this.maxValue) * this.animationProgress)
.backgroundColor(item.isHighlighted ? '#FF9500' : '#42A5F5')
.borderRadius({ topLeft: 4, topRight: 4 })
Text(item.label)
.fontSize(12)
.fontColor('#666666')
.margin({ top: 8 })
}
.justifyContent(FlexAlign.End)
}
每个柱子由柱体和标签组成。柱体高度通过数据值、最大值和动画进度三者相乘计算得出,这样在动画过程中柱子会从0逐渐增长到目标高度。borderRadius只设置顶部圆角,底部保持直角与X轴对齐。justifyContent设为End让内容底部对齐,确保所有柱子的底部在同一水平线上。
折线图实现
Flutter中实现折线图:
class LineChartWidget extends StatelessWidget {
final List<ChartData> data;
const LineChartWidget({Key? key, required this.data}) : super(key: key);
Widget build(BuildContext context) {
return CustomPaint(
size: const Size(double.infinity, 200),
painter: LineChartPainter(data: data),
);
}
}
class LineChartPainter extends CustomPainter {
final List<ChartData> data;
LineChartPainter({required this.data});
void paint(Canvas canvas, Size size) {
if (data.isEmpty) return;
final paint = Paint()
..color = Colors.blue
..strokeWidth = 2
..style = PaintingStyle.stroke;
final path = Path();
final stepX = size.width / (data.length - 1);
for (int i = 0; i < data.length; i++) {
final x = i * stepX;
final y = size.height * (1 - data[i].value);
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, paint);
}
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
折线图使用CustomPaint和CustomPainter实现自定义绑制。paint方法遍历数据点,计算每个点的X和Y坐标,然后使用Path连接所有点形成折线。X坐标根据数据点索引和步长计算,Y坐标根据数据值和画布高度计算(注意Y轴是反向的,所以用1减去数据值)。这种实现方式灵活且高效,可以轻松扩展为曲线图或面积图。
添加数据点标记:
void _drawDataPoints(Canvas canvas, Size size) {
final pointPaint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
final stepX = size.width / (data.length - 1);
for (int i = 0; i < data.length; i++) {
final x = i * stepX;
final y = size.height * (1 - data[i].value);
canvas.drawCircle(Offset(x, y), 4, pointPaint);
}
}
在折线的每个数据点位置绘制小圆点,让用户能够清楚地看到具体的数据位置。圆点半径设为4像素,既能清晰可见又不会过于突兀。这种数据点标记在折线图中非常常见,能够帮助用户准确读取数据值。
OpenHarmony折线图
鸿蒙中使用Canvas绘制折线图:
@Component
struct LineChartWidget {
@Prop data: ChartData[] = []
private settings: RenderingContextSettings = new RenderingContextSettings(true)
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
build() {
Canvas(this.context)
.width('100%')
.height(200)
.onReady(() => this.drawChart())
}
}
鸿蒙使用Canvas组件进行自定义绑制,需要创建RenderingContextSettings和CanvasRenderingContext2D对象。onReady回调在Canvas准备就绪后触发,我们在这里调用绑制方法。这种API设计与Web的Canvas API非常相似,对于有Web开发经验的开发者来说很容易上手。
实现折线绑制:
drawChart() {
const width = this.context.width
const height = this.context.height
const stepX = width / (this.data.length - 1)
this.context.beginPath()
this.context.strokeStyle = '#2196F3'
this.context.lineWidth = 2
this.data.forEach((item, index) => {
const x = index * stepX
const y = height * (1 - item.value)
if (index === 0) {
this.context.moveTo(x, y)
} else {
this.context.lineTo(x, y)
}
})
this.context.stroke()
}
折线绘制逻辑与Flutter版本类似,遍历数据点计算坐标并连接成线。beginPath开始新路径,moveTo移动到起始点,lineTo连接后续点,最后stroke绑制路径。strokeStyle设置线条颜色,lineWidth设置线条宽度。这种命令式的绘制API虽然比声明式UI稍显繁琐,但提供了更大的灵活性。
图表交互功能
添加触摸查看数据详情:
class InteractiveChart extends StatefulWidget {
final List<ChartData> data;
State<InteractiveChart> createState() => _InteractiveChartState();
}
class _InteractiveChartState extends State<InteractiveChart> {
int? _selectedIndex;
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: (details) {
final index = _calculateIndex(details.localPosition);
setState(() => _selectedIndex = index);
},
child: CustomPaint(
painter: InteractiveChartPainter(
data: widget.data,
selectedIndex: _selectedIndex,
),
),
);
}
}
交互式图表允许用户点击查看具体数据点的详细信息。GestureDetector捕获触摸事件,通过触摸位置计算对应的数据点索引。选中的数据点会高亮显示,并可以展示数值标签。这种交互功能让图表从静态展示变成了可探索的数据工具,大大提升了实用性。
总结
本文详细介绍了在Flutter和OpenHarmony平台上实现打卡数据图表组件的完整方案。通过柱状图和折线图两种图表类型,用户可以从不同角度分析自己的打卡数据。动画效果让图表更加生动,交互功能让用户能够深入探索数据。两个平台都提供了强大的自定义绑制能力,Flutter使用CustomPainter,OpenHarmony使用Canvas,都能实现丰富的图表效果。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)