Flutter+开源鸿蒙实战|智联邻里Day4 底部导航栏+邻里互助页面+闲置发布表单+本地缓存

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

摘要

本文承接Day3的网络请求与政务服务页面开发,聚焦「智联邻里」核心业务模块——邻里互助,同时完善项目整体交互框架。Day4全程围绕Flutter技术栈,详细讲解底部导航栏搭建、邻里互助闲置共享页面开发、闲置信息发布表单实现、本地缓存数据存储四大核心功能,补充更多文字解释的同时,保持代码精简(每块仅5-6行核心代码),重点拆解开发逻辑、适配细节和避坑技巧,全程适配鸿蒙手机、平板、DAYU200开发板,贴合十五五智慧社区“邻里共治、便民共享”的导向,让新手不仅能抄代码,更能理解背后的开发思路,为项目后续功能落地筑牢基础。
在这里插入图片描述

<!-- Schema.org 结构化数据 -->
<script type="application/ld+json">
{
  "@context":"https://schema.org",
  "@type":"BlogPosting",
  "headline":"Flutter+开源鸿蒙实战 智联邻里Day4 底部导航+邻里互助+闲置发布+本地缓存",
  "author":{"@type":"Person","name":"鸿蒙跨端开发者"},
  "publisher":{"@type":"Organization","name":"CSDN开源鸿蒙跨平台社区"},
  "datePublished":"2026-05-04",
  "description":"智联邻里Day4:Flutter底部导航栏搭建(Tab切换)、邻里互助闲置共享页面开发、闲置发布表单实现、shared_preferences本地缓存、鸿蒙多端适配,文字详解+精简代码,零基础友好",
  "keywords":"开源鸿蒙,OpenHarmony,Flutter,智联邻里Day4,Flutter底部导航栏,邻里互助页面,闲置发布表单,本地缓存,shared_preferences"
}
</script>

一、前言(详细衔接,明确今日核心)

哈喽各位小伙伴,Day4正式上线!经过前3天的开发,我们已经完成了项目基础搭建、首页UI开发、全局组件封装、网络请求封装和政务服务页面,今天咱们要做两件关键事:一是完善项目的交互框架——搭建底部导航栏,实现首页、政务服务、邻里互助、我的页面的Tab切换,让项目更具完整性;二是开发核心业务模块——邻里互助,包括闲置共享列表页面和闲置信息发布表单,同时实现本地缓存功能,让发布的闲置信息能够持久化存储,即使关闭APP再打开,数据也不会丢失。

今天的重点的是“交互体验+业务落地”,相比前3天,会补充更多文字解释,比如“为什么这么写”“这段代码的作用是什么”“鸿蒙适配需要注意什么”,避免大家只抄代码、不懂逻辑。依旧遵守老规矩:核心代码精简(每块仅5-6行)、口语化讲解、全程适配鸿蒙多设备,贴合十五五智慧社区“邻里互助、资源共享”的民生导向,让项目从“能看”变成“能用”,逐步落地实际业务场景。

先跟大家明确一下今天的开发逻辑:先搭建底部导航栏(统一页面切换入口)→ 开发邻里互助首页(闲置共享列表)→ 开发闲置发布表单(新增闲置信息)→ 集成本地缓存(存储闲置数据)→ 完善鸿蒙多端适配和交互细节,每一步都衔接前3天的内容,不脱节、不做无用功,确保新手能跟着一步步落地。

二、Day4 今日核心开发目标(详细拆解,清晰可落地)

  1. 搭建Flutter底部导航栏(BottomNavigationBar),实现“首页、政务服务、邻里互助、我的”4个Tab页面的无缝切换,适配鸿蒙多设备尺寸;
  2. 开发邻里互助首页(闲置共享页面),复用之前封装的组件,实现闲置信息列表渲染,支持下拉刷新、点击查看详情(铺垫后续功能);
  3. 开发闲置发布表单页面,包含标题、描述、联系方式输入框和发布按钮,做基础表单校验,贴合适老化设计;
  4. 集成shared_preferences本地缓存,实现闲置信息的存储、读取和展示,确保数据持久化,适配鸿蒙本地存储特性;
  5. 完善路由配置,实现底部导航切换、发布表单跳转、闲置列表点击跳转等交互逻辑;
  6. 优化鸿蒙多端适配细节(底部导航栏尺寸、表单布局、列表适配),解决多设备显示错乱、触控不灵敏等问题;
  7. 补充交互反馈(发布成功提示、下拉刷新动画、按钮点击反馈),提升用户体验,贴合智慧社区“便捷、友好”的调性。

三、版块1:搭建Flutter底部导航栏(核心框架,详解逻辑)

底部导航栏是跨端APP的核心交互组件,能够让用户快速切换不同页面,今天我们搭建一个适配鸿蒙多设备的底部导航栏,采用Flutter原生的BottomNavigationBar组件,详细拆解每一步的开发逻辑,让大家理解背后的原理。

