Flutter橫向樹(shù)形選擇器實(shí)現(xià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é)處理
底部安全區(qū) - iPhone 那個(gè)小橫條要留出來(lái),用
MediaQuery.of(context).padding.bottom泛型支持 - 整個(gè)組件是泛型的,
T extends OptionChildrenItem,這樣業(yè)務(wù)方自己的數(shù)據(jù)模型也能用兩種模式 -
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)文章
簡(jiǎn)單談?wù)刟ndroid studio 的單元測(cè)試
昨天在完善項(xiàng)目的時(shí)候,需要進(jìn)行單元測(cè)試,在Eclipse環(huán)境中進(jìn)行是很簡(jiǎn)單的,但是在Android Studio環(huán)境中進(jìn)行單元測(cè)試,在國(guó)內(nèi)找了很多資料,大都是人云亦云,本文發(fā)布出來(lái)供大家學(xué)習(xí)參考。2016-08-08
ERROR/AndroidRuntime(17121)的問(wèn)題解決
ERROR/AndroidRuntime(17121)的問(wèn)題解決,需要的朋友可以參考一下2013-05-05
Kotlin構(gòu)造函數(shù)與成員變量和init代碼塊執(zhí)行順序詳細(xì)講解
這篇文章主要介紹了Kotlin構(gòu)造函數(shù)與成員變量和init代碼塊執(zhí)行順序,kotlin里面的構(gòu)造函數(shù)分為主構(gòu)造函數(shù)和次構(gòu)造函數(shù)。主構(gòu)造函數(shù)只能有一個(gè),次構(gòu)造函數(shù)個(gè)數(shù)不限制,可以有一個(gè)或者多個(gè)2022-11-11
Android中new Notification創(chuàng)建實(shí)例的最佳方法
這篇文章主要介紹了Android中new Notification創(chuàng)建實(shí)例的最佳方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08
如何使用SurfaceView實(shí)現(xiàn)魚(yú)兒游動(dòng)動(dòng)畫
這篇文章主要教大家如何使用SurfaceView實(shí)現(xiàn)魚(yú)兒游動(dòng)動(dòng)畫,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-04-04
Android仿QQ復(fù)制昵稱效果的實(shí)現(xiàn)方法
這篇文章主要介紹了Android仿QQ復(fù)制昵稱效果的實(shí)現(xiàn)方法,主要依賴的是一個(gè)開(kāi)源項(xiàng)目,需要的朋友可以參考下2019-05-05

