您好,登錄后才能下訂單哦!
這篇文章主要講解了“UI開源組件Flutter圖表范圍選擇器怎么使用”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“UI開源組件Flutter圖表范圍選擇器怎么使用”吧!
最近有一個(gè)小需求:圖表支持局部顯示,如下底部的區(qū)域選擇器支持
左右拖動(dòng)調(diào)節(jié)中間區(qū)域
拖拽中間區(qū)域,可以進(jìn)行移動(dòng)
圖表數(shù)據(jù)根據(jù)中間區(qū)域的占比進(jìn)行顯示部分?jǐn)?shù)據(jù)
這樣當(dāng)圖表的數(shù)據(jù)量過大,不宜全部展示時(shí),可選擇的局部展示就是個(gè)不錯(cuò)的解決方案。由于一般的圖表庫沒有提供該功能,這里自己通過繪制來實(shí)現(xiàn)
目前這個(gè)范圍選擇器已經(jīng)發(fā)布到 pub
上了,名字是 chart_range_selector。大家可以通過依賴進(jìn)行添加
dependencies: chart_range_selector: ^1.0.0
這個(gè)庫本身是作為獨(dú)立 UI
組件存在的,在拖拽過程中改變區(qū)域范圍時(shí),會觸發(fā)回調(diào)。使用者可以通過監(jiān)聽來獲取當(dāng)前區(qū)域的范圍。這里的區(qū)域起止是以分率的形式給出的,也就是最左側(cè)是 0
最右側(cè)是 1
。如下的區(qū)域范圍是 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
,提供了如下的一些配置參數(shù):
配置項(xiàng) | 類型 | 簡述 |
---|---|---|
initStart | double | 范圍啟始值 0~1 |
initEnd | double | 范圍終止值 0~1 |
height | double | 高度值 |
onChartRangeChange | OnChartRangeChange | 范圍變化回調(diào) |
bgStorkColor | Color | 背景線條顏色 |
bgFillColor | Color | 背景填充顏色 |
rangeColor | Color | 區(qū)域顏色 |
rangeActiveColor | Color | 區(qū)域激活顏色 |
dragBoxColor | Color | 左右拖拽塊顏色 |
dragBoxActiveColor | Color | 左右拖拽塊激活顏色 |
這個(gè)組件整體上是通過 ChartRangeSelectorPainter
繪制出來的,其實(shí)這些圖形都是挺規(guī)整的,繪制來說并不是什么難事。
重點(diǎn)在于事件的處理,拖拽不同的部位需要處理不同的邏輯,還涉及對拖拽部位的校驗(yàn)、高亮示意,對這塊的整合還是需要一定的功力的。
代碼中通過 RangeData
可監(jiān)聽對象為繪制提供必要的數(shù)據(jù),其中 minGap
用于控制范圍的最小值,保證范圍不會過小。
另外定義了 OperationType
枚舉表示操作,其中有四個(gè)元素,none
表示沒有拖拽的普通狀態(tài);
dragHead
表示拖動(dòng)起始塊,dragTail
表示拖動(dòng)終止塊,dragZone
表示拖動(dòng)范圍區(qū)域。
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}); //暫略相關(guān)方法... }
在組件構(gòu)建中,通過 LayoutBuilder
獲取組件的約束信息,從而獲得約束區(qū)域?qū)挾茸畲笾?,也就是說組件區(qū)域的寬度值由使用者自行約束,該組件并不強(qiáng)制指定。
使用 SizedBox
限定畫板的高度,通過 CustomPaint
組件使用 ChartRangeSelectorPainter
進(jìn)行繪制。
使用 GestureDetector
組件進(jìn)行手勢交互監(jiān)聽,這就是該組件整體上實(shí)現(xiàn)的思路。
可以看出,這個(gè)組件的核心就是 繪制
+ 手勢交互
。其中繪制比較簡單,就是根據(jù) RangeData
數(shù)據(jù)和顏色配置畫些方塊而已,稍微困難一點(diǎn)的是對左右控制柄位置的計(jì)算。
另外,三個(gè)可拖拽物的激活狀態(tài)是通過 RangeData#operationType
進(jìn)行判斷的。
也就是說所有問題的焦點(diǎn)都集中在 手勢交互
中對 RangeData
數(shù)據(jù)的更新。如下是處理按下的邏輯,當(dāng)觸電橫坐標(biāo)左右 10
邏輯像素之內(nèi),表示激活頭部。
如下 tag1
處通過 dragHead
方法更新 operationType
并觸發(fā)通知,這樣畫板繪制時(shí)就會激活頭部塊,右側(cè)和中間的激活同理。
---->[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(); } }
對于拖手勢的處理,是比較復(fù)雜的。如下根據(jù) operationType
進(jìn)行不同的邏輯處理,比如當(dāng) dragHead
時(shí),觸發(fā) RangeData#moveHead
方法移動(dòng) start
值。這里將具體地邏輯封裝在 RangeData
類中。
可以使代碼更加簡潔明了,每個(gè)操作都有 bool
返回值用于校驗(yàn)區(qū)域也沒有發(fā)生變化,比如拖拽到 0
時(shí),繼續(xù)拖拽是會觸發(fā)事件的,此時(shí)返回 false
,避免無意義的 onChartRangeChange
回調(diào)觸發(fā)。
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
用于記錄起始值,如果移動(dòng)后未改變,返回 false
。表示不執(zhí)行通知和觸發(fā)回調(diào)。
---->[RangeData#moveHead]---- bool moveHead(double ds) { start += ds; start = start.clamp(0, end - minGap); if (start == _recordStart) return false; _recordStart = start; notifyListeners(); return true; }
下面是結(jié)合 charts_flutter
圖標(biāo)庫實(shí)現(xiàn)的范圍顯示案例。其中核心點(diǎn)是 domainAxis
可以通過 NumericAxisSpec
來顯示某個(gè)范圍的數(shù)據(jù),而 ChartRangeSelector
提供拽的交互操作來更新這個(gè)范圍,可謂相輔相成。
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圖表范圍選擇器怎么使用”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對UI開源組件Flutter圖表范圍選擇器怎么使用這一問題有了更深刻的體會,具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是億速云,小編將為大家推送更多相關(guān)知識點(diǎn)的文章,歡迎關(guān)注!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。