美文网首页
微信项目 - 通讯录与索引条

微信项目 - 通讯录与索引条

作者: 浅墨入画 | 来源:发表于2021-11-30 23:30 被阅读0次

前面我们开发了微信项目的发现页面与我的页面,下面我们来开发通讯录页面

通讯录导航栏

  • 新建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界面是否显示一般两种判断方法

  1. 界面内部判断,根据成员是否有值 -> 是否显示
  2. 创建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文件,抽取索引条
  1. 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

相关文章

网友评论

      本文标题:微信项目 - 通讯录与索引条

      本文链接:https://www.haomeiwen.com/subject/vcugtrtx.html