Flutter for OpenHarmony打造你的第一个 聊天机器人:从零实现 AI 助手界面
Flutter for OpenHarmony打造你的第一个聊天机器人:从零实现 AI 助手界面
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 到产品
当前代码是一个优秀的起点,若要投入生产,可考虑以下增强:
-
持久化消息
使用shared_preferences或hive保存聊天记录,避免重启清空。 -
真正的 AI 集成
替换_getAIResponse为 HTTP 请求,对接大模型 API:final response = await http.post(apiUrl, body: {'prompt': input}); -
输入状态动画
将“AI 正在思考…”文字替换为 三个跳动的圆点动画(AnimatedContainer+Timer)。 -
消息时间戳 & 头像优化
为每条消息添加发送时间,并使用更精致的 AI 头像(如自定义 SVG)。 -
语音输入支持
集成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,
),
),
],
),
),
],
),
);
}
}
更多推荐



所有评论(0)