3.1 为什么选择BottomNavigationBar?

Flutter原生的BottomNavigationBar组件轻量、易适配,支持自定义图标、文字、颜色,能够完美适配鸿蒙手机、平板、DAYU200开发板,而且与我们前3天搭建的路由系统、全局主题能够无缝衔接,无需引入第三方插件,减少开发成本,同时契合十五五“集约高效利用资源”的导向。

3.2 核心步骤(分步讲解,清晰易懂)

第一步:新建主页面(承载底部导航栏和所有Tab页面),新建lib/pages/main_page.dart,这是项目的主入口页面,所有Tab页面都会在这里展示。

第二步:定义Tab页面列表和导航栏选项,核心代码(附带详细注释,每一行都讲解作用):

import 'package:flutter/material.dart';
import 'package:zhilian_linli/pages/home_page.dart';
import 'package:zhilian_linli/pages/service_page.dart';
import 'package:zhilian_linli/pages/neighbor_page.dart'; // 后续新建的邻里互助页面
import 'package:zhilian_linli/pages/mine_page.dart'; // 后续新建的我的页面

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

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

class _MainPageState extends State<MainPage> {
  // 1. 定义Tab页面列表,顺序与底部导航选项一致
  final List<Widget> _tabPages = [
    const HomePage(), // 首页(Day2开发)
    const ServicePage(), // 政务服务页(Day3开发)
    const NeighborPage(), // 邻里互助页(今日开发)
    const MinePage(), // 我的页面(今日简易搭建)
  ];

  // 2. 定义底部导航选项(图标+文字),适配鸿蒙多设备
  final List<BottomNavigationBarItem> _navItems = [
    BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
    BottomNavigationBarItem(icon: Icon(Icons.service), label: "政务服务"),
    BottomNavigationBarItem(icon: Icon(Icons.people), label: "邻里互助"),
    BottomNavigationBarItem(icon: Icon(Icons.person), label: "我的"),
  ];

  // 3. 定义当前选中的Tab索引,默认选中首页(索引0)
  int _currentIndex = 0;

  
  Widget build(BuildContext context) {
    // 判断是否为鸿蒙大屏设备(平板/DAYU200),动态调整导航栏高度
    final isLargeScreen = MediaQuery.of(context).size.width >= 600;
    return Scaffold(
      // 4. 展示当前选中的Tab页面
      body: _tabPages[_currentIndex],
      // 5. 底部导航栏,核心组件
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex, // 当前选中的索引
        items: _navItems, // 导航选项
        type: BottomNavigationBarType.fixed, // 固定所有选项,不折叠(适配多选项)
        iconSize: isLargeScreen ? 24.sp : 20.sp, // 大屏放大图标,适配开发板/平板
        selectedFontSize: isLargeScreen ? 14.sp : 12.sp, // 选中文字放大
        unselectedFontSize: isLargeScreen ? 12.sp : 10.sp, // 未选中文字尺寸
        // 6. 点击导航选项,切换Tab页面
        onTap: (index) {
          setState(() {
            _currentIndex = index; // 更新选中索引,刷新页面
          });
        },
        // 适配全局主题色(Day1定义的墨绿色)
        selectedItemColor: Color(0xFF2E8B57),
        unselectedItemColor: Colors.grey,
      ),
    );
  }
}

3.3 关键细节讲解(鸿蒙适配重点)

  1. 尺寸适配:通过isLargeScreen判断设备尺寸,动态调整导航栏图标和文字大小,确保鸿蒙平板、DAYU200开发板上显示协调,避免图标过小、文字模糊;
  2. 类型设置:type: BottomNavigationBarType.fixed,确保4个导航选项全部显示,不折叠,适配智慧社区多模块的需求;
  3. 主题适配:选中颜色使用Day1定义的全局主色调(墨绿色),保持项目风格统一,提升整体美观度;
  4. 状态管理:使用_currentIndex记录当前选中的Tab索引,通过setState更新页面,实现无缝切换,这是Flutter状态管理的基础用法,后续会逐步深入。

3.4 修改主入口路由(衔接Day3)

打开lib/routes/routes.dart,将默认启动页面改为MainPage(底部导航主页面),核心代码修改:

import 'package:zhilian_linli/pages/main_page.dart';

// 主入口路由,替换之前的首页路由
static const String main = "/";

static Map<String, WidgetBuilder> routes = {
  main: (context) => const MainPage(), // 默认启动主页面(底部导航)
  home: (context) => const HomePage(),
  service: (context) => const ServicePage(),
  // 后续新增邻里互助、我的页面路由
};

同时修改main.dartinitialRoute,确保默认启动主页面:

