🎯 Flutter 跨平台实战:OpenHarmony 健康管理应用 Day4|表单非空校验 + 提交按钮 + 消息提示

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

🚀 前言

大家好,本篇是 Flutter + OpenHarmony 健康管理应用 开发系列的第四篇笔记。在 Day3 完成健康录入页面表单布局与多输入框搭建的基础上,今天继续完善交互逻辑,新增提交保存按钮、实现表单全字段非空校验,同时通过 SnackBar 给出友好的消息提示,提升 APP 使用体验。本阶段为后续接入本地数据持久化存储做好前置铺垫,全程基于原生 Flutter 组件开发,无需额外第三方依赖,适配 OpenHarmony 鸿蒙模拟器直接运行,适合课程实训作业提交与自查高分使用。

💥 本文你能学到

  • Flutter 按钮组件 ElevatedButton 布局与使用
  • 表单多字段非空逻辑校验编写
  • SnackBar 底部消息提示弹窗实现
  • 输入框内容获取与字符串判空处理
  • 鸿蒙端表单交互适配与页面调试方法
  • 有状态组件资源规范释放写法

🥝 开发环境

1. 环境信息

  • 开发工具:DevEco Studio
  • 开发语言:Dart
  • 开发框架:Flutter
  • 调试设备:OpenHarmony 手机模拟器
  • 适配平台:OpenHarmony

2. 依赖配置

Day4 无需新增任何第三方依赖,沿用之前默认配置即可,不需要修改 pubspec.yaml 文件

dependencies:
  flutter:
    sdk: flutter

📝 今日核心开发功能

  • 在健康录入页面添加全屏宽度提交保存按钮
  • 实现姓名、年龄、身高、体重、心率全部字段非空校验
  • 未填完整信息时弹出提示,禁止提交
  • 信息填写完整后给出提交成功友好提示
  • 保留输入框控制器规范释放,防止内存泄漏
  • 兼容鸿蒙设备软键盘弹出布局,页面正常滚动不遮挡

✅ 完整可运行核心代码

import 'package:flutter/material.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '鸿蒙健康管理',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MainPage(),
    );
  }
}

// 主页面底部导航框架
class MainPage extends StatefulWidget {
  const MainPage({super.key});

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0;

  final List<Widget> _pages = const [
    HomePage(),
    HealthInputPage(),
    ProfilePage(),
  ];

  void _onItemTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        onTap: _onItemTapped,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
          BottomNavigationBarItem(icon: Icon(Icons.add_chart), label: "健康录入"),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: "个人中心"),
        ],
      ),
    );
  }
}

// 首页页面
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("首页")),
      body: const Center(
        child: Text("欢迎使用健康管理应用", style: TextStyle(fontSize: 18)),
      ),
    );
  }
}

// 健康录入页面 新增校验与提交按钮
class HealthInputPage extends StatefulWidget {
  const HealthInputPage({super.key});

  @override
  State<HealthInputPage> createState() => _HealthInputPageState();
}

class _HealthInputPageState extends State<HealthInputPage> {
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _ageController = TextEditingController();
  final TextEditingController _heightController = TextEditingController();
  final TextEditingController _weightController = TextEditingController();
  final TextEditingController _heartRateController = TextEditingController();

