实现的效果如下:
image.png

1 定义聊天界面ChatPage

显示一个ListView,在ListView的顶部cell是SearchCell,其他cell为统一的聊天记录cell

网络请求封装 http_manager.dart

  1. import 'package:dio/dio.dart';
  2. class HttpRespons {
  3. final int status;
  4. final body;
  5. HttpRespons({this.status, this.body});
  6. }
  7. class HttpManager {
  8. static final Dio dio = Dio();
  9. static Future<HttpRespons> request(String url,
  10. {String method = 'get',
  11. Map<String, String> headers,
  12. Map<String, dynamic> queryParameters,
  13. int timeOut}) async {
  14. //1.创建配置,什么方式请求
  15. final options = Options(headers:headers,method: method, receiveTimeout: timeOut);
  16. //2.发网络请求
  17. Response response = await dio.request(
  18. url,
  19. queryParameters: queryParameters,
  20. options: options,
  21. );
  22. // print(response.data);
  23. return HttpRespons(status:response.statusCode,body: response.data);
  24. }
  25. }
  26. Future<HttpRespons> get(url,
  27. { Map<String, String> headers,
  28. Map<String, dynamic> queryParameters,
  29. int timeout}) {
  30. return HttpManager.request(url,headers: headers,
  31. queryParameters: queryParameters, method: 'get', timeOut: timeout);
  32. }

需要显示的数据,数据模型定义 Chat

  1. class Chat {
  2. final String name;
  3. final String message;
  4. final String imageUrl;
  5. const Chat({this.name, this.message, this.imageUrl});
  6. factory Chat.fromJson(Map json) {
  7. return Chat(
  8. name: json['name'],
  9. message: json['message'],
  10. imageUrl: json['imageUrl'],
  11. );
  12. }
  13. }

SearchCell和SearchBar的定义 search_bar.dart

  1. import 'package:flutter/material.dart';
  2. import 'chat_page.dart';
  3. import 'search_page.dart';
  4. class SearchCell extends StatelessWidget {
  5. final List<Chat> datas;
  6. const SearchCell({this.datas});
  7. @override
  8. Widget build(BuildContext context) {
  9. return GestureDetector(
  10. onTapDown: (d) {
  11. Navigator.of(context).push(MaterialPageRoute(builder: (context) {
  12. return SearchPage(
  13. datas: datas,
  14. );
  15. }));
  16. },
  17. child: Container(
  18. height: 44,
  19. color: Color.fromRGBO(220, 220, 220, 1),
  20. padding: EdgeInsets.only(left: 5, right: 5, bottom: 5),
  21. child: Stack(
  22. alignment: Alignment.center,
  23. children: [
  24. Container(
  25. decoration: BoxDecoration(
  26. color: Colors.white,
  27. borderRadius: BorderRadius.circular(6.0),
  28. ),
  29. ), //白色底
  30. Row(
  31. mainAxisAlignment: MainAxisAlignment.center,
  32. children: <Widget>[
  33. Image(
  34. image: AssetImage('images/放大镜b.png'),
  35. width: 15,
  36. color: Colors.grey,
  37. ),
  38. Text(
  39. ' 搜索',
  40. style: TextStyle(fontSize: 15, color: Colors.grey),
  41. ),
  42. ],
  43. ), //中间的文字和放大镜
  44. ],
  45. ),
  46. ),
  47. );
  48. }
  49. }
  50. class SearchBar extends StatefulWidget {
  51. final ValueChanged<String> onChanged;
  52. const SearchBar({this.onChanged});
  53. @override
  54. _SearchBarState createState() => _SearchBarState();
  55. }
  56. class _SearchBarState extends State<SearchBar> {
  57. final TextEditingController _controller = TextEditingController();
  58. bool _showClear = false;
  59. _onChange(String text) {
  60. if (text.length > 0) {
  61. widget.onChanged(text);
  62. setState(() {
  63. _showClear = true;
  64. });
  65. } else {
  66. widget.onChanged('');
  67. _showClear = false;
  68. setState(() {});
  69. }
  70. }
  71. @override
  72. Widget build(BuildContext context) {
  73. return Container(
  74. height: 84,
  75. color: Color.fromRGBO(220, 220, 220, 1),
  76. child: Column(
  77. children: <Widget>[
  78. SizedBox(
  79. height: 40,
  80. ),
  81. Container(
  82. height: 44,
  83. // color: Colors.red,
  84. child: Row(
  85. children: <Widget>[
  86. Container(
  87. width: MediaQuery.of(context).size.width - 60,
  88. height: 34,
  89. margin: EdgeInsets.only(left: 5),
  90. padding: EdgeInsets.only(left: 5, right: 5),
  91. decoration: BoxDecoration(
  92. color: Colors.white,
  93. borderRadius: BorderRadius.circular(6.0)),
  94. child: Row(
  95. children: <Widget>[
  96. Image(
  97. image: AssetImage('images/放大镜b.png'),
  98. width: 20,
  99. color: Colors.grey,
  100. ), //放大镜
  101. Expanded(
  102. flex: 1,
  103. child: TextField(
  104. controller: _controller,
  105. onChanged: _onChange,
  106. autofocus: true,
  107. cursorColor: Colors.green,
  108. style: TextStyle(
  109. fontSize: 18.0,
  110. color: Colors.black,
  111. ),
  112. decoration: InputDecoration(
  113. contentPadding:
  114. EdgeInsets.only(left: 5, bottom: 10),
  115. border: InputBorder.none,
  116. hintText: '搜索'),
  117. ),
  118. ), //输入框
  119. _showClear
  120. ? GestureDetector(
  121. onTap: () {
  122. setState(() {
  123. _controller.clear();
  124. _onChange(''); //如果不出发onChange,那么外接将无法回去数据!
  125. });
  126. },
  127. child: Icon(
  128. Icons.cancel,
  129. size: 20,
  130. color: Colors.grey,
  131. ),
  132. )
  133. : Container(), //取消按钮
  134. ],
  135. ),
  136. ), //左边的圆角背景
  137. GestureDetector(
  138. onTap: () {
  139. Navigator.pop(context);
  140. },
  141. child: Text(' 取消'),
  142. )
  143. ],
  144. ),
  145. ), //下面的搜索条
  146. ],
  147. ),
  148. );
  149. }
  150. }