MaterialApp(
  title: '智联邻里',
  theme: appTheme(),
  initialRoute: Routes.main, // 启动主页面(底部导航)
  routes: Routes.routes,
),

3.5 简易搭建“我的页面”(铺垫后续功能)

新建lib/pages/mine_page.dart,简易搭建页面结构,后续再完善功能,核心代码(复用全局组件):

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:zhilian_linli/widgets/custom_button.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("我的")),
      body: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            // 头像占位(后续可扩展头像上传)
            Container(
              width: 80.w,
              height: 80.w,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.grey[200],
              ),
              alignment: Alignment.center,
              child: Icon(Icons.person, size: 40.sp, color: Colors.grey),
            ),
            SizedBox(height: 20.h),
            // 我的订单、我的收藏等入口(后续完善)
            CustomButton(text: "我的订单", onTap: () {}),
            SizedBox(height: 12.h),
            CustomButton(text: "我的收藏", onTap: () {}),
          ],
        ),
      ),
    );
  }
}

同时在routes.dart中添加“我的页面”路由:

static const String mine = "/mine";

static Map<String, WidgetBuilder> routes = {
  main: (context) => const MainPage(),
  home: (context) => const HomePage(),
  service: (context) => const ServicePage(),
  mine: (context) => const MinePage(),
};

四、版块2:开发邻里互助页面(闲置共享列表,详解业务逻辑)

邻里互助是本次项目的核心业务模块之一,贴合十五五“邻里共治、资源共享”的智慧社区导向,主要实现“闲置物品共享”功能——居民可以发布自己的闲置物品,也可以查看其他居民发布的闲置物品,实现资源循环利用。今天我们先开发邻里互助首页,也就是闲置共享列表页面,后续再完善详情、兑换等功能。
在这里插入图片描述

4.1 页面布局规划(详细拆解,贴合民生需求)

邻里互助首页(NeighborPage)的布局逻辑的是:顶部搜索栏 + 闲置列表 + 发布按钮,具体拆解如下:

  1. 顶部:搜索栏(复用Day2封装的CustomInput组件),用于搜索闲置物品(比如“家电”“书籍”);
  2. 中间:闲置物品列表(垂直滚动),每一项展示闲置物品的标题、描述、发布人(简易版),复用自定义组件;
  3. 底部:悬浮发布按钮,点击跳转至闲置发布表单页面,方便居民快速发布闲置信息;
  4. 适配鸿蒙多设备:大屏(平板/开发板)列表显示2列,小屏(手机)显示1列,文字、图片尺寸自适应。

4.2 新建闲置物品实体类(衔接数据渲染)

新建lib/models/idle_model.dart,定义闲置物品的数据结构,后续用于列表渲染和本地缓存,核心代码(附带字段说明):

// 闲置物品实体类,对应发布表单的字段,用于数据存储和渲染
class IdleModel {
  final String id; // 闲置物品唯一ID(用于区分不同闲置)
  final String title; // 闲置物品标题(比如“闲置洗衣机”)
  final String description; // 闲置物品描述(比如“9成新,使用1年,无故障”)
  final String contact; // 联系方式(比如手机号、微信)
  final String time; // 发布时间(简易版,后续可优化为时间戳)

  // 构造函数,初始化所有字段
  IdleModel({
    required this.id,
    required this.title,
    required this.description,
    required this.contact,
    required this.time,
  });
}

字段说明:每一个字段都对应后续发布表单的输入项,确保数据结构统一,方便后续存储和渲染,避免数据错乱。

4.3 邻里互助首页核心代码(详解每一部分功能)

新建lib/pages/neighbor_page.dart,核心代码(附带详细注释,讲解每一段的作用):

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:zhilian_linli/widgets/custom_input.dart';
import 'package:zhilian_linli/widgets/custom_button.dart';
import 'package:zhilian_linli/models/idle_model.dart';
import 'package:zhilian_linli/routes/routes.dart';

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

  
  State<NeighborPage> createState() => _NeighborPageState();
}

