flutter_for_openharmony逆向思维训练app实战+成就系统实现
本文介绍了训练类App中成就系统的设计与实现。该系统通过ListView展示成就项,使用Card和ListTile构建统一的UI布局,采用颜色和图标区分已解锁/未解锁状态。主要特点包括: 采用响应式设计(16.w/24.h等)适配不同屏幕尺寸 使用CircleAvatar作为成就徽章容器,图标颜色随解锁状态变化 每个成就项包含标题、描述和状态图标(✓或🔒) 目前使用StatelessWidget

成就系统在训练类 App 里承担两个核心角色:
- 正向激励:让用户看到自己的成长里程碑
- 目标引导:给用户明确的努力方向
当前实现是纯展示型,用 ListView 展示若干成就项,并用颜色和图标区分“已解锁/未解锁”。
本文涉及文件
lib/feature_pages.dartlib/app.dartlib/main.dart
1. 入口在哪里:从“进度统计”进入
成就系统属于 ProgressStatsPage(进度统计)下的一个入口项。
入口页负责 push 到 AchievementSystemPage,核心跳转代码示例如下:
// 进度统计页面的入口按钮点击事件
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const AchievementSystemPage(),
),
);
},
- 跳转逻辑采用 Flutter 标准的
Navigator.push方式,保证路由跳转的稳定性 - 使用
MaterialPageRoute适配 Material 设计规范,过渡动画更符合用户习惯 - 传入
AchievementSystemPage实例时添加const关键字,减少不必要的对象重建
2. AchievementSystemPage 的真实代
下面这段实现来自你项目 lib/feature_pages.dart。
为了保证“代码真实可运行”,我分模块拆解并补充说明:
2.1 页面基础结构定义
class AchievementSystemPage extends StatelessWidget {
const AchievementSystemPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('成就系统')),
- 采用
StatelessWidget符合当前静态展示的业务场景,无状态管理成本 - 构造方法添加
const修饰,提升组件复用效率,减少内存占用 AppBar配置明确的标题文本,保证页面导航的可读性super.key传递父类 key,避免组件重建时的标识混乱
2.2 页面主体布局容器
body: Padding(
padding: EdgeInsets.all(16.w),
child: ListView(
children: [
Text('成就徽章', style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold
)),
Padding采用16.w响应式间距,适配不同屏幕尺寸的边距需求- 外层使用
ListView而非Column,核心优势如下:- 自动处理内容溢出,小屏设备下支持滚动浏览
- 懒加载渲染(默认构造器虽非懒加载,但基础版已满足当前需求)
- 适配不同数量的成就项扩展,无需修改布局结构
- 标题文本设置
20.sp响应式字号 + 加粗样式,层级区分更清晰
2.3 成就列表项占位与间距控制
SizedBox(height: 24.h),
_buildAchievement(
'逆向新手',
'完成第一个逆向思维题',
Icons.star,
Colors.brown,
true
),
SizedBox(height: 24.h)采用响应式高度,保证不同屏幕下的间距一致性_buildAchievement方法调用时参数清晰,按“标题-描述-图标-颜色-状态”顺序传递,可读性强- 第一个成就项“逆向新手”作为入门级成就,引导用户完成基础任务
- 图标选择
Icons.star贴合“新手徽章”的视觉认知,颜色选用Colors.brown区分不同成就类别
2.4 更多成就项配置
_buildAchievement(
'逻辑大师',
'完成50道逻辑题',
Icons.psychology,
Colors.blue,
true
),
_buildAchievement(
'模式专家',
'完成30道模式题',
Icons.grid_on,
Colors.purple,
false
),
- “逻辑大师”成就绑定
Icons.psychology图标,视觉上贴合“逻辑思维”的业务属性 - “模式专家”选用
Icons.grid_on图标,匹配“模式题”的网格/规律类题型特征 - 不同成就项配置不同的主题色(蓝色/紫色),视觉区分度更高
- 解锁状态通过布尔值直接传递,逻辑简单直观
2.5 剩余成就项补充
_buildAchievement(
'文字高手',
'完成40道文字题',
Icons.text_fields,
Colors.green,
false
),
_buildAchievement(
'全能冠军',
'完成所有题目',
Icons.emoji_events,
Colors.yellow,
false
),
],
),
),
);
}
- “文字高手”使用
Icons.text_fields图标,精准匹配“文字题”的业务场景 - “全能冠军”选用
Icons.emoji_events奖杯图标,强化“最高成就”的视觉感知 - 黄色主题色(
Colors.yellow)符合“冠军”的视觉认知,提升成就的荣誉感 - 所有成就项包裹在
ListView的children列表中,保证布局的统一性
2.6 成就项构建方法定义
Widget _buildAchievement(
String title,
String description,
IconData icon,
Color color,
bool unlocked
) {
return Card(
margin: EdgeInsets.only(bottom: 12.h),
child: ListTile(
- 方法参数做了清晰的类型标注,提升代码的可维护性
Card组件提供基础的卡片样式,包含圆角、阴影效果,提升视觉质感margin: EdgeInsets.only(bottom: 12.h)仅设置底部间距,避免上下间距重复叠加- 响应式单位
h保证不同屏幕下的间距比例一致
2.7 成就项左侧图标区域
leading: CircleAvatar(
backgroundColor: unlocked ? color : Colors.grey,
child: Icon(icon, color: Colors.white),
),
CircleAvatar实现圆形图标容器,符合成就徽章的视觉形态- 背景色根据解锁状态动态切换:
- 已解锁:使用成就主题色,强化视觉识别
- 未解锁:统一使用
Colors.grey,弱化显示,引导用户完成任务
- 图标固定使用
Colors.white,保证在不同背景色下的可读性
2.8 成就项标题与副标题
title: Text(title, style: TextStyle(
fontSize: 16.sp,
color: unlocked ? Colors.black : Colors.grey,
)),
subtitle: Text(description),
- 标题字号设置为
16.sp,比页面标题小一级,形成合理的视觉层级 - 标题颜色随解锁状态动态变化,与左侧图标背景色形成视觉呼应
- 副标题直接展示成就描述文本,无额外样式修饰,保证信息的简洁性
- 未解锁状态下标题置灰,进一步强化“未完成”的视觉提示
2.9 成就项右侧状态图标
trailing: unlocked
? const Icon(Icons.check, color: Colors.green)
: const Icon(Icons.lock),
),
);
}
}
- 三元运算符简洁区分两种状态图标,代码可读性高
- 已解锁使用
Icons.check+ 绿色,符合“完成/成功”的视觉认知 - 未解锁使用
Icons.lock原生样式,直观表达“锁定”状态 - 图标添加
const修饰,减少组件重建开销 - 方法结尾闭合
Card、ListTile等组件,保证布局结构的完整性
3. 为什么用 StatelessWidget:当前成就数据是静态写死
你把成就系统写成 StatelessWidget,核心原因和扩展建议如下:
3.1 核心原因
// 静态写死的成就数据示例
_buildAchievement('逆向新手', '完成第一个逆向思维题', Icons.star, Colors.brown, true),
- 成就列表直接写在 UI 代码中,无动态数据来源
unlocked状态为固定布尔值,无运行时状态变更逻辑- 页面无交互操作(如点击解锁、下拉刷新),无需状态管理
- StatelessWidget 代码量更少,维护成本更低
3.2 扩展方向1:改为 StatefulWidget 动态拉取数据
class AchievementSystemPage extends StatefulWidget {
const AchievementSystemPage({super.key});
State<AchievementSystemPage> createState() => _AchievementSystemPageState();
}
- 若需从本地/服务端拉取成就数据,需改为
StatefulWidget - 通过
createState方法创建状态类,管理动态数据 - 可在
initState中初始化数据请求,保证页面加载时获取最新状态
3.3 扩展方向2:保留 StatelessWidget 接收外部数据
// 改造后的构造方法,接收外部成就列表数据
class AchievementSystemPage extends StatelessWidget {
final List<AchievementModel> achievements;
const AchievementSystemPage({
super.key,
required this.achievements,
});
- 定义
AchievementModel数据模型,统一成就数据结构 - 通过构造方法接收外部传入的成就列表,保持组件无状态特性
- 父组件负责数据管理和更新,子组件仅负责展示,符合单一职责原则
3.4 成就数据模型定义
// 成就数据模型示例
class AchievementModel {
final String title;
final String description;
final IconData icon;
final Color color;
final bool unlocked;
const AchievementModel({
required this.title,
required this.description,
required this.icon,
required this.color,
required this.unlocked,
});
}
- 使用不可变模型(所有字段
final),保证数据安全性 - 构造方法添加
required修饰,强制传入所有必要字段 - 支持
const构造,提升实例创建效率 - 统一管理成就数据,便于后续扩展和维护
4. ListView:成就列表可滚动
你用 ListView 包裹所有成就项,补充优化点和对比代码:
4.1 基础 ListView 实现
ListView(
children: [
// 成就标题和成就项
],
)
- 核心优势:
- 自动处理内容溢出,小屏设备下支持垂直滚动
- 无需手动计算高度,适配不同数量的成就项
- 继承
ScrollView特性,支持滚动物理效果(如回弹)
4.2 优化版 ListView.builder
ListView.builder(
itemCount: achievements.length,
itemBuilder: (context, index) {
final achievement = achievements[index];
return _buildAchievement(
achievement.title,
achievement.description,
achievement.icon,
achievement.color,
achievement.unlocked,
);
},
)
- 使用
ListView.builder实现懒加载,仅渲染可视区域内的成就项 - 通过
itemCount控制列表长度,避免空列表异常 - 索引遍历获取成就数据,适配动态列表场景
- 减少内存占用,提升大量成就项时的渲染性能
4.3 与 Column 的对比
// 不推荐的 Column 写法(易溢出)
Column(
children: [
// 成就项列表
],
)
- 缺点分析:
- 成就项数量增加时,内容会超出屏幕高度导致溢出
- 无滚动能力,用户无法查看超出可视区域的内容
- 需要手动嵌套
SingleChildScrollView,代码冗余
5. _buildAchievement:把重复 UI 抽成方法
你将重复的成就项布局抽成独立方法,补充方法设计细节和扩展优化:
5.1 方法定义核心价值
Widget _buildAchievement(
String title,
String description,
IconData icon,
Color color,
bool unlocked
) {
return Card(
margin: EdgeInsets.only(bottom: 12.h),
child: ListTile(
- 方法参数覆盖成就项的所有视觉属性,扩展性强
- 统一渲染逻辑,后续修改样式仅需调整该方法,符合“单一修改点”原则
- 参数命名语义化(title/description/icon 等),代码可读性高
- 方法返回值为
Widget,可直接嵌入 ListView 列表,集成性好
5.2 方法扩展:添加点击事件
// 扩展:为成就项添加点击事件
Widget _buildAchievement(
String title,
String description,
IconData icon,
Color color,
bool unlocked, {
VoidCallback? onTap,
}) {
return Card(
margin: EdgeInsets.only(bottom: 12.h),
child: ListTile(
onTap: unlocked ? onTap : null,
- 新增可选参数
onTap,支持为已解锁成就添加点击事件 - 未解锁成就禁用点击(
onTap: null),符合交互逻辑 - 使用命名参数(
{}包裹),避免参数传递顺序混乱 - 点击事件仅对已解锁成就生效,引导用户先完成任务解锁
5.3 方法扩展:添加成就进度
// 扩展:显示成就完成进度
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(description),
if (!unlocked)
Padding(
padding: EdgeInsets.only(top: 4.h),
child: LinearProgressIndicator(
value: 0.3, // 示例进度值,实际从数据获取
backgroundColor: Colors.grey[200],
valueColor: AlwaysStoppedAnimation<Color>(color),
),
),
],
),
- 未解锁成就显示进度条,直观展示完成进度
- 进度条使用成就主题色,保持视觉一致性
- 进度值
value可从用户做题数据中动态计算 - 进度条添加顶部间距,避免与描述文本重叠
6. Card + ListTile:成就项的标准布局
每条成就用 Card 包裹 ListTile,补充布局细节和视觉设计思路:
6.1 Card 组件的视觉优化
Card(
elevation: 2, // 阴影高度
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
margin: EdgeInsets.only(bottom: 12.h),
child: ListTile(
elevation: 2设置轻微阴影,提升卡片层次感,不突兀shape自定义圆角半径(8.r响应式),适配不同屏幕的圆角比例- Card 组件默认有内边距,与 ListTile 配合无需额外调整间距
- 仅设置底部 margin,保证列表项之间的间距均匀
6.2 ListTile 组件的布局优势
ListTile(
leading: CircleAvatar(...),
title: Text(...),
subtitle: Text(...),
trailing: Icon(...),
)
- ListTile 是 Flutter 内置的标准化列表项布局,包含四个核心区域:
leading:左侧前置组件(成就图标)title:主标题(成就名称)subtitle:副标题(成就描述)trailing:右侧后置组件(状态图标)
- 无需手动调整各区域的间距和对齐方式,减少布局代码量
- 适配不同屏幕尺寸的自动布局,兼容性强
6.3 视觉层次设计
- 已解锁成就:
- 圆形图标背景为主题色,视觉突出
- 标题为黑色,对比度高
- 右侧对勾图标为绿色,强化完成状态
- 未解锁成就:
- 圆形图标背景为灰色,视觉弱化
- 标题为灰色,降低关注度
- 右侧锁图标为默认色,表达锁定状态
- 整体视觉层次清晰,用户可快速区分成就状态
7. leading:用 CircleAvatar + Icon 做徽章
你用 CircleAvatar 包裹 Icon 实现成就徽章,补充设计细节和优化点:
7.1 核心实现代码与解析
leading: CircleAvatar(
radius: 24.r, // 自定义半径
backgroundColor: unlocked ? color : Colors.grey,
child: Icon(
icon,
size: 18.sp,
color: Colors.white,
),
),
radius: 24.r自定义圆形头像半径,适配成就徽章的视觉大小- 图标尺寸
18.sp与半径匹配,避免图标过大/过小 Colors.white图标颜色在彩色/灰色背景下均清晰可见- 响应式单位
r/sp保证不同屏幕下的比例一致
7.2 扩展:添加成就等级标识
// 扩展:在徽章中添加等级标识
CircleAvatar(
backgroundColor: unlocked ? color : Colors.grey,
child: Stack(
alignment: Alignment.center,
children: [
Icon(icon, color: Colors.white),
Positioned(
bottom: 0,
right: 0,
child: Container(
padding: EdgeInsets.all(2.r),
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(8.r),
),
child: Text('1', style: TextStyle(
fontSize: 8.sp,
color: Colors.white,
)),
),
),
],
),
),
- 使用
Stack叠加图标和等级标识,丰富徽章信息 - 等级标识使用小尺寸文本 + 红色背景,视觉突出但不喧宾夺主
- 圆角容器包裹等级文本,符合整体圆润的设计风格
- 仅在已解锁成就中显示等级,未解锁状态隐藏
8. title 的颜色:解锁/未解锁的视觉区分
你根据解锁状态设置标题颜色,补充视觉设计和扩展优化:
8.1 核心实现与设计思路
title: Text(title, style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500, // 补充字重
color: unlocked ? Colors.black : Colors.grey[600],
)),
- 新增
FontWeight.w500,比普通文本稍粗,突出成就名称 - 未解锁状态使用
Colors.grey[600](中灰色),比纯灰色更柔和 - 字号
16.sp介于页面标题(20.sp)和副标题之间,层级清晰 - 颜色对比:已解锁(黑色)vs 未解锁(中灰色),区分度适中
8.2 扩展:添加成就解锁时间
// 扩展:已解锁成就显示解锁时间
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: TextStyle(
fontSize: 16.sp,
color: unlocked ? Colors.black : Colors.grey[600],
)),
if (unlocked)
Text(
'解锁于: 2024-05-20', // 实际从数据获取
style: TextStyle(fontSize: 10.sp, color: Colors.grey[500]),
),
],
),
- 已解锁成就下方显示解锁时间,增强成就感和记录感
- 时间文本使用小号字体(10.sp)+ 浅灰色,不抢主标题风头
- 仅在已解锁状态下显示,避免未解锁成就的信息冗余
- 时间格式统一为“年-月-日”,符合用户阅读习惯
9. trailing:右侧用 check/lock 表达状态
你用对勾/锁图标表达解锁状态,补充交互设计和扩展优化:
9.1 核心实现与交互逻辑
trailing: unlocked
? Icon(Icons.check_circle, color: Colors.green[500], size: 20.sp)
: Icon(Icons.lock_outline, size: 20.sp),
- 替换为
Icons.check_circle(圆形对勾),视觉更饱满 - 绿色使用
Colors.green[500],比纯绿色更自然 - 锁图标使用
Icons.lock_outline(轮廓版),与对勾图标视觉风格统一 - 统一设置图标尺寸
20.sp,保证视觉一致性
9.2 扩展:添加成就点击反馈
// 扩展:已解锁成就添加点击波纹效果
trailing: unlocked
? InkWell(
onTap: () {
// 展示成就详情弹窗
showAchievementDetail(context, title);
},
child: Icon(Icons.check_circle, color: Colors.green[500]),
)
: Icon(Icons.lock_outline),
- 为已解锁成就的右侧图标添加点击事件,支持查看成就详情
- 使用
InkWell实现水波纹点击反馈,符合 Material 设计规范 - 未解锁成就仍为普通图标,无点击反馈,交互逻辑清晰
- 点击后调用
showAchievementDetail方法展示详情弹窗
9.3 成就详情弹窗实现
// 成就详情弹窗示例
void showAchievementDetail(BuildContext context, String title) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('成就详情'),
content: Text('你已解锁「$title」成就,继续加油!'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('确定'),
),
],
),
);
}
- 使用
showDialog弹出标准对话框,交互体验统一 - 弹窗标题和内容清晰,强化成就解锁的正向反馈
- 提供“确定”按钮关闭弹窗,操作简单明确
- 适配不同屏幕尺寸,弹窗自动居中显示
10. 成就项的语义设计:从“新手”到“冠军”
你设计的 5 个成就形成了一个递进路径,补充语义设计细节和扩展建议:
10.1 成就层级设计
| 成就名称 | 解锁条件 | 难度层级 | 设计意图 |
|---|---|---|---|
| 逆向新手 | 完成1道逆向思维题 | 入门 | 降低首次解锁门槛,提升用户初期参与感 |
| 逻辑大师 | 完成50道逻辑题 | 进阶 | 引导用户深耕逻辑题型,提升专项能力 |
| 模式专家 | 完成30道模式题 | 进阶 | 覆盖另一核心题型,丰富训练维度 |
| 文字高手 | 完成40道文字题 | 进阶 | 覆盖文字类题型,保证题型全覆盖 |
| 全能冠军 | 完成所有题目 | 终极 | 设定最高目标,激励用户完成全部训练 |
10.2 扩展成就体系
// 扩展成就项示例
_buildAchievement(
'每日打卡',
'连续7天完成每日训练',
Icons.calendar_today,
Colors.orange,
false
),
_buildAchievement(
'速通达人',
'10分钟内完成10道题',
Icons.speed,
Colors.red,
false
),
- 新增“每日打卡”成就,鼓励用户持续参与,提升留存率
- 新增“速通达人”成就,激励用户提升解题速度,增加挑战性
- 不同成就类别使用差异化图标和颜色,视觉区分度更高
- 解锁条件覆盖“持续参与”和“解题效率”,丰富成就维度
10.3 成就排序逻辑
// 成就列表排序示例(按解锁状态+难度)
final sortedAchievements = [
...achievements.where((a) => a.unlocked).toList()
..sort((a, b) => a.title.compareTo(b.title)),
...achievements.where((a) => !a.unlocked).toList()
..sort((a, b) => getDifficultyLevel(a).compareTo(getDifficultyLevel(b))),
];
- 已解锁成就按名称排序,保证展示顺序稳定
- 未解锁成就按难度层级排序,引导用户从易到难完成
- 使用扩展运算符(
...)合并两个列表,代码简洁 - 提取
getDifficultyLevel方法获取成就难度,逻辑复用
11. 如何接入真实解锁逻辑:从做题记录统计
你当前 unlocked 是写死的,补充真实解锁逻辑的实现方案:
11.1 本地数据存储
// 引入 Hive 存储用户做题记录
import 'package:hive/hive.dart';
// 初始化 Hive 盒子
final box = Hive.box('user_data');
// 存储做题记录
void saveProblemRecord(String type, int count) {
box.put('${type}_count', count);
}
- 使用 Hive 轻量级本地存储,无需复杂配置,性能高
- 按题型分类存储完成数量,便于后续统计
- 键名格式统一为“题型_count”,便于数据管理
- 支持跨会话持久化存储,用户重启 App 数据不丢失
11.2 成就解锁逻辑计算
// 计算成就解锁状态
bool checkAchievementUnlocked(String achievementType) {
switch (achievementType) {
case 'reverse_newbie':
return box.get('reverse_count', defaultValue: 0) >= 1;
case 'logic_master':
return box.get('logic_count', defaultValue: 0) >= 50;
case 'pattern_expert':
return box.get('pattern_count', defaultValue: 0) >= 30;
case 'text_master':
return box.get('text_count', defaultValue: 0) >= 40;
case 'all_champion':
return checkAllProblemsCompleted();
default:
return false;
}
}
- 使用
switch语句区分不同成就的解锁条件,逻辑清晰 - 从 Hive 中获取对应题型的完成数量,默认值为 0,避免空值异常
- 封装
checkAllProblemsCompleted方法判断是否完成所有题目 - 返回布尔值直接映射到成就的
unlocked状态,集成简单
11.3 全题完成判断方法
// 判断是否完成所有题目
bool checkAllProblemsCompleted() {
final reverseCount = box.get('reverse_count', defaultValue: 0);
final logicCount = box.get('logic_count', defaultValue: 0);
final patternCount = box.get('pattern_count', defaultValue: 0);
final textCount = box.get('text_count', defaultValue: 0);
// 假设各题型总题数
const totalReverse = 10;
const totalLogic = 50;
const totalPattern = 30;
const totalText = 40;
return reverseCount >= totalReverse &&
logicCount >= totalLogic &&
patternCount >= totalPattern &&
textCount >= totalText;
}
- 分别获取各题型的完成数量,与总题数对比
- 总题数使用
const常量定义,便于后续修改 - 所有题型都完成时返回
true,否则返回false - 逻辑清晰,便于后续扩展更多题型的判断
11.4 成就状态更新时机
// 完成题目后更新成就状态
void updateAchievementStatusAfterSolve() {
// 保存本次做题记录
saveProblemRecord(currentProblemType, currentCount + 1);
// 重新计算所有成就解锁状态
setState(() {
achievements = achievements.map((a) {
return AchievementModel(
title: a.title,
description: a.description,
icon: a.icon,
color: a.color,
unlocked: checkAchievementUnlocked(a.type),
);
}).toList();
});
}
- 在用户完成题目后立即更新成就状态,保证数据实时性
- 先保存做题记录,再重新计算成就状态,数据顺序正确
- 使用
setState更新状态(StatefulWidget 场景),触发 UI 刷新 - 遍历成就列表重新计算解锁状态,保证所有成就同步更新
12. 小结:成就系统实现的关键点
- 展示清晰:Card + ListTile 组合,符合 Material 设计规范,视觉层次清晰
- 状态直观:颜色 + 图标区分解锁/未解锁,用户可快速识别成就状态
- 复用到位:_buildAchievement 抽取重复逻辑,后续修改仅需调整一处
- 语义递进:从新手到冠军的成就路径,形成完整的激励体系
- 扩展灵活:支持静态展示/动态数据两种模式,适配不同业务阶段
- 性能优化:采用响应式单位、懒加载列表等,保证多设备适配和渲染性能
- 交互友好:添加点击反馈、进度展示、详情弹窗等,提升用户体验
13. 常见踩坑点:忘记 StatelessWidget 导致状态混乱
如果误用 StatefulWidget 而没有状态管理,会导致不必要的复杂性.
你用了 StatelessWidget,页面简洁且符合需求.
这是"组件选择"的正确体现.
13.1 错误示例(StatefulWidget 滥用)
// 不推荐的写法(无状态却用 StatefulWidget)
class AchievementSystemPage extends StatefulWidget {
const AchievementSystemPage({super.key});
State<AchievementSystemPage> createState() => _AchievementSystemPageState();
}
class _AchievementSystemPageState extends State<AchievementSystemPage> {
Widget build(BuildContext context) {
// 无任何状态变量,纯静态展示
return Scaffold(...);
}
}
- 无状态变量却使用
StatefulWidget,增加代码冗余 - 状态类的创建和维护增加不必要的成本
- 组件重建时无状态可更新,违背 StatefulWidget 的设计初衷
14. 常见踩坑点:ListView 缺失导致溢出
如果用 Column 替代 ListView,成就项过多时会溢出.
你用了 ListView,确保内容可滚动.
这是"布局安全"的体现.
14.1 错误示例(Column 导致溢出)
// 错误写法:Column 无滚动能力
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
Text('成就徽章', style: TextStyle(fontSize: 20.sp)),
// 多个成就项,超出屏幕高度时溢出
_buildAchievement(...),
_buildAchievement(...),
// 更多成就项...
],
),
),
- 成就项数量超过屏幕高度时,会触发
BottomOverflowed异常 - 无滚动能力,用户无法查看超出可视区域的成就项
- 需手动添加
SingleChildScrollView包裹,代码冗余
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)