Flutter 仿写微信搜索页
如下图所示,我们用 Flutter
来仿写搜索页面,这里聊天首页点击搜索栏会跳转到搜索页,搜索页面包含顶部搜索框跟底部 ListView
,在搜索框内我们输入搜索词会检索聊天列表模型中 name
属性中包含搜索词的模型,并在底部列表中展示,且搜索词高亮显示。下面我们分别来介绍下这些功能的实现。
顶部搜索栏
class SearchBar extends StatefulWidget {
final ValueChanged<String>? onChanged;
const SearchBar({this.onChanged});
@override
_SearchBarState createState() => _SearchBarState();
}
class _SearchBarState extends State<SearchBar> {
final TextEditingController _editingController = TextEditingController();
bool _showClose = false;
void _onChange(text) {
if (null != widget.onChanged) {
widget.onChanged!(text);
}
setState(() {
_showClose = ((text as String).length > 0);
});
}
@override
Widget build(BuildContext context) {
return Container(
height: 84,
color: CahtThemColor,
child: Column(
children: [
SizedBox(height: 40,), //上半部分留空
Container(
height: 44,
child: Row(
children: [
Container(
width: screenWidth(context) - 50,
height: 34,
margin: EdgeInsets.only(left: 5, right: 10),
padding: EdgeInsets.only(left: 5, right: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6.0),
),
child: Row(
children: [
Image(image: AssetImage('images/放大镜b.png'), width: 20, color: Colors.grey,), //放大镜
Expanded(child: TextField(
controller: _editingController,
onChanged: _onChange,
autofocus: true,
cursorColor: Colors.green,
style: TextStyle(
fontSize: 16.0,
color: Colors.black87,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 5, right: 5, bottom: 12,),
border: InputBorder.none,
hintText: '搜索',
hintStyle: TextStyle(
fontSize: 16.0,
color: Colors.grey,
fontWeight: FontWeight.w400,
),
),
),),
if (_showClose) GestureDetector(
onTap: () {
//清空搜索框
_editingController.clear();
setState(() {
_onChange('');
});
},
child: Icon(
Icons.cancel,
color: Colors.grey,
size: 20,
),
),
],
mainAxisAlignment: MainAxisAlignment.spaceBetween,
),
), //左边白色背景
GestureDetector (
onTap: () {
Navigator.pop(context);
},
child: Text('取消'),
), //右边取消按钮
],
),
), //搜索条部分
],
),
);
}
}
针对搜索页的整体布局我们可以分为顶部的搜索栏跟底部的搜索列表两部分,针对搜索栏我们单独定义了一个 SearchBar
的类来实现,底部搜索列表我们用 Expanded
部件进行了包装。
SearchBar 实现细节
针对 SearchBar
我们这里整体高度设置为了 84
,这里最好高度定义为一个变量,根据机型来设置。针对搜索栏我们分为上下两部分,顶部留白及内容部分,顶部留白用 SizedBox
部件实现,底部内容用 Row
部件来实现,分为左边搜索框及右边取消按钮。
左边搜索框实现
搜索框整体部分我们用 Container
部件来实现,可以设置宽、高及圆角属性。child
属性我们也是用 Row
部件来实现,分为左边搜索图标、中间 TextField
及右边 关闭按钮
。
TextField 实现细节
TextField
部件有两个比较重要的属性,onChanged
跟 controller
。onChanged
我们传入了一个外部方法 _onChange
,可以监听输入框文字的变化,当有文字时展示关闭按钮,没有文字时隐藏关闭按钮。controller
我们传入了一个外部定义的属性 _editingController
,可以用来控制 TextField
的编辑。
关闭按钮的实现
关闭按钮我们根据 _showClose
来判断是否展示,且通过 GestureDetector
部件进行包装,点击的时候清空输入框及调用 _onChange
方法,参数传入空字符串,_onChange
方法中字符串为空的时候就会隐藏关闭按钮。
右边取消按钮实现
取消按钮点击的时候就是返回到上一个页面。
内容的检索
我们监听到文字输入框的变化后,就需要进行内容的检索,并且在底部列表中进行展示。
内容的传递
class SearchCell extends StatelessWidget {
final List<ChatModel>? datas;
const SearchCell({this.datas});
}
class SearchPage extends StatefulWidget {
final List<ChatModel>? datas;
const SearchPage({this.datas});
}
这里我们是基于聊天页面列表数据模型进行检索,找到名称中包含搜索词的模型进行展示。所以我们在 SearchCell
中定义了 datas
属性,并且在 SearchPage
中也定义了 datas
属性,分别在初始化的时候进行传递,这样我们在搜索页面就可以拿到了聊天列表的模型数据。
内容的检索
//满足查找条件的数据数组
List<ChatModel> _models = [];
//正在搜索的内容
String _searchStr = '';
// 搜索数据查找
void _searchData(String text) {
//每次搜索前先清空
_models.clear();
_searchStr = text;
if (text.length < 1) {
setState(() {});
return;
}
for (int i = 0; i < datas.length; i++) {
String name = widget.datas?[i].name as String;
if(name.contains(text)) {
_models.add(widget.datas?[i] as ChatModel);
}
}
setState(() {});
}
我们把检索逻辑都抽取到了 _searchData
方法中,输入内容变化的时候调用 _searchData
方法。这里我们定义了一个 _models
数组,专门存放检索数据,我们遍历 datas
,把检索到的模型添加到 _models
中。
搜索列表实现
TextStyle _normalStyle = TextStyle(
fontSize: 16,
color: Colors.black,
);
TextStyle _hightlightedStyle = TextStyle(
fontSize: 16,
color: Colors.green,
);
Widget _searchTitle(String name) {
List<TextSpan> textSpans = [];
List<String> searchStrs = name.split(_searchStr);
for (int i = 0; i < searchStrs.length; i++) {
String str = searchStrs[i];
if (str == '' && i < searchStrs.length - 1) {
textSpans.add(TextSpan(text: _searchStr, style: _hightlightedStyle));
} else {
textSpans.add(TextSpan(text: str, style: _normalStyle));
if (i < searchStrs.length - 1) {
textSpans.add(TextSpan(text: _searchStr, style: _hightlightedStyle));
}
}
}
return RichText(text: TextSpan(children: textSpans));
}
搜索列表的 cell
就是复用聊天列表的 cell
,但是需要变化的是搜索内容的高亮显示,所以 ListTile
部件的 title
属性我们用 RichText
来实现,实现内容这里抽取到了 _searchTitle
方法中。name.split(_searchStr)
会返回一个根据搜索词 _searchStr
进行分割的数组,但是当字符串的开头或者结尾有跟 _searchStr
相同的字符的时候在 searchStrs
数组中的元素就是空字符串,所以我们就需要进行特别判 str == ''
。我们循环遍历 searchStrs
数组来创建 TextSpan
部件,并且根据内容是否跟搜索内容一样来分别指定样式 _normalStyle
与 _hightlightedStyle
。