class _NeighborPageState extends State<NeighborPage> {
  // 定义闲置物品列表(后续从本地缓存读取,今天先模拟几条数据)
  List<IdleModel> idleList = [
    IdleModel(
      id: "1",
      title: "闲置洗衣机",
      description: "9成新,全自动,使用1年,无故障,自提",
      contact: "138xxxx1234",
      time: "2026-05-04",
    ),
    IdleModel(
      id: "2",
      title: "闲置书籍",
      description: "考研书籍一套,几乎全新,低价转让",
      contact: "139xxxx5678",
      time: "2026-05-03",
    ),
  ];

  
  Widget build(BuildContext context) {
    // 判断鸿蒙设备尺寸,动态调整列表布局
    final isLargeScreen = MediaQuery.of(context).size.width >= 600;
    return Scaffold(
      appBar: AppBar(title: Text("邻里互助·闲置共享")),
      body: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            // 1. 顶部搜索栏(复用Day2的CustomInput组件)
            CustomInput(hintText: "搜索闲置物品(如家电、书籍)"),
            SizedBox(height: 16.h),

            // 2. 闲置物品列表(核心部分)
            Expanded(
              child: isLargeScreen
                  ? GridView.count(
                      // 大屏显示2列,充分利用空间
                      crossAxisCount: 2,
                      crossAxisSpacing: 16.w,
                      mainAxisSpacing: 16.h,
                      childAspectRatio: 1.5,
                      children: idleList.map((idle) {
                        return _buildIdleItem(idle); // 构建单个闲置物品卡片
                      }).toList(),
                    )
                  : ListView.builder(
                      // 小屏显示1列,垂直滚动
                      itemCount: idleList.length,
                      itemBuilder: (ctx, index) {
                        return _buildIdleItem(idleList[index]);
                      },
                    ),
            ),
          ],
        ),
      ),
      // 3. 悬浮发布按钮(点击跳转发布表单)
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          // 跳转至闲置发布表单页面
          Navigator.pushNamed(context, Routes.idlePublish);
        },
        backgroundColor: Color(0xFF2E8B57), // 全局主题色
        child: Icon(Icons.add, size: 24.sp),
      ),
    );
  }

  // 封装单个闲置物品卡片(复用,减少重复代码)
  Widget _buildIdleItem(IdleModel idle) {
    return Container(
      padding: EdgeInsets.all(12.w),
      decoration: BoxDecoration(
        border: Border.all(color: Colors.grey[200]),
        borderRadius: BorderRadius.circular(8.r),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 闲置标题
          Text(
            idle.title,
            style: TextStyle(fontSize: 15.sp, fontWeight: FontWeight.bold),
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
          ),
          SizedBox(height: 8.h),
          // 闲置描述
          Text(
            idle.description,
            style: TextStyle(fontSize: 13.sp, color: Colors.grey[600]),
            maxLines: 2,
            overflow: TextOverflow.ellipsis,
          ),
          SizedBox(height: 8.h),
          // 发布时间和联系方式(简易版)
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(idle.time, style: TextStyle(fontSize: 11.sp, color: Colors.grey)),
              Text("联系我", style: TextStyle(fontSize: 11.sp, color: Color(0xFF2E8B57))),
            ],
          ),
        ],
      ),
    );
  }
}

4.4 关键细节讲解(业务+适配)

  1. 布局适配:通过isLargeScreen判断设备尺寸,大屏用GridView(2列),小屏用ListView(1列),确保鸿蒙平板、开发板上显示协调,避免布局空洞或拥挤;
  2. 组件复用:复用Day2封装的CustomInput组件作为搜索栏,同时封装_buildIdleItem方法构建单个闲置卡片,减少重复代码,提升开发效率;
  3. 交互铺垫:悬浮发布按钮绑定跳转逻辑,后续点击会跳转到发布表单页面;闲置卡片暂未添加点击事件,后续Day5会完善详情页面跳转;
  4. 数据模拟:今天先模拟2条闲置数据,后续会通过本地缓存读取真实发布的数据,实现“发布-存储-展示”的完整流程;
  5. 适老化设计:文字尺寸使用sp单位,自动适配多设备,触控区域放大(卡片整体可点击,后续补充),方便老年人操作,贴合十五五“关爱特殊群体”的导向。

4.5 添加邻里互助页面路由

打开lib/routes/routes.dart,添加邻里互助页面和闲置发布表单页面的路由(发布表单页面后续开发):

import 'package:zhilian_linli/pages/neighbor_page.dart';
import 'package:zhilian_linli/pages/idle_publish_page.dart'; // 后续新建的发布表单页面

static const String neighbor = "/neighbor";
static const String idlePublish = "/idlePublish";

static Map<String, WidgetBuilder> routes = {
  main: (context) => const MainPage(),
  home: (context) => const HomePage(),
  service: (context) => const ServicePage(),
  mine: (context) => const MinePage(),
  neighbor: (context) => const NeighborPage(),
  idlePublish: (context) => const IdlePublishPage(),
};

五、版块3:开发闲置发布表单页面(核心业务,详解表单逻辑)

闲置发布表单是邻里互助模块的核心交互页面,居民通过表单填写闲置物品的标题、描述、联系方式,点击发布后,数据会存储到本地缓存,同时在邻里互助首页展示。今天我们开发表单页面,实现基础的表单输入、校验和发布逻辑,详细讲解表单开发的核心要点。
在这里插入图片描述

5.1 表单页面布局规划(贴合民生需求,适老化设计)

