在这里插入图片描述

这一页在产品里的定位更像“思维热身”:

  • 核心设计目标:降低用户操作门槛,无需复杂交互即可理解核心概念
  • 视觉表达逻辑:通过极简的图形对照,替代文字说教
  • 训练核心目标:让用户下意识形成“换位思考、反向观察”的思维习惯

它的实现也非常干净:一个 StatelessWidget,用 Row + Column 组织左右两块图形,然后用圆角位置对调来表达镜像。

本文涉及文件

  • lib/feature_pages.dart:核心实现页,承载镜像识别的UI与布局
  • lib/app.dart:应用入口配置,关联路由与主题
  • lib/main.dart:程序启动入口,初始化依赖与页面路由

1. 入口在哪里:从模式识别列表进入

镜像识别属于 PatternRecognitionPage(模式识别)里的一个功能入口。
入口写法和其他特性页一致:点击卡片后 push 新页面。

这种“聚合+导航”的分层结构有明确的工程价值:

  1. 入口页职责单一:仅负责功能分类与路由跳转,不掺杂业务逻辑
  2. 子页聚焦核心:每个训练页只关注自身的展示/交互逻辑,降低耦合
  3. 扩展成本低:新增训练页只需在入口页加卡片,无需改动其他子页

核心入口代码示例:

ListTile(
  title: Text('镜像识别训练'),
  onTap: () => Navigator.push(
    context,
    MaterialPageRoute(builder: (_) => MirrorRecognitionPage()),
  ),
)
  • 路由跳转使用MaterialPageRoute,保持与App全局导航风格一致
  • 点击事件直接指向目标页,无中间态,符合“训练页快速访问”的产品设计

2. MirrorRecognitionPage

下面分段拆解项目 lib/feature_pages.dart 中的核心实现,每段代码配套详细设计思路:

2.1 页面类定义:选择StatelessWidget的核心逻辑

class MirrorRecognitionPage extends StatelessWidget {
  const MirrorRecognitionPage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('镜像识别')),
  • 类修饰符:使用const构造方法,减少Widget重建开销
  • 继承选择:无状态组件适配“纯展示型”页面,避免State冗余
  • 基础结构:Scaffold作为页面容器,提供标准的AppBar+Body结构
  • 标题设计:AppBar标题直接明确训练主题,无多余装饰,符合训练页简洁风格

2.2 页面边距与整体布局:统一视觉规范

      body: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [
            Text('镜像思维训练', 
              style: TextStyle(
                fontSize: 24.sp, 
                fontWeight: FontWeight.bold
              )
            ),
  • 边距设计:16.w的全局统一边距,适配不同屏幕尺寸(基于flutter_screenutil)
  • 布局容器:Column垂直排布,符合“标题-内容-提示”的阅读流
  • 标题样式:24.sp加粗字体,视觉层级优先,快速传递页面核心主题
  • 单位规范:全量使用.w/.h/.sp,保证多终端视觉一致性

2.3 间距控制:提升视觉呼吸感

            SizedBox(height: 24.h),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Column(
                  children: [
                    Text('原图', style: TextStyle(fontSize: 16.sp)),
  • 间距设计:24.h的垂直间距,分隔标题与核心内容区,避免视觉拥挤
  • 行布局:Row+spaceEvenly,让左右两组图形均匀分布,强化“对照”属性
  • 子列结构:每个图形块用Column包裹,实现“文字说明+图形”的垂直组合
  • 文字尺寸:16.sp的说明文字,层级低于标题,高于提示文字,主次分明

2.4 原图图形实现:核心视觉元素

                    Container(
                      width: 80.w,
                      height: 80.w,
                      decoration: BoxDecoration(
                        color: Colors.blue,
                        borderRadius: BorderRadius.only(
                          topLeft: Radius.circular(40.r),
                          bottomRight: Radius.circular(40.r),
                        ),
                      ),
                    ),
  • 尺寸设计:80.w正方形,兼顾辨识度与屏幕占比,适配手机/平板等终端
  • 背景色:纯蓝色填充,无渐变/纹理,避免视觉干扰,聚焦形状差异
  • 圆角逻辑:40.r圆角半径(边长的50%),形成强视觉特征的对角圆角
  • 圆角位置:左上+右下,构建“方向性”视觉标识,为镜像对比做铺垫

2.5 镜像图形实现:对称设计的核心

                  ],
                ),
                Column(
                  children: [
                    Text('镜像', style: TextStyle(fontSize: 16.sp)),
                    Container(
                      width: 80.w,
                      height: 80.w,
                      decoration: BoxDecoration(
                        color: Colors.blue,
  • 视觉一致性:与原图保持相同尺寸/颜色,仅修改圆角位置,强化“镜像”而非“新图形”
  • 文字标识:明确标注“镜像”,降低用户理解成本
  • 容器复用:完全复用Container结构,仅调整 borderRadius 参数,符合“最小变更表达核心差异”的设计原则

2.6 镜像圆角的反向设计:核心差异点

                        borderRadius: BorderRadius.only(
                          topRight: Radius.circular(40.r),
                          bottomLeft: Radius.circular(40.r),
                        ),
                      ),
                    ),
                  ],
                ),
              ],
            ),
  • 圆角反转:左上↔右上、右下↔左下,精准表达“水平镜像”的核心逻辑
  • 无其他修改:仅调整圆角位置,避免多余视觉元素干扰用户对“镜像”的理解
  • 布局闭合:Row包裹的左右Column完整闭合,布局层级清晰,无冗余嵌套

