Flutter伪微信-007-Badge、Avatar组件实现未读小红点

828 阅读3分钟

前言

我们来完成上一节剩下的任务消息未读数量小红点

  • 1.导航栏右边菜单按钮(扫一扫等)未完成❌
  • 2.导航栏下方的搜索框 完成✅
  • 3.聊天列表 完成✅
  • 4.消息表左滑 未完成❌
  • 5.消息未读数量小红点 完成✅

预览

小红点

Badge简介

Badge的构造器如下:

const Badge({
  super.key,
  this.backgroundColor,
  this.textColor,
  this.smallSize,
  this.largeSize,
  this.textStyle,
  this.padding,
  this.alignment,
  this.label,
  this.isLabelVisible = true,
  this.child,
})

其中常用的属性为:

  • backgroundColor: 背景颜色
  • textColor: 文本颜色
  • smallSize: 徽章的直径(如果label为空)。默认为 BadgeTheme 的小尺寸,如果主题值为null,则为 6
  • largeSize: 徽章的高度(如果label为非空)。默认为 BadgeTheme 的大尺寸,如果主题值为 null,则默认为 16。如果默认值被覆盖,则覆盖 paddingalignment 也可能很有用。
  • textStyle: 文本样式
  • padding: 内边距
  • alignment: 对齐位置
  • label: 文本内容
  • isLabelVisible: 是否显示,默认true

实现

徽章与头像确定位置

  • 数值大于99时显示99
  • 数值大于1位数时,调整显示的位置
Badge(
  label: Text("${num > 99 ? '99' : num}"),
  backgroundColor: Colors.red,
  alignment: num < 10
      ? const AlignmentDirectional(40, -6)
      : const AlignmentDirectional(30, -6),
  child: Image.network(
    avatar,
    width: 50,
    height: 50,
    fit: BoxFit.cover,
  ),
)

抽离控件

为了方便管理维护,抽离成单独的组件 新建文件lib/ui/badge_avatar.dart

import 'package:flutter/material.dart';

class BadgeAvatar extends StatelessWidget {
  final String avatar;
  final int num;

  const BadgeAvatar({Key? key, required this.avatar, required this.num})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    if (num == 0) {
      return Image.network(
        avatar,
        width: 50,
        height: 50,
        fit: BoxFit.cover,
      );
    }
    return Badge(
      label: Text("${num > 99 ? '99' : num}"),
      backgroundColor: Colors.red,
      alignment: num < 10
          ? const AlignmentDirectional(40, -6)
          : const AlignmentDirectional(30, -6),
      child: Image.network(
        avatar,
        width: 50,
        height: 50,
        fit: BoxFit.cover,
      ),
    );
  }
}

调整message.dart

import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:pseudo_we_chat/ui/badge_avatar.dart';

import '../../constant/style.dart';

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

  @override
  State<MessagePage> createState() => _MessagePageState();
}