表单页面的布局逻辑的是:标题输入框 + 描述输入框 + 联系方式输入框 + 发布按钮,同时添加基础表单校验(比如输入不能为空),具体拆解如下:

  1. 顶部:导航栏(带“发布闲置”标题和返回按钮);
  2. 中间:三个输入框(标题、描述、联系方式),其中描述输入框支持多行输入,贴合闲置物品描述的需求;
  3. 底部:发布按钮,点击后校验表单,校验通过则存储数据,跳转回邻里互助首页;
  4. 适老化设计:输入框触控区域放大、文字加粗、提示文字清晰,按钮尺寸放大,方便老年人填写。

5.2 核心代码(详解表单校验和交互逻辑)

新建lib/pages/idle_publish_page.dart,核心代码(附带详细注释,讲解表单校验、数据提交逻辑):

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:zhilian_linli/widgets/custom_input.dart';
import 'package:zhilian_linli/widgets/custom_button.dart';
import 'package:zhilian_linli/models/idle_model.dart';
import 'package:zhilian_linli/utils/cache_util.dart'; // 后续新建的缓存工具类
import 'package:zhilian_linli/routes/routes.dart';

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

  
  State<IdlePublishPage> createState() => _IdlePublishPageState();
}

class _IdlePublishPageState extends State<IdlePublishPage> {
  // 1. 定义表单控制器,用于获取输入框内容(核心,表单开发必备)
  final TextEditingController _titleController = TextEditingController();
  final TextEditingController _descController = TextEditingController();
  final TextEditingController _contactController = TextEditingController();

  // 2. 表单校验方法(判断输入是否为空,基础校验)
  bool _validateForm() {
    if (_titleController.text.isEmpty) {
      // 提示用户输入标题
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("请输入闲置物品标题")),
      );
      return false;
    }
    if (_descController.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("请输入闲置物品描述")),
      );
      return false;
    }
    if (_contactController.text.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("请输入联系方式")),
      );
      return false;
    }
    // 所有输入都不为空,校验通过
    return true;
  }

  // 3. 发布闲置物品方法(核心业务逻辑)
  void _publishIdle() {
    // 先校验表单
    if (!_validateForm()) return;

    // 校验通过,创建闲置物品对象
    IdleModel idle = IdleModel(
      id: DateTime.now().millisecondsSinceEpoch.toString(), // 用时间戳作为唯一ID
      title: _titleController.text, // 获取标题输入
      description: _descController.text, // 获取描述输入
      contact: _contactController.text, // 获取联系方式输入
      time: "2026-05-04", // 简易版时间,后续可优化为当前日期
    );

    // 存储闲置物品到本地缓存(后续封装缓存工具类)
    CacheUtil.saveIdleData(idle);

    // 发布成功提示
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("闲置发布成功!")),
    );

    // 跳转回邻里互助首页
    Navigator.popAndPushNamed(context, Routes.neighbor);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("发布闲置物品"),
        leading: IconButton(
          // 返回按钮,点击返回上一页
          icon: Icon(Icons.arrow_back),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            // 1. 标题输入框(单行)
            CustomInput(
              hintText: "请输入闲置物品标题(如:闲置洗衣机)",
              controller: _titleController, // 绑定控制器,获取输入内容
            ),
            SizedBox(height: 16.h),

            // 2. 描述输入框(多行,允许换行)
            CustomInput(
              hintText: "请输入闲置物品描述(如:9成新,自提)",
              controller: _descController,
              maxLines: 3, // 允许3行输入,适配长描述
            ),
            SizedBox(height: 16.h),

            // 3. 联系方式输入框(单行)
            CustomInput(
              hintText: "请输入联系方式(手机号/微信)",
              controller: _contactController,
            ),
            SizedBox(height: 30.h),

            // 4. 发布按钮(复用CustomButton组件)
            CustomButton(
              text: "发布闲置",
              onTap: _publishIdle, // 绑定发布方法
            ),
          ],
        ),
      ),
    );
  }

  // 销毁控制器,避免内存泄漏(Flutter开发必做)
  
  void dispose() {
    _titleController.dispose();
    _descController.dispose();
    _contactController.dispose();
    super.dispose();
  }
}

5.3 关键细节讲解(表单开发+避坑)

  1. 表单控制器:使用TextEditingController绑定输入框,用于获取输入框的内容,这是Flutter表单开发的核心用法,能够轻松获取用户输入;
  2. 表单校验:封装_validateForm方法,判断三个输入框是否为空,若为空则弹出提示,提升用户体验,避免无效提交;
  3. 唯一ID:使用DateTime.now().millisecondsSinceEpoch.toString()作为闲置物品的唯一ID,确保每一条闲置信息都不重复,后续删除、修改时会用到;
  4. 内存泄漏:在dispose方法中销毁控制器,这是Flutter开发的规范,避免控制器占用内存,导致APP卡顿;
  5. 交互反馈:发布成功后弹出提示,同时跳转回邻里互助首页,让用户明确知道发布结果,提升交互体验;
  6. 适老化优化:输入框提示文字清晰,按钮尺寸放大,触控区域充足,方便老年人填写,贴合十五五“完善民生服务”的导向;
  7. 避坑提醒:若输入框无法获取内容,大概率是没有绑定controller;若提示不显示,检查ScaffoldMessenger的上下文是否正确。