2.7 底部提示文案:强化训练核心

            SizedBox(height: 32.h),
            Text('镜像思维:从不同角度观察问题', 
              style: TextStyle(
                fontSize: 16.sp, 
                color: Colors.purple
              )
            ),
          ],
        ),
      ),
    );
  }
}
  • 间距设计:32.h的垂直间距,分隔图形区与提示区,视觉上形成“观察-总结”的节奏
  • 文案设计:直接点出训练核心,将视觉差异升维到思维方法
  • 颜色区分:紫色文字,与蓝色图形形成温和对比,吸引注意力但不刺眼
  • 布局闭合:Column/Row/Scaffold层级完整闭合,符合Flutter Widget树规范

3. 为什么用 StatelessWidget:这是一页“展示型训练”

你把页面定义为 StatelessWidget,原因可拆解为以下核心点:

  1. 状态维度:页面无用户输入(按钮点击/输入框/滑动等),无需维护state
  2. 数据维度:展示内容固定,无动态数据源(网络/本地存储/状态管理)
  3. 性能维度:StatelessWidget重建开销更低,适合纯展示场景
  4. 维护维度:减少State类的冗余代码,降低后期维护成本

反例警示:若强行使用StatefulWidget,会出现以下问题:

  • 多余的createState/setState方法,增加代码量
  • 无意义的状态生命周期,增加调试复杂度
  • 违背“最小功能匹配”的工程原则,降低代码可读性

4. 页面骨架:Scaffold + Padding + Column

这页的骨架是Flutter中“展示型页面”的典型范式,拆解如下:

4.1 Scaffold:页面基础容器

return Scaffold(
  appBar: AppBar(title: const Text('镜像识别')),
  body: Padding(...),
);
  • 职责:提供AppBar、Body、FloatingActionButton等标准页面组件
  • 适配性:自动适配系统状态栏/导航栏,无需手动处理屏幕适配
  • 扩展性:如需新增操作按钮(如“重置”“下一题”),可直接在Scaffold中添加

4.2 Padding:统一页边距

padding: EdgeInsets.all(16.w),
  • 设计原则:全局统一16.w边距,保证App内所有页面边距视觉一致
  • 适配逻辑:.w单位基于屏幕宽度适配,不同尺寸设备边距比例统一
  • 体验优化:避免内容贴边,提升阅读舒适度,符合移动端设计规范

4.3 Column:垂直内容组织

child: Column(
  children: [
    // 标题
    // 图形对照区
    // 提示文案
  ],
)
  • 布局逻辑:垂直方向线性排布,符合用户“从上到下”的阅读习惯
  • 灵活性:可通过mainAxisAlignment/crossAxisAlignment调整整体对齐方式
  • 局限性:内容超出屏幕时需配合SingleChildScrollView,当前页面内容固定无需滚动

5. 核心布局:Row 的两个 Column,左右对照

镜像识别的核心是“视觉对照”,Row+双Column的布局设计有以下优势:

