Flutter+开源鸿蒙实战|智联邻里Day4 底部导航栏+邻里互助页面+闲置发布表单+本地缓存
Flutter+开源鸿蒙实战|智联邻里Day4 开发摘要 本文重点介绍了智联邻里项目的第四天开发内容,主要围绕Flutter底部导航栏搭建和邻里互助核心功能实现。开发包含四大模块: 底部导航栏:使用BottomNavigationBar组件实现多Tab切换,适配鸿蒙多设备尺寸,支持动态调整图标和文字大小。 邻里互助页面:开发闲置物品共享列表,支持下拉刷新和点击查看详情功能,复用前期封装的组件保持风
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 今日核心开发目标(详细拆解,清晰可落地)
- 搭建Flutter底部导航栏(BottomNavigationBar),实现“首页、政务服务、邻里互助、我的”4个Tab页面的无缝切换,适配鸿蒙多设备尺寸;
- 开发邻里互助首页(闲置共享页面),复用之前封装的组件,实现闲置信息列表渲染,支持下拉刷新、点击查看详情(铺垫后续功能);
- 开发闲置发布表单页面,包含标题、描述、联系方式输入框和发布按钮,做基础表单校验,贴合适老化设计;
- 集成shared_preferences本地缓存,实现闲置信息的存储、读取和展示,确保数据持久化,适配鸿蒙本地存储特性;
- 完善路由配置,实现底部导航切换、发布表单跳转、闲置列表点击跳转等交互逻辑;
- 优化鸿蒙多端适配细节(底部导航栏尺寸、表单布局、列表适配),解决多设备显示错乱、触控不灵敏等问题;
- 补充交互反馈(发布成功提示、下拉刷新动画、按钮点击反馈),提升用户体验,贴合智慧社区“便捷、友好”的调性。
三、版块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 关键细节讲解(鸿蒙适配重点)
- 尺寸适配:通过
isLargeScreen判断设备尺寸,动态调整导航栏图标和文字大小,确保鸿蒙平板、DAYU200开发板上显示协调,避免图标过小、文字模糊; - 类型设置:
type: BottomNavigationBarType.fixed,确保4个导航选项全部显示,不折叠,适配智慧社区多模块的需求; - 主题适配:选中颜色使用Day1定义的全局主色调(墨绿色),保持项目风格统一,提升整体美观度;
- 状态管理:使用
_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.dart的initialRoute,确保默认启动主页面:
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)的布局逻辑的是:顶部搜索栏 + 闲置列表 + 发布按钮,具体拆解如下:
- 顶部:搜索栏(复用Day2封装的CustomInput组件),用于搜索闲置物品(比如“家电”“书籍”);
- 中间:闲置物品列表(垂直滚动),每一项展示闲置物品的标题、描述、发布人(简易版),复用自定义组件;
- 底部:悬浮发布按钮,点击跳转至闲置发布表单页面,方便居民快速发布闲置信息;
- 适配鸿蒙多设备:大屏(平板/开发板)列表显示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 关键细节讲解(业务+适配)
- 布局适配:通过
isLargeScreen判断设备尺寸,大屏用GridView(2列),小屏用ListView(1列),确保鸿蒙平板、开发板上显示协调,避免布局空洞或拥挤; - 组件复用:复用Day2封装的CustomInput组件作为搜索栏,同时封装
_buildIdleItem方法构建单个闲置卡片,减少重复代码,提升开发效率; - 交互铺垫:悬浮发布按钮绑定跳转逻辑,后续点击会跳转到发布表单页面;闲置卡片暂未添加点击事件,后续Day5会完善详情页面跳转;
- 数据模拟:今天先模拟2条闲置数据,后续会通过本地缓存读取真实发布的数据,实现“发布-存储-展示”的完整流程;
- 适老化设计:文字尺寸使用
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 表单页面布局规划(贴合民生需求,适老化设计)
表单页面的布局逻辑的是:标题输入框 + 描述输入框 + 联系方式输入框 + 发布按钮,同时添加基础表单校验(比如输入不能为空),具体拆解如下:
- 顶部:导航栏(带“发布闲置”标题和返回按钮);
- 中间:三个输入框(标题、描述、联系方式),其中描述输入框支持多行输入,贴合闲置物品描述的需求;
- 底部:发布按钮,点击后校验表单,校验通过则存储数据,跳转回邻里互助首页;
- 适老化设计:输入框触控区域放大、文字加粗、提示文字清晰,按钮尺寸放大,方便老年人填写。
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 关键细节讲解(表单开发+避坑)
- 表单控制器:使用
TextEditingController绑定输入框,用于获取输入框的内容,这是Flutter表单开发的核心用法,能够轻松获取用户输入; - 表单校验:封装
_validateForm方法,判断三个输入框是否为空,若为空则弹出提示,提升用户体验,避免无效提交; - 唯一ID:使用
DateTime.now().millisecondsSinceEpoch.toString()作为闲置物品的唯一ID,确保每一条闲置信息都不重复,后续删除、修改时会用到; - 内存泄漏:在
dispose方法中销毁控制器,这是Flutter开发的规范,避免控制器占用内存,导致APP卡顿; - 交互反馈:发布成功后弹出提示,同时跳转回邻里互助首页,让用户明确知道发布结果,提升交互体验;
- 适老化优化:输入框提示文字清晰,按钮尺寸放大,触控区域充足,方便老年人填写,贴合十五五“完善民生服务”的导向;
- 避坑提醒:若输入框无法获取内容,大概率是没有绑定
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 关键细节讲解(缓存逻辑+避坑)
- 缓存原理:
shared_preferences只能存储字符串、列表等简单数据,无法直接存储对象,所以我们需要通过json.encode将IdleModel对象转为字符串,存储到本地;读取时再通过json.decode将字符串转为对象,这是Flutter本地缓存的常用技巧; - 缓存key:
_idleKey是唯一标识,用于区分不同的缓存数据,后续读取、删除闲置数据时,都需要使用这个key,避免与其他缓存数据冲突; - 异步操作:
SharedPreferences的所有操作都是异步的,所以需要用async/await,确保操作完成后再进行后续逻辑,避免数据读取不到; - 空值处理:
prefs.getStringList(_idleKey) ?? [],表示如果缓存中没有闲置列表数据,就返回一个空列表,避免出现空指针异常,这是Flutter开发中常见的避坑技巧; - 全局复用:这个缓存工具类可以全局复用,后续新增、删除闲置数据,都可以调用这里的方法,无需重复编写缓存逻辑,提升开发效率;
- 鸿蒙适配:
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 表单页面适配优化(适老化+大屏)
- 输入框适配:放大输入框的高度和文字尺寸,适配鸿蒙开发板的触控和显示特性,核心代码修改(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,
),
- 按钮适配:放大发布按钮的高度,适配老年人触控,核心代码修改(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:底部导航栏在平板端显示过窄 → 解决方案:通过
isLargeScreen动态调整导航栏高度、内边距,放大图标和文字; - 坑2:表单输入框在开发板上触控不灵敏 → 解决方案:放大输入框的高度和内边距,增加触控区域,同时优化输入框的焦点交互;
- 坑3:本地缓存读取不到数据 → 解决方案:检查缓存key是否一致,确保
json.encode和json.decode的字段与IdleModel一致,避免字段拼写错误; - 坑4:大屏列表布局拥挤 → 解决方案:增加列表项的间距,调整
childAspectRatio,确保卡片显示协调; - 坑5:发布成功后,首页数据不刷新 → 解决方案:确保
initState中调用了读取缓存的方法,跳转回首页时,initState会重新执行,读取最新数据。
八、版块6:今日效果测试(详细测试步骤,新手必做)
开发完成后,一定要在鸿蒙多设备上进行测试,确保功能正常、适配协调,测试步骤详细拆解如下,新手可以照着操作:
-
测试底部导航栏:
- 鸿蒙手机端:点击4个Tab选项,确认能够无缝切换页面,导航栏显示正常,选中状态清晰;
- 鸿蒙平板端:确认导航栏高度、图标、文字尺寸适配,无拥挤、模糊问题;
- DAYU200开发板:确认导航栏触控灵敏,点击后切换流畅,无卡顿。
-
测试邻里互助页面:
- 确认列表显示正常,大屏2列、小屏1列,文字清晰;
- 点击悬浮发布按钮,确认能够正常跳转至发布表单页面。
-
测试闲置发布表单:
- 不输入任何内容,点击发布按钮,确认会弹出提示(输入不能为空);
- 填写完整信息,点击发布按钮,确认会弹出“发布成功”提示,同时跳转回邻里互助首页;
- 确认发布的闲置信息能够在列表中显示(从缓存读取)。
-
测试本地缓存:
- 关闭APP,重新打开,确认之前发布的闲置信息依然存在(缓存生效);
- 多次发布闲置信息,确认列表能够正常渲染所有数据,无重复、无错乱。
-
适配测试:
- 检查所有页面在鸿蒙多设备上的显示,确保无布局错乱、文字模糊、触控不灵敏等问题;
- 测试适老化效果,确认按钮、输入框触控区域充足,文字清晰。
测试避坑提醒
- 若缓存数据不显示,关闭APP重新打开,确保缓存数据已成功存储;
- 若表单提示不显示,检查
ScaffoldMessenger的上下文是否正确,确保在Scaffold内部使用; - 若开发板运行卡顿,关闭Flutter默认动画,在
MaterialApp中添加theme: ThemeData(animationDuration: Duration.zero); - 若页面跳转异常,检查路由配置是否正确,确保路由名称与跳转时的名称一致。
九、Day4 开发总结(详细复盘,衔接后续)
今天Day4,我们聚焦Flutter业务逻辑和交互体验,完成了项目的核心模块开发和框架完善,全程补充了详细的文字解释,让大家不仅能抄代码,更能理解开发逻辑,核心成果总结如下,每一点都对应今日的开发重点:
- 搭建了Flutter底部导航栏:使用原生BottomNavigationBar组件,实现了4个Tab页面的无缝切换,动态适配鸿蒙多设备尺寸,完善了项目的交互框架,让项目更具完整性;
- 开发了邻里互助首页:实现了闲置共享列表的渲染,适配大屏/小屏布局,封装了闲置卡片组件,贴合十五五“邻里互助、资源共享”的导向;
- 开发了闲置发布表单页面:实现了表单输入、基础校验和发布逻辑,使用TextEditingController获取输入内容,添加了交互反馈,贴合适老化设计;
- 封装了全局本地缓存工具类:基于shared_preferences,实现了闲置物品数据的存储和读取,确保数据持久化,适配鸿蒙本地存储特性,实现“发布-存储-展示”的完整流程;
- 完善了路由配置:新增了邻里互助、闲置发布、我的页面的路由,实现了页面之间的无缝跳转;
- 优化了鸿蒙多端适配:针对平板、开发板的显示和交互问题,优化了底部导航栏、表单、列表的布局和尺寸,解决了常见适配坑;
- 补充了详细的测试步骤:帮助新手快速测试功能,确保开发的功能能够正常落地,避免出现问题。
核心提醒:今天的重点是“业务落地+数据持久化”,本地缓存和表单开发是Flutter实战中非常常用的技巧,后续很多功能(比如登录状态保存、历史记录)都会用到;另外,邻里互助模块的核心逻辑已经完成,后续会逐步完善详情页面、兑换功能、删除功能等。
十、下期内容预告(Day5,详细明确)
Day5我们将继续完善邻里互助模块,同时补充更多交互细节,重点落地3件事,全程衔接Day4的内容,依旧保持详细的文字解释和精简的代码:
- 开发闲置物品详情页面:实现闲置信息的详细展示,点击列表项跳转至详情页,展示完整的标题、描述、联系方式;
- 完善本地缓存功能:新增闲置物品删除功能,实现“发布-删除-刷新”的完整交互;
- 优化交互体验:添加下拉刷新(刷新闲置列表)、上拉加载更多(后续扩展)、按钮点击动画、页面跳转动画,同时补充鸿蒙适配细节,让项目体验更流畅。
更多推荐


所有评论(0)