Flutter伪微信-010-使用ListView实现微信通讯录

707 阅读3分钟

前言

通过了解分析微信的通讯录界面,我们拆分功能点如下:

  • 1.顶部搜索按钮 未完成 ❌
  • 2.通讯录列表 完成 ✅
  • 3.右侧字母导航 未完成 ❌
  • 4.底部朋友数量汇总 未完成 ❌
  • 5.通讯录列表滑动时,分组字母顶部停留 未完成 ❌

20210615104950687.png

预览

本节完成通讯录列表的渲染

通讯录列表

思路

  • 定义一个Map结构,key:保存分组字母 ,value: 该字母对应的通讯录数据
  • 初始化添加新的朋友群聊标签公众号到Map结构,分组字母为空串
  • 从后端获取通讯录数据,经过分组后添加到Map结构中
  • 使用ListView 进行列表渲染

实现

数据分组

final List<DirectoryGroupData> _list = [
  DirectoryGroupData(groupName: "", list: [
    DirectoryData(
      id: BigInt.from(-1),
      avatar: "images/friend.png",
      name: "新的朋友",
      initial: "",
    ),
    DirectoryData(
      id: BigInt.from(-1),
      avatar: "images/group_chat.png",
      name: "群聊",
      initial: "",
    ),
    DirectoryData(
      id: BigInt.from(-1),
      avatar: "images/label.png",
      name: "标签",
      initial: "",
    ),
    DirectoryData(
      id: BigInt.from(-1),
      avatar: "images/official_accounts.png",
      name: "公众号",
      initial: "",
    )
  ])
];

final List<DirectoryData> _dataList = [
  DirectoryData(
    id: BigInt.from(1),
    avatar:
        "https://d36tnp772eyphs.cloudfront.net/blogs/1/2018/02/Taj-Mahal.jpg",
    name: "啊三",
    initial: "a",
  ),
  DirectoryData(
    id: BigInt.from(11),
    avatar: "https://www.itying.com/images/flutter/1.png",
    name: "阿衰",
    initial: "a",
  ),
  DirectoryData(
    id: BigInt.from(12),
    avatar: "https://www.itying.com/images/flutter/1.png",
    name: "阿里巴巴",
    initial: "a",
  ),
  DirectoryData(
    id: BigInt.from(2),
    avatar: "https://www.itying.com/images/flutter/2.png",
    name: "在水一方",
    initial: "z",
    remarks: "xxxx",
  ),
  DirectoryData(
    id: BigInt.from(2),
    avatar: "https://www.itying.com/images/flutter/2.png",
    name: "晚秋骄阳",
    initial: "w",
  ),
  DirectoryData(
    id: BigInt.from(2),
    avatar: "https://www.itying.com/images/flutter/3.png",
    name: "云淡风轻",
    initial: "y",
  ),
  DirectoryData(
    id: BigInt.from(2),
    avatar: "https://www.itying.com/images/flutter/3.png",
    name: "人已老心未老",
    initial: "r",
  ),
  DirectoryData(
    id: BigInt.from(2),
    avatar: "https://www.itying.com/images/flutter/1.png",
    name: "回忆",
    initial: "h",
  ),
  DirectoryData(
    id: BigInt.from(2),
    avatar: "https://www.itying.com/images/flutter/1.png",
    name: "人生岁月老添辉",
    initial: "r",
  ),
  DirectoryData(
    id: BigInt.from(3),
    avatar: "https://www.itying.com/images/flutter/2.png",
    name: "浪漫人生",
    initial: "l",
  ),
  DirectoryData(
    id: BigInt.from(4),
    avatar: "https://www.itying.com/images/flutter/1.png",
    name: "时光荏苒",
    initial: "s",
  ),
  DirectoryData(
    id: BigInt.from(4),
    avatar: "https://www.itying.com/images/flutter/3.png",
    name: "上善若水",
    initial: "s",
  ),
];
@override
void initState() {
  super.initState();

  Map<String, List<DirectoryData>> map = {
    for (var e in _dataList)
      e.initial: _dataList.where((item) => item.initial == e.initial).toList()
  };
  var tempData = map.keys
      .map((key) => DirectoryGroupData(groupName: key, list: map[key]!))
      .toList();
  _list.addAll(tempData);
  _list.sort((a, b) => a.groupName.compareTo(b.groupName));
}

下划线构建

