Flutter for OpenHarmony打造你的第一个 聊天机器人:从零实现 AI 助手界面

在人工智能日益融入日常生活的今天,一个简洁、流畅的聊天界面已成为人机交互的核心载体。本文将带你深入剖析一段完整的 Flutter
聊天应用代码
,展示如何用不到 200 行 Dart 代码,构建一个具备真实对话感、深色主题、气泡式消息布局和“AI
正在输入”反馈的智能助手原型。


完整效果
在这里插入图片描述

一、应用概览:轻量但完整的聊天体验

这款名为《AI 智能助手》的应用虽为演示性质,却包含了现代聊天界面的关键要素:

  • 双向消息流:用户消息靠右(蓝色),AI 回复靠左(灰色)
  • 气泡式对话气泡:圆角方向区分发送者身份
  • AI 输入状态提示:模拟“正在思考”动画(文字形式)
  • 基础 NLP 响应逻辑:支持关键词识别(如“名字”、“笑话”、“天气”)
  • 深色 Material 3 主题:适配夜间使用场景,视觉舒适

整个应用结构清晰,仅包含 ChatApp(入口)和 ChatScreen(主界面)两个核心组件,是学习 Flutter 状态管理与 UI 构建的绝佳范例。


二、UI 架构解析:如何构建专业级聊天界面

1. 消息列表:反向 ListView 实现“新消息置底”

ListView.builder(
  reverse: true, // 关键!使索引 0 显示在底部
  itemCount: _messages.length + (_isTyping ? 1 : 0),
)

在这里插入图片描述

  • 设置 reverse: true 后,列表从底部开始渲染,最新消息自然出现在可视区域底部,符合聊天习惯;
  • _isTyping == true 时,动态增加一个“AI 正在思考…”的占位项。

2. 消息气泡:通过条件圆角实现身份区分

borderRadius: BorderRadius.only(
  topLeft: Radius.circular(18),
  topRight: Radius.circular(18),
  bottomLeft: isUser ? Radius.circular(18) : Radius.circular(5),
  bottomRight: isUser ? Radius.circular(5) : Radius.circular(18),
)

在这里插入图片描述

  • 用户消息(右侧):右下角为直角(5),左下角圆润(18)→ 气泡“尾巴”朝右
  • AI 消息(左侧):左下角为直角,右下角圆润 → 气泡“尾巴”朝左
  • 这种设计语言源自 iMessage 和微信,用户无需思考即可识别发言者。

3. 输入区域:圆角 TextField + 发送按钮

  • 输入框采用 filled: true + fillColor: grey[800],与深色背景融合;
  • 发送按钮嵌入 CircleAvatar,蓝色背景 + 白色图标,形成视觉焦点;
  • 支持 回车发送onSubmitted)和 点击发送 双触发。

三、交互逻辑:模拟真实 AI 对话流程

1. 状态驱动:_isTyping 控制加载态

void _sendMessage() {
  // ... 添加用户消息
  setState(() { _isTyping = true; });

  Future.delayed(Duration(milliseconds: 600), () {
    // 生成回复
    setState(() { 
      _messages.insert(0, {'text': response, 'isUser': false});
      _isTyping = false;
    });
  });
}

在这里插入图片描述

  • 发送后立即显示“AI 正在思考…”,避免界面卡顿感
  • 600ms 延迟模拟网络请求或模型推理时间,提升真实感。

2. 简易 NLP 引擎:关键词匹配回复

String _getAIResponse(String input) {
  if (input.contains('名字')) return '我是千问...';
  if (input.contains('笑话')) return '为什么程序员...';
  // ...
}

在这里插入图片描述

  • 虽为硬编码规则,但覆盖了常见开场白(问候、告别、功能询问);
  • 未命中时返回引导性通用回复(如“请详细说明一下…”),鼓励用户继续对话。

四、设计细节:深色模式下的用户体验优化

元素 设计考量
背景色 #121212 —— Google 推荐的深色主题基准色,减少 OLED 屏幕功耗
用户气泡 Colors.blue —— 主题色,突出用户主动性
AI 气泡 Colors.grey[800] —— 中性灰,体现辅助角色
文字颜色 用户消息纯白,AI 消息 white70 —— 视觉层级分明
最大宽度 MediaQuery.of(context).size.width * 0.7 —— 防止长文本撑满屏幕

此外,AppBar 保留了 more_vert 按钮,点击弹出 SnackBar,为未来扩展(如清除记录、切换模型)预留入口。


五、可扩展方向:从 Demo 到产品

当前代码是一个优秀的起点,若要投入生产,可考虑以下增强:

  1. 持久化消息
    使用 shared_preferenceshive 保存聊天记录,避免重启清空。

  2. 真正的 AI 集成
    替换 _getAIResponse 为 HTTP 请求,对接大模型 API:

    final response = await http.post(apiUrl, body: {'prompt': input});
    
  3. 输入状态动画
    将“AI 正在思考…”文字替换为 三个跳动的圆点动画AnimatedContainer + Timer)。

  4. 消息时间戳 & 头像优化
    为每条消息添加发送时间,并使用更精致的 AI 头像(如自定义 SVG)。

  5. 语音输入支持
    集成 speech_to_text 插件,实现“按住说话”功能。


结语

这段简洁的 Flutter 代码,完美诠释了 “少即是多” 的设计哲学。它没有复杂的架构,却通过精准的 UI 细节、合理的状态管理和拟真的交互反馈,营造出一个令人愉悦的对话环境。

🌐 加入社区

欢迎加入 开源鸿蒙跨平台开发者社区,获取最新资源与技术支持:
👉 开源鸿蒙跨平台开发者社区
完整代码展示

