Flutter聊天界面插件flutter_chat_ui的使用

发布于 1周前 作者 bupafengyu 来自 Flutter

Flutter聊天界面插件flutter_chat_ui的使用

简介

Flutter Chat UI 是一个由社区驱动、积极维护的聊天UI实现,适用于Flutter应用。它提供了可选的Firebase后端即服务(BaaS)支持,但也可以与任何其他后端集成。以下是该插件的一些特点:

  • 免费、开源和社区驱动:没有付费插件,旨在为任何应用程序提供易于使用的几乎开箱即用的聊天体验。
  • 后端无关:可以选择你喜欢的后端,但如果不需要,可以使用提供的Firebase实现快速创建聊天应用。
  • 可定制化:支持自定义主题、语言等更多选项。
  • 最少依赖:包非常轻量,可以根据需要选择其他喜欢的库进行扩展。

快速开始

要求

确保你的项目满足以下要求:

  • Dart >= 2.19.0
  • Flutter >= 3.0.0

你可以通过阅读官方文档或查看示例项目来了解更多信息。

示例代码

下面是一个完整的示例程序,展示了如何在Flutter中使用flutter_chat_ui创建一个简单的聊天界面:

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:mime/mime.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart';

void main() {
  initializeDateFormatting().then((_) => runApp(const MyApp()));
}

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

  @override
  Widget build(BuildContext context) => const MaterialApp(
        home: Directionality(
          textDirection: TextDirection.ltr,
          child: ChatPage(),
        ),
      );
}

class ChatPage extends StatefulWidget {
  const ChatPage({super.key});