5.1 Row的对齐策略

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [/* 左列:原图 | 右列:镜像 */],
)
  • 对齐方式:spaceEvenly让左右内容均匀分布,视觉上形成“对称对照”
  • 空间分配:自动平分Row的可用空间,无需手动计算宽度
  • 响应式:适配不同屏幕宽度,左右列始终保持等距,不会挤压/重叠

5.2 左右Column的结构一致性

// 左列:原图
Column(
  children: [Text('原图'), Container(/* 原图图形 */)],
)
// 右列:镜像
Column(
  children: [Text('镜像'), Container(/* 镜像图形 */)],
)
  • 结构复用:左右列完全相同的层级结构,仅修改文字和圆角参数
  • 视觉对称:保证用户注意力集中在“差异点(圆角)”,而非“结构差异”
  • 可维护性:后续修改样式时,只需统一调整Column结构,无需分别修改

5.3 布局层级的工程价值

  1. 扁平化:Row直接包裹两个Column,无多余嵌套,Widget树层级浅
  2. 可读性:代码结构与视觉结构完全一致,新人可快速理解布局逻辑
  3. 扩展性:如需新增“旋转对照”“缩放对照”,可直接在Row中新增Column

6. 用 BorderRadius.only 表达镜像:这是这页最巧的地方

没有使用图片/自定义绘制,而是用“单色方块+定向圆角”表达镜像,设计巧思拆解:

6.1 原图圆角的设计逻辑

borderRadius: BorderRadius.only(
  topLeft: Radius.circular(40.r),
  bottomRight: Radius.circular(40.r),
),
  • 对角圆角:选择左上+右下,形成“对角线”视觉特征,易识别
  • 圆角半径:40.r=80.w/2,形成半圆角,强化视觉特征
  • 无其他装饰:纯色块+圆角,避免纹理/渐变干扰核心差异

6.2 镜像圆角的反向设计

borderRadius: BorderRadius.only(
  topRight: Radius.circular(40.r),
  bottomLeft: Radius.circular(40.r),
),
  • 精准反转:仅交换左右方向的圆角位置,严格匹配“水平镜像”的物理逻辑
  • 最小变更:仅修改两个圆角的位置参数,其余属性完全复用,符合“DRY”原则
  • 直观易懂:即使无文字标注,用户也能通过圆角位置感知“镜像”关系

6.3 该设计的产品价值

  1. 开发成本:无需设计/切图,纯代码实现,降低跨团队协作成本
  2. 性能:纯Widget渲染,比图片/自定义绘制更高效,无内存占用问题
  3. 适配性:矢量级别的图形,缩放/适配不同屏幕无模糊问题
  4. 可扩展性:如需调整圆角大小/颜色,只需修改参数,无需重新设计

7. 为什么圆角半径是 40.r

方块尺寸为80.w×80.w,圆角半径40.r的设计逻辑:

7.1 尺寸比例的视觉效果

// 图形尺寸
width: 80.w,
height: 80.w,
// 圆角半径
Radius.circular(40.r),
  • 比例关系:圆角半径=边长的50%,形成“半圆角”,而非“轻微圆角”
  • 视觉特征:半圆角的辨识度远高于小圆角,用户可快速捕捉形状差异
  • 边界清晰:80.w的正方形+40.r的半圆角,形状轮廓明确,无模糊地带

7.2 训练场景的适配性

  1. 辨识度优先:训练页需要用户快速感知差异,40.r的圆角让差异最大化
  2. 屏幕适配:80.w的尺寸在手机端约占屏幕宽度的20%-30%,不会过大/过小
  3. 交互友好:图形尺寸足够大,即使是老年用户/小屏设备也能清晰识别

7.3 反例对比:若使用20.r圆角

  • 视觉特征弱化:圆角不明显,用户可能无法快速感知镜像差异
  • 训练效果降低:需要更多时间观察,违背“快速建立镜像思维”的设计目标
  • 一致性破坏:与80.w的尺寸比例不协调,视觉上显得“小气”

8. 为什么两边的颜色一样

两个图形均使用color: Colors.blue,设计决策的核心逻辑:

8.1 颜色选择的原则

decoration: BoxDecoration(
  color: Colors.blue, // 左右图形统一颜色
  borderRadius: ...,
),
  • 无干扰原则:统一颜色避免用户将注意力集中在“颜色差异”,而非“形状差异”
  • 品牌一致性:蓝色为App主色调,符合全局视觉规范
  • 视觉舒适度:蓝色为冷色调,长时间观察不易疲劳,适合训练场景

