在聊天界面添加一个搜索框。那么就在ListView里面添加一个cell,那么就需要itemCount里面加1。
itemCount: _datas.length + 1,
创建一个chat package,然后将chat_page拖进来并且重新创建一个search_cell文件。
将ListView里面的itemBuilder的代码抽取成一个方法,然后在里面判断如果index == 0 则返回 SearchCell。这里后面需要index–,否则index就会从1开始。
接下来写search cell 的界面,先赋予一个高度和颜色,来看是否能显示。
return Container(
height: 45,
width: 200,
color: Colors.red,
);
运行后看到显示了出来。
搜索框需要响应点击时间,所以这里需要用GestureDetector包一下,然后颜色改为weChatThemColor,里面用Stack来写。
return GestureDetector(
child: Container(
height: 45,
width: 200,
color: weChatThemColor,
padding: EdgeInsets.all(5),
child: Stack(
children: [
],
),
),
);
在stack里面先添加一个白底
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6.0),
),
),//白底
然后添加Row里面放图片和文字,为了让其居中设置row的mainAxisAlignment为MainAxisAlignment.center,而让row居中则将stack的alignment改为Alignment.center。
child: Stack(
alignment: Alignment.center,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6.0),
),
),//白底
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image(image: AssetImage('images/放大镜b.png'),width: 15,color: Colors.grey,),
Text(' 搜索',style: TextStyle(fontSize: 15,color: Colors.grey),),
],),
],
),
这样搜索框页面就完成了
接下来点进去需要到一个新的界面,那么就添加onTap,然后创建一个文件来写新页面SearchPage。
import 'package:flutter/material.dart';
class SearchPage extends StatefulWidget {
const SearchPage({Key? key}) : super(key: key);
@override
_SearchPageState createState() => _SearchPageState();
}
class _SearchPageState extends State {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
SearchBar(),
Expanded(
flex: 1,
child: ListView.builder(
itemBuilder: itemBuilder,
itemCount: 3,
),
),
],
),
);
}
Widget itemBuilder(BuildContext context, int index) {
return Container(
child:Text("Column$index"),
);
}
}
class SearchBar extends StatefulWidget {
const SearchBar({Key? key}) : super(key: key);
@override
_SearchBarState createState() => _SearchBarState();
}
class _SearchBarState extends State {
@override
Widget build(BuildContext context) {
return Container(
height: 84,
color: weChatThemColor,
);
}
}
然后在SearchCell的GestureDetector里面添加页面push。
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
SearchPage()));
},
运行后得到下面的界面:
发现这里有间距,那么就使用removePadding将ListView头部间距去掉。
child: MediaQuery.removePadding(
removeTop: true,
context: context,
child: ListView.builder(
itemBuilder: itemBuilder,
itemCount: 3,
),
),
然后开始编写SearchBar。
return Container(
height: 84,
color: weChatThemColor,
child: Column(
children: [
SizedBox(height: 40,),
Container(
height: 44,
color:Colors.red,
child: Row(
children: [
Container(
width: screenWidth(context) - 40,
height: 34,
color: Colors.yellow,
),//圆角背景
Text('取消'),//取消按钮
],
),
),
],
),
);
return Container(
height: 84,
color: weChatThemColor,
child: Column(
children: [
SizedBox(
height: 40,
),
Container(
height: 44,
color: Colors.red,
child: Row(
children: [
Container(
width: screenWidth(context) - 50,
height: 34,
margin: EdgeInsets.only(left: 5, right: 5),
padding: EdgeInsets.only(left: 5, right: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Image(
image: AssetImage('images/放大镜b.png'),
width: 20,
color: Colors.grey,
), //放大镜
Icon(Icons.cancel),
],
),
), //圆角背景
Text('取消'), //取消按钮
],
),
),
],
),
);
然后还需要添加TextField。
return Container(
height: 84,
color: weChatThemColor,
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: 5),
padding: EdgeInsets.only(left: 5, right: 5),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Image(
image: AssetImage('images/放大镜b.png'),
width: 20,
color: Colors.grey,
), //放大镜
Expanded(
child: TextField(
cursorColor: Colors.green,
autofocus: true,
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.w300,
),
decoration: InputDecoration(
contentPadding: EdgeInsets.only(left: 5,bottom: 10),
border:InputBorder.none,
hintText:'搜索',
),
),
),
Icon(Icons.cancel,size: 20,color: Colors.grey,),
],
),
), //圆角背景
Text('取消'), //取消按钮
],
),
),
],
),
);
之前用Expanded包着ListView是因为ListView没有大小,当我们设置了 shrinkWrap: true之后,那么就会根据ListView的内容大小来展示,就不需要Expanded了。我们这里还是用之前的写法,让ListView占满屏幕下面部分。
return Scaffold(
body: Column(
children: [
SearchBar(),
ListView.builder(
shrinkWrap: true,
itemBuilder: itemBuilder,
itemCount: 3,
),
],
),
);
接下来处理点击和逻辑事件
这里先为取消添加一个pop的点击事件。
GestureDetector(
child: Text('取消'),
onTap: () {
Navigator.pop(context);
},
),
接下来需要监听搜索框,当没有任何东西的时候,那么就不显示cancel icon。创建一个TextEditingController变量。
final TextEditingController _textEditingController = TextEditingController();
为TextField添加Controller
controller: _textEditingController,
然后监听TextField的onChanged
onChanged: _onChanged,
void _onChanged(value) {}
然后创建_showClear来判断是否显示TextField后面的取消按钮,默认为false。
bool _showClear = false;
在_onChanged方法里面进行字符串长度的判断以及_showClear的赋值。
void _onChanged(String text) {
if (text.length > 0) {
setState(() {
_showClear = true;
});
} else {
setState(() {
_showClear = false;
});
}
}
根据_showClear的值觉得是否显示Icon。
if (_showClear) Icon(
Icons.cancel,
size: 20,
color: Colors.grey,
)
为 cancel Icon添加点击手势清空输入内容。
if (_showClear) GestureDetector(
child: Icon(
Icons.cancel,
size: 20,
color: Colors.grey,
),
onTap: () {
_textEditingController.clear();
setState(() {
_onChanged("");
});
},
)
这样页面部分就完成了。这里有个问题,ChatPage点击进去SearchPage的时候那么SearchPage一定要有数据,那么SearchBar 是否有数据呢?这里有两种情况,一种是有,将数据传给SearchBar,搜索完之后将结果返回给SearchPage,第二种是没有,SearchBar直接通过回调将输入内容给SearchPage。
先在SearchCell添加
final List? datas;
const SearchCell({ this.datas});
然后在ChatPage为其赋值,这样数据就给了SearchCell。
if (index == 0) {
return SearchCell(datas: _datas,);
}
然后再传给SearchPage。在SearchPage添加:
final List? datas;
const SearchPage({ this.datas});
然后在SearchCell进来的地方将data传进来
SearchPage(datas: datas,)
在SearchBar里面添加一个回调:
const SearchBar({Key? key, this.onChanged}) : super(key: key);
final ValueChanged? onChanged;
然后在_onChanged里面调用这个回调。这里if else 判断太长了,可以直接使用 _showClear = text.length > 0进行设值。
void _onChanged(String text) {
if (widget.onChanged != null) {
widget.onChanged!(text);
}
setState(() {
_showClear = text.length > 0;
});
}
这个时候SearchPage就可以传回调了
SearchBar(onChanged: ( String text){},),
创建一个 _searchData方法来处理回调传过来的文字。
SearchBar(onChanged: (String text){
_searchData(text);
},),
void _searchData(String text) {
}
接下来创建一个数组来装符合条件的数据
List _models = [];
_searchData里面循环检索然后添加符合条件的数据到_models。
void _searchData(String text) {
_models.clear(); // 每次搜索先情况
if (text.length > 0 ) {
if (widget.datas != null){ // 循环检索
for (int i = 0; i < widget.datas!.length;i++ ) {
String? name = widget.datas![i].name;
if ((name ?? "").contains(text)) {
_models.add(widget.datas![i]);
}
}
}
}
setState(() {
});
}
接下来根据_models显示ListView的内容,将itemCount改为_models.length。
itemCount: _models.length,
在itemBuilder里面返回
Widget itemBuilder(BuildContext context, int index) {
return Container(
color: Colors.red,
child: Text("${_models[index].name}"),
);
}
这样搜索就可以显示符合条件的Model了。
接下来需要来编写Cell的界面,这里可以直接把之前聊天界面的样式复制过来。
ListTile(
title: Text(_models[index].name ?? ""),
subtitle: Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.only(right: 10),
height: 25,
child: Text(
_models[index].message ?? "",
overflow: TextOverflow.ellipsis,
),
),
leading: ClipRRect(
//剪裁为圆角矩形
borderRadius: BorderRadius.circular(5.0),
child: Image(image: NetworkImage(_models[index].imageUrl ?? "")),
),
);
运行后:
这里需要对名字做高亮处理,那么就把title部分提取出来做处理。
title: _title(_models[index].name ?? ""),
Text _title(String name) {
return Text(name);
}
我们要知道搜索的名字是什么,所以这里声明一个变量
String _searchStr = "";
然后在_searchData里面赋值。
_searchStr = text
然后在声明2个TextStyle
TextStyle _normalStyle = TextStyle(fontSize: 16,color: Colors.black);
TextStyle _highLightedStyle = TextStyle(fontSize: 16,color: Colors.green);
然后再_title方法里面处理。
idget _title(String name) {
List spans = [];
List strs = name.split(_searchStr);
for (int i = 0; i < strs.length; i ++) {
String str = strs[i];
print(strs);
if (str == "" && i < strs.length - 1) {
print('here1');
spans.add(TextSpan(text: _searchStr,style:_highLightedStyle));
} else {
print('here2');
spans.add(TextSpan(text: str,style:_normalStyle));
if (i < strs.length - 1) {
print('here3');
spans.add(TextSpan(text: _searchStr,style:_highLightedStyle));
}
}
}