实现的效果如下:
1 定义聊天界面ChatPage
显示一个ListView,在ListView的顶部cell是SearchCell,其他cell为统一的聊天记录cell
网络请求封装 http_manager.dart
import 'package:dio/dio.dart';class HttpRespons {final int status;final body;HttpRespons({this.status, this.body});}class HttpManager {static final Dio dio = Dio();static Future<HttpRespons> request(String url,{String method = 'get',Map<String, String> headers,Map<String, dynamic> queryParameters,int timeOut}) async {//1.创建配置,什么方式请求final options = Options(headers:headers,method: method, receiveTimeout: timeOut);//2.发网络请求Response response = await dio.request(url,queryParameters: queryParameters,options: options,);// print(response.data);return HttpRespons(status:response.statusCode,body: response.data);}}Future<HttpRespons> get(url,{ Map<String, String> headers,Map<String, dynamic> queryParameters,int timeout}) {return HttpManager.request(url,headers: headers,queryParameters: queryParameters, method: 'get', timeOut: timeout);}
需要显示的数据,数据模型定义 Chat
class Chat {final String name;final String message;final String imageUrl;const Chat({this.name, this.message, this.imageUrl});factory Chat.fromJson(Map json) {return Chat(name: json['name'],message: json['message'],imageUrl: json['imageUrl'],);}}
SearchCell和SearchBar的定义 search_bar.dart
import 'package:flutter/material.dart';import 'chat_page.dart';import 'search_page.dart';class SearchCell extends StatelessWidget {final List<Chat> datas;const SearchCell({this.datas});@overrideWidget build(BuildContext context) {return GestureDetector(onTapDown: (d) {Navigator.of(context).push(MaterialPageRoute(builder: (context) {return SearchPage(datas: datas,);}));},child: Container(height: 44,color: Color.fromRGBO(220, 220, 220, 1),padding: EdgeInsets.only(left: 5, right: 5, bottom: 5),child: Stack(alignment: Alignment.center,children: [Container(decoration: BoxDecoration(color: Colors.white,borderRadius: BorderRadius.circular(6.0),),), //白色底Row(mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[Image(image: AssetImage('images/放大镜b.png'),width: 15,color: Colors.grey,),Text(' 搜索',style: TextStyle(fontSize: 15, color: Colors.grey),),],), //中间的文字和放大镜],),),);}}class SearchBar extends StatefulWidget {final ValueChanged<String> onChanged;const SearchBar({this.onChanged});@override_SearchBarState createState() => _SearchBarState();}class _SearchBarState extends State<SearchBar> {final TextEditingController _controller = TextEditingController();bool _showClear = false;_onChange(String text) {if (text.length > 0) {widget.onChanged(text);setState(() {_showClear = true;});} else {widget.onChanged('');_showClear = false;setState(() {});}}@overrideWidget build(BuildContext context) {return Container(height: 84,color: Color.fromRGBO(220, 220, 220, 1),child: Column(children: <Widget>[SizedBox(height: 40,),Container(height: 44,// color: Colors.red,child: Row(children: <Widget>[Container(width: MediaQuery.of(context).size.width - 60,height: 34,margin: EdgeInsets.only(left: 5),padding: EdgeInsets.only(left: 5, right: 5),decoration: BoxDecoration(color: Colors.white,borderRadius: BorderRadius.circular(6.0)),child: Row(children: <Widget>[Image(image: AssetImage('images/放大镜b.png'),width: 20,color: Colors.grey,), //放大镜Expanded(flex: 1,child: TextField(controller: _controller,onChanged: _onChange,autofocus: true,cursorColor: Colors.green,style: TextStyle(fontSize: 18.0,color: Colors.black,),decoration: InputDecoration(contentPadding:EdgeInsets.only(left: 5, bottom: 10),border: InputBorder.none,hintText: '搜索'),),), //输入框_showClear? GestureDetector(onTap: () {setState(() {_controller.clear();_onChange(''); //如果不出发onChange,那么外接将无法回去数据!});},child: Icon(Icons.cancel,size: 20,color: Colors.grey,),): Container(), //取消按钮],),), //左边的圆角背景GestureDetector(onTap: () {Navigator.pop(context);},child: Text(' 取消'),)],),), //下面的搜索条],),);}}
聊天页面chat_page.dart
import 'dart:async';import 'package:flutter/material.dart';import 'search_bar.dart';import '../../tools/http_manager.dart' as http;class ChatPage extends StatefulWidget {@override_ChatPageState createState() => _ChatPageState();}class _ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin {List<Chat> _datas = [];@overridevoid initState() {super.initState();print('Chat的init来了!');getDatas().then((List<Chat> datas) {print('数据来了!');print('更新数据');setState(() {_datas = datas;});}).catchError((e) {print('错误$e');}).whenComplete(() {print('完毕!');});}@overridevoid dispose(){super.dispose();}Future<List<Chat>> getDatas() async {//不再是取消连接了!final response = await http.get('http://rap2api.taobao.org/app/mock/283772/api/chat/list',timeout: 1000);if (response.status == 200) {//获取相应数据,并转成Map类型!final responseBody = response.body;//转模型数组 map中遍历的结果需要返回出去List<Chat> chatList = responseBody['chat_lists'].map<Chat>((item) {return Chat.fromJson(item);}).toList();return chatList;} else {throw Exception('statusCode:${response.status}');}}@overrideWidget build(BuildContext context) {super.build(context);return Scaffold(appBar: AppBar(elevation: 0.0,title: Text('微信'),backgroundColor: Color.fromRGBO(220, 220, 220, 1),),body: Container(child: _datas.length == 0? Center(child: Text("Loading..."),): ListView.builder(itemCount: _datas.length+1, //search栏+ 聊天列表itemBuilder: _buildCellForRow,),),);}Widget _buildCellForRow(BuildContext context, int index) {if(index == 0){ //search栏在列表的顶部return SearchCell(datas: _datas,);}//保证从模型数组中取出正确的数据(从0开始),search栏在列表的顶部index--;return ListTile(title: Text(_datas[index].name),subtitle: Container(alignment: Alignment.bottomCenter,padding: EdgeInsets.only(right: 10),height: 25,child: Text(_datas[index].message,overflow: TextOverflow.ellipsis,),),leading: Container(width: 44,height: 44,decoration: BoxDecoration(borderRadius: BorderRadius.circular(6.0),image: DecorationImage(image: NetworkImage(_datas[index].imageUrl))),));}@overridebool get wantKeepAlive => true;}
2 定义搜索页面 SearchPage
显示一个ListView,ListView的顶部cell是SearchBar,其他cell为统一的聊天搜索后显示部分文字高亮的cell
import 'package:flutter/material.dart';import 'search_bar.dart';import 'chat_page.dart';class SearchPage extends StatefulWidget {final List<Chat> datas;const SearchPage({this.datas});@override_SearchPageState createState() => _SearchPageState();}class _SearchPageState extends State<SearchPage> {List<Chat> _models = [];String _searchStr = '';TextStyle _normalStyle = TextStyle(fontSize: 16,color: Colors.black,);TextStyle _highlightStyle = TextStyle(fontSize: 16,color: Colors.green,);//搜索后的部分高亮富文本显示Widget _title(String name) {List<TextSpan> spans = [];List<String> strs = name.split(_searchStr);for (int i = 0; i < strs.length; i++) {String str = strs[i]; //拿出字符串if (str == '' && i < strs.length - 1) {//空字符字符串:1.以这个字符开头,2 以这个字符结束,3.连续出现这个字符spans.add(TextSpan(text: _searchStr, style: _highlightStyle));} else {spans.add(TextSpan(text: str, style: _normalStyle));if (i < strs.length - 1) {spans.add(TextSpan(text: _searchStr, style: _highlightStyle));}}}return RichText(text: TextSpan(children: spans),);}//搜索算法void _searchData(String text) {//每次进来都是重新搜索!_models.clear();_searchStr = text;if (text.length > 0) {//你有内容我就搜索!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(() {});}@overrideWidget build(BuildContext context) {return Scaffold(body: Column(children: <Widget>[SearchBar(onChanged: (text) {_searchData(text);},),Expanded(flex: 1,child: MediaQuery.removePadding(context: context,removeTop: true,removeBottom: true,child: ListView.builder(itemCount: _models.length, itemBuilder: _buildCellForRow),))],),);}Widget _buildCellForRow(BuildContext context, int index) {return ListTile(title: _title(_models[index].name),subtitle: Container(alignment: Alignment.bottomCenter,padding: EdgeInsets.only(right: 10),height: 25,child: Text(_models[index].message,overflow: TextOverflow.ellipsis,),),leading: Container(width: 44,height: 44,decoration: BoxDecoration(borderRadius: BorderRadius.circular(6.0),image: DecorationImage(image: NetworkImage(_models[index].imageUrl))),)); //聊天Cell}}
搜索算法
搜索后的部分高亮富文本显示

