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

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

作者: 浅墨入画 | 来源:发表于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