
我们完成了"闲置换"二手物品置换App的开发。今天来做一个项目总结,回顾学到的知识点,并展望后续的优化方向。
项目回顾
我们从零开始,搭建了一个完整的二手交易App。首页模块包含搜索栏、活动Banner、分类导航和商品瀑布流列表;搜索模块实现了搜索历史和热门搜索;分类模块支持筛选和排序;商品详情展示图片画廊、价格和卖家信息;发布模块处理图片选择和表单验证;消息模块包含会话列表和聊天对话;个人中心展示用户信息和功能菜单;设置模块管理通知和缓存。
技术栈总结
我们使用Flutter框架进行跨平台UI开发,一套代码运行在多个平台。GetX负责状态管理、路由管理和依赖注入,API简洁易用。flutter_screenutil处理屏幕适配,让UI在不同设备上保持一致。convex_bottom_bar实现凸起底部导航栏,突出发布按钮。这套技术栈组合起来,开发效率很高,代码也比较好维护。
项目结构
lib/
├── main.dart # 应用入口
├── pages/
│ ├── splash_page.dart # 启动页
│ ├── main_page.dart # 主框架
│ ├── home/
│ │ └── home_page.dart # 首页
│ ├── search/
│ │ └── search_page.dart # 搜索页
│ ├── category/
│ │ └── category_page.dart # 分类页
│ ├── product/
│ │ └── product_detail_page.dart # 商品详情
│ ├── publish/
│ │ └── publish_page.dart # 发布页
│ ├── message/
│ │ ├── message_page.dart # 消息列表
│ │ └── chat_page.dart # 聊天页
│ └── profile/
│ ├── profile_page.dart # 个人中心
│ ├── settings_page.dart # 设置页
│ ├── my_products_page.dart # 我的发布
│ └── favorites_page.dart # 我的收藏
├── widgets/ # 自定义通用组件
├── models/ # 数据模型
├── services/ # 网络/本地存储服务
└── controllers/ # GetX状态管理控制器
以上是优化后的项目目录结构,在原有基础上拆分出widgets、models、services、controllers目录,让代码职责更清晰,符合"单一职责原则"。
核心知识点
1. 应用入口main.dart
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'pages/splash_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: const Size(375, 812), // 设计稿尺寸
minTextAdapt: true,
splitScreenMode: true,
builder: (context, child) {
return GetMaterialApp(
title: '闲置换',
theme: ThemeData(primarySwatch: Colors.blue),
home: const SplashPage(),
debugShowCheckedModeBanner: false,
);
},
);
}
}
这段代码是App的入口,核心做了三件事:初始化屏幕适配工具flutter_screenutil(指定设计稿尺寸,保证多设备UI一致)、初始化GetX的GetMaterialApp(替代原生MaterialApp,支持GetX路由/状态管理)、设置启动页为SplashPage。其中ScreenUtilInit是适配的关键,minTextAdapt开启文字适配,splitScreenMode支持分屏适配。
2. 首页商品瀑布流
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/home_controller.dart';
import '../../widgets/product_item_widget.dart';
class HomePage extends GetView<HomeController> {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('首页')),
body: Obx(() {
if (controller.productList.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
// 瀑布流列表
return GridView.builder(
padding: EdgeInsets.all(10.w),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // 列数
crossAxisSpacing: 10.w, // 列间距
mainAxisSpacing: 10.h, // 行间距
childAspectRatio: 0.75, // 宽高比
),
itemCount: controller.productList.length,
itemBuilder: (context, index) {
// 复用商品卡片组件
return ProductItemWidget(
product: controller.productList[index],
onTap: () => Get.toNamed('/productDetail', arguments: controller.productList[index]),
);
},
);
}),
);
}
}
这里使用GridView.builder实现商品瀑布流,核心优势是"懒加载"------只渲染当前可视区域的商品卡片,避免一次性加载所有数据导致内存占用过高。Obx是GetX的响应式组件,当controller.productList数据变化时,会自动刷新UI,无需手动调用setState。
3. GetX控制器
dart
import 'package:get/get.dart';
import '../../models/product_model.dart';
import '../../services/api_service.dart';
class HomeController extends GetxController {
// 响应式商品列表(.obs标记为响应式)
final RxList<ProductModel> productList = <ProductModel>[].obs;
@override
void onInit() {
super.onInit();
// 初始化加载数据
loadProductList();
}
// 加载商品列表数据
Future<void> loadProductList() async {
try {
final result = await ApiService.getProductList();
productList.assignAll(result); // 赋值并触发UI刷新
} catch (e) {
Get.snackbar('提示', '数据加载失败:$e'); // 全局提示
}
}
// 下拉刷新
Future<void> onRefresh() async {
productList.clear();
await loadProductList();
}
}
这是首页的GetX控制器,核心是响应式数据管理:用.obs标记productList为响应式,onInit生命周期方法中加载数据(对应页面初始化时执行),loadProductList中调用网络服务获取数据,通过assignAll更新列表并触发UI刷新。GetX控制器的生命周期(onInit/onReady/onClose)能精准管理数据加载/释放,避免内存泄漏。
4. 商品详情页参数接收
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../models/product_model.dart';
class ProductDetailPage extends StatelessWidget {
const ProductDetailPage({super.key});
@override
Widget build(BuildContext context) {
// 获取路由传递的商品参数
final ProductModel product = Get.arguments;
return Scaffold(
appBar: AppBar(title: Text(product.name)),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 商品图片画廊
SizedBox(
height: 300.h,
child: PageView.builder(
itemCount: product.images.length,
itemBuilder: (context, index) {
return Image.network(
product.images[index],
fit: BoxFit.cover,
);
},
),
),
// 商品价格
Padding(
padding: EdgeInsets.all(10.w),
child: Text(
'¥${product.price}',
style: TextStyle(fontSize: 20.sp, color: Colors.red),
),
),
// 商品描述
Padding(
padding: EdgeInsets.all(10.w),
child: Text(
product.description,
style: TextStyle(fontSize: 14.sp, color: Colors.grey[800]),
),
),
],
),
),
);
}
}
GetX路由传参非常简洁,通过Get.arguments即可获取上一页传递的参数(无需像原生路由那样手动解析)。这里用PageView.builder实现商品图片画廊,SingleChildScrollView包裹Column解决内容超出屏幕的滚动问题,结合screenutil的尺寸单位(w/h/sp)保证不同设备显示比例一致。
5. 发布页表单验证
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../controllers/publish_controller.dart';
class PublishPage extends GetView<PublishController> {
const PublishPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('发布商品')),
body: Form(
key: controller.formKey,
child: ListView(
padding: EdgeInsets.all(15.w),
children: [
// 商品名称输入框
TextFormField(
controller: controller.nameController,
decoration: InputDecoration(
hintText: '请输入商品名称',
hintStyle: TextStyle(fontSize: 14.sp),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '商品名称不能为空';
}
return null;
},
),
SizedBox(height: 15.h),
// 商品价格输入框
TextFormField(
controller: controller.priceController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
hintText: '请输入商品价格',
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8.r)),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '商品价格不能为空';
}
if (double.tryParse(value) == null) {
return '请输入有效的价格';
}
return null;
},
),
SizedBox(height: 20.h),
// 提交按钮
ElevatedButton(
onPressed: controller.submitForm,
style: ElevatedButton.styleFrom(
fixedSize: Size(double.infinity, 45.h),
borderRadius: BorderRadius.circular(8.r),
),
child: Text('发布', style: TextStyle(fontSize: 16.sp)),
),
],
),
),
);
}
}
这段代码实现发布页的表单验证,核心是结合Flutter原生Form组件和GetX控制器:Form的key绑定控制器的formKey,TextFormField的validator实现字段校验(非空、价格格式),提交按钮调用controller.submitForm方法。通过表单验证能避免无效数据提交,提升用户体验,而将表单逻辑抽离到控制器中,也符合"视图与逻辑分离"的设计思想。
6. 发布控制器
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../services/api_service.dart';
class PublishController extends GetxController {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final TextEditingController nameController = TextEditingController();
final TextEditingController priceController = TextEditingController();
// 提交表单
void submitForm() {
if (formKey.currentState!.validate()) {
// 表单验证通过,提交数据
_publishProduct();
}
}
// 发布商品接口调用
Future<void> _publishProduct() async {
try {
await ApiService.publishProduct(
name: nameController.text,
price: double.parse(priceController.text),
);
Get.snackbar('成功', '商品发布成功');
Get.back(); // 返回上一页
} catch (e) {
Get.snackbar('失败', '发布失败:$e');
}
}
@override
void onClose() {
// 释放控制器,避免内存泄漏
nameController.dispose();
priceController.dispose();
super.onClose();
}
}
这是发布页的控制器,核心处理表单提交逻辑:submitForm方法先校验表单,通过后调用_publishProduct方法提交数据;onClose生命周期释放TextEditingController,避免内存泄漏。其中ApiService.publishProduct是封装的网络请求方法,调用成功后通过Get.snackbar给出全局提示,并返回上一页,失败则提示错误信息,错误处理更友好。
后续优化方向
这个项目还有很多可以优化的地方。以下是结合代码层面的优化方向:
1. 用户认证
dart
// 示例:手机号登录接口封装
Future<void> loginWithPhone(String phone, String code) async {
final response = await dio.post('/api/login/phone', data: {
'phone': phone,
'code': code,
});
if (response.statusCode == 200) {
// 存储token到本地
await GetStorage().write('token', response.data['token']);
}
}
用户认证是App的基础功能,通过手机号+验证码登录是最常用的方式。上述代码封装了登录接口,登录成功后将token存储到本地(使用GetStorage),后续接口请求时携带token完成身份验证,保证接口访问的安全性。
2. 实时消息
dart
// 示例:WebSocket初始化
import 'package:web_socket_channel/io.dart';
class ChatController extends GetxController {
final RxList<String> messageList = <String>[].obs;
late IOWebSocketChannel channel;
@override
void onInit() {
super.onInit();
// 连接WebSocket
channel = IOWebSocketChannel.connect('ws://your-server-url/ws/chat');
// 监听消息
channel.stream.listen((message) {
messageList.add(message);
});
}
// 发送消息
void sendMessage(String msg) {
channel.sink.add(msg);
}
@override
void onClose() {
channel.sink.close(); // 关闭连接
super.onClose();
}
}
通过WebSocket可以实现实时聊天功能,上述代码在ChatController中初始化WebSocket连接,监听服务端消息并更新响应式列表,发送消息时调用sink.add方法。onClose生命周期关闭连接,避免资源浪费,这也是实时消息功能的核心代码逻辑。
开发建议
写代码要遵循Dart规范,用lint检查代码质量。重复的UI要封装成组件(如上述的ProductItemWidget),提高复用性。状态管理要合理使用,避免状态混乱导致bug难查。错误处理要做好,给用户友好的提示而不是直接崩溃。性能问题要注意,避免卡顿和内存泄漏影响体验。最重要的是关注用户体验,让App用起来舒服自然。
结语
通过这个项目的学习,相信你已经掌握了Flutter开发的核心技能。Flutter是一个强大的跨平台框架,能够高效地开发出精美的App。希望你能继续深入学习,开发出更多优秀的应用。感谢你的阅读,祝你在Flutter开发的道路上越走越远!
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net