Flutter实例——搜索条的实现

SearchDelegate

在Flutter中实现搜索框可以使用SearchDelegate来实现,我们需要将自己要实现搜索框的类继承于它,并且实现相关的方法。

  • buildSuggestions

    这个方法监听这query,query是用户键入的内容,这个方法一般返回一个ListView,然后将建议的列表放置其中,当列表中某一项被点击的时候应该通过回调showResults方法来显示结果。

  • buildResults

    用来构建搜索结果被提交后的显示结果

  • buildLeading

    查询条的左边头放置的部件,通常返回一个返回按钮并且触发close事件

  • buildActions

    查询条右边放置的组件,返回一个widget数组,通常里面存放着清除按钮,搜索按钮等

SearchDelegate源码及详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
abstract class SearchDelegate<T> {

// 建议显示在搜索页的正文中,而用户在搜索字段中键入一个查询。每当[Query]的内容发生变化时,都会调用委托方法。建议应该基于当前[Query]字符串。如果查询String为空,则根据PASS查询或当前上下文显示建议的查询是很好的做法。通常,此方法将返回一个[listview],其中包含每个建议中的一个列表。当[listtile.ontab]被调用时,[query]应该用相应的建议来更新,结果页面应该通过调用[showResults]来显示。

Widget buildSuggestions(BuildContext context);


// 用户从搜索页面提交搜索后显示的结果。可使用[Query]的当前值来确定用户搜索的内容。此方法可多次应用于同一查询。如果您的[构建结果]方法在计算上开销很大,您可能希望缓存一个或多个查询的搜索结果。通常,此方法返回带有搜索结果的[listview]。当用户点击特定的搜索结果时,[close]应该被调用,并将选定的结果作为参数。这将关闭搜索页面并将结果传回[SHOWSEARCH]的初始调用方。
Widget buildResults(BuildContext context);

// 在[appbar]中当前查询之前显示的一个小部件。通常是一个配置了一个[backtton图标]的[偶像按钮],该按钮退出带有[Close]的搜索。还可以使用由[过渡动画]驱动的[动画图标],当搜索覆盖逐渐消失时,[动画图标]从汉堡包菜单到Back按钮。如果不显示小部件,则返回NULL。
Widget buildLeading(BuildContext context);

// 在[appbar]中的搜索查询之后显示的小部件。如果[查询]不是空的,这通常应该包含到清除查询的按钮,并再次显示建议(通过[显示建议])
List<Widget> buildActions(BuildContext context);


// 用于设置主题 默认为白色主题
ThemeData appBarTheme(BuildContext context) {
assert(context != null);
final ThemeData theme = Theme.of(context);
assert(theme != null);
return theme.copyWith(
primaryColor: Colors.white,
primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey),
primaryColorBrightness: Brightness.light,
primaryTextTheme: theme.textTheme,
);
}

// 显示在[appbar]中的当前查询字符串。用户通过键盘操作此字符串。如果用户点击[buildSuggestions]提供的建议,则应通过setter将该string更新为该建议。
String get query => _queryTextController.text;
set query(String value) {
assert(query != null);
_queryTextController.text = value;
}

// 从[BuildSuggestions]返回的建议转换到[BuildResults]返回的[查询]结果如果用户点击[BuildSuggestions]提供的建议,屏幕应典型地过渡到显示所建议查询的搜索结果的页面。同样的,使用[showSuggestions]可再次显示搜索建议。
void showResults(BuildContext context) {
_focusNode.unfocus();
_currentBody = _SearchBody.results;
}

// 从显示[buildResults]返回的结果到显示/显示[BuildSuggestions]返回的建议。调用此方法还会将输入焦点放回[appbar]的搜索字段中。如果目前显示了结果,则可以使用此方法返回以显示搜索建议。
void showSuggestions(BuildContext context) {
FocusScope.of(context).requestFocus(_focusNode);
_currentBody = _SearchBody.suggestions;
}

// 关闭搜索页面并返回到底层路由。Result为为底层路由返回的值
void close(BuildContext context, T result) {
_currentBody = null;
_focusNode.unfocus();
Navigator.of(context)
..popUntil((Route<dynamic> route) => route == _route)
..pop(result);
}

// [animation]在搜索页面淡入或淡出时触发。此动画通常用于为[buildleading]或[buildactions]返回的[iconbutton]s设置动画。它还可以用于设置搜索页面下方路线中包含的[iconbutton]的动画。
Animation<double> get transitionAnimation => _proxyAnimation;

// 是否选中
final FocusNode _focusNode = FocusNode();

// 输入框的控制器
final TextEditingController _queryTextController = TextEditingController();

// 设置过度动画
final ProxyAnimation _proxyAnimation = ProxyAnimation(kAlwaysDismissedAnimation);

final ValueNotifier<_SearchBody> _currentBodyNotifier = ValueNotifier<_SearchBody>(null);

_SearchBody get _currentBody => _currentBodyNotifier.value;
set _currentBody(_SearchBody value) {
_currentBodyNotifier.value = value;
}

_SearchPageRoute<T> _route;

}

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'search bar',
theme: ThemeData(primarySwatch: Colors.cyan),
home: SearchBarDemo(),
);
}
}

class SearchBarDemo extends StatefulWidget {
@override
_SearchBarDemoState createState() => _SearchBarDemoState();
}

class _SearchBarDemoState extends State<SearchBarDemo> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('search bar demo'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(context: context, delegate: SearchBarDelegate());
},
)
],
),
);
}
}

class SearchBarDelegate extends SearchDelegate<String> {
@override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () => query = '',
)
];
}

@override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () => close(context, null),
);
}

@override
Widget buildResults(BuildContext context) {
return Center(
child: Container(
width: 100.0,
height: 100.0,
child: Card(
elevation: 50.0,
color: Colors.green,
child: Text(
'$query',
style: TextStyle(color: Colors.red),
),
),
),
);
}

@override
Widget buildSuggestions(BuildContext context) {
final suggestionList = query.isEmpty
? recentSuggest
: searchList.where((input) => input.startsWith(query)).toList();
return ListView.builder(
itemCount: suggestionList.length,
itemBuilder: (context, index) => ListTile(
onTap: () {
showResults(context);
},
title: RichText(
text: TextSpan(
text: suggestionList[index].substring(0, query.length),
style: TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
children: [
TextSpan(
text: suggestionList[index].substring(query.length),
style: TextStyle(color: Colors.grey))
])),
));
}
}

const searchList = [
"jiejie-大长腿",
"jiejie-水蛇腰",
"gege1-帅气欧巴",
"gege2-小鲜肉"
];

const recentSuggest = [
"推荐-1",
"推荐-2"
];
-------------本文结束感谢阅读-------------