Flutter for OpenHarmony垃圾分类指南App实战:编辑资料实现
摘要 本文介绍了在Flutter for OpenHarmony环境下实现编辑资料页面的关键技术。主要内容包括:使用TextEditingController初始化表单并管理用户输入状态;通过Stack组件实现头像和相机图标的叠加布局;采用表单验证确保输入有效性;利用GetX进行状态管理。文章详细讲解了页面结构设计、头像区域交互实现(包括拍照/相册选择功能)以及增强版输入框的实现方案(包含浮动标签

前言
用户想改个昵称、换个头像,就得来编辑资料页面。这个页面功能不复杂,但涉及到表单处理和状态管理,是个很好的练手场景。本文将详细介绍如何在Flutter for OpenHarmony环境下实现一个完整的编辑资料页面,包括表单初始化、头像上传、输入验证以及数据持久化等核心技术点。
技术要点概览
在开始实现之前,让我们先了解本页面涉及的核心技术点:
- TextEditingController:表单输入控制器的使用
- StatefulWidget:需要管理表单状态
- Stack组件:头像和相机图标的叠加布局
- 表单验证:输入内容的有效性检查
- GetX状态管理:与ProfileController的交互
页面初始化
编辑资料页面需要显示用户当前的信息,所以要在初始化时获取数据:
class EditProfilePage extends StatefulWidget {
const EditProfilePage({super.key});
State<EditProfilePage> createState() => _EditProfilePageState();
}
class _EditProfilePageState extends State<EditProfilePage> {
// 使用late关键字延迟初始化
late TextEditingController _nameController;
// 获取ProfileController实例
final controller = Get.find<ProfileController>();
void initState() {
super.initState();
// 用当前用户名初始化输入框
_nameController = TextEditingController(text: controller.userName.value);
}
初始化要点说明
关键点:
TextEditingController在initState里初始化,并且用当前的用户名作为初始值。这样用户进入页面时,输入框里已经有了现在的昵称,用户可以在此基础上修改。
late关键字告诉Dart这个变量会在使用前初始化,避免空安全检查报错。
资源释放
别忘了在dispose中释放Controller:
void dispose() {
_nameController.dispose();
super.dispose();
}
页面结构
页面包含头像区域和昵称输入框,AppBar右边有保存按钮:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('编辑资料'),
actions: [
TextButton(
onPressed: _save,
child: const Text('保存', style: TextStyle(color: Colors.white)),
),
],
),
保存按钮放在AppBar右边是常见的设计模式,用户改完资料后顺手就能点保存。
头像区域
头像区域展示当前头像,右下角有个相机图标表示可以更换:
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
Center(
child: Stack(
children: [
// 头像圆形容器
CircleAvatar(
radius: 50.r,
backgroundColor: AppTheme.primaryColor.withOpacity(0.2),
child: Icon(Icons.person, size: 60.sp, color: AppTheme.primaryColor),
),
// 相机图标定位到右下角
Positioned(
bottom: 0,
right: 0,
child: Container(
padding: EdgeInsets.all(4.w),
decoration: const BoxDecoration(
color: AppTheme.primaryColor,
shape: BoxShape.circle,
),
child: Icon(Icons.camera_alt, size: 20.sp, color: Colors.white),
),
),
],
),
),
Stack布局说明
用Stack组件实现头像和相机图标的叠加。Positioned把相机图标定位到右下角。
交互提示:相机图标暗示用户这里可以点击更换头像。实际项目中点击后应该弹出选择框,让用户从相册选择或拍照。
头像点击处理
GestureDetector(
onTap: _showAvatarOptions,
child: Stack(
// ... 头像内容
),
)
void _showAvatarOptions() {
Get.bottomSheet(
Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16.r)),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.camera_alt),
title: Text('拍照'),
onTap: () {
Get.back();
_takePhoto();
},
),
ListTile(
leading: Icon(Icons.photo_library),
title: Text('从相册选择'),
onTap: () {
Get.back();
_pickFromGallery();
},
),
ListTile(
leading: Icon(Icons.person),
title: Text('使用默认头像'),
onTap: () {
Get.back();
_useDefaultAvatar();
},
),
],
),
),
);
}
昵称输入框
输入框使用Material Design风格,带有浮动标签:
SizedBox(height: 32.h),
TextField(
controller: _nameController,
decoration: InputDecoration(
labelText: '昵称',
filled: true,
fillColor: Colors.white,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
),
),
],
),
),
);
}
输入框设计说明
输入框用labelText而不是hintText,这样标签会在输入时浮动到上方,用户始终能看到这是昵称输入框。
增强版输入框
TextField(
controller: _nameController,
maxLength: 20, // 限制最大长度
decoration: InputDecoration(
labelText: '昵称',
hintText: '请输入2-20个字符',
filled: true,
fillColor: Colors.white,
prefixIcon: Icon(Icons.person_outline),
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: () => _nameController.clear(),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
),
// 错误提示样式
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.r),
borderSide: BorderSide(color: Colors.red),
),
),
)
保存逻辑
保存前要验证输入是否有效:
void _save() {
// 验证昵称不能为空
if (_nameController.text.trim().isEmpty) {
Get.snackbar('提示', '请输入昵称');
return;
}
// 调用控制器保存数据
controller.saveUserName(_nameController.text.trim());
// 显示成功提示
Get.snackbar('成功', '资料已更新');
// 返回上一页
Get.back();
}
}
验证逻辑说明
验证逻辑很简单:昵称不能为空。trim()去掉首尾空格,避免用户输入纯空格。
保存成功后做三件事:
- 调用控制器的方法保存数据
- 显示成功提示
- 返回上一页
增强版验证
void _save() {
final name = _nameController.text.trim();
// 空值检查
if (name.isEmpty) {
Get.snackbar('提示', '请输入昵称');
return;
}
// 长度检查
if (name.length < 2) {
Get.snackbar('提示', '昵称至少需要2个字符');
return;
}
if (name.length > 20) {
Get.snackbar('提示', '昵称不能超过20个字符');
return;
}
// 敏感词检查
if (_containsSensitiveWords(name)) {
Get.snackbar('提示', '昵称包含敏感词,请修改');
return;
}
// 特殊字符检查
final validPattern = RegExp(r'^[\u4e00-\u9fa5a-zA-Z0-9_]+$');
if (!validPattern.hasMatch(name)) {
Get.snackbar('提示', '昵称只能包含中文、英文、数字和下划线');
return;
}
// 保存数据
controller.saveUserName(name);
Get.snackbar('成功', '资料已更新');
Get.back();
}
bool _containsSensitiveWords(String text) {
final sensitiveWords = ['敏感词1', '敏感词2'];
return sensitiveWords.any((word) => text.contains(word));
}
控制器里的保存方法
ProfileController里应该有对应的保存方法:
// ProfileController中的方法
class ProfileController extends GetxController {
final userName = '用户'.obs;
final avatarUrl = ''.obs;
void saveUserName(String name) {
userName.value = name;
// 持久化存储
storage.write('userName', name);
}
void saveAvatar(String url) {
avatarUrl.value = url;
storage.write('avatarUrl', url);
}
void onInit() {
super.onInit();
// 读取本地存储的数据
userName.value = storage.read('userName') ?? '用户';
avatarUrl.value = storage.read('avatarUrl') ?? '';
}
}
数据保存到本地存储,下次打开App时读取出来,用户的昵称就不会丢失。
头像上传功能
完整的头像上传实现:
import 'package:image_picker/image_picker.dart';
import 'package:image_cropper/image_cropper.dart';
Future<void> _takePhoto() async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.camera);
if (image != null) {
await _cropAndUpload(image.path);
}
}
Future<void> _pickFromGallery() async {
final picker = ImagePicker();
final image = await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
await _cropAndUpload(image.path);
}
}
Future<void> _cropAndUpload(String imagePath) async {
// 裁剪图片为圆形
final croppedFile = await ImageCropper().cropImage(
sourcePath: imagePath,
aspectRatio: CropAspectRatio(ratioX: 1, ratioY: 1),
cropStyle: CropStyle.circle,
uiSettings: [
AndroidUiSettings(
toolbarTitle: '裁剪头像',
toolbarColor: AppTheme.primaryColor,
toolbarWidgetColor: Colors.white,
),
],
);
if (croppedFile != null) {
// 上传到服务器
final url = await _uploadImage(croppedFile.path);
if (url != null) {
controller.saveAvatar(url);
Get.snackbar('成功', '头像已更新');
}
}
}
Future<String?> _uploadImage(String path) async {
try {
// 调用上传API
final response = await ApiService.uploadAvatar(path);
return response.url;
} catch (e) {
Get.snackbar('错误', '头像上传失败');
return null;
}
}
更多资料字段
除了昵称,还可以让用户填写更多信息:
// 性别选择
DropdownButtonFormField<String>(
value: _selectedGender,
decoration: InputDecoration(labelText: '性别'),
items: ['男', '女', '保密'].map((gender) {
return DropdownMenuItem(value: gender, child: Text(gender));
}).toList(),
onChanged: (value) => setState(() => _selectedGender = value),
)
// 生日选择
ListTile(
title: Text('生日'),
subtitle: Text(_birthday ?? '未设置'),
trailing: Icon(Icons.chevron_right),
onTap: () async {
final date = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(1900),
lastDate: DateTime.now(),
);
if (date != null) {
setState(() => _birthday = DateFormat('yyyy-MM-dd').format(date));
}
},
)
// 个性签名
TextField(
controller: _signatureController,
maxLines: 3,
maxLength: 100,
decoration: InputDecoration(
labelText: '个性签名',
hintText: '介绍一下自己吧',
),
)
修改确认
如果用户修改了内容但没保存就返回,弹出确认框询问是否放弃修改:
Future<bool> _onWillPop() async {
// 检查是否有修改
if (_nameController.text.trim() != controller.userName.value) {
final result = await Get.dialog<bool>(
AlertDialog(
title: Text('提示'),
content: Text('您有未保存的修改,确定要离开吗?'),
actions: [
TextButton(
onPressed: () => Get.back(result: false),
child: Text('取消'),
),
TextButton(
onPressed: () => Get.back(result: true),
child: Text('确定'),
),
],
),
);
return result ?? false;
}
return true;
}
// 在build中使用
WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
// ...
),
)
表单处理的通用模式
编辑资料页面展示了Flutter表单处理的通用模式:
- 初始化:在
initState里创建TextEditingController,设置初始值 - 绑定:把controller绑定到
TextField - 验证:提交前检查输入是否有效
- 保存:调用业务逻辑保存数据
- 反馈:给用户成功或失败的提示
掌握了这个模式,做其他表单页面也能举一反三。
总结
编辑资料页面虽然功能简单,但涉及的技术点很全面。本文介绍的实现方案包括:
- 表单初始化:使用TextEditingController管理输入状态
- 头像上传:图片选择、裁剪、上传的完整流程
- 输入验证:空值、长度、敏感词等多重验证
- 数据持久化:使用GetStorage保存用户数据
通过合理的表单设计和完善的验证逻辑,可以为用户提供良好的资料编辑体验。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)