实现的效果如下:
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});
@override
Widget 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(() {});
}
}
@override
Widget 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 = [];
@override
void initState() {
super.initState();
print('Chat的init来了!');
getDatas()
.then((List<Chat> datas) {
print('数据来了!');
print('更新数据');
setState(() {
_datas = datas;
});
})
.catchError((e) {
print('错误$e');
})
.whenComplete(() {
print('完毕!');
});
}
@override
void 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}');
}
}
@override
Widget 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))),
));
}
@override
bool 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(() {});
}
@override
Widget 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
}
}