聊天页面chat_page.dart

  1. import 'dart:async';
  2. import 'package:flutter/material.dart';
  3. import 'search_bar.dart';
  4. import '../../tools/http_manager.dart' as http;
  5. class ChatPage extends StatefulWidget {
  6. @override
  7. _ChatPageState createState() => _ChatPageState();
  8. }
  9. class _ChatPageState extends State<ChatPage> with AutomaticKeepAliveClientMixin {
  10. List<Chat> _datas = [];
  11. @override
  12. void initState() {
  13. super.initState();
  14. print('Chat的init来了!');
  15. getDatas()
  16. .then((List<Chat> datas) {
  17. print('数据来了!');
  18. print('更新数据');
  19. setState(() {
  20. _datas = datas;
  21. });
  22. })
  23. .catchError((e) {
  24. print('错误$e');
  25. })
  26. .whenComplete(() {
  27. print('完毕!');
  28. });
  29. }
  30. @override
  31. void dispose(){
  32. super.dispose();
  33. }
  34. Future<List<Chat>> getDatas() async {
  35. //不再是取消连接了!
  36. final response = await http
  37. .get('http://rap2api.taobao.org/app/mock/283772/api/chat/list',timeout: 1000);
  38. if (response.status == 200) {
  39. //获取相应数据,并转成Map类型!
  40. final responseBody = response.body;
  41. //转模型数组 map中遍历的结果需要返回出去
  42. List<Chat> chatList = responseBody['chat_lists'].map<Chat>((item) {
  43. return Chat.fromJson(item);
  44. }).toList();
  45. return chatList;
  46. } else {
  47. throw Exception('statusCode:${response.status}');
  48. }
  49. }
  50. @override
  51. Widget build(BuildContext context) {
  52. super.build(context);
  53. return Scaffold(
  54. appBar: AppBar(
  55. elevation: 0.0,
  56. title: Text('微信'),
  57. backgroundColor: Color.fromRGBO(220, 220, 220, 1),
  58. ),
  59. body: Container(
  60. child: _datas.length == 0
  61. ? Center(
  62. child: Text("Loading..."),
  63. )
  64. : ListView.builder(
  65. itemCount: _datas.length+1, //search栏+ 聊天列表
  66. itemBuilder: _buildCellForRow,
  67. ),
  68. ),
  69. );
  70. }
  71. Widget _buildCellForRow(BuildContext context, int index) {
  72. if(index == 0){ //search栏在列表的顶部
  73. return SearchCell(
  74. datas: _datas,
  75. );
  76. }
  77. //保证从模型数组中取出正确的数据(从0开始),search栏在列表的顶部
  78. index--;
  79. return ListTile(
  80. title: Text(_datas[index].name),
  81. subtitle: Container(
  82. alignment: Alignment.bottomCenter,
  83. padding: EdgeInsets.only(right: 10),
  84. height: 25,
  85. child: Text(
  86. _datas[index].message,
  87. overflow: TextOverflow.ellipsis,
  88. ),
  89. ),
  90. leading: Container(
  91. width: 44,
  92. height: 44,
  93. decoration: BoxDecoration(
  94. borderRadius: BorderRadius.circular(6.0),
  95. image: DecorationImage(
  96. image: NetworkImage(_datas[index].imageUrl))),
  97. ));
  98. }
  99. @override
  100. bool get wantKeepAlive => true;
  101. }