六、版块4:封装本地缓存工具类(数据持久化,详解缓存逻辑)

我们使用shared_preferences插件实现本地缓存,这是Flutter中最常用的轻量缓存工具,能够将简单数据(如字符串、列表)存储到设备本地,即使关闭APP再打开,数据也不会丢失,适配鸿蒙本地存储特性,同时契合十五五“数据高效供给”的导向。今天我们封装全局缓存工具类,专门用于存储和读取闲置物品数据。

6.1 新增缓存依赖

打开pubspec.yaml,添加shared_preferences依赖,核心代码:

dependencies:
  flutter:
    sdk: flutter
  flutter_screenutil: ^5.9.0
  dio: ^5.4.0
  shared_preferences: ^2.2.2 # 本地缓存插件

终端执行命令,安装依赖:

flutter pub get

6.2 封装缓存工具类(全局复用,详解每一步)

新建lib/utils/cache_util.dart,封装缓存工具类,专门处理闲置物品的存储、读取和删除(今天先实现存储和读取),核心代码(附带详细注释):

import 'package:shared_preferences/shared_preferences.dart';
import 'package:zhilian_linli/models/idle_model.dart';
import 'dart:convert'; // 用于将对象转为字符串(shared_preferences只能存储字符串)

class CacheUtil {
  // 1. 定义缓存的key(唯一标识,用于读取和存储)
  static const String _idleKey = "idle_list";

  // 2. 存储闲置物品数据(核心方法)
  static Future<void> saveIdleData(IdleModel idle) async {
    // 获取SharedPreferences实例(异步操作,需要用await)
    SharedPreferences prefs = await SharedPreferences.getInstance();
    // 先读取已有的闲置列表数据
    List<String> idleStrList = prefs.getStringList(_idleKey) ?? [];
    // 将新的闲置物品对象转为字符串(因为缓存只能存储字符串)
    String idleStr = json.encode({
      "id": idle.id,
      "title": idle.title,
      "description": idle.description,
      "contact": idle.contact,
      "time": idle.time,
    });
    // 将新的闲置字符串添加到列表中
    idleStrList.add(idleStr);
    // 存储更新后的列表到本地缓存
    await prefs.setStringList(_idleKey, idleStrList);
  }

  // 3. 读取闲置物品数据(核心方法)
  static Future<List<IdleModel>> getIdleData() async {
    // 获取SharedPreferences实例
    SharedPreferences prefs = await SharedPreferences.getInstance();
    // 读取缓存中的闲置列表字符串
    List<String> idleStrList = prefs.getStringList(_idleKey) ?? [];
    // 将字符串列表转为IdleModel对象列表
    List<IdleModel> idleList = idleStrList.map((str) {
      // 将字符串转为Map
      Map<String, dynamic> map = json.decode(str);
      // 将Map转为IdleModel对象
      return IdleModel(
        id: map["id"],
        title: map["title"],
        description: map["description"],
        contact: map["contact"],
        time: map["time"],
      );
    }).toList();
    // 返回闲置物品列表
    return idleList;
  }
}

6.3 关键细节讲解(缓存逻辑+避坑)

  1. 缓存原理:shared_preferences只能存储字符串、列表等简单数据,无法直接存储对象,所以我们需要通过json.encode将IdleModel对象转为字符串,存储到本地;读取时再通过json.decode将字符串转为对象,这是Flutter本地缓存的常用技巧;
  2. 缓存key:_idleKey是唯一标识,用于区分不同的缓存数据,后续读取、删除闲置数据时,都需要使用这个key,避免与其他缓存数据冲突;
  3. 异步操作:SharedPreferences的所有操作都是异步的,所以需要用async/await,确保操作完成后再进行后续逻辑,避免数据读取不到;
  4. 空值处理:prefs.getStringList(_idleKey) ?? [],表示如果缓存中没有闲置列表数据,就返回一个空列表,避免出现空指针异常,这是Flutter开发中常见的避坑技巧;
  5. 全局复用:这个缓存工具类可以全局复用,后续新增、删除闲置数据,都可以调用这里的方法,无需重复编写缓存逻辑,提升开发效率;
  6. 鸿蒙适配:shared_preferences插件完全适配鸿蒙系统,无需额外配置,能够正常存储和读取数据,解决鸿蒙设备本地存储的适配问题。

6.4 优化邻里互助首页(从缓存读取数据)

修改NeighborPage的代码,将之前的模拟数据替换为从本地缓存读取的数据,实现“发布-存储-展示”的完整流程,核心代码修改:

