Flutter聊天索引栏插件chat_index_bar的使用

Flutter聊天索引栏插件chat_index_bar的使用

chat_index_bar 是一个用于聊天界面的搜索控制包,它允许用户展开索引列表以便快速搜索聊天记录。

安装插件

pubspec.yaml 文件中添加以下依赖:

dependencies:
  chat_index_bar: ^1.0.0

然后运行 flutter pub get 来安装该插件。

导入库

在你的 Dart 文件中导入 chat_index_bar 包:

import 'package:chat_index_bar/chat_index_bar.dart';

使用示例

下面是一个完整的示例代码,展示了如何在 Flutter 应用中使用 chat_index_bar 插件。

import 'package:chat_index_bar/chat_index_bar.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

final Color WeChatThemeColor = Color.fromRGBO(220, 220, 220, 1.0);

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'use cases for chat index bar.',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.standard,
      ),
      home: const FriendsPage(),
    );
  }
}

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

  @override
  _FriendsPageState createState() => _FriendsPageState();
}

class _FriendsPageState extends State<FriendsPage> {
  // 字典里存放item和高度的对应数据
  final Map<String, double> _groupOffsetMap = {
    'A': 0.0,
    'B': 0.0,
    // 其他字母映射
  };

  final ScrollController _scrollController = ScrollController();

  final List<Friends> _listDatas = [];

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

    // 方法二 step1 链式 添加【增量数据】
    _listDatas
      ..addAll(datas);

    // step2 排序
    _listDatas.sort((Friends a, Friends b) {
      return a.indexLetter.compareTo(b.indexLetter);
    });

    var _groupOffset = 54.5 * 4;
    // 经过循环计算,将每一个头的位置算出来,放入字典。
    for (int i = 0; i < _listDatas.length; i++) {
      if (i < 1) {
        // 第一个Cell
        _groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
        // 保存完了再加_groupOffset偏移
        _groupOffset += 84.5;
      } else if (_listDatas[i].indexLetter == _listDatas[i - 1].indexLetter) {
        // 没有头部,只需要加偏移量
        _groupOffset += 54.5;
      } else {
        // 这部分是有头部的Cell
        _groupOffsetMap.addAll({_listDatas[i].indexLetter: _groupOffset});
        _groupOffset += 84.5;
      }
    }
  }

  final List _headerData = [
    Friends(imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/9/99/Sample_User_Icon.png', name: '新的朋友'),
    Friends(imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/9/99/Sample_User_Icon.png', name: '群聊'),
    Friends(imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/9/99/Sample_User_Icon.png', name: '标签'),
    Friends(imageUrl: 'https://upload.wikimedia.org/wikipedia/commons/9/99/Sample_User_Icon.png', name: '公众号'),
  ];

  Widget _itemForRow(BuildContext context, int index) {
    // 显示头部 系统图标的cell
    if (index < _headerData.length) {
      return _FriendCell(
        imageUrl: _headerData[index].imageUrl,
        name: _headerData[index].name,
      );
    }

    // 显示剩下的cell
    // 如果当前和上一个cell的IndexLetter一样, 就不显示
    bool _hideIndexLetter = (index - 4 > 0 && (_listDatas[index - 4].indexLetter == _listDatas[index - 5].indexLetter));
    return _FriendCell(
      imageUrl: _listDatas[index - 4].imageUrl,
      name: _listDatas[index - 4].name,
      groupTitle: _hideIndexLetter ? "" : _listDatas[index - 4].indexLetter,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: WeChatThemeColor,
        title: const Text('通讯录'),
        actions: <Widget>[
          GestureDetector(
            child: Container(
              margin: const EdgeInsets.only(right: 10),
              child: const Icon(Icons.add, size: 25,),
            ),
            onTap: () {},
          )
        ],
      ),
      body: Stack(
        children: <Widget>[
          ListView.builder(
            controller: _scrollController,
            itemCount: _listDatas.length + _headerData.length,
            itemBuilder: _itemForRow,
          ), // 列表
          IndexBar(
            indexBarCallBack: (String str) {
              if (kDebugMode) {
                print('我收到了$str');
              }

              if (_groupOffsetMap[str] != null) {
                _scrollController.animateTo(_groupOffsetMap[str],
                    duration: const Duration(milliseconds: 300),
                    curve: Curves.easeIn);
              }
            },
            bubbleImage: const Image(
              image: NetworkImage('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTTxO8mUQcWnFsN2aEnvdSKNyJ9Bx61N-KxrA&usqp=CAU'),
            ),
          ), // 悬浮检索控件
        ],
      ),
    );
  }
}

class _FriendCell extends StatelessWidget {
  final String imageUrl;
  final String name;
  final String groupTitle;
  final String imageAssets;

  const _FriendCell({
    this.imageUrl = '',
    this.name = '',
    this.groupTitle = '',
    this.imageAssets = '',
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(
          alignment: Alignment.centerLeft,
          padding: const EdgeInsets.only(left: 10),
          height: groupTitle != "" ? 30 : 0,
          color: const Color.fromRGBO(1, 1, 1, 0.0),
          child: groupTitle != ""
              ? Text(
                  groupTitle,
                  style: const TextStyle(color: Colors.grey),
                )
              : null,
        ), // Cell的头
        Container(
          color: Colors.white,
          child: Row(
            children: <Widget>[
              Container(
                margin: const EdgeInsets.all(10),
                width: 34,
                height: 34,
                decoration: BoxDecoration(
                    borderRadius: BorderRadius.circular(6.0),
                    image: DecorationImage(
                      image: imageUrl != ""
                          ? NetworkImage(imageUrl)
                          : AssetImage(imageAssets) as ImageProvider,
                    )),
              ), // 图片
              Text(
                name,
                style: const TextStyle(fontSize: 17),
              ), // 昵称
            ],
          ),
        ), // Cell的内容
        Container(
          height: 0.5,
          color: WeChatThemeColor,
          child: Row(
            children: <Widget>[
              Container(
                width: 50,
                color: Colors.white,
              ),
            ],
          ),
        ), // 分割线
      ],
    );
  }
}