2 定义搜索页面 SearchPage

显示一个ListView,ListView的顶部cell是SearchBar,其他cell为统一的聊天搜索后显示部分文字高亮的cell

  1. import 'package:flutter/material.dart';
  2. import 'search_bar.dart';
  3. import 'chat_page.dart';
  4. class SearchPage extends StatefulWidget {
  5. final List<Chat> datas;
  6. const SearchPage({this.datas});
  7. @override
  8. _SearchPageState createState() => _SearchPageState();
  9. }
  10. class _SearchPageState extends State<SearchPage> {
  11. List<Chat> _models = [];
  12. String _searchStr = '';
  13. TextStyle _normalStyle = TextStyle(
  14. fontSize: 16,
  15. color: Colors.black,
  16. );
  17. TextStyle _highlightStyle = TextStyle(
  18. fontSize: 16,
  19. color: Colors.green,
  20. );
  21. //搜索后的部分高亮富文本显示
  22. Widget _title(String name) {
  23. List<TextSpan> spans = [];
  24. List<String> strs = name.split(_searchStr);
  25. for (int i = 0; i < strs.length; i++) {
  26. String str = strs[i]; //拿出字符串
  27. if (str == '' && i < strs.length - 1) {
  28. //空字符字符串:1.以这个字符开头,2 以这个字符结束,3.连续出现这个字符
  29. spans.add(TextSpan(text: _searchStr, style: _highlightStyle));
  30. } else {
  31. spans.add(TextSpan(text: str, style: _normalStyle));
  32. if (i < strs.length - 1) {
  33. spans.add(TextSpan(text: _searchStr, style: _highlightStyle));
  34. }
  35. }
  36. }
  37. return RichText(
  38. text: TextSpan(children: spans),
  39. );
  40. }
  41. //搜索算法
  42. void _searchData(String text) {
  43. //每次进来都是重新搜索!
  44. _models.clear();
  45. _searchStr = text;
  46. if (text.length > 0) {
  47. //你有内容我就搜索!
  48. for (int i = 0; i < widget.datas.length; i++) {
  49. String name = widget.datas[i].name;
  50. if (name.contains(text)) {
  51. _models.add(widget.datas[i]);
  52. }
  53. }
  54. }
  55. setState(() {});
  56. }
  57. @override
  58. Widget build(BuildContext context) {
  59. return Scaffold(
  60. body: Column(
  61. children: <Widget>[
  62. SearchBar(
  63. onChanged: (text) {
  64. _searchData(text);
  65. },
  66. ),
  67. Expanded(
  68. flex: 1,
  69. child: MediaQuery.removePadding(
  70. context: context,
  71. removeTop: true,
  72. removeBottom: true,
  73. child: ListView.builder(
  74. itemCount: _models.length, itemBuilder: _buildCellForRow),
  75. ))
  76. ],
  77. ),
  78. );
  79. }
  80. Widget _buildCellForRow(BuildContext context, int index) {
  81. return ListTile(
  82. title: _title(_models[index].name),
  83. subtitle: Container(
  84. alignment: Alignment.bottomCenter,
  85. padding: EdgeInsets.only(right: 10),
  86. height: 25,
  87. child: Text(
  88. _models[index].message,
  89. overflow: TextOverflow.ellipsis,
  90. ),
  91. ),
  92. leading: Container(
  93. width: 44,
  94. height: 44,
  95. decoration: BoxDecoration(
  96. borderRadius: BorderRadius.circular(6.0),
  97. image: DecorationImage(
  98. image: NetworkImage(_models[index].imageUrl))),
  99. )); //聊天Cell
  100. }
  101. }

搜索算法

image.png

搜索后的部分高亮富文本显示

image.png