flutter_for_openharmony逆向思维训练app实战+逻辑门谜题实现

目标
- 从“逻辑谜题”模块进入
逻辑门谜题 - 用
ChoiceChip切换逻辑门类型 - 用
Switch控制输入 A/B - 用一个
getter实时计算输出值 - UI 用颜色和文字把输出结果表达出来
本文涉及文件
lib/feature_pages.dartlib/app.dartlib/main.dart
1. 入口在哪里:从功能列表 push 进入
“逻辑门谜题”属于 LogicPuzzlesPage 的一个入口项。
你当前代码里对应的是这一行(位于 lib/feature_pages.dart)
_buildFeatureCard(context, '逻辑门谜题', Icons.settings_input_component, const LogicGatesPage()),
核心设计思路:
- 采用“入口页+功能页”分离架构,符合单一职责原则
Navigator.push实现页面跳转,保持 Flutter 原生路由体验- 功能卡片统一使用
_buildFeatureCard封装,保证全局UI风格一致
这样拆分结构的好处是:
- 入口页只负责导航
- 功能页只负责交互与演示
2. 为什么这个页面适合用 StatefulWidget
逻辑门模拟器的 UI 是强交互。
- 你要切换逻辑门(
selectedGate) - 你要切换输入开关(
inputA/inputB)
这些都需要实时刷新 UI。
所以最直接、可读性最高的实现就是 StatefulWidget + setState。
选择 StatefulWidget 的核心原因:
- 页面存在多个可变状态
setState能触发局部UI重建,性能优于全局状态管理- 交互逻辑简单,无需引入 Provider/BLoC 等复杂状态库
3. LogicGatesPage
下面这段实现来自你项目的 lib/feature_pages.dart。
我保持原样引用,避免出现“代码写得像示例但跑不起来”的问题。
class LogicGatesPage extends StatefulWidget {
const LogicGatesPage({super.key});
State<LogicGatesPage> createState() => _LogicGatesPageState();
}
- 遵循 Flutter 状态组件标准写法,类名采用大驼峰命名
- 构造函数加
const优化性能,减少不必要的重建 createState方法返回私有状态类,封装状态逻辑
这是功能页的标准入口,StatefulWidget 保证交互状态可控。createState 返回状态类,后续所有状态更新依赖 setState。
结构与其他训练页保持一致,便于统一维护。
class _LogicGatesPageState extends State<LogicGatesPage> {
bool inputA = false;
bool inputB = false;
String selectedGate = 'AND';
}
状态设计细节:
- 输入状态用布尔值直接映射开关状态,语义直观
- 逻辑门类型用字符串标识,便于后续扩展新门类型
- 初始值设置为
false/AND,符合用户默认操作直觉 - 所有状态字段私有化,仅在状态类内部修改
三个状态字段支撑整个逻辑门模拟器。inputA 与 inputB 为两个输入开关,selectedGate 为当前逻辑门类型。
布尔值直接对应开关状态,字符串对应门类型,简洁且直观。
bool get output {
switch (selectedGate) {
case 'AND': return inputA && inputB;
case 'OR': return inputA || inputB;
case 'NOT': return !inputA;
case 'XOR': return inputA ^ inputB;
default: return false;
}
}
Getter 计算逻辑优势:
- 派生状态不存储,避免数据不一致问题
- switch 分支覆盖所有支持的逻辑门,逻辑清晰
- Dart 原生
^运算符实现异或,代码更简洁 - default 分支兜底,防止新增门类型时出现异常
用 getter 计算输出,避免存储派生状态。
每次 UI 重建都会自动拿到最新结果,不易出错。switch 覆盖四种逻辑门,default 保证健壮性。^ 是 Dart 对布尔异或的直接支持,代码简洁。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('逻辑门谜题')),
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
Text('逻辑门模拟器', style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.bold
)),
UI 基础布局设计:
- 采用 Scaffold + AppBar 标准结构,符合 Material Design
- 全局 Padding 使用
16.w适配不同屏幕尺寸 - Column 作为根布局,实现纵向流式排版
- 标题文字加粗 + 24.sp 字号,突出视觉层级
进入 UI 构建部分,Scaffold + AppBar 为标准结构。Padding 保持全局间距,Column 为纵向布局。
标题字号 24.sp,突出主题。
SizedBox(height: 24.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: ['AND', 'OR', 'NOT', 'XOR'].map((gate) =>
ChoiceChip(
label: Text(gate),
selected: selectedGate == gate,
onSelected: (selected) => setState(() => selectedGate = gate),
),
).toList(),
),
ChoiceChip 实现逻辑:
- 数据驱动生成 Chip,新增门类型仅需修改数组
spaceEvenly保证 Chip 均匀分布,视觉更美观- 选中状态与
selectedGate双向绑定,状态同步 - 点击回调直接更新状态,逻辑无冗余
Row + map 动态生成 ChoiceChip,selectedGate 控制选中状态。
SizedBox(height: 32.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Column(
children: [
Text('输入A', style: TextStyle(fontSize: 16.sp)),
Switch(
value: inputA,
onChanged: (value) => setState(() => inputA = value),
),
Text(inputA ? '1' : '0', style: TextStyle(fontSize: 20.sp)),
],
),
输入A组件设计:
- Column 垂直布局,实现“标签-开关-值显示”一体化
- Switch 组件直接绑定
inputA,状态单一数据源 - 开关值用 1/0 显示,贴合逻辑门二进制语义
- 字号分层设计(16.sp/20.sp),提升可读性
输入区用 Row 排列,spaceEvenly 均匀分布。
每个输入用 Column 垂直排列:标签、开关、状态值。Switch 直接绑定状态,onChanged 只更新对应字段。
if (selectedGate != 'NOT') Column(
children: [
Text('输入B', style: TextStyle(fontSize: 16.sp)),
Switch(
value: inputB,
onChanged: (value) => setState(() => inputB = value),
),
Text(inputB ? '1' : '0', style: TextStyle(fontSize: 20.sp)),
],
),
输入B条件渲染逻辑:
- NOT 门仅需单输入,条件渲染隐藏输入B,符合逻辑语义
- 输入B结构与输入A完全一致,保证UI统一性
- 状态保留但不渲染,切换回其他门类型时状态不丢失
- 条件判断写在布局层,不影响核心计算逻辑
if (selectedGate != 'NOT') 条件渲染输入B,符合 NOT 门语义。
Column(
children: [
Text('输出', style: TextStyle(fontSize: 16.sp)),
Container(
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: output ? Colors.green : Colors.red,
borderRadius: BorderRadius.circular(30.r),
),
输出容器设计:
- 宽高均用
60.w,保证圆形在所有屏幕比例下不变形 - borderRadius 设置为宽高一半,完美实现圆形
- 背景色根据输出值动态切换,视觉反馈直观
- 尺寸单位使用
w/r,适配 OpenHarmony 多设备
输出区同样用 Column 垂直布局。Container 为圆形,宽高都用 .w 保证比例一致。
背景色根据 output 动态切换,白色文字保证对比度。
child: Center(
child: Text(
output ? '1' : '0',
style: TextStyle(
fontSize: 24.sp,
color: Colors.white
),
),
),
),
],
),
],
),
],
),
),
);
}
}
输出文字设计:
- Center 组件保证文字在圆形容器中居中
- 白色文字在红绿背景下对比度充足,符合无障碍设计
- 24.sp 字号突出输出值,视觉优先级高于输入值
- 文字内容用 1/0 表示,贴合二进制逻辑门场景
整体结构清晰,交互直观,适合教学演示。
4. 逻辑门计算为什么写成 getter
你的输出计算写成了
bool get output { ... }
Getter 写法核心优势:
- 自动响应状态变化:inputA/inputB/selectedGate 任一修改,输出立即更新
- 无冗余代码:无需在每个 setState 中手动更新 output
- 易维护:计算逻辑集中,修改时仅需改动一处
- 内存优化:不占用额外内存存储派生状态
这种写法的优势在于
- 它是“派生状态”,不需要额外存储
inputA、inputB、selectedGate任一变化时,UI 重建会自动拿到最新输出
这能避免一个常见错误:
- 把
output存成字段 - 忘记在某次交互里更新它
- UI 就会出现“显示结果不对”的问题
5. 为什么 NOT 门要隐藏输入B
你在 UI 层用了条件渲染
if (selectedGate != 'NOT') Column(...输入B...)
条件渲染设计考量:
- 语义一致性:NOT 门逻辑上仅需一个输入,UI 层做对应适配
- 减少用户困惑:避免用户误以为 B 输入会影响 NOT 门输出
- 简化交互:用户无需关注无关的输入项
- 状态兼容性:切换门类型时保留 inputB 状态,切回其他门时无需重新设置
这是一种很干净的表达方式。
- 对 NOT 门来说,逻辑上只需要一个输入
- UI 直接不渲染 B,避免用户误以为 B 会参与计算
同时也和你的 output 计算逻辑对应
case 'NOT': return !inputA;
NOT 门计算逻辑细节:
- 仅依赖 inputA,完全符合 NOT 门单输入特性
- 逻辑非运算符
!语义明确,代码易读 - 与 UI 层隐藏 inputB 形成闭环,无逻辑漏洞
注意这里一个真实的产品细节:
当你从 AND/OR/XOR 切到 NOT 时,inputB 的值不会被清空。
但这不影响输出,因为 NOT 分支不会读取 inputB。
这种“状态保留但不使用”的处理,在演示类页面里是合理的。
6. ChoiceChip 切换门类型:为什么用 map + toList
你在 Row 里写的是
children: ['AND', 'OR', 'NOT', 'XOR'].map((gate) => ChoiceChip(...)).toList(),
数据驱动设计优势:
- 可扩展性:新增 NAND/NOR 仅需在数组中添加字符串
- 代码简洁:避免重复编写多个 ChoiceChip 组件
- 一致性:所有 Chip 生成逻辑一致,减少人为错误
- 易维护:门类型列表集中管理,便于后续修改
这种写法的好处
- 逻辑门列表是数据驱动的
- 以后你想加
NAND/NOR,只需要列表里加字符串
并且 ChoiceChip 的 selected 状态也很直观
selected: selectedGate == gate
选中状态设计:
- 直接比较字符串,逻辑简单无冗余
- 单向数据流:选中状态由 selectedGate 唯一决定
- 无中间状态:始终有且仅有一个 Chip 处于选中状态
onSelected 回调里直接 setState 更新 selectedGate。
onSelected 回调设计:
- 简化逻辑:无需判断 selected 参数,直接赋值
- 状态更新及时:setState 触发 UI 立即重建
- 符合单选逻辑:点击任意 Chip 都将其设为选中项
7. Switch 的交互:为什么 setState 只改一个字段
你对输入开关的写法是
onChanged: (value) => setState(() => inputA = value)
setState 最佳实践:
- 最小化状态更新:仅修改当前开关对应的状态字段
- 代码简洁:箭头函数简化回调写法,减少嵌套
- 易调试:每个 setState 只做一件事,便于定位问题
- 性能优化:减少不必要的状态变更,降低重建成本
这是一个好的习惯。
setState 的闭包里只改动必要字段,可读性和可维护性更强。
同时配合 getter output,你不需要写
- “更新 output”
- “刷新显示”
8. 输出的表达:用颜色 + 数字做双重反馈
输出区域用了一个圆形容器
output == true:绿色,显示1output == false:红色,显示0
代码是
color: output ? Colors.green : Colors.red,
child: Text(output ? '1' : '0')
双重反馈设计价值:
- 视觉分层:颜色快速识别,数字精确表达
- 多感官反馈:兼顾视觉直观性和数值准确性
- 符合用户认知:绿色=成功/真,红色=失败/假,1/0=二进制值
- 无障碍友好:颜色+文字双重提示,适配色弱用户
这种“颜色 + 字符”的组合比只用文字更直观。
而且实现很轻,不需要额外组件。
9. AND / OR / XOR / NOT:把逻辑门语义对齐到“输入开关”
这个页面虽然是“逻辑门谜题”,但它的落点不是背概念,而是把概念落到交互上。
你在 UI 上做了三个区域:
- 输入A
- 输入B(有条件显示)
- 输出
这三个区域一旦固定,逻辑门就只剩下“运算规则”。
9.1 AND
AND 的规则是:两个输入都为真,输出才为真。
对应代码
case 'AND': return inputA && inputB;
AND 门逻辑解析:
- 逻辑与运算符
&&严格匹配 AND 门语义 - 双输入校验,缺一不可
- 只有 inputA 和 inputB 都为 true 时输出才为 true
当用户把两个 Switch 都打开时,输出变绿并显示 1。
交互体验设计:
- 开关操作即时反馈,符合用户操作直觉
- 输出状态与逻辑规则完全一致,无认知偏差
- 二进制显示(1/0)贴合逻辑门教学场景
9.2 OR
OR 的规则是:只要有一个输入为真,输出就为真。
case 'OR': return inputA || inputB;
OR 门逻辑解析:
- 逻辑或运算符
||精准表达 OR 门“有一为真则真”的特性 - 单输入满足即可输出真,符合 OR 门语义
- 代码简洁,无冗余判断
9.3 NOT
NOT 的规则是:对输入取反。
case 'NOT': return !inputA;
NOT 门逻辑解析:
- 逻辑非运算符
!直接实现取反操作 - 仅依赖 inputA,符合单输入特性
- 代码无冗余,语义完全匹配
你在 UI 层同时做了“输入B隐藏”,让规则更明确。
UI 与逻辑对齐:
- 视觉层与逻辑层保持一致,减少用户理解成本
- 隐藏无关输入项,聚焦 NOT 门核心逻辑
- 操作流程简化,用户只需关注一个输入
9.4 XOR
XOR 的规则是:两个输入不同,输出为真。
case 'XOR': return inputA ^ inputB;
XOR 门逻辑解析:
- Dart 原生
^运算符直接支持布尔异或,代码更简洁 - 严格实现“相同为假,不同为真”的 XOR 门规则
- 无需额外判断,一行代码完成计算
这里用 ^ 是 Dart 对布尔异或运算的直接支持。
语言特性利用:
- 充分利用 Dart 运算符特性,减少代码量
- 运算符语义与 XOR 门完全匹配,易读易理解
- 性能优于自定义判断逻辑
对用户来说,XOR 的体验是:
- A=1,B=0 => 输出 1
- A=0,B=1 => 输出 1
- A=0,B=0 => 输出 0
- A=1,B=1 => 输出 0
交互场景覆盖:
- 完整展示 XOR 门的四种输入组合
- 输出结果直观反映“异或”核心特性
- 帮助用户理解 XOR 门与 OR 门的区别
10. 为什么说这个页面“状态少但表达力强”
你这个页面的状态字段只有三个:
bool inputAbool inputBString selectedGate
但是它能表达的变化很多。
原因是:
- UI 的变化大多来自
output(派生状态) output又完全由三个状态决定
状态设计原则:
- 最小状态集:仅保留核心可变状态,无冗余字段
- 派生状态计算:通过 getter 生成衍生数据,避免数据冗余
- 状态单一来源:所有 UI 状态都可追溯到三个核心字段
- 状态不可变:核心状态仅通过 setState 修改,可预测性强
在这种结构下,页面不容易出现“逻辑分叉”。
也就是说你不会出现
- 某次切换门类型后 output 没更新
- 某个 Switch 改了但输出没刷新
因为 output 根本不存储,只计算。
避免逻辑分叉的核心:
- 无状态存储冗余,减少数据不一致风险
- 计算逻辑纯函数化,输入确定则输出确定
- UI 渲染完全依赖状态,无隐藏逻辑
11. ChoiceChip 的 onSelected:为什么不用 selected 参数
ChoiceChip 的回调签名是 (bool selected)。
很多人会写成
onSelected: (selected) {
if (selected) {
setState(() => selectedGate = gate);
}
}
冗余写法问题:
- 增加不必要的条件判断,代码冗余
- 选中状态由 selectedGate 控制,无需额外判断
- 降低代码可读性,增加维护成本
onSelected: (selected) => setState(() => selectedGate = gate)
简化写法优势:
- 去掉冗余判断,代码更简洁
- 符合单选场景需求,始终设置选中项
- 减少分支逻辑,降低出错概率
这在当前结构下是合理的。
理由
- 你展示的是“单选组”,不存在取消选择后变成“无选择”的需求
- 点击任何 chip,目标就是把它设为当前 gate
因此直接赋值更简洁。
12. UI 布局上的一个真实选择:Row + Column,而不是复杂容器
你的布局非常朴素:
- 外层
Column - 中间两行
Row - 每个输入输出区各自一个
Column
布局设计原则:
- 扁平化布局:嵌套层级少,性能更好
- 语义化布局:Row/Column 对应行列关系,易理解
- 组件化拆分:每个输入/输出区独立成 Column,易维护
- 无过度封装:简单布局无需自定义组件,降低复杂度
好处
- 读代码就能想象界面结构
- 不依赖额外组件
- 对新人友好,后续扩展也更容易
页面的“重心”在交互上,不在复杂排版上。
布局优先级设计:
- 交互逻辑优先于视觉设计
- 功能完整性优先于布局复杂度
- 可读性优先于简洁性
13. ScreenUtil 的一致性:宽高都用 .w
输出圆形区域
width: 60.w,
height: 60.w,
borderRadius: BorderRadius.circular(30.r)
尺寸适配设计:
- 宽高均用
.w单位,保证圆形比例一致 - borderRadius 用
.r单位,适配圆角半径 - 60.w + 30.r 完美实现正圆形,无变形风险
这里有个细节:
- 你让 width/height 都用
.w - 让圆形在不同尺寸下保持比例一致
如果写成 height: 60.h,在极端比例屏幕上可能会变成椭圆。
现在这样写更稳。
屏幕适配原则:
- 等比例元素使用同一维度单位(.w)
- 避免混合使用 .w/.h 导致比例失调
- 圆角半径与宽高匹配,保证形状完整性
同理,文字字号用了 .sp,避免字体在缩放后过大或过小。
文字适配设计:
.sp单位随屏幕尺寸和系统字体设置缩放- 字号分层设计(16.sp/20.sp/24.sp),保持视觉层级
- 无固定像素值,适配所有设备
14. 输出区为什么要同时用颜色和数字
你现在的输出表达是
- 颜色:绿/红
- 文本:1/0
这样做有两点好处。
第一,颜色是快速感知。
用户瞟一眼就知道真假。
颜色反馈设计:
- 利用色彩心理学:绿色=正确/真,红色=错误/假
- 快速视觉识别,无需阅读文字
- 符合用户普遍认知,学习成本低
第二,数字是精确信息。
当用户想把它对应到“二进制/真值表”的概念时,1/0 更贴近。
数字反馈设计:
- 二进制表示,贴合逻辑门教学场景
- 精确表达状态,避免颜色歧义
- 与输入值显示格式一致,保持UI统一性
15. 这个页面如何扩展:加 NAND/NOR 只需要改两处
你现在的门类型列表是
['AND', 'OR', 'NOT', 'XOR']
扩展准备工作:
- 列表集中管理,扩展只需添加字符串
- 无硬编码逻辑,新增类型不影响现有代码
- 结构预留扩展空间,符合开闭原则
输出计算是 switch。
如果你要加 NAND,最直接的扩展路径就是:
- 列表加一个字符串
- switch 加一个 case
例如(这里只说明方向,不要求你现在改项目代码)
case 'NAND': return !(inputA && inputB);
NAND 门扩展示例:
- 基于 AND 门逻辑取反,语义清晰
- 代码简洁,与现有逻辑门写法一致
- 无需修改 UI 布局,仅需添加计算逻辑
这就是“数据驱动 + 规则集中”带来的扩展性。
如果以后门类型越来越多,可以考虑把规则抽成一个 Map
final Map<String, bool Function(bool, bool)> gateRules = {
'AND': (a, b) => a && b,
'OR': (a, b) => a || b,
'NOT': (a, b) => !a,
'XOR': (a, b) => a ^ b,
'NAND': (a, b) => !(a && b),
};
高级扩展方案:
- 规则与UI分离,关注点更清晰
- 函数式编程,每个规则都是独立函数
- 易于测试,可单独验证每个门的逻辑
但在当前规模下,switch 反而更直观。
规模适配原则:
- 小体量用简单结构(switch),易读易写
- 大体量用高级结构(Map+函数),易扩展易维护
- 按需选择,不过度设计
16. 一个小的交互建议:NOT 门切换时,输入B区域的留白
你现在的结构是
if (selectedGate != 'NOT') Column(...输入B...)
当前实现优势:
- 逻辑简单,条件判断直接
- 减少不必要的 UI 渲染,性能更好
- 语义明确,NOT 门仅显示必要输入
当切到 NOT 时,Row 少了一列。
这在当前页面里是可以接受的。
如果你想让视觉更稳定,有两种常见做法:
- 继续保留一个占位(例如
SizedBox(width: xx)或空 Column)
selectedGate != 'NOT' ? Column(...) : SizedBox(width: 120.w),
占位方案优势:
- 布局稳定,无跳动感
- 保持 Row 子节点数量一致,布局计算更稳定
- 占位宽度可精准控制,视觉更协调
- 或者让输出区在 NOT 情况下居中
selectedGate == 'NOT'
? Expanded(child: Center(child: outputColumn))
: outputColumn,
居中方案优势:
- 视觉焦点更突出,聚焦输出结果
- 利用 Expanded 填充空间,布局更合理
- 符合 NOT 门单输入的视觉重心
这属于体验增强,不影响当前实现的正确性。
17. 小结:逻辑门谜题实现的关键点
- 数据驱动的门列表:
['AND','OR','NOT','XOR'] - 派生状态 getter:
bool get output - 必要的条件渲染:NOT 门隐藏输入B
- 双重反馈:颜色 + 1/0 输出
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)