Widget _buildUnderline(
    List<DirectoryData> data, int currentIndex, bool isLastGroup) {
  int dataSize = data.length;
  bool isLastItem = (dataSize - 1 == currentIndex);
  // 最后一组,最后一个不需要缩进,直接添加下划线,其余的添加缩进下划线
  if (isLastGroup) {
    return Padding(
      padding: isLastItem
          ? const EdgeInsets.only()
          : const EdgeInsets.only(left: 80),
      child: const Divider(),
    );
  }

  //如果只有一个元素不添加下划线
  if (dataSize <= 1 || isLastItem) {
    return const SizedBox.shrink();
  }
  
  //其余添加缩进下划线
  return const Padding(
    padding: EdgeInsets.only(left: 80),
    child: Divider(),
  );
}

添加通讯录页分组背景颜色

import 'package:flutter/material.dart';

// 本 app 颜色
class Style {

  ///脚手架组件背景颜色
  static const Color scaffoldBackgroundColor = Color.fromRGBO(247,247,247,5);

  ///AppBar背景颜色
  static const Color appBarBackgroundColor = Color.fromRGBO(247,247,247,5);
  static const Color appBarTextColor = Colors.black;

  ///底部导航栏背景颜色
  static const Color navBarBgColor = Colors.white70;
  ///底部导航栏选中颜色
  static const Color navBarSelectedItemColor = Colors.green;
  ///底部导航栏不选中颜色
  static const Color navBarUnSelectedItemColor = Colors.black45;

  ///内容背景颜色
  static const Color contentBackgroundColor = Colors.white;

  ///消息页搜索背景颜色
  static const Color messageSearchBackgroundColor = Color.fromRGBO(247,247,247,5);
  ///通讯录页分组背景
  static const Color groupBackgroundColor = Color.fromRGBO(247,247,247,5);
}

完整代码

import 'package:flutter/material.dart';
import 'package:pseudo_we_chat/constant/style.dart';

class DirectoryPage extends StatefulWidget {
  const DirectoryPage({Key? key}) : super(key: key);

  @override
  State<DirectoryPage> createState() => _DirectoryPageState();
}

class _DirectoryPageState extends State<DirectoryPage> {
  final List<DirectoryGroupData> _list = [
    DirectoryGroupData(groupName: "", list: [
      DirectoryData(
        id: BigInt.from(-1),
        avatar: "images/friend.png",
        name: "新的朋友",
        initial: "",
      ),
      DirectoryData(
        id: BigInt.from(-1),
        avatar: "images/group_chat.png",
        name: "群聊",
        initial: "",
      ),
      DirectoryData(
        id: BigInt.from(-1),
        avatar: "images/label.png",
        name: "标签",
        initial: "",
      ),
      DirectoryData(
        id: BigInt.from(-1),
        avatar: "images/official_accounts.png",
        name: "公众号",
        initial: "",
      )
    ])
  ];

  final List<DirectoryData> _dataList = [
    DirectoryData(
      id: BigInt.from(1),
      avatar:
          "https://d36tnp772eyphs.cloudfront.net/blogs/1/2018/02/Taj-Mahal.jpg",
      name: "啊三",
      initial: "a",
    ),
    DirectoryData(
      id: BigInt.from(11),
      avatar: "https://www.itying.com/images/flutter/1.png",
      name: "阿衰",
      initial: "a",
    ),
    DirectoryData(
      id: BigInt.from(12),
      avatar: "https://www.itying.com/images/flutter/1.png",
      name: "阿里巴巴",
      initial: "a",
    ),
    DirectoryData(
      id: BigInt.from(2),
      avatar: "https://www.itying.com/images/flutter/2.png",
      name: "在水一方",
      initial: "z",
      remarks: "xxxx",
    ),
    DirectoryData(
      id: BigInt.from(2),
      avatar: "https://www.itying.com/images/flutter/2.png",
      name: "晚秋骄阳",
      initial: "w",
    ),
    DirectoryData(
      id: BigInt.from(2),
      avatar: "https://www.itying.com/images/flutter/3.png",
      name: "云淡风轻",
      initial: "y",
    ),
    DirectoryData(
      id: BigInt.from(2),
      avatar: "https://www.itying.com/images/flutter/3.png",
      name: "人已老心未老",
      initial: "r",
    ),
    DirectoryData(
      id: BigInt.from(2),
      avatar: "https://www.itying.com/images/flutter/1.png",
      name: "回忆",
      initial: "h",
    ),
    DirectoryData(
      id: BigInt.from(2),
      avatar: "https://www.itying.com/images/flutter/1.png",
      name: "人生岁月老添辉",
      initial: "r",
    ),
    DirectoryData(
      id: BigInt.from(3),
      avatar: "https://www.itying.com/images/flutter/2.png",
      name: "浪漫人生",
      initial: "l",
    ),
    DirectoryData(
      id: BigInt.from(4),
      avatar: "https://www.itying.com/images/flutter/1.png",
      name: "时光荏苒",
      initial: "s",
    ),
    DirectoryData(
      id: BigInt.from(4),
      avatar: "https://www.itying.com/images/flutter/3.png",
      name: "上善若水",
      initial: "s",
    ),
  ];

