在这里插入图片描述

目标

  • 从“逻辑谜题”模块进入 逻辑门谜题
  • ChoiceChip 切换逻辑门类型
  • Switch 控制输入 A/B
  • 用一个 getter 实时计算输出值
  • UI 用颜色和文字把输出结果表达出来

本文涉及文件

  • lib/feature_pages.dart
  • lib/app.dart
  • lib/main.dart

1. 入口在哪里:从功能列表 push 进入

“逻辑门谜题”属于 LogicPuzzlesPage 的一个入口项。
你当前代码里对应的是这一行(位于 lib/feature_pages.dart

_buildFeatureCard(context, '逻辑门谜题', Icons.settings_input_component, const LogicGatesPage()),

核心设计思路:

  1. 采用“入口页+功能页”分离架构,符合单一职责原则
  2. Navigator.push 实现页面跳转,保持 Flutter 原生路由体验
  3. 功能卡片统一使用 _buildFeatureCard 封装,保证全局UI风格一致

这样拆分结构的好处是:

  • 入口页只负责导航
  • 功能页只负责交互与演示

2. 为什么这个页面适合用 StatefulWidget

逻辑门模拟器的 UI 是强交互。

  • 你要切换逻辑门(selectedGate
  • 你要切换输入开关(inputA / inputB

这些都需要实时刷新 UI。
所以最直接、可读性最高的实现就是 StatefulWidget + setState

选择 StatefulWidget 的核心原因:

  1. 页面存在多个可变状态
  2. setState 能触发局部UI重建,性能优于全局状态管理
  3. 交互逻辑简单,无需引入 Provider/BLoC 等复杂状态库

3. LogicGatesPage

下面这段实现来自你项目的 lib/feature_pages.dart
我保持原样引用,避免出现“代码写得像示例但跑不起来”的问题。

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

  
  State<LogicGatesPage> createState() => _LogicGatesPageState();
}
  1. 遵循 Flutter 状态组件标准写法,类名采用大驼峰命名
  2. 构造函数加 const 优化性能,减少不必要的重建
  3. createState 方法返回私有状态类,封装状态逻辑

这是功能页的标准入口,StatefulWidget 保证交互状态可控。
createState 返回状态类,后续所有状态更新依赖 setState
结构与其他训练页保持一致,便于统一维护。

class _LogicGatesPageState extends State<LogicGatesPage> {
  bool inputA = false;
  bool inputB = false;
  String selectedGate = 'AND';
}

状态设计细节:

  1. 输入状态用布尔值直接映射开关状态,语义直观
  2. 逻辑门类型用字符串标识,便于后续扩展新门类型
  3. 初始值设置为 false/AND,符合用户默认操作直觉
  4. 所有状态字段私有化,仅在状态类内部修改

三个状态字段支撑整个逻辑门模拟器。
inputAinputB 为两个输入开关,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 计算逻辑优势:

  1. 派生状态不存储,避免数据不一致问题
  2. switch 分支覆盖所有支持的逻辑门,逻辑清晰
  3. Dart 原生 ^ 运算符实现异或,代码更简洁
  4. 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 基础布局设计:

  1. 采用 Scaffold + AppBar 标准结构,符合 Material Design
  2. 全局 Padding 使用 16.w 适配不同屏幕尺寸
  3. Column 作为根布局,实现纵向流式排版
  4. 标题文字加粗 + 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 实现逻辑:

  1. 数据驱动生成 Chip,新增门类型仅需修改数组
  2. spaceEvenly 保证 Chip 均匀分布,视觉更美观
  3. 选中状态与 selectedGate 双向绑定,状态同步
  4. 点击回调直接更新状态,逻辑无冗余

Row + map 动态生成 ChoiceChipselectedGate 控制选中状态。

            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组件设计:

  1. Column 垂直布局,实现“标签-开关-值显示”一体化
  2. Switch 组件直接绑定 inputA,状态单一数据源
  3. 开关值用 1/0 显示,贴合逻辑门二进制语义
  4. 字号分层设计(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条件渲染逻辑:

  1. NOT 门仅需单输入,条件渲染隐藏输入B,符合逻辑语义
  2. 输入B结构与输入A完全一致,保证UI统一性
  3. 状态保留但不渲染,切换回其他门类型时状态不丢失
  4. 条件判断写在布局层,不影响核心计算逻辑

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),
                      ),

输出容器设计:

  1. 宽高均用 60.w,保证圆形在所有屏幕比例下不变形
  2. borderRadius 设置为宽高一半,完美实现圆形
  3. 背景色根据输出值动态切换,视觉反馈直观
  4. 尺寸单位使用 w/r,适配 OpenHarmony 多设备

输出区同样用 Column 垂直布局。
Container 为圆形,宽高都用 .w 保证比例一致。
背景色根据 output 动态切换,白色文字保证对比度。

                      child: Center(
                        child: Text(
                          output ? '1' : '0', 
                          style: TextStyle(
                            fontSize: 24.sp, 
                            color: Colors.white
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

输出文字设计:

  1. Center 组件保证文字在圆形容器中居中
  2. 白色文字在红绿背景下对比度充足,符合无障碍设计
  3. 24.sp 字号突出输出值,视觉优先级高于输入值
  4. 文字内容用 1/0 表示,贴合二进制逻辑门场景

整体结构清晰,交互直观,适合教学演示。


4. 逻辑门计算为什么写成 getter

你的输出计算写成了

bool get output { ... }

Getter 写法核心优势:

  1. 自动响应状态变化:inputA/inputB/selectedGate 任一修改,输出立即更新
  2. 无冗余代码:无需在每个 setState 中手动更新 output
  3. 易维护:计算逻辑集中,修改时仅需改动一处
  4. 内存优化:不占用额外内存存储派生状态

这种写法的优势在于

  • 它是“派生状态”,不需要额外存储
  • inputAinputBselectedGate 任一变化时,UI 重建会自动拿到最新输出

这能避免一个常见错误:

  • output 存成字段
  • 忘记在某次交互里更新它
  • UI 就会出现“显示结果不对”的问题

5. 为什么 NOT 门要隐藏输入B

你在 UI 层用了条件渲染

if (selectedGate != 'NOT') Column(...输入B...)

条件渲染设计考量:

  1. 语义一致性:NOT 门逻辑上仅需一个输入,UI 层做对应适配
  2. 减少用户困惑:避免用户误以为 B 输入会影响 NOT 门输出
  3. 简化交互:用户无需关注无关的输入项
  4. 状态兼容性:切换门类型时保留 inputB 状态,切回其他门时无需重新设置

这是一种很干净的表达方式。

  • 对 NOT 门来说,逻辑上只需要一个输入
  • UI 直接不渲染 B,避免用户误以为 B 会参与计算

同时也和你的 output 计算逻辑对应

case 'NOT': return !inputA;

NOT 门计算逻辑细节:

  1. 仅依赖 inputA,完全符合 NOT 门单输入特性
  2. 逻辑非运算符 ! 语义明确,代码易读
  3. 与 UI 层隐藏 inputB 形成闭环,无逻辑漏洞

注意这里一个真实的产品细节:
当你从 AND/OR/XOR 切到 NOT 时,inputB 的值不会被清空。
但这不影响输出,因为 NOT 分支不会读取 inputB

这种“状态保留但不使用”的处理,在演示类页面里是合理的。


6. ChoiceChip 切换门类型:为什么用 map + toList

你在 Row 里写的是

children: ['AND', 'OR', 'NOT', 'XOR'].map((gate) => ChoiceChip(...)).toList(),

数据驱动设计优势:

  1. 可扩展性:新增 NAND/NOR 仅需在数组中添加字符串
  2. 代码简洁:避免重复编写多个 ChoiceChip 组件
  3. 一致性:所有 Chip 生成逻辑一致,减少人为错误
  4. 易维护:门类型列表集中管理,便于后续修改

这种写法的好处

  • 逻辑门列表是数据驱动的
  • 以后你想加 NAND / NOR,只需要列表里加字符串

并且 ChoiceChip 的 selected 状态也很直观

selected: selectedGate == gate

选中状态设计:

  1. 直接比较字符串,逻辑简单无冗余
  2. 单向数据流:选中状态由 selectedGate 唯一决定
  3. 无中间状态:始终有且仅有一个 Chip 处于选中状态

onSelected 回调里直接 setState 更新 selectedGate

onSelected 回调设计:

  1. 简化逻辑:无需判断 selected 参数,直接赋值
  2. 状态更新及时:setState 触发 UI 立即重建
  3. 符合单选逻辑:点击任意 Chip 都将其设为选中项

7. Switch 的交互:为什么 setState 只改一个字段

你对输入开关的写法是

onChanged: (value) => setState(() => inputA = value)

setState 最佳实践:

  1. 最小化状态更新:仅修改当前开关对应的状态字段
  2. 代码简洁:箭头函数简化回调写法,减少嵌套
  3. 易调试:每个 setState 只做一件事,便于定位问题
  4. 性能优化:减少不必要的状态变更,降低重建成本

这是一个好的习惯。
setState 的闭包里只改动必要字段,可读性和可维护性更强。

同时配合 getter output,你不需要写

  • “更新 output”
  • “刷新显示”

8. 输出的表达:用颜色 + 数字做双重反馈

输出区域用了一个圆形容器

  • output == true:绿色,显示 1
  • output == false:红色,显示 0

代码是

color: output ? Colors.green : Colors.red,
child: Text(output ? '1' : '0')

双重反馈设计价值:

  1. 视觉分层:颜色快速识别,数字精确表达
  2. 多感官反馈:兼顾视觉直观性和数值准确性
  3. 符合用户认知:绿色=成功/真,红色=失败/假,1/0=二进制值
  4. 无障碍友好:颜色+文字双重提示,适配色弱用户

这种“颜色 + 字符”的组合比只用文字更直观。
而且实现很轻,不需要额外组件。


9. AND / OR / XOR / NOT:把逻辑门语义对齐到“输入开关”

这个页面虽然是“逻辑门谜题”,但它的落点不是背概念,而是把概念落到交互上。

你在 UI 上做了三个区域:

  • 输入A
  • 输入B(有条件显示)
  • 输出

这三个区域一旦固定,逻辑门就只剩下“运算规则”。

9.1 AND

AND 的规则是:两个输入都为真,输出才为真。

对应代码

case 'AND': return inputA && inputB;

AND 门逻辑解析:

  1. 逻辑与运算符 && 严格匹配 AND 门语义
  2. 双输入校验,缺一不可
  3. 只有 inputA 和 inputB 都为 true 时输出才为 true

当用户把两个 Switch 都打开时,输出变绿并显示 1

交互体验设计:

  1. 开关操作即时反馈,符合用户操作直觉
  2. 输出状态与逻辑规则完全一致,无认知偏差
  3. 二进制显示(1/0)贴合逻辑门教学场景

9.2 OR

OR 的规则是:只要有一个输入为真,输出就为真。

case 'OR': return inputA || inputB;

OR 门逻辑解析:

  1. 逻辑或运算符 || 精准表达 OR 门“有一为真则真”的特性
  2. 单输入满足即可输出真,符合 OR 门语义
  3. 代码简洁,无冗余判断

9.3 NOT

NOT 的规则是:对输入取反。

case 'NOT': return !inputA;

NOT 门逻辑解析:

  1. 逻辑非运算符 ! 直接实现取反操作
  2. 仅依赖 inputA,符合单输入特性
  3. 代码无冗余,语义完全匹配

你在 UI 层同时做了“输入B隐藏”,让规则更明确。

UI 与逻辑对齐:

  1. 视觉层与逻辑层保持一致,减少用户理解成本
  2. 隐藏无关输入项,聚焦 NOT 门核心逻辑
  3. 操作流程简化,用户只需关注一个输入

9.4 XOR

XOR 的规则是:两个输入不同,输出为真。

case 'XOR': return inputA ^ inputB;

XOR 门逻辑解析:

  1. Dart 原生 ^ 运算符直接支持布尔异或,代码更简洁
  2. 严格实现“相同为假,不同为真”的 XOR 门规则
  3. 无需额外判断,一行代码完成计算

这里用 ^ 是 Dart 对布尔异或运算的直接支持。

语言特性利用:

  1. 充分利用 Dart 运算符特性,减少代码量
  2. 运算符语义与 XOR 门完全匹配,易读易理解
  3. 性能优于自定义判断逻辑

对用户来说,XOR 的体验是:

  • A=1,B=0 => 输出 1
  • A=0,B=1 => 输出 1
  • A=0,B=0 => 输出 0
  • A=1,B=1 => 输出 0

交互场景覆盖:

  1. 完整展示 XOR 门的四种输入组合
  2. 输出结果直观反映“异或”核心特性
  3. 帮助用户理解 XOR 门与 OR 门的区别

10. 为什么说这个页面“状态少但表达力强”

你这个页面的状态字段只有三个:

  • bool inputA
  • bool inputB
  • String selectedGate

但是它能表达的变化很多。

原因是:

  • UI 的变化大多来自 output(派生状态)
  • output 又完全由三个状态决定

状态设计原则:

  1. 最小状态集:仅保留核心可变状态,无冗余字段
  2. 派生状态计算:通过 getter 生成衍生数据,避免数据冗余
  3. 状态单一来源:所有 UI 状态都可追溯到三个核心字段
  4. 状态不可变:核心状态仅通过 setState 修改,可预测性强

在这种结构下,页面不容易出现“逻辑分叉”。
也就是说你不会出现

  • 某次切换门类型后 output 没更新
  • 某个 Switch 改了但输出没刷新

因为 output 根本不存储,只计算。

避免逻辑分叉的核心:

  1. 无状态存储冗余,减少数据不一致风险
  2. 计算逻辑纯函数化,输入确定则输出确定
  3. UI 渲染完全依赖状态,无隐藏逻辑

11. ChoiceChip 的 onSelected:为什么不用 selected 参数

ChoiceChip 的回调签名是 (bool selected)
很多人会写成

onSelected: (selected) {
  if (selected) {
    setState(() => selectedGate = gate);
  }
}

冗余写法问题:

  1. 增加不必要的条件判断,代码冗余
  2. 选中状态由 selectedGate 控制,无需额外判断
  3. 降低代码可读性,增加维护成本
onSelected: (selected) => setState(() => selectedGate = gate)

简化写法优势:

  1. 去掉冗余判断,代码更简洁
  2. 符合单选场景需求,始终设置选中项
  3. 减少分支逻辑,降低出错概率

这在当前结构下是合理的。

理由

  • 你展示的是“单选组”,不存在取消选择后变成“无选择”的需求
  • 点击任何 chip,目标就是把它设为当前 gate

因此直接赋值更简洁。


12. UI 布局上的一个真实选择:Row + Column,而不是复杂容器

你的布局非常朴素:

  • 外层 Column
  • 中间两行 Row
  • 每个输入输出区各自一个 Column

布局设计原则:

  1. 扁平化布局:嵌套层级少,性能更好
  2. 语义化布局:Row/Column 对应行列关系,易理解
  3. 组件化拆分:每个输入/输出区独立成 Column,易维护
  4. 无过度封装:简单布局无需自定义组件,降低复杂度

好处

  • 读代码就能想象界面结构
  • 不依赖额外组件
  • 对新人友好,后续扩展也更容易

页面的“重心”在交互上,不在复杂排版上。

布局优先级设计:

  1. 交互逻辑优先于视觉设计
  2. 功能完整性优先于布局复杂度
  3. 可读性优先于简洁性

13. ScreenUtil 的一致性:宽高都用 .w

输出圆形区域

width: 60.w,
height: 60.w,
borderRadius: BorderRadius.circular(30.r)

尺寸适配设计:

  1. 宽高均用 .w 单位,保证圆形比例一致
  2. borderRadius 用 .r 单位,适配圆角半径
  3. 60.w + 30.r 完美实现正圆形,无变形风险

这里有个细节:

  • 你让 width/height 都用 .w
  • 让圆形在不同尺寸下保持比例一致

如果写成 height: 60.h,在极端比例屏幕上可能会变成椭圆。
现在这样写更稳。

屏幕适配原则:

  1. 等比例元素使用同一维度单位(.w)
  2. 避免混合使用 .w/.h 导致比例失调
  3. 圆角半径与宽高匹配,保证形状完整性

同理,文字字号用了 .sp,避免字体在缩放后过大或过小。

文字适配设计:

  1. .sp 单位随屏幕尺寸和系统字体设置缩放
  2. 字号分层设计(16.sp/20.sp/24.sp),保持视觉层级
  3. 无固定像素值,适配所有设备

14. 输出区为什么要同时用颜色和数字

你现在的输出表达是

  • 颜色:绿/红
  • 文本:1/0

这样做有两点好处。

第一,颜色是快速感知。
用户瞟一眼就知道真假。

颜色反馈设计:

  1. 利用色彩心理学:绿色=正确/真,红色=错误/假
  2. 快速视觉识别,无需阅读文字
  3. 符合用户普遍认知,学习成本低

第二,数字是精确信息。
当用户想把它对应到“二进制/真值表”的概念时,1/0 更贴近。

数字反馈设计:

  1. 二进制表示,贴合逻辑门教学场景
  2. 精确表达状态,避免颜色歧义
  3. 与输入值显示格式一致,保持UI统一性

15. 这个页面如何扩展:加 NAND/NOR 只需要改两处

你现在的门类型列表是

['AND', 'OR', 'NOT', 'XOR']

扩展准备工作:

  1. 列表集中管理,扩展只需添加字符串
  2. 无硬编码逻辑,新增类型不影响现有代码
  3. 结构预留扩展空间,符合开闭原则

输出计算是 switch。

如果你要加 NAND,最直接的扩展路径就是:

  1. 列表加一个字符串
  2. switch 加一个 case

例如(这里只说明方向,不要求你现在改项目代码)

case 'NAND': return !(inputA && inputB);

NAND 门扩展示例:

  1. 基于 AND 门逻辑取反,语义清晰
  2. 代码简洁,与现有逻辑门写法一致
  3. 无需修改 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),
};

高级扩展方案:

  1. 规则与UI分离,关注点更清晰
  2. 函数式编程,每个规则都是独立函数
  3. 易于测试,可单独验证每个门的逻辑

但在当前规模下,switch 反而更直观。

规模适配原则:

  1. 小体量用简单结构(switch),易读易写
  2. 大体量用高级结构(Map+函数),易扩展易维护
  3. 按需选择,不过度设计

16. 一个小的交互建议:NOT 门切换时,输入B区域的留白

你现在的结构是

if (selectedGate != 'NOT') Column(...输入B...)

当前实现优势:

  1. 逻辑简单,条件判断直接
  2. 减少不必要的 UI 渲染,性能更好
  3. 语义明确,NOT 门仅显示必要输入

当切到 NOT 时,Row 少了一列。
这在当前页面里是可以接受的。

如果你想让视觉更稳定,有两种常见做法:

  • 继续保留一个占位(例如 SizedBox(width: xx) 或空 Column)
selectedGate != 'NOT' ? Column(...) : SizedBox(width: 120.w),

占位方案优势:

  1. 布局稳定,无跳动感
  2. 保持 Row 子节点数量一致,布局计算更稳定
  3. 占位宽度可精准控制,视觉更协调
  • 或者让输出区在 NOT 情况下居中
selectedGate == 'NOT' 
    ? Expanded(child: Center(child: outputColumn))
    : outputColumn,

居中方案优势:

  1. 视觉焦点更突出,聚焦输出结果
  2. 利用 Expanded 填充空间,布局更合理
  3. 符合 NOT 门单输入的视觉重心

这属于体验增强,不影响当前实现的正确性。


17. 小结:逻辑门谜题实现的关键点

  • 数据驱动的门列表['AND','OR','NOT','XOR']
  • 派生状态 getterbool get output
  • 必要的条件渲染:NOT 门隐藏输入B
  • 双重反馈:颜色 + 1/0 输出

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