8.2 颜色差异化的弊端

  1. 注意力分散:若左右颜色不同,用户会先关注颜色,而非圆角位置
  2. 训练目标偏离:镜像识别的核心是“形状反转”,而非“颜色区分”
  3. 认知负荷增加:多维度差异会提升用户的认知成本,违背“轻量化训练”的定位

8.3 颜色的可扩展设计

若未来需强化提示,可保留颜色统一,仅在“交互反馈”时调整:

Container(
  color: userIsCorrect ? Colors.green : Colors.blue,
  ...
)
  • 反馈时机:仅在用户交互后调整颜色,不影响初始的“纯形状对照”
  • 情绪引导:绿色=正确、红色=错误,符合用户的认知习惯
  • 最小变更:仅修改颜色参数,保持布局/形状不变

9. 文案的位置:标题在上、提示在下

文案布局遵循“先告知、再展示、最后总结”的认知逻辑:

9.1 标题文案:明确训练主题

Text('镜像思维训练', 
  style: TextStyle(
    fontSize: 24.sp, 
    fontWeight: FontWeight.bold
  )
)
  • 位置:页面顶部,第一眼可见,快速传递页面核心
  • 样式:24.sp加粗,视觉层级最高,与其他文案形成明显区分
  • 内容:简洁直接,无冗余修饰,符合训练页“高效传递信息”的目标

9.2 说明文案:图形标注

Text('原图', style: TextStyle(fontSize: 16.sp)),
Text('镜像', style: TextStyle(fontSize: 16.sp)),
  • 位置:图形正上方,与图形一一对应,视觉关联紧密
  • 样式:16.sp常规字体,层级低于标题,高于底部提示
  • 内容:单字标注,无多余文字,降低阅读成本

9.3 提示文案:升华训练意义

Text('镜像思维:从不同角度观察问题', 
  style: TextStyle(
    fontSize: 16.sp, 
    color: Colors.purple
  )
)
  • 位置:页面底部,图形对照区下方,用户观察完图形后自然阅读
  • 样式:紫色文字,与蓝色图形形成温和对比,吸引注意力但不刺眼
  • 内容:从“图形镜像”升维到“思维镜像”,点明训练的核心价值

9.4 文案布局的阅读节奏

  1. 顶部标题:建立认知预期(“这是镜像思维训练”)
  2. 中间图形+标注:提供视觉素材(“观察原图和镜像的差异”)
  3. 底部提示:总结核心价值(“镜像思维是从不同角度看问题”)

10. 这页如何扩展成更像“训练”

当前页面为纯展示型,扩展为“用户参与型训练”的两种核心方向:

10.1 方向1:用户判断型训练

核心思路:随机生成图形,让用户判断“哪个是镜像”,需升级为StatefulWidget:

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

  
  State<MirrorRecognitionPage> createState() => _MirrorRecognitionPageState();
}

class _MirrorRecognitionPageState extends State<MirrorRecognitionPage> {
  final Map<String, double> _originalCorners = {
    'topLeft': 40.r,
    'bottomRight': 40.r,
  };
  int? _userAnswer;
  int _correctAnswer = 0;
  • 状态管理:新增_originalCorners存储随机图形配置,_userAnswer记录用户选择
  • 随机逻辑:初始化时随机生成正确答案索引,增加训练的随机性
  • 组件升级:从Stateless→Stateful,适配交互状态的维护
  Map<String, double> _getDistractorCorners() {
    return {
      'topLeft': 40.r,
      'bottomLeft': 40.r,
    };
  }

