【Flutter 开发必备】AzListView 组件全解析,打造丝滑索引列表!

简介: 在 Flutter 开发中,AzListView 是实现字母索引分类列表的理想选择。它支持 A-Z 快速跳转、悬浮分组标题、自定义 UI 和高效性能,适用于通讯录、城市选择等场景。本文将详细解析 AzListView 的核心参数和实战示例,助你轻松实现流畅的索引列表。

【Flutter 开发必备】AzListView 组件全解析,打造丝滑索引列表!

在 Flutter 开发中,遇到需要按字母索引分类展示列表的需求怎么办?AzListView 组件让你的列表不仅美观,还能流畅滚动!今天,我们就来全面解析 AzListView 的用法,让你的 Flutter 开发更上一层楼!

🔥 为什么选择 AzListView?

在开发通讯录、城市选择等需要索引的列表时,普通 ListView 显然不够用,而 AzListView 提供了以下超强能力:

字母索引导航:支持 A-Z 索引快速跳转。
Sticky Headers:分组标题悬浮效果,让分类一目了然。
可定制 UI:支持灵活修改索引样式、列表样式。
高效性能:基于 ListView.builder,性能优秀,支持大数据量列表。

🛠 AzListView 组件核心参数解析

在使用 AzListView 之前,我们先来看一下它的核心参数:

参数名 类型 说明
data List\ 数据源,需继承 ISuspensionBean 接口
itemBuilder IndexedWidgetBuilder 列表项构建方法
suspensionWidget Widget 悬浮索引组件(分组标题)
indexBarOptions IndexBarOptions 索引栏配置,如颜色、字体等
header Widget 头部组件,可选

📌 实战示例:快速实现一个索引通讯录

接下来,我们用一个示例来展示 AzListView 的强大之处。

1️⃣ 准备数据

class Contact extends ISuspensionBean {
   
  String name;
  String tagIndex;
  Contact({
   required this.name, required this.tagIndex});

  
  String getSuspensionTag() => tagIndex;
}

List<Contact> contacts = [
  Contact(name: "Alice", tagIndex: "A"),
  Contact(name: "Bob", tagIndex: "B"),
  Contact(name: "Charlie", tagIndex: "C"),
];

2️⃣ 使用 AzListView 渲染 UI

AzListView(
  data: contacts,
  itemBuilder: (context, index) {
   
    final contact = contacts[index];
    return ListTile(
      title: Text(contact.name),
    );
  },
  suspensionWidget: Container(
    alignment: Alignment.centerLeft,
    color: Colors.blue,
    padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    child: Text("索引", style: TextStyle(color: Colors.white, fontSize: 16)),
  ),
  indexBarOptions: IndexBarOptions(
    indexHintAlignment: Alignment.centerRight,
    indexHintTextStyle: TextStyle(color: Colors.white, fontSize: 24),
    indexHintDecoration: BoxDecoration(
      color: Colors.blueAccent,
      shape: BoxShape.circle,
    ),
  ),
)

3️⃣ 运行效果

运行后,你将会看到一个丝滑的索引列表,点击右侧索引还能快速跳转到相应分组!

🎨 高级玩法:自定义索引栏

如果想让索引栏更符合你的 UI 需求,可以使用 IndexBarOptions 进行自定义。

indexBarOptions: IndexBarOptions(
  needRebuild: true,
  indexBarMargin: EdgeInsets.all(10),
  indexHintOffset: Offset(-20, 0),
  selectTextStyle: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
  selectItemDecoration: BoxDecoration(
    shape: BoxShape.circle,
    color: Colors.redAccent,
  ),
)

💡 总结

AzListView 让 Flutter 开发中的索引列表变得超级简单,无论是通讯录、城市列表还是分类商品,都可以轻松实现。掌握这些用法,你的应用将更加专业、流畅!

🔥 你在开发中遇到哪些列表索引的挑战?欢迎在评论区交流,一起学习进步!

完整示例如下:

import 'package:azlistview/azlistview.dart';
import "package:beehive/constants/constant.dart";
import "package:beehive/controllers/friend/friend_list_logic.dart";
import "package:beehive/controllers/user_manager.dart";
import "package:beehive/pages/constants/user_profile_panel/user_profile_panel_view.dart";
import "package:beehive/protos/ws/ws.pb.dart";
import "package:beehive/routes/routes.dart";
import "package:cached_network_image/cached_network_image.dart";
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pinyin/pinyin.dart';  // 导入拼音库

class FriendListPage extends StatelessWidget {
   
