溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

UI開源組件Flutter圖表范圍選擇器怎么使用

發(fā)布時(shí)間:2022-08-30 10:22:07 來源:億速云 閱讀:118 作者:iii 欄目:開發(fā)技術(shù)

這篇文章主要講解了“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ù)

UI開源組件Flutter圖表范圍選擇器怎么使用

這樣當(dāng)圖表的數(shù)據(jù)量過大,不宜全部展示時(shí),可選擇的局部展示就是個(gè)不錯(cuò)的解決方案。由于一般的圖表庫沒有提供該功能,這里自己通過繪制來實(shí)現(xiàn)

1. 使用 chart_range_selector

目前這個(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 。

UI開源組件Flutter圖表范圍選擇器怎么使用

ChartRangeSelector(
  height: 30,
  initStart: 0.4,
  initEnd: 0.6,
  onChartRangeChange: _onChartRangeChange,
),
void _onChartRangeChange(double start, double end) {
  print("start:$start, end:$end");
}

封裝的組件名為: ChartRangeSelector ,提供了如下的一些配置參數(shù):

UI開源組件Flutter圖表范圍選擇器怎么使用

配置項(xiàng)類型簡述
initStartdouble范圍啟始值 0~1
initEnddouble范圍終止值 0~1
heightdouble高度值
onChartRangeChangeOnChartRangeChange范圍變化回調(diào)
bgStorkColorColor背景線條顏色
bgFillColorColor背景填充顏色
rangeColorColor區(qū)域顏色
rangeActiveColorColor區(qū)域激活顏色
dragBoxColorColor左右拖拽塊顏色
dragBoxActiveColorColor左右拖拽塊激活顏色

2. ChartRangeSelector 實(shí)現(xiàn)思路分析

這個(gè)組件整體上是通過 ChartRangeSelectorPainter 繪制出來的,其實(shí)這些圖形都是挺規(guī)整的,繪制來說并不是什么難事。

重點(diǎn)在于事件的處理,拖拽不同的部位需要處理不同的邏輯,還涉及對拖拽部位的校驗(yàn)、高亮示意,對這塊的整合還是需要一定的功力的。

UI開源組件Flutter圖表范圍選擇器怎么使用

代碼中通過 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)的思路。

UI開源組件Flutter圖表范圍選擇器怎么使用

3.核心代碼實(shí)現(xiàn)分析

可以看出,這個(gè)組件的核心就是 繪制 + 手勢交互 。其中繪制比較簡單,就是根據(jù) RangeData 數(shù)據(jù)和顏色配置畫些方塊而已,稍微困難一點(diǎn)的是對左右控制柄位置的計(jì)算。

另外,三個(gè)可拖拽物的激活狀態(tài)是通過 RangeData#operationType 進(jìn)行判斷的。

UI開源組件Flutter圖表范圍選擇器怎么使用

也就是說所有問題的焦點(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();
}

UI開源組件Flutter圖表范圍選擇器怎么使用

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;
}

4. 結(jié)合圖表使用

下面是結(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)注!

向AI問一下細(xì)節(jié)

免責(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)容。

AI