  void _onAnswerSelected(int index) {
    setState(() {
      _userAnswer = index;
    });
  }
  • 干扰项生成:通过修改部分圆角位置,制造“似是而非”的错误选项
  • 状态更新:用户选择后调用setState,更新UI展示结果
  • 交互闭环:从“展示”到“选择”再到“反馈”,形成完整训练闭环

10.2 方向2:多样本题库化

核心思路:抽离图形配置为数据,通过列表渲染多组对照样本:

final List<Map<String, Map<String, double>>> _mirrorQuestions = [
  {
    'original': {'topLeft': 40.r, 'bottomRight': 40.r},
    'mirror': {'topRight': 40.r, 'bottomLeft': 40.r},
  },
  {
    'original': {'topRight': 30.r, 'bottomLeft': 30.r},
    'mirror': {'topLeft': 30.r, 'bottomRight': 30.r},
  },
  {
    'original': {'topLeft': 50.r, 'bottomLeft': 50.r},
    'mirror': {'topRight': 50.r, 'bottomRight': 50.r},
  },
];
  • 数据结构:统一的图形配置格式,包含原图和镜像的圆角参数
  • 题库扩展:新增样本只需添加数据,无需修改UI逻辑
  • 渲染逻辑:通过ListView.builder遍历题库,动态生成对照样本
ListView.builder(
  itemCount: _mirrorQuestions.length,
  itemBuilder: (context, index) {
    final question = _mirrorQuestions[index];
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        _buildShape(question['original']!),
        _buildShape(question['mirror']!),
      ],
    );
  },
)
Widget _buildShape(Map<String, double> corners) {
  return Container(
    width: 80.w,
    height: 80.w,
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(corners['topLeft'] ?? 0),
        topRight: Radius.circular(corners['topRight'] ?? 0),
        bottomLeft: Radius.circular(corners['bottomLeft'] ?? 0),
        bottomRight: Radius.circular(corners['bottomRight'] ?? 0),
      ),
    ),
  );
}
  • 复用性:_buildShape方法统一构建图形,避免重复代码
  • 动态渲染:ListView.builder按需加载,适配大量题库数据
  • 扩展性:可新增圆角半径/颜色等参数,支持更多样的图形配置

11. 小结:镜像识别实现的关键点

  • 页面足够轻:用 StatelessWidget 直接展示,无冗余状态管理
  • 对照结构清晰:Row 左右两列对比,spaceEvenly保证均匀分布
  • 镜像表达成本低:用 BorderRadius.only 对调角位,无需图片/绘制
  • 视觉聚焦正确:同色块避免干扰,把注意力留给形状差异
  • 布局规范统一:全量使用flutter_screenutil的.w/.h/.sp,保证多端适配
  • 文案节奏合理:标题-图形-提示的层级,符合用户认知习惯

12. 这页为什么“看起来简单”但很有价值

镜像识别页没有按钮、没有输入框、没有复杂状态,但核心价值体现在:

  • 训练路径起点:作为模式识别的基础训练,为后续复杂训练铺路
  • 体验一致性:与其他训练页的设计风格统一,提升产品整体感
  • 轻量化设计:符合“思维热身”的定位,不占用用户过多时间

13. 为什么用 Column 而不是 ListView

当前页面选择Column而非ListView的核心原因:

13.1 内容特性适配

Column(
  children: [
    Text('镜像思维训练'), 
    SizedBox(height: 24.h), 
    Row(/* 图形对照区 */), 
    SizedBox(height: 32.h), 
    Text('镜像思维:从不同角度观察问题'), 
  ],
)
  • 内容高度固定:所有元素高度均可预测,无动态内容/长文本
  • 无需滚动:整体高度远低于屏幕高度,Column可完全展示
  • 视觉完整性:所有内容在一屏内展示,用户可同时看到标题/图形/提示

13.2 ListView的副作用

  1. 视觉割裂:滚动可能导致图形对照区离开视野,破坏“对照”的核心逻辑
  2. 交互冗余:用户无需滚动即可看完内容,ListView的滚动功能无意义
  3. 性能损耗:ListView的复用逻辑对固定短内容来说是性能浪费

13.3 特殊场景的折中方案

若未来内容扩展,可使用Column+SingleChildScrollView的组合:

SingleChildScrollView(
  child: Column(/* 扩展后的内容 */),
)
  • 保留Column的布局逻辑,新增滚动能力
  • 避免ListView的复用逻辑,降低复杂度
  • 保证内容超出屏幕时的可访问性

14. 这页的可维护性:你已经避免了重复代码吗

当前页面左右两块Column存在重复代码,不同规模下的优化策略:

14.1 当前规模(无优化):直接编写重复代码

Column(
  children: [
    Text('原图', style: TextStyle(fontSize: 16.sp)),
    Container(/* 原图图形 */),
  ],
),
Column(
  children: [
    Text('镜像', style: TextStyle(fontSize: 16.sp)),
    Container(/* 镜像图形 */),
  ],
),
  • 合理性:页面元素少,重复代码量小,抽方法的收益低于成本
  • 可读性:代码平铺直叙,新人可直接理解左右列的差异
  • 维护成本:修改样式时需同时调整两处,但频率低