  final FriendListLogic friendListLogic = Get.find<FriendListLogic>();

  
  Widget build(BuildContext context) {
   
    // 获取好友列表并分组
    List<AZFriendItem> azItems = _buildAzItems();
    // 生成字母索引列表
    List<String> indexTags = _getIndexTags(azItems);
    // 初始化 AZListView
    return AzListView(
      data: azItems,
      itemCount: azItems.length,
      itemBuilder: (context, index) {
   
        final item = azItems[index];
        return Container(
          padding: const EdgeInsets.symmetric(horizontal: 4,vertical: 2),
          margin: EdgeInsets.symmetric(vertical: 1), // 为每个分组项添加间距
          decoration: BoxDecoration(
            color: Colors.white,  // 设置背景颜色
            borderRadius: BorderRadius.circular(8),  // 设置圆角
          ),
          child: ListTile(
            leading: _buildAvatar(
              item.info.friendUser.faceURL,
              friendListLogic.getFriendOnlineStatus(item.info.friendUser.userID),
            ),
            title: Text(
              UserManager.to.userNameMap[item.info.friendUser.userID] ?? item.info.friendUser.nickname,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                color: Color(0xFFDBA100),
                fontSize: 18,
                fontFamily: 'Alibaba PuHuiTi 3.0',
                fontWeight: FontWeight.w700,
                height: 0.07,
                letterSpacing: -0.32,
              ),
            ),
            onTap: () {
   
              Get.toNamed(
                Routes.userProfilePanel,
                arguments: {
   
                  'userInfo': item.info.friendUser,
                  'actionType': UserDetailActionType.deleteFriend,
                },
              );
            },
          ),
        );
      },
      // 分组悬停头部
      susItemBuilder: (context, index) {
   
        final tag = azItems[index].getSuspensionTag();
        return Container(
            height: 29,
            padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
            clipBehavior: Clip.antiAlias,
            decoration: BoxDecoration(),
            alignment: Alignment.centerLeft,
            child: Row(
              mainAxisSize: MainAxisSize.min,
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text(
                  tag,
                  style: TextStyle(
                    color: Color(0xFF787878),
                    fontSize: 14,
                    fontFamily: 'Alibaba PuHuiTi 3.0',
                    fontWeight: FontWeight.w500,
                    height: 0.11,
                    letterSpacing: -0.32,
                  ),
                ),
              ],
            ),
          );
      },
      //字母导航栏与其他组件的间距
      indexBarMargin: EdgeInsets.all(0.0),
      indexBarItemHeight: 18, //字母索引项的高度
      indexHintBuilder: (context, hint) => CircleAvatar(
        child: Text(hint, style: TextStyle(color: Colors.white)),
        backgroundColor: Colors.blue,
      ),
      indexBarData: indexTags,
      //定制字母导航栏
      indexBarOptions: IndexBarOptions(
        needRebuild: true,
        selectItemDecoration: BoxDecoration(
          color: Color(0xFFDBA100),  // 选中字母的背景颜色
          shape: BoxShape.circle,
        ),
      ),
    );
  }

  List<AZFriendItem> _buildAzItems() {
   
    List<AZFriendItem> azItems = friendListLogic.friendList.map((friendInfo) {
   
      String tag = friendInfo.friendUser.nickname;

      // 如果昵称为空,则归为 #
      if (tag.isEmpty) {
   
        tag = "#";
      } else {
   
        // 判断第一个字符是否为字母或汉字
        String firstChar = tag[0];

        // 处理中文昵称,获取拼音首字母
        if (isChinese(firstChar)) {
   
          String pinyin = PinyinHelper.getPinyin(tag)[0].toUpperCase();
          // 如果拼音首字母是非字母字符(如符号),则归为 #
          tag = RegExp(r'[A-Z]').hasMatch(pinyin) ? pinyin : '#';
        } else if (RegExp(r'[A-Za-z]').hasMatch(firstChar)) {
   
          // 对于英文,直接使用首字母
          tag = firstChar.toUpperCase();
        } else {
   
          // 对于其他非字母字符(如符号、数字等),归为 #
          tag = '#';
        }
      }

      return AZFriendItem(
        tag: tag,
        info: friendInfo,
      );
    }).toList();

    // 排序并初始化分组标签
    SuspensionUtil.sortListBySuspensionTag(azItems);
    SuspensionUtil.setShowSuspensionStatus(azItems);

    return azItems;
  }

  List<String> _getIndexTags(List<AZFriendItem> azItems) {
   
    // 提取所有标签,并去重
    Set<String> tagSet = {
   };
    for (var item in azItems) {
   
      tagSet.add(item.getSuspensionTag());
    }
    return tagSet.toList()..sort(); // 对标签进行排序
  }

  // 判断字符是否是中文
  bool isChinese(String char) {
   
    final regExp = RegExp(r'[\u4e00-\u9fa5]');
    return regExp.hasMatch(char);
  }


  // 构建头像组件
  Widget _buildAvatar(String? faceURL, bool onlineStatus) {
   
    Widget avatar;
    const double avatarSize = 44.0;
    if (faceURL != null && faceURL.isNotEmpty) {
   
      if (faceURL.startsWith('http')) {
   
        avatar = CachedNetworkImage(
          imageUrl: faceURL,
          placeholder: (context, url) => CircleAvatar(
            backgroundColor: Colors.grey.shade300,
            radius: avatarSize/2,
          ),
          imageBuilder: (context, imageProvider) => CircleAvatar(
            backgroundColor: Colors.grey.shade300,
            backgroundImage: imageProvider,
            radius: avatarSize/2,
          ),
        );
      } else {
   
        avatar = CircleAvatar(
          backgroundColor: Colors.grey.shade300,
          backgroundImage: AssetImage(faceURL),
          radius: avatarSize/2,
        );
      }
    } else {
   
      avatar = CircleAvatar(
        backgroundImage: const AssetImage(defaultAvatar),
        backgroundColor: Colors.grey.shade300,
        radius: avatarSize/2,
      );
    }

    return Stack(
      children: [
        avatar,
        Positioned(
          right: 0,
          bottom: 0,
          child: Container(
            width: 12,
            height: 12,
            decoration: BoxDecoration(
              shape: BoxShape.circle,
              color: onlineStatus ? Colors.green : Colors.grey,
              border: Border.all(
                color: Colors.white,
                width: 2,
              ),
            ),
          ),
        ),
      ],
    );
  }
}

