網站首頁 編程語言 正文
前言
最近有一個小需求:圖表支持局部顯示,如下底部的區域選擇器支持
- 左右拖動調節中間區域
- 拖拽中間區域,可以進行移動
- 圖表數據根據中間區域的占比進行顯示部分數據
這樣當圖表的數據量過大,不宜全部展示時,可選擇的局部展示就是個不錯的解決方案。由于一般的圖表庫沒有提供該功能,這里自己通過繪制來實現以下,操作效果如下所示:
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); }
原文鏈接:https://juejin.cn/post/7134885139980484615
相關推薦
- 2022-09-21 Golang?channel為什么不會阻塞的原因詳解_Golang
- 2024-01-28 springboot登錄認證JWT令牌
- 2022-08-01 GoLand一鍵上傳項目到遠程服務器的方法步驟_Golang
- 2023-06-19 圖文詳解Go中的channel_Golang
- 2022-11-09 Android性能優化之plt?hook與native線程監控詳解_Android
- 2022-08-19 android九宮格鎖屏控件使用詳解_Android
- 2023-03-04 React使用redux基礎操作詳解_React
- 2022-05-02 關于Nginx中虛擬主機的一些冷門知識小結_nginx
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支