14.2 抽离_buildShapeColumn方法

Widget _buildShapeColumn(String title, Map<String, double> corners) {
  return Column(
    children: [
      Text(title, style: TextStyle(fontSize: 16.sp)),
      Container(
        width: 80.w,
        height: 80.w,
        decoration: BoxDecoration(
          color: Colors.blue,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(corners['topLeft'] ?? 0),
            topRight: Radius.circular(corners['topRight'] ?? 0),
            bottomLeft: Radius.circular(corners['bottomLeft'] ?? 0),
            bottomRight: Radius.circular(corners['bottomRight'] ?? 0),
          ),
        ),
      ),
    ],
  );
}
  • 复用性:通过参数传递标题和圆角配置,适配原图/镜像/扩展图形
  • 可维护性:修改样式只需调整方法内部,无需修改调用处
  • 扩展性:新增图形类型(如旋转图形),只需新增参数/逻辑

14.3 调用优化后的代码

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    _buildShapeColumn('原图', {'topLeft': 40.r, 'bottomRight': 40.r}),
    _buildShapeColumn('镜像', {'topRight': 40.r, 'bottomLeft': 40.r}),
  ],
)
  • 代码量减少:从重复的20+行缩减为2行调用
  • 逻辑清晰:调用处仅关注“参数差异”,无需关注布局细节
  • 错误减少:避免修改一处忘改另一处的问题

15. 一条很实用的工程细节:16.w 的 padding

全局统一使用EdgeInsets.all(16.w)的工程价值:

15.1 单位规范的重要性

padding: EdgeInsets.all(16.w),
padding: EdgeInsets.all(16),
  • 适配性:.w单位基于屏幕宽度的比例,不同设备边距视觉一致
  • 一致性:全局统一的边距值,保证所有页面的视觉风格统一
  • 可维护性:修改全局边距时,只需调整基础配置,无需逐页修改

15.2 16.w的设计依据

  1. 移动端设计规范:16dp是移动端最常用的边距值,符合用户阅读习惯
  2. 视觉舒适度:16.w的边距让内容与屏幕边缘保持足够距离,不拥挤
  3. 交互友好:避免内容贴边,提升点击/阅读的舒适度

15.3 特殊场景的边距调整

若需局部调整边距,建议基于16.w做增量修改:

SizedBox(height: 16.w * 1.5), 
SizedBox(height: 16.w * 2),  
  • 比例调整:保持与基础边距的比例关系,视觉更协调
  • 可维护性:修改基础边距时,相关间距自动同步调整
  • 一致性:避免出现无规律的数值(如23.h/31.h),降低调试成本

16. 如果用户看不出镜像差异,怎么增强提示

在不改变核心布局的前提下,新增视觉提示元素:

16.1 箭头标注提示

Widget _buildShapeWithArrow(String title, Map<String, double> corners) {
  return Column(
    children: [
      Text(title, style: TextStyle(fontSize: 16.sp)),
      Stack(
        alignment: Alignment.topLeft,
        children: [
          Container(/* 原有图形 */),
          // 箭头标注
          if (title == '原图')
            Icon(Icons.arrow_upward, size: 20.sp, color: Colors.white),
          if (title == '镜像')
            Icon(Icons.arrow_upward, size: 20.sp, color: Colors.white)
              ..transform = Matrix4.identity()..translate(60.w, 0),
        ],
      ),
    ],
  );
}
  • 视觉强化:通过箭头明确指向圆角位置,用户可快速定位差异点
  • 不破坏布局:使用Stack叠加箭头,不改变原有图形的尺寸/位置
  • 适配性:箭头尺寸使用.sp单位,与图形适配不同屏幕

16.2 动态提示

GestureDetector(
  onTap: () {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('镜像图形的圆角位置与原图左右相反')),
    );
  },
  child: Container(/* 镜像图形 */),
)
  • 交互提示:用户点击图形时触发提示,主动探索时获得反馈
  • 轻量化:使用SnackBar提示,不占用页面空间,自动消失
  • 友好性:避免强制弹窗,尊重用户的阅读节奏

16.3 颜色渐变提示