// 移除之前的模拟数据,改为从缓存读取
List<IdleModel> idleList = [];

// 新增:读取缓存数据的方法
Future<void> getIdleFromCache() async {
  // 调用缓存工具类的方法,读取闲置数据
  List<IdleModel> data = await CacheUtil.getIdleData();
  setState(() {
    idleList = data; // 更新列表数据,刷新页面
  });
}

// 页面初始化时,读取缓存数据

void initState() {
  super.initState();
  getIdleFromCache();
}

同时,在发布闲置成功后,邻里互助首页会自动刷新数据(因为跳转回首页时,initState会重新执行,读取最新的缓存数据),实现数据实时同步。

七、版块5:鸿蒙多端适配优化(重点补充,详解适配技巧)

承接前3天的鸿蒙适配基础,今天重点优化底部导航栏、表单页面、列表页面的多端适配细节,解决鸿蒙平板、DAYU200开发板上的显示和交互问题,确保多设备运行流畅、体验一致。

7.1 底部导航栏适配优化(大屏重点)

针对鸿蒙平板、DAYU200开发板,优化底部导航栏的尺寸和布局,避免导航栏过窄或图标过小,核心代码修改(MainPage中):

bottomNavigationBar: BottomNavigationBar(
  currentIndex: _currentIndex,
  items: _navItems,
  type: BottomNavigationBarType.fixed,
  // 大屏优化:增加导航栏高度,放大图标和文字
  height: isLargeScreen ? 60.h : 50.h,
  iconSize: isLargeScreen ? 26.sp : 20.sp,
  selectedFontSize: isLargeScreen ? 16.sp : 12.sp,
  unselectedFontSize: isLargeScreen ? 14.sp : 10.sp,
  // 增加导航栏内边距,避免拥挤
  padding: EdgeInsets.symmetric(horizontal: 20.w),
  onTap: (index) {
    setState(() {
      _currentIndex = index;
    });
  },
  selectedItemColor: Color(0xFF2E8B57),
  unselectedItemColor: Colors.grey,
),

7.2 表单页面适配优化(适老化+大屏)

  1. 输入框适配:放大输入框的高度和文字尺寸,适配鸿蒙开发板的触控和显示特性,核心代码修改(CustomInput组件中):
TextField(
  controller: controller,
  style: TextStyle(fontSize: isLargeScreen ? 16.sp : 15.sp), // 大屏放大文字
  decoration: InputDecoration(
    hintText: hintText,
    hintStyle: TextStyle(fontSize: isLargeScreen ? 15.sp : 14.sp, color: Colors.grey),
    // 放大输入框内边距,提升触控体验
    contentPadding: EdgeInsets.symmetric(horizontal: 16.w, vertical: isLargeScreen ? 20.h : 18.h),
    border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
  ),
  maxLines: maxLines ?? 1,
),
  1. 按钮适配:放大发布按钮的高度,适配老年人触控,核心代码修改(CustomButton组件中):
Container(
  width: double.infinity,
  height: isLargeScreen ? 60.h : 56.h, // 大屏放大按钮高度
  alignment: Alignment.center,
  decoration: BoxDecoration(color: Color(0xFF2E8B57), borderRadius: BorderRadius.circular(8.r)),
  child: Text(text, style: TextStyle(fontSize: isLargeScreen ? 17.sp : 16.sp, fontWeight: FontWeight.bold, color: Colors.white)),
),

7.3 列表页面适配优化(大屏布局)

优化邻里互助首页的列表布局,大屏(平板/开发板)增加列表项的间距和尺寸,提升显示效果,核心代码修改(NeighborPage中):

GridView.count(
  crossAxisCount: 2,
  crossAxisSpacing: isLargeScreen ? 20.w : 16.w, // 大屏增加横向间距
  mainAxisSpacing: isLargeScreen ? 20.h : 16.h, // 大屏增加纵向间距
  childAspectRatio: 1.4, // 调整宽高比,避免卡片过高或过宽
  children: idleList.map((idle) {
    return _buildIdleItem(idle);
  }).toList(),
)

7.4 鸿蒙适配常见坑(详细避坑,新手必看)

  1. 坑1:底部导航栏在平板端显示过窄 → 解决方案:通过isLargeScreen动态调整导航栏高度、内边距,放大图标和文字;
  2. 坑2:表单输入框在开发板上触控不灵敏 → 解决方案:放大输入框的高度和内边距,增加触控区域,同时优化输入框的焦点交互;
  3. 坑3:本地缓存读取不到数据 → 解决方案:检查缓存key是否一致,确保json.encodejson.decode的字段与IdleModel一致,避免字段拼写错误;
  4. 坑4:大屏列表布局拥挤 → 解决方案:增加列表项的间距,调整childAspectRatio,确保卡片显示协调;
  5. 坑5:发布成功后,首页数据不刷新 → 解决方案:确保initState中调用了读取缓存的方法,跳转回首页时,initState会重新执行,读取最新数据。

