Flutter for OpenHarmony 实战:标签输入框实现
组件化开发采用组件化开发思想,将标签输入功能拆分为标签项组件和标签输入主组件,提高代码复用性和可维护性。使用和实现不同类型的组件,根据需要管理状态。Flutter 核心布局技术使用Container实现带边框和背景的容器。使用Row和Column实现灵活的水平和垂直布局。使用Wrap实现标签的自动换行显示,适应不同屏幕尺寸。使用确保内容在小屏幕上也能正常显示。输入管理技术使用TextField实现
前言:跨生态开发的新机遇
在移动开发领域,我们总是面临着选择与适配。今天,你的Flutter应用在Android和iOS上跑得正欢,明天可能就需要考虑一个新的平台:HarmonyOS(鸿蒙)。这不是一道选答题,而是很多团队正在面对的现实。
Flutter的优势很明确——写一套代码,就能在两个主要平台上运行,开发体验流畅。而鸿蒙代表的是下一个时代的互联生态,它不仅仅是手机系统,更着眼于未来全场景的体验。将现有的Flutter应用适配到鸿蒙,听起来像是一个“跨界”任务,但它本质上是一次有价值的技术拓展:让产品触达更多用户,也让技术栈覆盖更广。
不过,这条路走起来并不像听起来那么简单。Flutter和鸿蒙,从底层的架构到上层的工具链,都有着各自的设计逻辑。会遇到一些具体的问题:代码如何组织?原有的功能在鸿蒙上如何实现?那些平台特有的能力该怎么调用?更实际的是,从编译打包到上架部署,整个流程都需要重新摸索。
这篇文章想做的,就是把这些我们趟过的路、踩过的坑,清晰地摊开给你看。我们不会只停留在“怎么做”,还会聊到“为什么得这么做”,以及“如果出了问题该往哪想”。这更像是一份实战笔记,源自真实的项目经验,聚焦于那些真正卡住过我们的环节。
无论你是在为一个成熟产品寻找新的落地平台,还是从一开始就希望构建能面向多端的应用,这里的思路和解决方案都能提供直接的参考。理解了两套体系之间的异同,掌握了关键的衔接技术,不仅能完成这次迁移,更能积累起应对未来技术变化的能力。
混合工程结构深度解析
项目目录架构
当Flutter项目集成鸿蒙支持后,典型的项目结构会发生显著变化。以下是经过ohos_flutter插件初始化后的项目结构:
my_flutter_harmony_app/
├── lib/ # Flutter业务代码(基本不变)
│ ├── main.dart # 应用入口
│ ├── home_page.dart # 首页
│ └── utils/
│ └── platform_utils.dart # 平台工具类
├── pubspec.yaml # Flutter依赖配置
├── ohos/ # 鸿蒙原生层(核心适配区)
│ ├── entry/ # 主模块
│ │ └── src/main/
│ │ ├── ets/ # ArkTS代码
│ │ │ ├── MainAbility/
│ │ │ │ ├── MainAbility.ts # 主Ability
│ │ │ │ └── MainAbilityContext.ts
│ │ │ └── pages/
│ │ │ ├── Index.ets # 主页面
│ │ │ └── Splash.ets # 启动页
│ │ ├── resources/ # 鸿蒙资源文件
│ │ │ ├── base/
│ │ │ │ ├── element/ # 字符串等
│ │ │ │ ├── media/ # 图片资源
│ │ │ │ └── profile/ # 配置文件
│ │ │ └── en_US/ # 英文资源
│ │ └── config.json # 应用核心配置
│ ├── ohos_test/ # 测试模块
│ ├── build-profile.json5 # 构建配置
│ └── oh-package.json5 # 鸿蒙依赖管理
└── README.md
展示效果图片
flutter 实时预览 效果展示
运行到鸿蒙虚拟设备中效果展示
目录
功能代码实现
标签项组件
标签项组件是标签输入功能的基础,用于显示单个标签和删除按钮。
核心设计
- 接收标签文本和删除回调函数作为参数
- 使用 Container 实现标签的圆角背景和边框
- 使用 Row 布局标签文本和删除图标
- 使用 GestureDetector 实现删除图标的点击事件
代码实现
import 'package:flutter/material.dart';
class TagItem extends StatelessWidget {
final String tag;
final VoidCallback onRemove;
const TagItem({
super.key,
required this.tag,
required this.onRemove,
});
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 6.0),
decoration: BoxDecoration(
color: Colors.deepPurple[100],
borderRadius: BorderRadius.circular(16.0),
border: Border.all(color: Colors.deepPurple[300]!),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
tag,
style: TextStyle(
color: Colors.deepPurple[800],
fontSize: 14,
),
),
const SizedBox(width: 6.0),
GestureDetector(
onTap: onRemove,
child: Icon(
Icons.close,
size: 16,
color: Colors.deepPurple[600],
),
),
],
),
);
}
}
技术要点
-
布局设计:使用
Container实现圆角背景和边框,Row实现标签文本和删除图标的水平布局。 -
尺寸控制:设置
mainAxisSize: MainAxisSize.min使标签宽度自适应内容,避免不必要的空间占用。 -
交互设计:使用
GestureDetector包装删除图标,实现点击删除功能。 -
样式设计:使用深紫色系的颜色方案,与应用主题保持一致,提高视觉统一性。
-
响应式设计:标签尺寸自适应内容长度,在不同屏幕尺寸上都能正常显示。
标签输入主组件
标签输入主组件是功能的核心,实现了标签的添加、删除、验证和管理功能。
核心设计
- 使用
TextEditingController管理输入框内容 - 使用
FocusNode管理输入框焦点 - 实现
_addTag方法处理标签添加逻辑 - 实现
_removeTag方法处理标签删除逻辑 - 使用
Wrap组件实现标签的自动换行显示 - 支持初始标签、最大标签限制、标签变化回调等功能
代码实现
import 'package:flutter/material.dart';
import 'tag_item.dart';
class TagInput extends StatefulWidget {
final List<String>? initialTags;
final int? maxTags;
final String hintText;
final Function(List<String>)? onTagsChanged;
const TagInput({
super.key,
this.initialTags,
this.maxTags,
this.hintText = '输入标签并按回车添加',
this.onTagsChanged,
});
State<TagInput> createState() => _TagInputState();
}
class _TagInputState extends State<TagInput> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focusNode = FocusNode();
late List<String> _tags;
void initState() {
super.initState();
_tags = widget.initialTags ?? [];
}
void dispose() {
_controller.dispose();
_focusNode.dispose();
super.dispose();
}
void _addTag(String tag) {
if (tag.isEmpty) return;
if (widget.maxTags != null && _tags.length >= widget.maxTags!) return;
if (_tags.contains(tag)) return;
setState(() {
_tags.add(tag);
_controller.clear();
_focusNode.requestFocus();
});
if (widget.onTagsChanged != null) {
widget.onTagsChanged!(_tags);
}
}
void _removeTag(String tag) {
setState(() {
_tags.remove(tag);
});
if (widget.onTagsChanged != null) {
widget.onTagsChanged!(_tags);
}
}
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(8.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 已添加的标签
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: _tags.map((tag) {
return TagItem(
tag: tag,
onRemove: () => _removeTag(tag),
);
}).toList(),
),
const SizedBox(height: 8.0),
// 输入框
TextField(
controller: _controller,
focusNode: _focusNode,
decoration: InputDecoration(
hintText: widget.hintText,
border: const OutlineInputBorder(),
suffixIcon: _tags.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
setState(() {
_tags.clear();
_controller.clear();
_focusNode.requestFocus();
});
if (widget.onTagsChanged != null) {
widget.onTagsChanged!(_tags);
}
},
)
: null,
),
onSubmitted: _addTag,
),
if (widget.maxTags != null)
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
'最多添加 ${widget.maxTags} 个标签',
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
),
),
],
),
);
}
}
技术要点
-
状态管理:使用
StatefulWidget和setState()管理标签列表状态,确保 UI 与数据保持同步。 -
输入管理:使用
TextEditingController管理输入框内容,使用FocusNode管理输入框焦点,提高用户体验。 -
标签添加逻辑:
- 验证标签非空
- 检查是否达到最大标签限制
- 检查标签是否重复
- 添加标签后清空输入框并重新获取焦点
-
标签删除逻辑:从标签列表中移除指定标签,并触发标签变化回调。
-
布局设计:
- 使用
Container实现带边框的容器 - 使用
Wrap实现标签的自动换行显示 - 使用
Column和CrossAxisAlignment实现灵活的垂直布局
- 使用
-
交互设计:
- 添加清空所有标签的功能
- 显示最大标签限制提示
- 提供标签变化回调,方便父组件获取标签变化
-
资源管理:在
dispose()方法中释放TextEditingController和FocusNode资源,避免内存泄漏。
首页集成使用
在首页中集成标签输入组件,展示不同的标签输入示例,无需按钮跳转。
核心设计
- 添加
AppBar:设置标题和背景色,提高应用的美观度 - 使用
SingleChildScrollView:确保内容在小屏幕上也能正常显示,避免内容溢出 - 实现两个标签输入示例:
- 基本标签输入,带有初始标签
- 带最大标签限制的标签输入
- 添加标签变化的回调处理,实时显示当前标签
代码实现
import 'package:flutter/material.dart';
import 'components/tag_input.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter for openHarmony',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
debugShowCheckedModeBanner: false,
home: const MyHomePage(title: 'Flutter for openHarmony'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> _tags = [];
void _onTagsChanged(List<String> tags) {
setState(() {
_tags = tags;
});
print('Tags changed: $tags');
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('标签输入框功能'),
backgroundColor: Colors.deepPurple,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'标签输入示例',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.deepPurple,
),
),
const SizedBox(height: 16.0),
// 基本标签输入
const Text('基本标签输入:'),
const SizedBox(height: 8.0),
TagInput(
initialTags: ['Flutter', 'OpenHarmony', '移动开发'],
hintText: '输入标签并按回车添加',
onTagsChanged: _onTagsChanged,
),
const SizedBox(height: 24.0),
// 带最大标签限制的标签输入
const Text('带最大标签限制的标签输入:'),
const SizedBox(height: 8.0),
TagInput(
maxTags: 5,
hintText: '最多添加5个标签',
onTagsChanged: (tags) {
print('Limited tags changed: $tags');
},
),
const SizedBox(height: 24.0),
// 显示当前标签
const Text('当前标签:'),
const SizedBox(height: 8.0),
Wrap(
spacing: 8.0,
runSpacing: 8.0,
children: _tags.map((tag) {
return Chip(
label: Text(tag),
backgroundColor: Colors.deepPurple[100],
labelStyle: TextStyle(color: Colors.deepPurple[800]),
);
}).toList(),
),
],
),
),
);
}
}
技术要点
-
应用配置:配置
MaterialApp的主题和调试横幅,提高应用的美观度。 -
布局优化:
- 使用
Scaffold作为根布局,提供AppBar和body,结构清晰。 - 使用
SingleChildScrollView包裹内容,确保在小屏幕设备上也能正常显示所有内容。 - 使用
Column和CrossAxisAlignment实现灵活的垂直布局。
- 使用
-
组件集成:
- 直接在
body中使用TagInput组件,展示不同的使用场景。 - 通过
initialTags参数设置初始标签。 - 通过
maxTags参数设置最大标签限制。 - 通过
onTagsChanged回调获取标签变化。
- 直接在
-
实时反馈:
- 实现
_onTagsChanged方法,实时更新当前标签列表。 - 使用
Wrap和Chip组件显示当前标签,提供直观的视觉反馈。 - 添加打印语句,便于调试和了解标签变化。
- 实现
-
主题一致性:设置
AppBar的背景色为Colors.deepPurple,与标签组件的主题保持一致,提高应用的整体美观度。
开发中容易遇到的问题
-
标签重复添加
- 问题描述:如果不检查标签是否重复,可能会导致相同的标签被多次添加,影响用户体验。
- 解决方案:在添加标签前检查标签是否已存在于标签列表中,避免重复添加。
-
输入框焦点管理
- 问题描述:如果不管理输入框焦点,添加标签后输入框可能会失去焦点,需要用户重新点击输入框才能继续输入。
- 解决方案:添加标签后,使用
FocusNode.requestFocus()重新获取输入框焦点,提高用户体验。
-
资源泄漏
- 问题描述:如果不释放
TextEditingController和FocusNode资源,可能会导致内存泄漏,影响应用性能。 - 解决方案:在
dispose()方法中调用每个TextEditingController和FocusNode的dispose()方法,释放资源。
- 问题描述:如果不释放
-
小屏幕适配
- 问题描述:在小屏幕设备上,标签输入组件可能会溢出屏幕,导致部分内容无法显示。
- 解决方案:使用
SingleChildScrollView包裹标签输入组件,确保在小屏幕上也能正常滚动显示。
-
标签换行显示
- 问题描述:如果不使用适当的布局组件,标签可能会在一行显示,超出屏幕宽度。
- 解决方案:使用
Wrap组件实现标签的自动换行显示,适应不同屏幕尺寸。
-
最大标签限制
- 问题描述:如果不设置最大标签限制,用户可能会添加过多标签,导致界面混乱。
- 解决方案:添加
maxTags参数,限制最多可以添加的标签数量,并显示限制提示。
-
标签变化回调
- 问题描述:如果不提供标签变化回调,父组件无法及时获取标签变化,影响数据同步。
- 解决方案:添加
onTagsChanged回调函数,在标签变化时通知父组件。
总结开发中用到的技术点
-
组件化开发
- 采用组件化开发思想,将标签输入功能拆分为标签项组件和标签输入主组件,提高代码复用性和可维护性。
- 使用
StatelessWidget和StatefulWidget实现不同类型的组件,根据需要管理状态。
-
Flutter 核心布局技术
- 使用
Container实现带边框和背景的容器。 - 使用
Row和Column实现灵活的水平和垂直布局。 - 使用
Wrap实现标签的自动换行显示,适应不同屏幕尺寸。 - 使用
SingleChildScrollView确保内容在小屏幕上也能正常显示。
- 使用
-
输入管理技术
- 使用
TextField实现文本输入功能。 - 使用
TextEditingController管理输入框内容,便于获取和清空输入。 - 使用
FocusNode管理输入框焦点,提高用户体验。
- 使用
-
状态管理技术
- 使用
StatefulWidget和setState()管理标签列表状态,确保 UI 与数据保持同步。 - 使用
late关键字延迟初始化标签列表,提高代码可读性。
- 使用
-
交互设计技术
- 实现标签的添加、删除和清空功能,提供完整的标签管理体验。
- 添加标签变化回调,方便父组件获取标签变化。
- 显示最大标签限制提示,提供清晰的用户引导。
-
样式设计技术
- 使用
BoxDecoration实现标签的圆角背景和边框。 - 使用深紫色系的颜色方案,与应用主题保持一致,提高应用的整体美观度。
- 使用
TextStyle控制文本样式,创建清晰的视觉层次。
- 使用
-
资源管理技术
- 在
dispose()方法中释放TextEditingController和FocusNode资源,避免内存泄漏。 - 使用
const构造函数和常量,减少不必要的重建,提高应用性能。
- 在
-
响应式设计技术
- 使用
Wrap实现标签的自动换行显示,适应不同屏幕尺寸。 - 使用
SingleChildScrollView确保内容在小屏幕上也能正常显示,避免内容溢出。 - 使用
MainAxisSize.min使标签宽度自适应内容,提高布局灵活性。
- 使用
-
回调函数技术
- 使用
Function(List<String>)类型的回调函数,实现子组件向父组件传递数据。 - 使用匿名函数作为回调参数,提供灵活的回调处理方式。
- 使用
-
主题配置技术
- 使用
ThemeData和ColorScheme.fromSeed创建统一的应用主题。 - 通过主题配置,确保应用的整体风格一致,提高用户体验。
- 使用
通过本次开发,我们成功实现了一个功能完整、用户体验良好的标签输入框功能,展示了 Flutter for OpenHarmony 平台上的组件化开发能力。该功能不仅可以直接应用于实际项目中,也为类似标签输入功能的开发提供了参考和借鉴。
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)