decoration: BoxDecoration(
  gradient: LinearGradient(
    begin: title == '原图' ? Alignment.topLeft : Alignment.topRight,
    end: title == '原图' ? Alignment.bottomRight : Alignment.bottomLeft,
    colors: [Colors.blue, Colors.blue.withOpacity(0.7)],
  ),
  borderRadius: ...,
),
  • 渐变方向:与圆角位置一致,强化“方向性”视觉特征
  • 不改变颜色:保持蓝色系,避免引入新颜色干扰
  • 视觉层次:渐变让形状差异更明显,同时保持简洁

17. 把它做成训练题:最小的交互闭环

最小化改造为“选择-反馈”的交互闭环,新增核心代码:

17.1 状态管理

class _MirrorRecognitionPageState extends State<MirrorRecognitionPage> {
  final List<Map<String, dynamic>> _questions = [
    {
      'original': {'topLeft': 40.r, 'bottomRight': 40.r},
      'options': [
        {'topRight': 40.r, 'bottomLeft': 40.r}, 
        {'topLeft': 40.r, 'bottomLeft': 40.r},  
        {'topRight': 40.r, 'bottomRight': 40.r}, 
      ],
      'correctIndex': 0,
    }
  ];
  int _currentQuestion = 0;
  int? _selectedOption;
  bool _showResult = false;
  • 题目配置:包含原图、选项、正确答案索引,数据驱动题目逻辑
  • 状态变量:管理当前题目、用户选择、结果展示,覆盖核心交互逻辑

17.2 选项渲染

Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: List.generate(3, (index) {
    final option = _questions[_currentQuestion]['options'][index];
    return GestureDetector(
      onTap: () => _onSelectOption(index),
      child: Container(
        width: 60.w,
        height: 60.w,
        decoration: BoxDecoration(
          color: _selectedOption == index ? Colors.grey : Colors.blue,
          borderRadius: _getBorderRadius(option),
          border: _showResult && index == _questions[_currentQuestion]['correctIndex']
              ? Border.all(color: Colors.green, width: 2.w)
              : null,
        ),
      ),
    );
  }),
)
  • 选项样式:选中状态变灰色,正确答案显示绿色边框,反馈直观
  • 尺寸调整:选项图形略小于原图(60.w),突出原图的核心地位
  • 交互逻辑:点击选项触发选择,更新状态并展示结果

17.3 结果反馈

void _onSelectOption(int index) {
  setState(() {
    _selectedOption = index;
    _showResult = true;
  });
  final isCorrect = index == _questions[_currentQuestion]['correctIndex'];
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(isCorrect ? '回答正确!' : '再试试~'),
      backgroundColor: isCorrect ? Colors.green : Colors.red,
    ),
  );
}

ElevatedButton(
  onPressed: _showResult 
      ? () => setState(() {
            _currentQuestion = (_currentQuestion + 1) % _questions.length;
            _selectedOption = null;
            _showResult = false;
          })
      : null,
  child: Text('下一题'),
)
  • 即时反馈:选择后通过SnackBar提示正确/错误,情绪引导
  • 下一题逻辑:结果展示后激活按钮,重置状态进入下一题
  • 循环题库:通过取模实现题库循环,无需额外逻辑

18. 从 UI 角度看:为什么 Container 80.w 是合适的

80.w×80.w的Container尺寸设计,符合移动端训练页的UI设计原则:

18.1 尺寸与屏幕的比例

width: 80.w,
height: 80.w,
  • 手机端适配:以375w(iPhone 14)为例,80.w约占屏幕宽度的21%,左右并排后占比约42%,预留足够留白
  • 平板端适配:比例不变,图形尺寸随屏幕宽度等比放大,保持视觉一致性
  • 触控友好:80.w的尺寸足够大,若后续添加点击交互,触控区域充足

18.2 尺寸与圆角的配合

  • 80.w的边长 + 40.r的圆角 = 半圆角,视觉特征最大化
  • 尺寸过小(如40.w):圆角不明显,差异难以识别
  • 尺寸过大(如120.w):左右并排挤压,留白不足,视觉拥挤

18.3 尺寸与布局的协调

  • Row+spaceEvenly布局下,80.w的两个图形之间的间距约为屏幕宽度的16%,视觉呼吸感充足
  • 与文字的比例:图形尺寸是标题文字(24.sp)的3倍多,视觉上以图形为核心,符合“视觉训练”的定位

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

Logo

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

更多推荐