八、版块6:今日效果测试(详细测试步骤,新手必做)

开发完成后,一定要在鸿蒙多设备上进行测试,确保功能正常、适配协调,测试步骤详细拆解如下,新手可以照着操作:

  1. 测试底部导航栏:

    • 鸿蒙手机端:点击4个Tab选项,确认能够无缝切换页面,导航栏显示正常,选中状态清晰;
    • 鸿蒙平板端:确认导航栏高度、图标、文字尺寸适配,无拥挤、模糊问题;
    • DAYU200开发板:确认导航栏触控灵敏,点击后切换流畅,无卡顿。
  2. 测试邻里互助页面:

    • 确认列表显示正常,大屏2列、小屏1列,文字清晰;
    • 点击悬浮发布按钮,确认能够正常跳转至发布表单页面。
  3. 测试闲置发布表单:

    • 不输入任何内容,点击发布按钮,确认会弹出提示(输入不能为空);
    • 填写完整信息,点击发布按钮,确认会弹出“发布成功”提示,同时跳转回邻里互助首页;
    • 确认发布的闲置信息能够在列表中显示(从缓存读取)。
  4. 测试本地缓存:

    • 关闭APP,重新打开,确认之前发布的闲置信息依然存在(缓存生效);
    • 多次发布闲置信息,确认列表能够正常渲染所有数据,无重复、无错乱。
  5. 适配测试:

    • 检查所有页面在鸿蒙多设备上的显示,确保无布局错乱、文字模糊、触控不灵敏等问题;
    • 测试适老化效果,确认按钮、输入框触控区域充足,文字清晰。

测试避坑提醒

  1. 若缓存数据不显示,关闭APP重新打开,确保缓存数据已成功存储;
  2. 若表单提示不显示,检查ScaffoldMessenger的上下文是否正确,确保在Scaffold内部使用;
  3. 若开发板运行卡顿,关闭Flutter默认动画,在MaterialApp中添加theme: ThemeData(animationDuration: Duration.zero)
  4. 若页面跳转异常,检查路由配置是否正确,确保路由名称与跳转时的名称一致。

九、Day4 开发总结(详细复盘,衔接后续)

今天Day4,我们聚焦Flutter业务逻辑和交互体验,完成了项目的核心模块开发和框架完善,全程补充了详细的文字解释,让大家不仅能抄代码,更能理解开发逻辑,核心成果总结如下,每一点都对应今日的开发重点:

  1. 搭建了Flutter底部导航栏:使用原生BottomNavigationBar组件,实现了4个Tab页面的无缝切换,动态适配鸿蒙多设备尺寸,完善了项目的交互框架,让项目更具完整性;
  2. 开发了邻里互助首页:实现了闲置共享列表的渲染,适配大屏/小屏布局,封装了闲置卡片组件,贴合十五五“邻里互助、资源共享”的导向;
  3. 开发了闲置发布表单页面:实现了表单输入、基础校验和发布逻辑,使用TextEditingController获取输入内容,添加了交互反馈,贴合适老化设计;
  4. 封装了全局本地缓存工具类:基于shared_preferences,实现了闲置物品数据的存储和读取,确保数据持久化,适配鸿蒙本地存储特性,实现“发布-存储-展示”的完整流程;
  5. 完善了路由配置:新增了邻里互助、闲置发布、我的页面的路由,实现了页面之间的无缝跳转;
  6. 优化了鸿蒙多端适配:针对平板、开发板的显示和交互问题,优化了底部导航栏、表单、列表的布局和尺寸,解决了常见适配坑;
  7. 补充了详细的测试步骤:帮助新手快速测试功能,确保开发的功能能够正常落地,避免出现问题。

核心提醒:今天的重点是“业务落地+数据持久化”,本地缓存和表单开发是Flutter实战中非常常用的技巧,后续很多功能(比如登录状态保存、历史记录)都会用到;另外,邻里互助模块的核心逻辑已经完成,后续会逐步完善详情页面、兑换功能、删除功能等。

十、下期内容预告(Day5,详细明确)

Day5我们将继续完善邻里互助模块,同时补充更多交互细节,重点落地3件事,全程衔接Day4的内容,依旧保持详细的文字解释和精简的代码:

  1. 开发闲置物品详情页面:实现闲置信息的详细展示,点击列表项跳转至详情页,展示完整的标题、描述、联系方式;
  2. 完善本地缓存功能:新增闲置物品删除功能,实现“发布-删除-刷新”的完整交互;
  3. 优化交互体验:添加下拉刷新(刷新闲置列表)、上拉加载更多(后续扩展)、按钮点击动画、页面跳转动画,同时补充鸿蒙适配细节,让项目体验更流畅。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