  @override
  State<ChatPage> createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {
  List<types.Message> _messages = [];
  final _user = const types.User(
    id: '82091008-a484-4a89-ae75-a22bf8d6f3ac',
  );

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

  void _addMessage(types.Message message) {
    setState(() {
      _messages.insert(0, message);
    });
  }

  void _handleAttachmentPressed() {
    showModalBottomSheet<void>(
      context: context,
      builder: (BuildContext context) => SafeArea(
        child: SizedBox(
          height: 144,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: <Widget>[
              TextButton(
                onPressed: () {
                  Navigator.pop(context);
                  _handleImageSelection();
                },
                child: const Align(
                  alignment: AlignmentDirectional.centerStart,
                  child: Text('Photo'),
                ),
              ),
              TextButton(
                onPressed: () {
                  Navigator.pop(context);
                  _handleFileSelection();
                },
                child: const Align(
                  alignment: AlignmentDirectional.centerStart,
                  child: Text('File'),
                ),
              ),
              TextButton(
                onPressed: () => Navigator.pop(context),
                child: const Align(
                  alignment: AlignmentDirectional.centerStart,
                  child: Text('Cancel'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  void _handleFileSelection() async {
    final result = await FilePicker.platform.pickFiles(
      type: FileType.any,
    );

    if (result != null && result.files.single.path != null) {
      final message = types.FileMessage(
        author: _user,
        createdAt: DateTime.now().millisecondsSinceEpoch,
        id: const Uuid().v4(),
        mimeType: lookupMimeType(result.files.single.path!),
        name: result.files.single.name,
        size: result.files.single.size,
        uri: result.files.single.path!,
      );

      _addMessage(message);
    }
  }

  void _handleImageSelection() async {
    final result = await ImagePicker().pickImage(
      imageQuality: 70,
      maxWidth: 1440,
      source: ImageSource.gallery,
    );

    if (result != null) {
      final bytes = await result.readAsBytes();
      final image = await decodeImageFromList(bytes);

      final message = types.ImageMessage(
        author: _user,
        createdAt: DateTime.now().millisecondsSinceEpoch,
        height: image.height.toDouble(),
        id: const Uuid().v4(),
        name: result.name,
        size: bytes.length,
        uri: result.path,
        width: image.width.toDouble(),
      );

      _addMessage(message);
    }
  }

  void _handleMessageTap(BuildContext _, types.Message message) async {
    if (message is types.FileMessage) {
      var localPath = message.uri;

      if (message.uri.startsWith('http')) {
        try {
          final index =
              _messages.indexWhere((element) => element.id == message.id);
          final updatedMessage =
              (_messages[index] as types.FileMessage).copyWith(
            isLoading: true,
          );

          setState(() {
            _messages[index] = updatedMessage;
          });

          final client = http.Client();
          final request = await client.get(Uri.parse(message.uri));
          final bytes = request.bodyBytes;
          final documentsDir = (await getApplicationDocumentsDirectory()).path;
          localPath = '$documentsDir/${message.name}';

          if (!File(localPath).existsSync()) {
            final file = File(localPath);
            await file.writeAsBytes(bytes);
          }
        } finally {
          final index =
              _messages.indexWhere((element) => element.id == message.id);
          final updatedMessage =
              (_messages[index] as types.FileMessage).copyWith(
            isLoading: null,
          );

          setState(() {
            _messages[index] = updatedMessage;
          });
        }
      }

      await OpenFilex.open(localPath);
    }
  }

  void _handlePreviewDataFetched(
    types.TextMessage message,
    types.PreviewData previewData,
  ) {
    final index = _messages.indexWhere((element) => element.id == message.id);
    final updatedMessage = (_messages[index] as types.TextMessage).copyWith(
      previewData: previewData,
    );

    setState(() {
      _messages[index] = updatedMessage;
    });
  }

  void _handleSendPressed(types.PartialText message) {
    final textMessage = types.TextMessage(
      author: _user,
      createdAt: DateTime.now().millisecondsSinceEpoch,
      id: const Uuid().v4(),
      text: message.text,
    );

    _addMessage(textMessage);
  }

  void _loadMessages() async {
    final response = await rootBundle.loadString('assets/messages.json');
    final messages = (jsonDecode(response) as List)
        .map((e) => types.Message.fromJson(e as Map<String, dynamic>))
        .toList();

    setState(() {
      _messages = messages;
    });
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        body: Chat(
          messages: _messages,
          onAttachmentPressed: _handleAttachmentPressed,
          onMessageTap: _handleMessageTap,
          onPreviewDataFetched: _handlePreviewDataFetched,
          onSendPressed: _handleSendPressed,
          showUserAvatars: true,
          showUserNames: true,
          user: _user,
        ),
      );
}

关键点解释

  • 消息加载_loadMessages方法从本地资源文件加载预设的消息列表。
  • 发送文本消息_handleSendPressed方法处理用户输入并创建新的文本消息。
  • 附件选择_handleAttachmentPressed方法允许用户选择图片或文件作为附件。
  • 消息点击事件_handleMessageTap处理不同类型的消息被点击时的行为,如下载远程文件并在设备上打开。

这个例子展示了如何结合多个功能来构建一个基本但功能齐全的聊天界面。根据实际需求,你还可以进一步扩展此代码以适应更复杂的应用场景。


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

1 回复

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


当然,以下是一个关于如何在Flutter项目中使用flutter_chat_ui插件来创建一个基本聊天界面的代码示例。这个示例展示了如何设置聊天界面、发送消息以及显示接收到的消息。

首先,确保你已经在pubspec.yaml文件中添加了flutter_chat_ui依赖:

dependencies:
  flutter:
    sdk: flutter
  flutter_chat_ui: ^x.y.z  # 请替换为最新版本号

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

接下来,在你的Flutter项目中创建一个聊天界面。以下是一个简单的示例:

import 'package:flutter/material.dart';
import 'package:flutter_chat_ui/flutter_chat_ui.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Chat UI Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ChatScreen(),
    );
  }
}

class ChatScreen extends StatefulWidget {
  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  final List<ChatMessage> messages = [
    ChatMessage(
      id: '1',
      sender: ChatUser(id: 'user1', name: 'Alice'),
      text: 'Hello, how are you?',
      timestamp: DateTime.now().subtract(Duration(minutes: 5)),
      type: MessageType.text,
    ),
    ChatMessage(
      id: '2',
      sender: ChatUser(id: 'user2', name: 'Bob'),
      text: 'I am good, thanks. And you?',
      timestamp: DateTime.now().subtract(Duration(minutes: 2)),
      type: MessageType.text,
    ),
  ];

  final TextEditingController _textController = TextEditingController();

  void _sendMessage() {
    if (_textController.text.isNotEmpty) {
      setState(() {
        messages.add(ChatMessage(
          id: '${messages.length + 1}',
          sender: ChatUser(id: 'user1', name: 'Alice'),
          text: _textController.text,
          timestamp: DateTime.now(),
          type: MessageType.text,
        ));
        _textController.clear();
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Chat'),
      ),
      body: Chat(
        user: ChatUser(id: 'user1', name: 'Alice'),
        messages: messages,
        onSendMessage: _sendMessage,
        inputField: ChatInputField(
          controller: _textController,
          sendButton: IconButton(
            icon: Icon(Icons.send),
            onPressed: _sendMessage,
          ),
        ),
      ),
    );
  }
}

在这个示例中:

  1. 依赖项:我们在pubspec.yaml文件中添加了flutter_chat_ui依赖。
  2. 主应用MyApp类是我们的主应用类,它使用MaterialApp来设置应用的主题和主页。
  3. 聊天屏幕ChatScreen是一个有状态的组件,它包含了一个消息列表和一个文本输入字段。
  4. 消息列表:我们在_ChatScreenState类中定义了一个消息列表messages,并在初始状态下添加了一些示例消息。
  5. 发送消息_sendMessage方法用于将用户输入的消息添加到消息列表中,并清空文本输入字段。
  6. 聊天界面Chat组件用于显示聊天界面,它接受当前用户信息、消息列表、发送消息回调函数以及输入字段作为参数。

这个示例展示了如何使用flutter_chat_ui插件来创建一个基本的聊天界面。你可以根据需要进一步自定义和扩展这个示例,例如添加图片消息、视频消息、用户头像等功能。

回到顶部