最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Flutter橫向樹(shù)形選擇器實(shí)現(xiàn)效果及完整代碼

 更新時(shí)間:2026年06月01日 09:33:32   作者:明君87997  
樹(shù)形菜單是Flutter本身不支持的,但在桌面端或 Web 端中是非常常見(jiàn),所以設(shè)計(jì)一個(gè)樹(shù)形菜單組件是非常必要,它屬于一種基礎(chǔ)設(shè)施,這篇文章主要介紹了Flutter橫向樹(shù)形選擇器實(shí)現(xiàn)效果及完整代碼的相關(guān)資料,需要的朋友可以參考下

最近項(xiàng)目里需要做一個(gè)多級(jí)分類選擇,類似淘寶那種"電子產(chǎn)品 > 手機(jī) > iPhone"的效果。找了一圈沒(méi)看到合適的輪子,索性自己擼一個(gè)。

先看效果

大概就是這個(gè)樣子,點(diǎn)擊一級(jí)會(huì)展開(kāi)下一級(jí),頂部有面包屑導(dǎo)航可以回退,支持搜索。

為什么要橫向?

說(shuō)實(shí)話一開(kāi)始我是想用縱向折疊的方案的,但產(chǎn)品覺(jué)得層級(jí)多了以后太丑了,而且用戶不知道自己選到第幾層。后來(lái)看了一些競(jìng)品,發(fā)現(xiàn)橫向展開(kāi)+面包屑這個(gè)方案確實(shí)清晰很多,用戶一眼就知道自己選了什么。

核心思路

其實(shí)整個(gè)組件的核心就是維護(hù)兩個(gè)狀態(tài):

  • _breadcrumbs - 面包屑路徑,記錄用戶點(diǎn)了哪些父級(jí)
  • _currentOptions - 當(dāng)前顯示的選項(xiàng)列表

每次點(diǎn)擊一個(gè)有子項(xiàng)的選項(xiàng),就把它加到面包屑里,然后把它的 children 設(shè)成當(dāng)前選項(xiàng)列表。點(diǎn)面包屑就反向操作,截取數(shù)組就行。

/// 導(dǎo)航到子項(xiàng)
void _navigateToChildren(T parent) {
  setState(() {
    _breadcrumbs.add(parent);
    _currentOptions = parent.children?.map((child) => child as T).toList() ?? [];
  });
}

/// 導(dǎo)航到面包屑指定位置
void _navigateToBreadcrumb(int index) {
  setState(() {
    _breadcrumbs = _breadcrumbs.sublist(0, index + 1);
    _currentOptions = _breadcrumbs[index].children?.map((child) => child as T).toList() ?? [];
  });
}

回顯這個(gè)坑

最麻煩的其實(shí)是回顯,就是用戶之前選過(guò)的值要顯示出來(lái)。這就需要在整棵樹(shù)里遞歸查找,找到后把完整路徑構(gòu)建出來(lái):

/// 遞歸查找項(xiàng)目路徑
bool _findItemPath(List<T> options, T target, List<T> path) {
  for (var option in options) {
    path.add(option);

    if (option.value == target.value) {
      return true;
    }

    if (option.children != null && option.children!.isNotEmpty) {
      List<T> children = option.children!.map((child) => child as T).toList();
      if (_findItemPath(children, target, path)) {
        return true;
      }
    }

    path.removeLast();  // 回溯
  }
  return false;
}

這個(gè)回溯算法挺經(jīng)典的,找到了就一路返回 true,沒(méi)找到就把剛加的節(jié)點(diǎn)刪掉繼續(xù)找下一個(gè)。

搜索功能

搜索功能也是后來(lái)加的,需求是只搜葉子節(jié)點(diǎn)(最終可選的項(xiàng)),然后顯示完整路徑讓用戶知道這個(gè)選項(xiàng)在哪。

void _searchInTree(List<T> options, String query, List<T> results, List<T> path) {
  for (var option in options) {
    final bool isLeaf = option.children == null || option.children!.isEmpty;
    final bool nameMatches = option.name?.toLowerCase().contains(query) == true;
    
    if (isLeaf && nameMatches) {
      results.add(option);
    }

    // 遞歸搜索子項(xiàng)
    if (option.children != null && option.children!.isNotEmpty) {
      List<T> newPath = List.from(path)..add(option);
      List<T> children = option.children!.map((child) => child as T).toList();
      _searchInTree(children, query, results, newPath);
    }
  }
}

數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)

為了通用性,我定義了一個(gè)抽象類,業(yè)務(wù)方繼承一下就能用:

abstract class OptionChildrenItem {
  String? name;
  String? value;
  List<OptionChildrenItem>? children;

  OptionChildrenItem({this.name, this.value, this.children});
}

也提供了一個(gè)默認(rèn)實(shí)現(xiàn) OptionChildrenItemImpl,不想自定義可以直接用。

使用方式

用起來(lái)很簡(jiǎn)單,調(diào)一個(gè)方法就行:

final result = await showHorizontalTreePopup(
  context: context,
  options: _demoOptions,
  title: '請(qǐng)選擇分類',
  themeColor: Colors.blue,
  showSearch: true,  // 要不要搜索框
  showConfirmButton: true,  // 要不要確定按鈕,false 的話選中直接返回
  onConfirm: (selectedItem) {
    print('選中了: ${selectedItem?.name}');
  },
);

一些細(xì)節(jié)處理

  1. 底部安全區(qū) - iPhone 那個(gè)小橫條要留出來(lái),用 MediaQuery.of(context).padding.bottom

  2. 泛型支持 - 整個(gè)組件是泛型的,T extends OptionChildrenItem,這樣業(yè)務(wù)方自己的數(shù)據(jù)模型也能用

  3. 兩種模式 - showConfirmButton 為 true 時(shí)需要點(diǎn)確定才返回,為 false 時(shí)點(diǎn)擊葉子節(jié)點(diǎn)直接返回,適合不同場(chǎng)景

總結(jié)

整體下來(lái)代碼量不大,700 多行,核心邏輯就是維護(hù)面包屑和當(dāng)前列表這兩個(gè)狀態(tài)。最坑的地方是泛型轉(zhuǎn)換,Dart 的泛型協(xié)變有點(diǎn)坑,children 那里需要手動(dòng) map 轉(zhuǎn)一下類型。

有需要的可以直接用,pub.dev 搜 horizontal_tree_selector 就行:

dependencies:
  horizontal_tree_selector: ^0.0.1

到此這篇關(guān)于Flutter橫向樹(shù)形選擇器實(shí)現(xiàn)效果及完整代碼的文章就介紹到這了,更多相關(guān)Flutter橫向樹(shù)形選擇器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

乌恰县| 盐城市| 丰台区| 长治市| 温泉县| 宜都市| 扎赉特旗| 都昌县| 志丹县| 德州市| 衡水市| 郸城县| 湖南省| 呼伦贝尔市| 盐山县| 崇信县| 鸡泽县| 灵武市| 宁海县| 荆州市| 龙山县| 浠水县| 淳安县| 巨野县| 赤壁市| 岢岚县| 伽师县| 西充县| 汤阴县| 乌兰察布市| 永泰县| 望江县| 贵南县| 汶川县| 临西县| 沾化县| 连山| 和田市| 宜兴市| 建水县| 内黄县|