【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});
@override
String getSuspensionTag() => tagIndex;
}
List<Contact> contacts = [
Contact(name: "Alice", tagIndex: "A"),
Contact(name: "Bob", tagIndex: "B"),
Contact(name: "Charlie", tagIndex: "C"),
];
AI 代码解读
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,
),
),
)
AI 代码解读
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,
),
)
AI 代码解读
💡 总结
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>();
@override
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});
@override
String getSuspensionTag() => tag;
}
AI 代码解读