class _MessagePageState extends State<MessagePage> {
  //定义列表数据
  final List<MessageData> _messageList = [
    MessageData(
        id: BigInt.from(1),
        avatar:
            "https://d36tnp772eyphs.cloudfront.net/blogs/1/2018/02/Taj-Mahal.jpg",
        name: "张三123",
        message:
            "ZFLEX对UIKit的一层封装,主要包含一个数据驱动的列表框架、和UIKit中常用控件的链式拓展,ZZFLEX相关资料正在整理中,目前已经开源,ZFLEX对UIKit的一层封装,主要包含一个数据驱动的列表框架、和UIKit中常用控件的链式拓展,ZZFLEX相关资料正在整理中,目前已经开源",
        lastTime: DateTime.now(),
        unReadNum: 13),
    MessageData(
        id: BigInt.from(2),
        avatar: "https://www.itying.com/images/flutter/3.png",
        name: "云淡风轻",
        message: "[图片]",
        lastTime: DateTime.now().subtract(const Duration(minutes: 30)),
        unReadNum: 0),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/2.png",
        name: "魅力人生",
        message: "今天是个好日子。",
        lastTime: DateTime.now().subtract(const Duration(hours: 1)),
        unReadNum: 9),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/1.png",
        name: "随访飘逸",
        message: "你好啊",
        lastTime: DateTime.now().subtract(const Duration(hours: 2)),
        unReadNum: 66),
    MessageData(
        id: BigInt.from(1),
        avatar:
            "https://d36tnp772eyphs.cloudfront.net/blogs/1/2018/02/Taj-Mahal.jpg",
        name: "张三",
        message: "Hello word!",
        lastTime: DateTime.now(),
        unReadNum: 120),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/3.png",
        name: "云淡风轻",
        message: "[图片]",
        lastTime: DateTime.now().subtract(const Duration(minutes: 30)),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/2.png",
        name: "魅力人生",
        message: "今天是个好日子。",
        lastTime: DateTime.now().subtract(const Duration(hours: 1)),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/1.png",
        name: "随访飘逸",
        message: "你好啊",
        lastTime: DateTime.now().subtract(const Duration(hours: 2)),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar:
            "https://d36tnp772eyphs.cloudfront.net/blogs/1/2018/02/Taj-Mahal.jpg",
        name: "张三",
        message: "Hello word!",
        lastTime: DateTime.now(),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/3.png",
        name: "云淡风轻",
        message: "[图片]",
        lastTime: DateTime.now().subtract(const Duration(minutes: 30)),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/2.png",
        name: "魅力人生",
        message: "今天是个好日子。",
        lastTime: DateTime.now().subtract(const Duration(hours: 1)),
        unReadNum: 10),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/1.png",
        name: "随访飘逸",
        message: "你好啊",
        lastTime: DateTime.now().subtract(const Duration(hours: 2)),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar:
            "https://d36tnp772eyphs.cloudfront.net/blogs/1/2018/02/Taj-Mahal.jpg",
        name: "张三",
        message: "Hello word!",
        lastTime: DateTime.now(),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/3.png",
        name: "云淡风轻",
        message: "[图片]",
        lastTime: DateTime.now().subtract(const Duration(minutes: 30)),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/2.png",
        name: "魅力人生",
        message: "今天是个好日子。",
        lastTime: DateTime.now().subtract(const Duration(hours: 1)),
        unReadNum: 1),
    MessageData(
        id: BigInt.from(1),
        avatar: "https://www.itying.com/images/flutter/1.png",
        name: "随访飘逸",
        message: "你好啊",
        lastTime: DateTime.now().subtract(const Duration(hours: 2)),
        unReadNum: 1)
  ];

  @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: Column(
              children: [
                //聊天列表
                Expanded(
                  flex: 1,
                  child: ListView.builder(
                      itemCount: _messageList.length,
                      itemBuilder: (BuildContext context, int index) {
                        var item = _messageList[index];
                        return Column(
                          children: [
                            //搜索按钮
                            index == 0
                                ? Container(
                                    width: double.infinity,
                                    padding: const EdgeInsets.only(
                                        left: 10, right: 10, top: 5, bottom: 5),
                                    decoration: const BoxDecoration(
                                        color:
                                            Style.messageSearchBackgroundColor),
                                    child: ElevatedButton(
                                      style: ButtonStyle(
                                          elevation:
                                              MaterialStateProperty.all(0),
                                          backgroundColor:
                                              MaterialStateProperty.all(
                                                  Colors.white)),
                                      child: Row(
                                        mainAxisAlignment:
                                            MainAxisAlignment.center,
                                        children: const [
                                          Icon(
                                            Icons.search,
                                            color: Colors.grey,
                                          ),
                                          Text(
                                            "搜索",
                                            style: TextStyle(
                                                fontSize: 20,
                                                color: Colors.grey),
                                          )
                                        ],
                                      ),
                                      onPressed: () {},
                                    ),
                                  )
                                : const SizedBox(),
                            Column(
                              children: [
                                ListTile(
                                  //头像
                                  leading: BadgeAvatar(
                                    avatar: item.avatar,
                                    num: item.unReadNum,
                                  ),
                                  //名称和时间
                                  title: Row(
                                    children: [
                                      //名称
                                      Expanded(
                                          flex: 17,
                                          child: Text(item.name,
                                              style: const TextStyle(
                                                  fontSize: 18,
                                                  fontWeight:
                                                      FontWeight.w500))),
                                      //右侧时间
                                      Expanded(
                                          flex: 3,
                                          child: Text(
                                            formatDate(
                                                item.lastTime, [HH, ':', nn]),
                                            style: const TextStyle(
                                                fontSize: 12,
                                                color: Colors.grey),
                                          )),
                                    ],
                                  ),
                                  //消息内容
                                  subtitle: Padding(
                                    padding: const EdgeInsets.only(top: 10),
                                    child: Text(item.message,
                                        overflow: TextOverflow.ellipsis,
                                        maxLines: 1,
                                        style: const TextStyle(
                                            fontSize: 16, color: Colors.grey)),
                                  ),
                                ),
                                //下划线
                                Padding(
                                  padding: index == _messageList.length - 1
                                      ? const EdgeInsets.only(left: 0)
                                      : const EdgeInsets.only(left: 80),
                                  child: const Divider(),
                                )
                              ],
                            )
                          ],
                        );
                      }),
                ),
              ],
            )));
  }
}

class MessageData {
  const MessageData(
      {required this.id,
      required this.avatar,
      required this.name,
      required this.message,
      required this.unReadNum,
      required this.lastTime});

  /// id
  final BigInt id;

  /// 头像
  final String avatar;

  /// 名称
  final String name;

  /// 消息
  final String message;

  /// 消息未读数量
  final int unReadNum;

  /// 消息时间
  final DateTime lastTime;
}

项目链接

github.com/BadKid90s/p…