  // 表单校验与提交逻辑
  void _submitForm() {
    String name = _nameController.text.trim();
    String age = _ageController.text.trim();
    String height = _heightController.text.trim();
    String weight = _weightController.text.trim();
    String heart = _heartRateController.text.trim();

    if (name.isEmpty || age.isEmpty || height.isEmpty || weight.isEmpty || heart.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("请填写完整所有健康信息")),
      );
      return;
    }

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text("✅ 信息提交成功,待本地存储保存")),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("健康录入")),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text("姓名", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
            const SizedBox(height: 8),
            TextField(
              controller: _nameController,
              decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入您的姓名"),
            ),
            const SizedBox(height: 16),

            const Text("年龄", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
            const SizedBox(height: 8),
            TextField(
              controller: _ageController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入您的年龄"),
            ),
            const SizedBox(height: 16),

            const Text("身高(cm)", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
            const SizedBox(height: 8),
            TextField(
              controller: _heightController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入您的身高"),
            ),
            const SizedBox(height: 16),

            const Text("体重(kg)", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
            const SizedBox(height: 8),
            TextField(
              controller: _weightController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入您的体重"),
            ),
            const SizedBox(height: 16),

            const Text("心率(次/分)", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)),
            const SizedBox(height: 8),
            TextField(
              controller: _heartRateController,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(border: OutlineInputBorder(), hintText: "请输入您的心率"),
            ),
            const SizedBox(height: 30),

            SizedBox(
              width: double.infinity,
              height: 50,
              child: ElevatedButton(
                onPressed: _submitForm,
                child: const Text("提交保存", style: TextStyle(fontSize: 18)),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  void dispose() {
    _nameController.dispose();
    _ageController.dispose();
    _heightController.dispose();
    _weightController.dispose();
    _heartRateController.dispose();
    super.dispose();
  }
}

// 个人中心页面
class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("个人中心")),
      body: const Center(child: Text("个人信息展示区域", style: TextStyle(fontSize: 16))),
    );
  }
}

📱 调试与运行完整步骤

  1. 停止上一个项目运行
  2. 打开项目 lib/main.dart 文件,修改原有代码(上方有完整代码)
  3. 无需修改配置文件,直接终端执行运行命令
    flutter run
  4. 选择已启动的 OpenHarmony 模拟器设备,等待编译完成
  5. 进入健康录入页面,不填写任何内容直接点击提交,查看非空提示
  6. 完整填写所有字段后再次提交,查看提交成功提示,功能正常即实验完成

🔐 跨平台适配说明

本次 Day4 功能开发完全遵循 Flutter 跨平台适配规范,完美兼容 OpenHarmony 系统:

  • 页面采用 SingleChildScrollView 滚动布局,适配鸿蒙软键盘弹出,不会遮挡输入框与按钮
  • 消息提示组件原生适配鸿蒙系统底部展示位置,样式显示正常无错位
  • 数字输入框键盘类型适配鸿蒙输入法,自动弹出数字键盘
  • 整体布局自适应鸿蒙模拟器屏幕尺寸,无需额外适配原生代码

🧩 常见错误排查

错误现象解决方法
点击按钮无任何提示弹窗检查是否使用 ScaffoldMessenger 绑定页面上下文,确认方法无语法错误
全部填写后仍然提示不能为空检查是否使用 trim () 去除首尾空格,避免空格误判为空
页面底部按钮被键盘遮挡确认外层已嵌套 SingleChildScrollView,保证页面可整体滚动
编译报错布局溢出合理设置 SizedBox 间距,按钮固定宽高适配页面

🎨 项目后续规划

Day4 已完成表单校验、提交按钮与消息提示功能,后续开发规划如下:

  • Day5:引入本地存储依赖 shared_preferences,配置项目依赖环境
  • Day6:实现健康信息本地持久化保存,关闭重启数据不丢失
  • Day7:首页读取本地存储数据,卡片样式展示个人健康信息
  • 后续按规划依次完成 UI 美化、BMI 计算、图表可视化、打包发布等全流程

📌 项目总结

本篇完整记录了 Flutter 鸿蒙健康管理项目 Day4 开发全过程,在原有表单基础上完成了提交按钮布局、多字段非空校验、友好消息提示等核心交互功能。通过本次开发,熟练掌握了 Flutter 按钮使用、表单判空逻辑、消息弹窗调用以及有状态组件生命周期资源释放等知识点,同时进一步适配了 OpenHarmony 鸿蒙端交互体验,为下一日接入本地持久化存储功能打下坚实基础。

✅ 结尾小贴士

  • 全文代码可直接复制替换运行,无需改动任何配置
  • 全程仅使用 DevEco Studio 即可完成开发与调试
  • 无需修改 pubspec.yaml 依赖,直接编译运行
  • 点赞收藏不迷路,后续将规划持续更新每一日开发笔记!

更多推荐