class _FriendHeaderCell extends StatelessWidget {
  final String name;
  final String imageAssets;

  const _FriendHeaderCell({this.name = '', this.imageAssets = ''});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      child: Row(
        children: <Widget>[
          Container(
            margin: const EdgeInsets.all(10),
            width: 34,
            height: 34,
            decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(6.0),
                image: DecorationImage(
                  image: AssetImage(imageAssets),
                )),
          ), // 图片
          Text(
            name,
            style: const TextStyle(fontSize: 17),
          ), // 昵称
        ],
      ),
    );
  }
}

class Friends {
  final String imageUrl;
  final String name;
  final String indexLetter;

  Friends({this.imageUrl = '', this.name = '', this.indexLetter = ''});
}

List<Friends> datas = [
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/22.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/women/16.jpg",
      name: "Eam",
      indexLetter: "E"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/16.jpg",
      name: "安莉",
      indexLetter: "A"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/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/27.jpg",
      name: "Abby",
      indexLetter: "A"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/31.jpg",
      name: "COCO",
      indexLetter: "C"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/17.jpg",
      name: "wkk",
      indexLetter: "W"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/16.jpg",
      name: "凯蒂",
      indexLetter: "K"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/31.jpg",
      name: "盖茨",
      indexLetter: "G"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/22.jpg",
      name: "Hank",
      indexLetter: "H"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/16.jpg",
      name: "Yuki",
      indexLetter: "Y"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/27.jpg",
      name: "张萌",
      indexLetter: "Z"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/22.jpg",
      name: "COCO",
      indexLetter: "C"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/17.jpg",
      name: "赛琳娜",
      indexLetter: "S"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/16.jpg",
      name: "Wangle",
      indexLetter: "W"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/31.jpg",
      name: "西涅",
      indexLetter: "X"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/22.jpg",
      name: "Right",
      indexLetter: "R"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/31.jpg",
      name: "Vivina",
      indexLetter: "V"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/27.jpg",
      name: "秦小君",
      indexLetter: "Q"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/31.jpg",
      name: "Peter",
      indexLetter: "P"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/17.jpg",
      name: "欧拉琳",
      indexLetter: "O"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/22.jpg",
      name: "Python",
      indexLetter: "P"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/31.jpg",
      name: "C++",
      indexLetter: "C"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/16.jpg",
      name: "Java",
      indexLetter: "J"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/16.jpg",
      name: "Betty",
      indexLetter: "B"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/17.jpg",
      name: "Objective-C",
      indexLetter: "O"),
  Friends(
      imageUrl: "https://randomuser.me/api/portraits/women/31.jpg",
      name: "Swift",
      indexLetter: "S"),
  Friends(
      imageUrl: 'https://randomuser.me/api/portraits/women/16.jpg',
      name: 'web',
      indexLetter: 'W'),
  Friends(
      imageUrl: 'https://randomuser.me/api/portraits/women/17.jpg',
      name: 'PHP',
      indexLetter: 'P'),
];

