前面我们开发了微信项目的发现页面与我的页面,下面我们来开发通讯录页面
通讯录导航栏
- 新建
friends
目录,把friends_page.dart
页面放入;再创建数据文件friends_data.dart
// friends_data.dart文件
class Friends {
Friends({this.imageUrl, this.name, this.indexLetter});
final String imageUrl;
final String name;
final String indexLetter;
}
List<Friends> datas = [
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/27.jpg',
name: 'Lina',
indexLetter: 'L'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/17.jpg',
name: '菲儿',
indexLetter: 'F'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/16.jpg',
name: '安莉',
indexLetter: 'A'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/31.jpg',
name: '阿贵',
indexLetter: 'A'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/22.jpg',
name: '贝拉',
indexLetter: 'B'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/37.jpg',
name: 'Lina',
indexLetter: 'L'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/18.jpg',
name: 'Nancy',
indexLetter: 'N'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/47.jpg',
name: '扣扣',
indexLetter: 'K'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/3.jpg',
name: 'Jack',
indexLetter: 'J'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/5.jpg',
name: 'Emma',
indexLetter: 'E'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/24.jpg',
name: 'Abby',
indexLetter: 'A'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/15.jpg',
name: 'Betty',
indexLetter: 'B'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/13.jpg',
name: 'Tony',
indexLetter: 'T'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/26.jpg',
name: 'Jerry',
indexLetter: 'J'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/men/36.jpg',
name: 'Colin',
indexLetter: 'C'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/12.jpg',
name: 'Haha',
indexLetter: 'H'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/11.jpg',
name: 'Ketty',
indexLetter: 'K'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/13.jpg',
name: 'Lina',
indexLetter: 'L'),
Friends(
imageUrl: 'https://randomuser.me/api/portraits/women/23.jpg',
name: 'Lina',
indexLetter: 'L'),
];
- 为了修改
AppBar
背景色,我们之前是在每个页面配置主题色
;其实我们可以新建文件来配置全局常量
// 之前配置背景色方式
class _FriendsPageState extends State<FriendsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// 配置AppBar背景色
backgroundColor: Color.fromRGBO(220, 220, 220, 1.0),
title: const Text('通讯录'),
),
......
- 新建文件
const.dart
来配置全局常量
import 'package:flutter/material.dart';
//主题色
//const修饰的常量,名称首字母可以是大写
const Color WeChatThemeColor = Color.fromRGBO(220, 220, 220, 1.0);
//屏幕宽度
//屏幕宽度是通过计算来获取,不能定义成常量;首字母需要小写
double screenWidth(BuildContext context) => MediaQuery.of(context).size.width;
double screenHeigth(BuildContext context) => MediaQuery.of(context).size.height;
- 使用
全局常量
来配置AppBar
背景色
// 导入头文件
import '../const.dart';
class _FriendsPageState extends State<FriendsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: WeChatThemeColor,
title: const Text('通讯录'),
),
......
-
通讯录页面
添加右上角添加好友
按钮
class _FriendsPageState extends State<FriendsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// 右上角添加好友按钮
actions: [
GestureDetector(
onTap: (){
// 点击跳转添加好友页面
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DiscoverChildPage(
title: '添加朋友',
)));
},
child: Container(
margin: EdgeInsets.only(right: 10),
child: Image(
image: AssetImage('images/icon_friends_add.png'),
width: 25,
),
)
)
],
backgroundColor: WeChatThemeColor,
title: const Text('通讯录'),
),
body: const Center(
child: Text('通讯录'),
),
);
}
}
运行效果
通讯录列表
Flutter
界面是否显示一般两种判断方法
- 界面内部判断,根据
成员是否有值
->是否显示
- 创建cell的时候,
通过参数判断
- 创建通讯录列表
Cell
,由于通讯录前四条数据是固定的
,需要本地图片资源属性imageAssets
class _FriendCell extends StatelessWidget {
_FriendCell(
{this.imageUrl,
this.name,
this.groupTitle,
this.imageAssets});
final String imageUrl;
final String name;
final String groupTitle;
final String imageAssets;
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Row(
children: [
Container(
margin: EdgeInsets.all(10),
width: 34,
height: 34,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
// 装饰器image
image: DecorationImage(
image: imageUrl != null
? NetworkImage(imageUrl)
: AssetImage(imageAssets),
),
),
), //图片
Container(
width: screenWidth(context) - 54,
child: Column(
children: [
Container(
alignment: Alignment.centerLeft,
height: 54,
child: Text(
name,
style: TextStyle(fontSize: 18),
),
),
// Cell下划线
Container(
height: 0.5,
color: WeChatThemeColor,
)
],
),
), //昵称+分割线
],
),
);
}
}
-
Friends
模型中添加获取本地资源的属性imageAssets
class Friends {
Friends({
this.imageAssets,
this.imageUrl,
this.name,
this.indexLetter
});
final String imageAssets;
final String imageUrl;
final String name;
final String indexLetter;
}
- 通讯录列表
ListView
class _FriendsPageState extends State<FriendsPage> {
// 前四条固定数据
final List<Friends> _headerData = [
Friends(imageAssets: 'images/新的朋友.png', name: '新的朋友'),
Friends(imageAssets: 'images/群聊.png', name: '群聊'),
Friends(imageAssets: 'images/标签.png', name: '标签'),
Friends(imageAssets: 'images/公众号.png', name: '公众号'),
];
Widget _itemForRow(BuildContext context, int index) {
//显示头部4个Cell
if(index < _headerData.length) {
return _FriendCell(
imageAssets: _headerData[index].imageAssets,
name: _headerData[index].name);
}
// 由于列表前四条数据是固定的,数据源需要减去4才能从零开始显示
return _FriendCell(imageUrl: datas[index - 4].imageUrl,
name: datas[index - 4].name);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DiscoverChildPage(
title: '添加朋友',
)));
},
child: Container(
margin: EdgeInsets.only(right: 10),
child: Image(
image: AssetImage('images/icon_friends_add.png'),
width: 25,
),
)
)
],
backgroundColor: WeChatThemeColor,
title: const Text('通讯录'),
),
body: Container(
color: WeChatThemeColor,
child: ListView.builder(
itemBuilder: _itemForRow,
itemCount: datas.length + _headerData.length,
),
),
);
}
}
运行效果
显示分组cell的头
- 给
Cell
添加分组头,Alignment.centerLeft
属性让分组头内容居左
class _FriendCell extends StatelessWidget {
_FriendCell(
{ this.imageUrl,
this.name,
this.groupTitle,
this.imageAssets});
final String imageUrl;
final String name;
final String groupTitle;
final String imageAssets;
@override
Widget build(BuildContext context) {
return Column(
children: [
// 分组头部
Container(
// 分组内容居左
alignment: Alignment.centerLeft,
padding: EdgeInsets.only(left: 10),
height: groupTitle != null ? 30 : 0,
color: WeChatThemeColor,
child: groupTitle != null
? Text(
groupTitle,
style: TextStyle(color: Colors.grey),
)
: null,
),
Container(
color: Colors.white,
child: Row(
children: [
Container(
margin: EdgeInsets.all(10),
width: 34,
height: 34,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
// 装饰器image
image: DecorationImage(
image: imageUrl != null
? NetworkImage(imageUrl)
: AssetImage(imageAssets),
),
),
), //图片
Container(
width: screenWidth(context) - 54,
child: Column(
children: [
Container(
alignment: Alignment.centerLeft,
height: 54,
child: Text(
name,
style: TextStyle(fontSize: 18),
),
),
Container(
height: 0.5,
color: WeChatThemeColor,
)
],
),
), //昵称+分割线
],
),
),
],
);
}
}
- 构造页面数据,
initState
方法只有在_FriendsPageState
页面载入的时候才会执行,热重载
是不会执行的。
切换Tab
,flutter页面会销毁,重新进入通讯录页面
相当于重新载入,会执行initState
方法。
class _FriendsPageState extends State<FriendsPage> {
final List<Friends> _headerData = [
Friends(imageAssets: 'images/新的朋友.png', name: '新的朋友'),
Friends(imageAssets: 'images/群聊.png', name: '群聊'),
Friends(imageAssets: 'images/标签.png', name: '标签'),
Friends(imageAssets: 'images/公众号.png', name: '公众号'),
];
final List<Friends> _listDatas = [];
@override
// _FriendsPageState页面载入的时候会执行,热重载是不会执行的
void initState() {
// TODO: implement initState
super.initState();
// 创建数据, 链式表达
_listDatas..addAll(datas)..addAll(datas); // 等价于 _listDatas.addAll(datas);_listDatas.addAll(datas);
// 数据排序
_listDatas.sort((Friends a, Friends b) {
return a.indexLetter.compareTo(b.indexLetter);
});
}
......
-
_itemForRow
方法调用修改
Widget _itemForRow(BuildContext context, int index) {
//显示头部4个Cell
if(index < _headerData.length) {
return _FriendCell(
imageAssets: _headerData[index].imageAssets,
name: _headerData[index].name);
}
// 剩下的Cell
// 是否显示组名字
bool _hiddenIndexLetter = (index - 4 > 0 &&
_listDatas[index - 4].indexLetter == _listDatas[index - 5].indexLetter);
//显示头
return _FriendCell(
imageUrl: _listDatas[index - 4].imageUrl,
name: _listDatas[index - 4].name,
// 分组头部数据
groupTitle: _hiddenIndexLetter ? null : _listDatas[index - 4].indexLetter,
);
}
// 注意⚠️ListView -> itemCount使用_listDatas数据
body: Container(
color: WeChatThemeColor,
child: ListView.builder(
itemBuilder: _itemForRow,
itemCount: _listDatas.length + _headerData.length,
),
),
运行效果
显示索引条
-
friends_data.dart
文件中添加右侧索引条数据
const INDEX_WORDS = [
'🔍',
'☆',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z'
];
- 构造索引条数据
class _FriendsPageState extends State<FriendsPage> {
final List<Widget> words = [];
......
@override
// _FriendsPageState页面载入的时候会执行,热重载是不会执行的
void initState() {
// TODO: implement initState
super.initState();
// 创建数据, 链式表达
_listDatas..addAll(datas)..addAll(datas); // 等价于 _listDatas.addAll(datas);_listDatas.addAll(datas);
// 数据排序
_listDatas.sort((Friends a, Friends b) {
return a.indexLetter.compareTo(b.indexLetter);
});
// 索引条数据
for (int i = 0; i < INDEX_WORDS.length; i++) {
words.add(Expanded(child: Text(
INDEX_WORDS[i],
style: TextStyle(fontSize: 10, color: Colors.grey),
)));
}
}
......
- 页面添加
悬浮索引条
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DiscoverChildPage(
title: '添加朋友',
)));
},
child: Container(
margin: EdgeInsets.only(right: 10),
child: Image(
image: AssetImage('images/icon_friends_add.png'),
width: 25,
),
)
)
],
backgroundColor: WeChatThemeColor,
title: const Text('通讯录'),
),
body: Stack(
children: [
Container(
color: WeChatThemeColor,
child: ListView.builder(
itemBuilder: _itemForRow,
itemCount: _listDatas.length + _headerData.length,
),
),
// 悬浮索引条
Positioned(
right: 0.0,
top: screenHeigth(context) / 8,
height: screenHeigth(context) / 2,
width: 30,
child: Column(
children: words,
),
),
],
),
);
}
运行效果
抽取索引条
- 新建
index_bar.dart
文件,抽取索引条
- 把
words
以及循环遍历数据,抽取到index_bar.dart
文件
import 'package:flutter/material.dart';
import '../const.dart';
import 'friends_data.dart';
class IndexBar extends StatefulWidget {
@override
State<StatefulWidget> createState() => _IndexBarState();
}
class _IndexBarState extends State<IndexBar> {
final List<Widget> words = [];
@override
void initState() {
// TODO: implement initState
super.initState();
// 右侧索引数据
for (int i = 0; i < INDEX_WORDS.length; i++) {
words.add(Expanded(child: Text(
INDEX_WORDS[i],
style: TextStyle(fontSize: 10, color: Colors.grey),
)));
}
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Positioned(
right: 0.0,
top: screenHeigth(context) / 8,
height: screenHeigth(context) / 2,
width: 30,
child: Column(
children: words,
),
);
}
}
-
friends_page.dart
文件使用IndexBar
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DiscoverChildPage(
title: '添加朋友',
)));
},
child: Container(
margin: EdgeInsets.only(right: 10),
child: Image(
image: AssetImage('images/icon_friends_add.png'),
width: 25,
),
)
)
],
backgroundColor: WeChatThemeColor,
title: const Text('通讯录'),
),
body: Stack(
children: [
Container(
color: WeChatThemeColor,
child: ListView.builder(
itemBuilder: _itemForRow,
itemCount: _listDatas.length + _headerData.length,
),
),
// 悬浮索引条
IndexBar()
],
),
);
}
运行效果
选中索引条
import 'package:flutter/material.dart';
import '../const.dart';
import 'friends_data.dart';
class IndexBar extends StatefulWidget {
@override
State<StatefulWidget> createState() => _IndexBarState();
}
// 获取选中的item的字符!!
String getIndex(BuildContext context, Offset globalPosition) {
// 获取当前小部件的盒子
RenderBox box = context.findRenderObject();
// 获取y值 globalToLocald当前位置距离部件原点(左上角)的位置
double y = box.globalToLocal(globalPosition).dy;
// 算出字符高度
var itemHeight = screenHeigth(context) / 2 / INDEX_WORDS.length;
// 算出第几个item, clamp约束index范围值
int index = (y ~/ itemHeight).clamp(0, INDEX_WORDS.length - 1);
return INDEX_WORDS[index];
}
class _IndexBarState extends State<IndexBar> {
// 索引条选中 背景色与文字颜色
Color _bkColor = Color.fromRGBO(1, 1, 1, 0.0);
Color _textColor = Colors.black;
@override
void initState() {
// TODO: implement initState
super.initState();
}
@override
Widget build(BuildContext context) {
// 与界面相关的数据要放入build中
final List<Widget> words = [];
for (int i = 0; i < INDEX_WORDS.length; i++) {
words.add(Expanded(child: Text(
INDEX_WORDS[i],
style: TextStyle(fontSize: 10, color: _textColor),
)));
}
// TODO: implement build
return Positioned(
right: 0.0,
top: screenHeigth(context) / 8,
height: screenHeigth(context) / 2,
width: 30,
// 添加手势
child: GestureDetector(
// 拖拽手势
onVerticalDragUpdate: (DragUpdateDetails details) {
String str = getIndex(context, details.globalPosition);
print('选中的是$str');
},
// 点击索引
onVerticalDragDown:(DragDownDetails details){
setState(() {
_bkColor = Color.fromRGBO(1, 1, 1, 0.5);
_textColor = Colors.white;
});
},
// 点击结束,颜色值还原
onVerticalDragEnd:(DragEndDetails details){
setState(() {
_bkColor = Color.fromRGBO(1, 1, 1, 0.0);
_textColor = Colors.black;
});
},
child: Container(
color: _bkColor,
child: Column(
children: words,
),
),
),
);
}
}
拖拽索引条打印选中item
网友评论