// AZListView 扩展数据模型
class AZFriendItem extends ISuspensionBean {
   
  final String tag;
  final FriendInfo info;

  AZFriendItem({
   required this.tag, required this.info});

  
  String getSuspensionTag() => tag;
}
目录
相关文章
|
11月前
|
前端开发 JavaScript
React 步骤条组件 Stepper 深入解析与常见问题
步骤条组件是构建多步骤表单或流程时的有力工具,帮助用户了解进度并导航。本文介绍了在React中实现简单步骤条的方法,包括基本结构、状态管理、样式处理及常见问题解决策略,如状态管理库的使用、自定义Hook的提取和CSS Modules的应用,以确保组件的健壮性和可维护性。
270 17
|
9月前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
584 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
10月前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
368 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
10月前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
344 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
9月前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
292 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
11月前
|
容器
Flutter Widget 解析
Flutter Widget 解析
163 2
|
存储 缓存 监控
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
【4月更文挑战第30天】本文探讨了Flutter中优化列表滚动性能的策略。建议使用`ListView.builder`以节省内存,避免一次性渲染所有列表项。为防止列表项重建,可使用`UniqueKey`或`ObjectKey`。缓存已渲染项、减少不必要的重绘和异步加载大数据集也是关键。此外,选择轻量级组件,如`StatelessWidget`,并利用Flutter DevTools监控性能以识别和解决瓶颈。持续测试和调整以提升用户体验。
611 0
【Flutter前端技术开发专栏】Flutter中的列表滚动性能优化
|
前端开发
关于flutter列表的性能优化,你必须要了解的
这里是坚果前端小课堂,大家喜欢的话,可以关注我的公众号“坚果前端,”,或者加我好友,获取更多精彩内容 嵌套列表 - ShrinkWrap 与 Slivers
770 0
关于flutter列表的性能优化,你必须要了解的
|
前端开发
flutter 中的列表的性能优化前奏
这里是坚果前端小课堂,大家喜欢的话,可以关注我的公众号“坚果前端,”,或者加我好友,获取更多精彩内容 嵌套列表 - ShrinkWrap 与 Slivers 使用 ShrinkWrap 的列表列表 下面是一些使用ListView对象呈现列表列表的代码,内部列表的shrinkWrap值设置为 true。shrinkWrap强行评估整个内部列表,允许它请求有限的高度,而不是通常的ListView对象高度,即无穷大!
400 0
flutter 中的列表的性能优化前奏
flutter中的列表的性能优化
我们接着上一期的继续今天的 内容 使用 Slivers 的列表列表 下面的代码构建了与之前相同的 UI,但这次它使用Slivers 而不是收缩包装ListView对象。本页的其余部分将引导您逐步完成更改。
571 0

推荐镜像

更多
  • DNS