代码解释

  1. 导入库

    import 'package:chat_index_bar/chat_index_bar.dart';
    
  2. 初始化数据

    final Map<String, double> _groupOffsetMap = {
      'A': 0.0,
      'B': 0.0,
      // 其他字母映射
    };
    
  3. 构建列表项

    Widget _itemForRow(BuildContext context, int index) {
      // 显示头部 系统图标的cell
      if (index < _headerData.length) {
        return _FriendCell(
          imageUrl: _headerData[index].imageUrl,
          name: _headerData[index].name,
        );
      }
    
      // 显示剩下的cell
      bool _hideIndexLetter = (index - 4 > 0 && (_listDatas[index - 4].indexLetter == _listDatas[index - 5].indexLetter));
      return _FriendCell(
        imageUrl: _listDatas[index - 4].imageUrl,
        name: _listDatas[index - 4].name,
        groupTitle: _hideIndexLetter ? "" : _listDatas[index - 4].indexLetter,
      );
    }
    
  4. 悬浮检索控件

    IndexBar(
      indexBarCallBack: (String str) {
        if (kDebugMode) {
          print('我收到了$str');
        }
    
        if (_groupOffsetMap[str] != null) {
          _scrollController.animateTo(_groupOffsetMap[str],
              duration: const Duration(milliseconds: 300),
              curve: Curves.easeIn);
        }
      },
      bubbleImage: const Image(
        image: NetworkImage('https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTTxO8mUQcWnFsN2aEnvdSKNyJ9Bx61N-KxrA&usqp=CAU'),
      ),
    ),
    

更多关于Flutter聊天索引栏插件chat_index_bar的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter聊天索引栏插件chat_index_bar的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


chat_index_bar 是一个用于 Flutter 的插件,它可以帮助你在聊天应用中快速实现一个索引栏(类似于联系人列表中的字母索引栏)。通过这个索引栏,用户可以快速滑动到特定字母开头的联系人。

安装

首先,你需要在 pubspec.yaml 文件中添加 chat_index_bar 依赖:

dependencies:
  flutter:
    sdk: flutter
  chat_index_bar: ^1.0.0  # 请确保使用最新版本

然后运行 flutter pub get 来安装依赖。

使用

  1. 导入包

    import 'package:chat_index_bar/chat_index_bar.dart';
    
  2. 创建数据源

    你需要一个有排序的数据源,通常是一个按字母排序的列表。

    List<String> contacts = [
      "Alice",
      "Bob",
      "Charlie",
      "David",
      "Eve",
      "Frank",
      "Grace",
      "Heidi",
      "Ivan",
      "Judy",
      "Kevin",
      "Linda",
      "Mallory",
      "Nancy",
      "Oscar",
      "Peggy",
      "Quentin",
      "Randy",
      "Steve",
      "Trent",
      "Uma",
      "Victor",
      "Walter",
      "Xander",
      "Yvonne",
      "Zoe"
    ];
    
  3. 使用 ChatIndexBar

    你可以将 ChatIndexBarListView 或其他滚动组件结合使用。以下是一个简单的示例:

    class ChatContactsPage extends StatefulWidget {
      @override
      _ChatContactsPageState createState() => _ChatContactsPageState();
    }
    
    class _ChatContactsPageState extends State<ChatContactsPage> {
      final ScrollController _scrollController = ScrollController();
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Contacts'),
          ),
          body: Stack(
            children: [
              ListView.builder(
                controller: _scrollController,
                itemCount: contacts.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(contacts[index]),
                  );
                },
              ),
              Positioned(
                right: 0,
                top: 0,
                bottom: 0,
                child: ChatIndexBar(
                  indexSymbols: [
                    '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'
                  ],
                  onIndexSelected: (String symbol) {
                    // 找到第一个以该字母开头的联系人
                    int index = contacts.indexWhere((contact) => contact.startsWith(symbol));
                    if (index != -1) {
                      _scrollController.animateTo(
                        index * 56.0, // 假设每个 ListTile 的高度为 56.0
                        duration: Duration(milliseconds: 300),
                        curve: Curves.easeInOut,
                      );
                    }
                  },
                ),
              ),
            ],
          ),
        );
      }
    }
回到顶部