UI 开源组件Flutter图表范围选择器使用详解
前言
最近有一个小需求:图表支持局部显示,如下底部的区域选择器支持
- 左右拖动调节中间区域
- 拖拽中间区域,可以进行移动
- 图表数据根据中间区域的占比进行显示部分数据
这样当图表的数据量过大,不宜全部展示时,可选择的局部展示就是个不错的解决方案。由于一般的图表库没有提供该功能,这里自己通过绘制来实现以下,操作效果如下所示:
1. 使用 chart_range_selector
目前这个范围选择器已经发布到 pub
上了,名字是 chart_range_selector。大家可以通过依赖进行添加
dependencies: chart_range_selector: ^1.0.0
这个库本身是作为独立 UI
组件存在的,在拖拽过程中改变区域范围时,会触发回调。使用者可以通过监听来获取当前区域的范围。这里的区域起止是以分率的形式给出的,也就是最左侧是 0
最右侧是 1
。如下的区域范围是 0.26 ~ 0.72
。
ChartRangeSelector( height: 30, initStart: 0.4, initEnd: 0.6, onChartRangeChange: _onChartRangeChange, ), void _onChartRangeChange(double start, double end) { print("start:$start, end:$end"); }
封装的组件名为: ChartRangeSelector
,提供了如下的一些配置参数:
配置项 | 类型 | 简述 |
---|---|---|
initStart | double | 范围启始值 0~1 |
initEnd | double | 范围终止值 0~1 |
height | double | 高度值 |
onChartRangeChange | OnChartRangeChange | 范围变化回调 |
bgStorkColor | Color | 背景线条颜色 |
bgFillColor | Color | 背景填充颜色 |
rangeColor | Color | 区域颜色 |
rangeActiveColor | Color | 区域激活颜色 |
dragBoxColor | Color | 左右拖拽块颜色 |
dragBoxActiveColor | Color | 左右拖拽块激活颜色 |
2. ChartRangeSelector 实现思路分析
这个组件整体上是通过 ChartRangeSelectorPainter
绘制出来的,其实这些图形都是挺规整的,绘制来说并不是什么难事。
重点在于事件的处理,拖拽不同的部位需要处理不同的逻辑,还涉及对拖拽部位的校验、高亮示意,对这块的整合还是需要一定的功力的。
代码中通过 RangeData
可监听对象为绘制提供必要的数据,其中 minGap
用于控制范围的最小值,保证范围不会过小。
另外定义了 OperationType
枚举表示操作,其中有四个元素,none
表示没有拖拽的普通状态;
dragHead
表示拖动起始块,dragTail
表示拖动终止块,dragZone
表示拖动范围区域。
enum OperationType{ none, dragHead, dragTail, dragZone } class RangeData extends ChangeNotifier { double start; double end; double minGap; OperationType operationType=OperationType.none; RangeData({this.start = 0, this.end = 1,this.minGap=0.1}); //暂略相关方法... }
在组件构建中,通过 LayoutBuilder
获取组件的约束信息,从而获得约束区域宽度最大值,也就是说组件区域的宽度值由使用者自行约束,该组件并不强制指定。
使用 SizedBox
限定画板的高度,通过 CustomPaint
组件使用 ChartRangeSelectorPainter
进行绘制。
使用 GestureDetector
组件进行手势交互监听,这就是该组件整体上实现的思路。
3.核心代码实现分析
可以看出,这个组件的核心就是 绘制
+ 手势交互
。其中绘制比较简单,就是根据 RangeData
数据和颜色配置画些方块而已,稍微困难一点的是对左右控制柄位置的计算。
另外,三个可拖拽物的激活状态是通过 RangeData#operationType
进行判断的。
也就是说所有问题的焦点都集中在 手势交互
中对 RangeData
数据的更新。如下是处理按下的逻辑,当触电横坐标左右 10
逻辑像素之内,表示激活头部。
如下 tag1
处通过 dragHead
方法更新 operationType
并触发通知,这样画板绘制时就会激活头部块,右侧和中间的激活同理。
---->[RangeData#dragHead]---- void dragHead(){ operationType=OperationType.dragHead; notifyListeners(); }
void _onPanDown(DragDownDetails details, double width) { double start = width * rangeData.start; double x = details.localPosition.dx; double end = width * rangeData.end; if (x >= start - 10 && x <= end + 10) { if ((start - details.localPosition.dx).abs() < 10) { rangeData.dragHead(); // tag1 return; } if ((end - details.localPosition.dx).abs() < 10) { rangeData.dragTail(); return; } rangeData.dragZone(); } }
对于拖手势的处理,是比较复杂的。如下根据 operationType
进行不同的逻辑处理,比如当 dragHead
时,触发 RangeData#moveHead
方法移动 start
值。这里将具体地逻辑封装在 RangeData
类中。
可以使代码更加简洁明了,每个操作都有 bool
返回值用于校验区域也没有发生变化,比如拖拽到 0
时,继续拖拽是会触发事件的,此时返回 false
,避免无意义的 onChartRangeChange
回调触发。
void _onUpdate(DragUpdateDetails details, double width) { bool changed = false; if (rangeData.operationType == OperationType.dragHead) { changed = rangeData.moveHead(details.delta.dx / width); } if (rangeData.operationType == OperationType.dragTail) { changed = rangeData.moveTail(details.delta.dx / width); } if (rangeData.operationType == OperationType.dragZone) { changed = rangeData.move(details.delta.dx / width); } if (changed) widget.onChartRangeChange.call(rangeData.start, rangeData.end); }
如下是 RangeData#moveHead
的处理逻辑,_recordStart
用于记录起始值,如果移动后未改变,返回 false
。表示不执行通知和触发回调。
---->[RangeData#moveHead]---- bool moveHead(double ds) { start += ds; start = start.clamp(0, end - minGap); if (start == _recordStart) return false; _recordStart = start; notifyListeners(); return true; }
4. 结合图表使用
下面是结合 charts_flutter
图标库实现的范围显示案例。其中核心点是 domainAxis
可以通过 NumericAxisSpec
来显示某个范围的数据,而 ChartRangeSelector
提供拽的交互操作来更新这个范围,可谓相辅相成。
class RangeChartDemo extends StatefulWidget { const RangeChartDemo({Key? key}) : super(key: key); @override State<RangeChartDemo> createState() => _RangeChartDemoState(); } class _RangeChartDemoState extends State<RangeChartDemo> { List<ChartData> data = []; int start = 0; int end = 0; @override void initState() { super.initState(); data = randomDayData(count: 96); start = 0; end = (0.8 * data.length).toInt(); } Random random = Random(); List<ChartData> randomDayData({int count = 1440}) { return List.generate(count, (index) { int value = 50 + random.nextInt(200); return ChartData(index, value); }); } @override Widget build(BuildContext context) { List<charts.Series<ChartData, int>> seriesList = [ charts.Series<ChartData, int>( id: 'something', colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, domainFn: (ChartData sales, _) => sales.index, measureFn: (ChartData sales, _) => sales.value, data: data, ) ]; return Column( children: [ Expanded( child: charts.LineChart(seriesList, animate: false, primaryMeasureAxis: const charts.NumericAxisSpec( tickProviderSpec: charts.BasicNumericTickProviderSpec(desiredTickCount: 5),), domainAxis: charts.NumericAxisSpec( viewport: charts.NumericExtents(start, end), )), ), const SizedBox( height: 10, ), SizedBox( width: 400, child: ChartRangeSelector( height: 30, initEnd: 0.5, initStart: 0.3, onChartRangeChange: (start, end) { this.start = (start * data.length).toInt(); this.end = (end * data.length).toInt(); setState(() {}); }), ), ], ); } } class ChartData { final int index; final int value; ChartData(this.index, this.value); }
以上就是UI 开源组件Flutter图表范围选择器使用详解的详细内容,更多关于Flutter图表范围选择器的资料请关注猪先飞其它相关文章!
原文出处:https://juejin.cn/post/7134885139980484615
相关文章
- 这篇文章主要介绍了c#从数据库里取得数据并异步更新ui的方法,大家参考使用吧...2020-06-25
No module named ‘win32gui‘ 的解决方法(踩坑之旅)
这篇文章主要介绍了No module named ‘win32gui‘ 的解决方法(踩坑之旅),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-18- 这篇文章主要介绍了解决vant-UI库修改样式无效的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-11-03
- 这篇文章主要为大家详细介绍了vue+element ui实现锚点定位,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2021-06-29
- 这篇文章主要为大家详细介绍了easyUI下拉列表点击事件的使用方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2017-05-22
- 由于业务需要,要求实现树形菜单,且菜单数据由后台返回,下面这篇文章主要给大家介绍了关于js如何构造elementUI树状菜单的数据结构的相关资料,需要的朋友可以参考下...2021-05-13
详解element-ui 表单校验 Rules 配置 常用黑科技
这篇文章主要介绍了element-ui 表单校验 Rules 配置 常用黑科技,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-07-11- SwiftUI是一种使用Swift语言在苹果设备上构建用户界面的创新且简单的方式,下面这篇文章主要给大家介绍了关于SwiftUI图片缩放、拼图等处理的相关资料,需要的朋友可以参考下...2021-08-23
jQuery EasyUI编辑DataGrid用combobox实现多级联动
本文给大家分享jQuery EasyUI编辑DataGrid用combobox实现多级联动效果的实例代码,代码简单易懂,非常不错,具有参考借鉴价值,感兴趣的朋友一起看看吧...2016-09-01jQuery Easyui使用(二)之可折叠面板动态加载无效果的解决方法
这篇文章主要介绍了jQuery Easyui使用之可折叠面板动态加载无效果的解决方案,非常不错,具有参考借鉴价值,感兴趣的朋友一起看下吧...2016-08-24如何解决easyui自定义标签 datagrid edit combobox 手动输入保存不上
这篇文章主要介绍了如何解决easyui自定义标签 datagrid edit combobox 手动输入保存不上,需要的朋友可以参考下...2015-12-28- 小米在3月9日下午5点推送了小米5的MIUI8.2更新,这次升级最大的改变是核心变成了Android7.0,因此加入了不少新功能。从MIUI1到MIUI8,历经了好几年了,下面小编为大家整理MIUI八个历史版本的系统图标,来看看吧...2017-07-06
- 这篇文章主要介绍了JS实现简单面向对象的颜色选择器,以完整实例形式分析了JavaScript基于面向对象实现颜色选择器的具体步骤与实现技巧,需要的朋友可以参考下...2016-04-23
解析element-ui中upload组件传递文件及其他参数的问题
这篇文章主要介绍了element-ui中upload组件如何传递文件及其他参数,分析一下我使用element-ui遇到的问题以及解决方法,需要的朋友可以参考下...2021-11-10vue+elementui实现点击table中的单元格触发事件--弹框
这篇文章主要介绍了vue+elementui实现点击table中的单元格触发事件--弹框,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-07-18- 这篇文章主要为大家详细介绍了Unity3D UGUI实现翻书特效,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
- 这篇文章主要为大家详细介绍了jQuery UI结合Ajax创建可定制的Web界面,如何利用Ajax和jQuery UI创建具有各种定制功能的高度可定制的UI,感兴趣的小伙伴们可以参考一下...2016-06-24
- 本文主要介绍jQueryUI DatePicker添加时分秒的方法,简单实用,需要的朋友可以参考下。...2016-06-12
- 这篇文章主要介绍了基于elementUI竖向表格、和并列的案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-10-26
- 这篇文章主要介绍了unity 判断鼠标是否在哪个UI上的两种实现方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-04-10