import 'dart:async';
import 'dart:math';
import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'AI 智能助手',
      theme: ThemeData(
        primaryColor: Colors.blue,
        colorScheme: ColorScheme.fromSeed(
            seedColor: Colors.blue, brightness: Brightness.dark),
        useMaterial3: true,
        scaffoldBackgroundColor: const Color(0xFF121212), // 深色背景
      ),
      home: const ChatScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

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

  @override
  State<ChatScreen> createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  // 消息列表
  final List<Map<String, dynamic>> _messages = [];
  final TextEditingController _controller = TextEditingController();
  bool _isTyping = false; // AI 是否正在思考

  // 发送消息
  void _sendMessage() {
    if (_controller.text.isEmpty || _isTyping) return;

    final String text = _controller.text;
    _controller.clear();

    // 添加用户消息
    setState(() {
      _messages.insert(0, {'text': text, 'isUser': true});
      _isTyping = true;
    });

    // 模拟 AI 思考时间
    Future.delayed(const Duration(milliseconds: 600), () {
      final String response = _getAIResponse(text);
      setState(() {
        _messages.insert(0, {'text': response, 'isUser': false});
        _isTyping = false;
      });
    });
  }

  // AI 回复逻辑 (模拟)
  String _getAIResponse(String input) {
    input = input.toLowerCase();

    if (input.contains('你叫什么') || input.contains('名字')) {
      return '我是千问,由阿里巴巴开发。';
    } else if (input.contains('你好') || input.contains('hello')) {
      return '你好!很高兴认识你。有什么我可以帮你的吗?';
    } else if (input.contains('再见') || input.contains('拜拜')) {
      return '再见!期待下次与你交流。';
    } else if (input.contains('天气')) {
      return '我无法获取实时天气信息,建议你查看天气预报应用。';
    } else if (input.contains('笑话')) {
      return '为什么程序员分不清万圣节和圣诞节?因为 Oct 31 == Dec 25!';
    } else {
      // 随机通用回复
      final List<String> generics = [
        '这是个很好的问题。我理解你的意思。',
        '我正在学习如何更好地回答这个问题。',
        '请详细说明一下,我可以提供更准确的帮助。',
        '从技术角度看,这取决于具体的上下文环境。',
      ];
      return generics[Random().nextInt(generics.length)];
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AI 智能助手'),
        centerTitle: true,
        actions: [
          IconButton(
            icon: const Icon(Icons.more_vert),
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('更多设置(演示用)')),
              );
            },
          )
        ],
      ),
      body: Column(
        children: [
          // 消息列表
          Expanded(
            child: ListView.builder(
              reverse: true, // 新消息在底部
              itemCount: _messages.length + (_isTyping ? 1 : 0),
              itemBuilder: (context, index) {
                // 处理 AI 正在输入的状态
                if (index == 0 && _isTyping) {
                  return const Padding(
                    padding: EdgeInsets.all(16.0),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.start,
                      children: [
                        CircleAvatar(
                          backgroundColor: Colors.grey,
                          child:
                              Icon(Icons.room, color: Colors.white, size: 20),
                        ),
                        SizedBox(width: 10),
                        Text(
                          'AI 正在思考...',
                          style: TextStyle(color: Colors.grey),
                        ),
                      ],
                    ),
                  );
                }

                // 正常消息
                final msg = _messages[index];
                final isUser = msg['isUser'];

                return Padding(
                  padding: EdgeInsets.symmetric(
                    horizontal: 16,
                    vertical: 8,
                  ),
                  child: Row(
                    mainAxisAlignment: isUser
                        ? MainAxisAlignment.end
                        : MainAxisAlignment.start,
                    children: [
                      if (!isUser) ...[
                        const CircleAvatar(
                          backgroundColor: Colors.blue,
                          child:
                              Icon(Icons.room, color: Colors.white, size: 20),
                        ),
                        const SizedBox(width: 10),
                      ],
                      Container(
                        padding: const EdgeInsets.symmetric(
                            horizontal: 16, vertical: 12),
                        decoration: BoxDecoration(
                          color: isUser ? Colors.blue : Colors.grey[800],
                          borderRadius: BorderRadius.only(
                            topLeft: const Radius.circular(18),
                            topRight: const Radius.circular(18),
                            bottomLeft: isUser
                                ? const Radius.circular(18)
                                : const Radius.circular(5),
                            bottomRight: isUser
                                ? const Radius.circular(5)
                                : const Radius.circular(18),
                          ),
                        ),
                        constraints: BoxConstraints(
                          maxWidth: MediaQuery.of(context).size.width * 0.7,
                        ),
                        child: Text(
                          msg['text'],
                          style: TextStyle(
                              color: isUser ? Colors.white : Colors.white70),
                        ),
                      ),
                      if (isUser) const SizedBox(width: 10),
                    ],
                  ),
                );
              },
            ),
          ),

          // 输入区域
          Container(
            padding: const EdgeInsets.all(16),
            decoration: const BoxDecoration(
              border: Border(
                top: BorderSide(color: Colors.grey, width: 0.5),
              ),
            ),
            child: Row(
              children: [
                Expanded(
                  child: TextField(
                    controller: _controller,
                    decoration: InputDecoration(
                      hintText: '输入消息...',
                      filled: true,
                      fillColor: Colors.grey[800],
                      border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(25),
                        borderSide: BorderSide.none,
                      ),
                      contentPadding:
                          const EdgeInsets.symmetric(horizontal: 20),
                    ),
                    onSubmitted: (value) => _sendMessage(),
                  ),
                ),
                const SizedBox(width: 10),
                CircleAvatar(
                  radius: 25,
                  backgroundColor: Colors.blue,
                  child: IconButton(
                    icon: const Icon(Icons.send, color: Colors.white),
                    onPressed: _sendMessage,
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