  @override
  void initState() {
    super.initState();

    Map<String, List<DirectoryData>> map = {
      for (var e in _dataList)
        e.initial: _dataList.where((item) => item.initial == e.initial).toList()
    };
    var tempData = map.keys
        .map((key) => DirectoryGroupData(groupName: key, list: map[key]!))
        .toList();
    _list.addAll(tempData);
    _list.sort((a, b) => a.groupName.compareTo(b.groupName));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
            elevation: 0,
            title: const Text(
              "通讯录",
              style: TextStyle(color: Style.appBarTextColor),
            ),
            backgroundColor: Style.appBarBackgroundColor),
        body: Container(
          color: Style.contentBackgroundColor,
          child: ListView.builder(
              itemCount: _list.length,
              itemBuilder: (context, index) {
                List<Widget> listView = [];

                var groupData = _list[index];

                bool isLastGroup = index == _list.length - 1;

                if (groupData.groupName != '') {
                  listView.add(Container(
                      alignment: Alignment.centerLeft,
                      height: 30,
                      color: Style.groupBackgroundColor,
                      padding:
                          const EdgeInsets.only(left: 15, top: 5, bottom: 5),
                      child: Text(groupData.groupName)));
                }

                var listTileList = groupData.list
                    .asMap()
                    .entries
                    .map((item) => Column(
                          children: [
                            //构建列表信息
                            ListTile(
                              leading: item.value.avatar.startsWith("images")
                                  ? Image.asset(
                                      item.value.avatar,
                                      width: 50,
                                      height: 50,
                                    )
                                  : Image.network(
                                      item.value.avatar,
                                      width: 50,
                                      height: 50,
                                      fit: BoxFit.cover,
                                    ),
                              title: Text(item.value.name),
                              subtitle: item.value.remarks != null
                                  ? Text(item.value.remarks!)
                                  : null,
                            ),

                            //构建下划线
                            _buildUnderline(
                                groupData.list, item.key, isLastGroup)
                          ],
                        ))
                    .toList();

                listView.addAll(listTileList);

                return Column(
                  children: listView,
                );
              }),
        ));
  }

  Widget _buildUnderline(
      List<DirectoryData> data, int currentIndex, bool isLastGroup) {
    int dataSize = data.length;
    bool isLastItem = (dataSize - 1 == currentIndex);
    // 最后一组,最后一个不需要缩进,直接添加下划线,其余的添加缩进下划线
    if (isLastGroup) {
      return Padding(
        padding: isLastItem
            ? const EdgeInsets.only()
            : const EdgeInsets.only(left: 80),
        child: const Divider(),
      );
    }

    //如果只有一个元素不添加下划线
    if (dataSize <= 1 || isLastItem) {
      return const SizedBox.shrink();
    }

    //其余添加缩进下划线
    return const Padding(
      padding: EdgeInsets.only(left: 80),
      child: Divider(),
    );
  }
}

class DirectoryData {
  const DirectoryData(
      {required this.id,
      required this.avatar,
      required this.initial,
      required this.name,
      this.remarks});

  /// id
  final BigInt id;

  /// 头像
  final String avatar;

  /// 名称
  final String name;

  /// 首字母
  final String initial;

  /// 备注
  final String? remarks;
}

class DirectoryGroupData {
  const DirectoryGroupData({
    required this.groupName,
    required this.list,
  });

  /// 分组名称
  final String groupName;

  /// 分组数据
  final List<DirectoryData> list;
}

项目地址

github.com/BadKid90s/p…