在这里插入图片描述

这篇文章基于你当前仓库 qwer 的真实代码来写,聚焦“反证法”训练页。

反证法是逆向思维里最经典、也最“可复用”的方法之一。
它训练的是一种非常实用的推理路径:

  • 想证明某个命题成立
  • 不直接正面推
  • 而是假设它不成立
  • 推导出矛盾
  • 从而证明原命题成立

你在项目里用一个很直观的例题“证明 √2 是无理数”来讲这个方法。

本文涉及文件

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

1. 入口在哪里:从逻辑谜题列表进入

反证法属于 LogicPuzzlesPage(逻辑谜题)里的一个训练项。
入口页通过卡片 push 到 ProofByContradictionPage,核心跳转逻辑如下:

// 逻辑谜题列表页的跳转代码片段
ListTile(
  title: Text('反证法训练'),
  onTap: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => ProofByContradictionPage(),
      ),
    );
  },
)
  • 采用 ListTile 作为列表项,符合 Flutter 原生交互习惯
  • onTap 回调触发页面跳转,是最基础的路由跳转方式
  • MaterialPageRoute 保证跳转时有原生的页面过渡动画
  • 跳转目标直接指向 ProofByContradictionPage,路由关系清晰

2. ProofByContradictionPage 的核心结构拆解

2.1 页面类定义与基础结构

首先是页面的核心类定义,作为纯展示型页面,选择 StatelessWidget 是最优解:

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('反证法')),
      body: Padding(
        padding: EdgeInsets.all(16.w),
        // 后续内容嵌套在此处
      ),
    );
  }
}
  • 类修饰为 const 构造方法,提升性能(不可变Widget可被缓存)
  • Scaffold 是 Flutter 页面的标准容器,包含导航栏和内容区
  • AppBar 设置明确的页面标题,保证导航层级清晰
  • 全局 Padding 设为 16.w,采用自适应单位适配不同屏幕尺寸
  • 16.w 是基于屏幕宽度的自适应单位,比固定像素更适配鸿蒙多终端

2.2 可滚动内容容器:ListView

页面主体内容采用 ListView 承载,解决小屏溢出问题:

child: ListView(
  children: [
    Text('反证法训练', 
      style: TextStyle(
        fontSize: 24.sp,
        fontWeight: FontWeight.bold
      )
    ),
    SizedBox(height: 24.h),
    // 证明卡片嵌套在此处
  ],
)
  • ListView 天然支持垂直滚动,无需额外封装 SingleChildScrollView
  • 标题文字使用 24.sp 加粗,突出页面核心主题
  • SizedBox(height: 24.h) 作为垂直间距,24.h 适配屏幕高度
  • 间距值选择遵循“8的倍数”设计原则,视觉节奏更统一
  • ListView 默认无固定高度,内容自适应,性能更优

2.3 证明步骤卡片封装

核心证明步骤放在 Card 组件中,提升视觉层次感:

Card(
  child: Padding(
    padding: EdgeInsets.all(16.w),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text('例题:证明√2是无理数', 
          style: TextStyle(
            fontSize: 18.sp,
            fontWeight: FontWeight.bold
          )
        ),
        SizedBox(height: 16.h),
        // 证明步骤文本在此处依次排列
      ],
    ),
  ),
)
  • Card 组件提供天然的阴影和圆角,区分内容区域
  • 卡片内部 16.w 内边距,避免文字贴边,提升阅读舒适度
  • Column 垂直排列证明步骤,符合线性阅读逻辑
  • crossAxisAlignment: CrossAxisAlignment.start 让文本左对齐,符合中文阅读习惯
  • 例题标题 18.sp 加粗,层级低于页面标题但高于步骤文本

2.4 反证法第一步:核心假设

反证法的关键第一步是“反向假设”,代码实现如下:

Text('1. 假设√2是有理数', 
  style: TextStyle(
    fontSize: 16.sp,
    height: 1.5 // 行高提升阅读体验
  )
),
SizedBox(height: 8.h),
  • 第一步明确写出“假设”,是反证法的核心动作
  • 字体大小 16.sp 适配正文阅读,行高 1.5 避免文字拥挤
  • 8.h 垂直间距,让步骤间有呼吸感,避免视觉疲劳
  • 假设命题必须与最终要证明的命题完全相反,这是反证法的逻辑基础
  • 此处的假设是后续所有推导的前提,必须清晰明确

2.5 第二步:有理数的分数表达

基于假设,将√2表示为最简分数形式:

Text('2. 则√2 = p/q (p,q互质)', 
  style: TextStyle(fontSize: 16.sp)
),
SizedBox(height: 8.h),
  • 有理数的定义就是“可以表示为两个整数的比值”
  • 特别标注 p,q互质,是为后续制造矛盾埋下伏笔
  • 最简分数形式是有理数的“最强条件”,让后续矛盾更有说服力
  • 互质意味着 p 和 q 没有除1以外的公因数,这是关键约束条件
  • 这一步是从“假设”到“推导”的过渡,逻辑衔接必须严谨

2.6 第三步到第五步:代数推导

通过等式变换推导 p 和 q 的偶性,核心代码片段:

Text('3. 2 = p²/q² → p² = 2q²', style: TextStyle(fontSize: 16.sp)),
SizedBox(height: 8.h),
Text('4. p²是偶数,所以p是偶数', style: TextStyle(fontSize: 16.sp)),
SizedBox(height: 8.h),
Text('5. 设p=2k,则4k²=2q² → q²=2k²', style: TextStyle(fontSize: 16.sp)),
  • 第三步是等式两边同乘 q² 的基本代数变换,逻辑无漏洞
  • 第四步利用“平方数为偶数则原数为偶数”的数论定理
  • 第五步代入替换,继续推导 q² 的偶性,层层递进
  • 每一步推导都基于前一步的结论,形成完整的逻辑链
  • 步骤间的 8.h 间距保持视觉一致性,阅读节奏更稳定

2.7 第六步:矛盾点暴露

推导出与前提矛盾的结论,这是反证法的核心转折点:

Text('6. q也是偶数,与p,q互质矛盾', 
  style: TextStyle(
    fontSize: 16.sp,
    fontWeight: FontWeight.w500 // 半粗体强调矛盾点
  )
),
SizedBox(height: 8.h),
  • 明确指出“q是偶数”与“p,q互质”的矛盾,这是反证法的关键
  • 使用 FontWeight.w500 半粗体,突出矛盾点,吸引用户注意
  • 矛盾点必须是“无法同时成立”的两个条件,否则反证法不成立
  • 此处的矛盾是“最强矛盾”:互质的两个数不可能都是偶数
  • 这一步是从“推导”到“结论”的关键桥梁

2.8 第七步:结论落地

最终得出原命题成立的结论,代码实现:

Text('7. 因此假设错误,√2是无理数', 
  style: TextStyle(
    fontSize: 16.sp,
    color: Colors.green,
    fontWeight: FontWeight.bold
  )
),
  • 绿色文字直观传达“结论正确”的视觉信号
  • 明确否定初始假设,回归原命题的证明
  • 加粗字体强化结论,让用户快速捕捉核心信息
  • 颜色选择符合用户认知习惯:绿色=正确/结论,黑色=推导过程
  • 这一步是反证法的收尾,必须清晰、明确、无歧义

3. 为什么用 StatelessWidget:这是“阅读型证明流程”

你把页面写成 StatelessWidget,因为它的定位是:

  • 展示一段固定的证明流程,无动态数据变化
    // 无状态组件的核心特征:无state,无setState
    class ProofByContradictionPage extends StatelessWidget {
      const ProofByContradictionPage({super.key});
    
      
      Widget build(BuildContext context) {
        // 仅返回固定UI,无业务逻辑
        return Scaffold(...);
      }
    }
    
  • 用户主要是阅读和理解,无需交互状态维护
    • 无输入框、无按钮、无滑动选择等交互元素
    • 页面生命周期内,UI结构和内容完全固定
  • 这类页面与“推理故事”一样,属于概念与方法论补给
    • 重点在于信息传递的准确性,而非交互体验
    • 无状态组件的性能更优,无需重建状态

4. 为什么外层用 ListView:长文本必须可滚动

证明流程是一串步骤文本,在小屏设备上很容易超过一屏高度。

你采用:

child: ListView(
  // 物理滚动特性,适配鸿蒙系统
  physics: ClampingScrollPhysics(),
  children: [
    // 标题、卡片等内容
  ]
)
  • ClampingScrollPhysics() 适配鸿蒙系统的滚动物理特性
  • ListView 相比 Column + SingleChildScrollView 有以下优势:
    1. 更简洁的代码结构,无需额外嵌套
    2. 天然支持按需加载,性能更优
    3. 滚动行为更符合用户对“长文本阅读”的预期
    4. 自动处理边界回弹,体验更原生
  • 能保证内容始终可读,不会出现溢出报错
    • 适配手机、平板、智慧屏等鸿蒙多终端尺寸
    • 即使未来增加更多证明步骤,也无需修改布局结构

5. 证明结构:标题 + 一张卡片承载步骤

页面结构很清楚:

  • 标题:反证法训练

    Text('反证法训练', 
      style: TextStyle(
        fontSize: 24.sp,
        fontWeight: FontWeight.bold,
        letterSpacing: 0.5 // 字间距优化
      )
    ),
    
    • 字间距 0.5 提升标题可读性,避免文字拥挤
    • 24.sp 字号在各种屏幕上都能保证标题的视觉优先级
    • 加粗处理让标题在页面中一眼可识别
  • Card:例题与 7 步推导

    Card(
      elevation: 2.0, // 阴影深度,提升层次感
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(8.w), // 圆角适配
      ),
      child: Padding(...),
    )
    
    • elevation: 2.0 轻微阴影,区分卡片与背景
    • 圆角 8.w 适配屏幕宽度,视觉更柔和
    • 卡片的视觉边界让证明步骤形成独立的阅读单元

Card 的价值在于:

  1. 把证明步骤限定在一个可视边界里,避免内容分散
  2. 阅读体验更像“看一张证明卡片”,符合记忆习惯
  3. 卡片的阴影和圆角提升页面的视觉质感
  4. 与页面其他元素形成清晰的视觉层次,信息分区明确
  5. 适配鸿蒙系统的设计规范,视觉风格统一

6. 反证法的关键动作:第 1 步“假设”

你在第 1 步写:

Text('1. 假设√2是有理数', 
  style: TextStyle(
    fontSize: 16.sp,
    color: Colors.blueAccent // 假设用蓝色标注
  )
),
  • 新增蓝色标注,直观区分“假设”与“推导”步骤
  • 这就是反证法最核心的动作:
    1. 不是直接证明 √2 无理,而是先假设它有理
    2. 假设必须是原命题的“否命题”,逻辑上完全对立
    3. 假设是后续所有推导的基础,必须清晰标注
    4. 假设的合理性直接决定反证法的有效性
  • 后续所有推导都建立在这个假设之上:
    • 每一步推导都不能脱离这个假设前提
    • 推导过程中不能引入新的未证明的假设
    • 所有代数变换都必须符合数学规则

7. “互质”是为了制造矛盾

第 2 步:

Text('2. 则√2 = p/q (p,q互质)', 
  style: TextStyle(
    fontSize: 16.sp,
    fontStyle: FontStyle.italic // 关键条件斜体强调
  )
),
  • 新增斜体样式,强调“互质”这个关键约束条件
  • 这一句的意义是:
    1. 如果 √2 是有理数,就能写成最简分数 p/q
      • 有理数的定义就是可以表示为两个整数的比值
      • 最简分数是有理数的标准表达形式
    2. 最简就意味着 p 与 q 互质
      • 互质是分数最简的充要条件
      • 这是数论中的基本定理,无需额外证明

反证法的技巧之一就是:

  1. 你先把假设推到“最强条件”
    • 最简分数是有理数的最强表达形式
    • 条件越强,后续推出的矛盾越有说服力
  2. 然后在最强条件上推出矛盾
    • 从“最简分数”推出“非最简”,矛盾更彻底
    • 避免“弱条件推导”导致的逻辑漏洞

8. 第 3~6 步:从偶性推出 p、q 同为偶数

你后面步骤的核心链条是:

// 偶性推导核心步骤封装
Column(
  children: [
    Text('3. 2 = p²/q² → p² = 2q²', style: TextStyle(fontSize: 16.sp)),
    SizedBox(height: 8.h),
    Text('4. p²是偶数,所以p是偶数', style: TextStyle(fontSize: 16.sp)),
    SizedBox(height: 8.h),
    Text('5. 设p=2k,则4k²=2q² → q²=2k²', style: TextStyle(fontSize: 16.sp)),
    SizedBox(height: 8.h),
    Text('6. q也是偶数,与p,q互质矛盾', style: TextStyle(fontSize: 16.sp)),
  ],
)
  • 用 Column 封装推导步骤,保持结构统一
  • 核心逻辑链条拆解:
    1. p² = 2q² ⇒ p² 是偶数 ⇒ p 是偶数
      • 偶数的平方是偶数,奇数的平方是奇数
      • 这是数论中的基本性质,无需额外证明
    2. p 是偶数 ⇒ p=2k ⇒ q² 也是偶数 ⇒ q 也是偶数
      • 代入替换后,q² 同样满足偶数条件
      • 同理可证 q 也是偶数

一旦 p、q 都是偶数,就意味着:

  1. p 与 q 有公因子 2
    • 偶数的定义就是能被 2 整除
    • 公因子 2 的存在直接否定了“互质”条件
  2. 这与“互质”矛盾
    • 互质的定义是“没有除1以外的公因子”
    • 矛盾点清晰、明确、无歧义

所以你的第 6 步写:

Text('6. q也是偶数,与p,q互质矛盾', style: TextStyle(fontSize: 16.sp)),

把矛盾点明确写出来,这是反证法的核心转折点。


9. 第 7 步用绿色强调:把结论落地

你最后一步写成绿色:

Text('7. 因此假设错误,√2是无理数', 
  style: TextStyle(
    fontSize: 16.sp, 
    color: Colors.green,
    fontSize: 16.sp,
    fontWeight: FontWeight.bold,
    letterSpacing: 0.3 // 字间距优化
  )
),
  • 新增字间距优化,提升结论可读性
  • 这让用户在阅读时快速捕捉到:
    1. 矛盾来自哪里
      • 明确指向“p,q同为偶数”与“互质”的矛盾
      • 矛盾点是反证法的核心依据
    2. 结论是什么
      • 明确否定初始假设,证明原命题成立
      • 结论必须与初始假设完全相反

这种视觉强调对“方法论训练”很重要:

  1. 因为用户往往需要先抓住整体框架,再回头看细节
    • 绿色结论能快速帮用户建立“假设→推导→矛盾→结论”的框架
  2. 颜色编码符合认知习惯
    • 蓝色(假设)→ 黑色(推导)→ 红色(矛盾)→ 绿色(结论)
  3. 强化记忆点,帮助用户理解反证法的完整流程

10. 如何把它升级成可训练交互

你当前是阅读型页面。如果要升级成“训练型”,最自然的方式是:

10.1 步骤分步展示

// 分步展示的核心逻辑
class InteractiveProofPage extends StatefulWidget {
  
  _InteractiveProofPageState createState() => _InteractiveProofPageState();
}

class _InteractiveProofPageState extends State<InteractiveProofPage> {
  int currentStep = 0; // 当前显示的步骤

  // 步骤列表
  final List<String> steps = [
    '1. 假设√2是有理数',
    '2. 则√2 = p/q (p,q互质)',
    // 其他步骤...
  ];

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text(steps[currentStep]),
        ElevatedButton(
          onPressed: () => setState(() => currentStep++),
          child: Text('下一步'),
        ),
      ],
    );
  }
}
  • 改为 StatefulWidget 维护当前步骤状态
  • 点击“下一步”按钮展示下一个步骤
  • 核心优势:用户需要主动触发步骤切换,加深理解

10.2 步骤选择题

// 步骤选择题示例
Column(
  children: [
    Text('第3步:从√2 = p/q 可以推导出什么?'),
    // 选项按钮
    ElevatedButton(
      onPressed: () => checkAnswer('p² = 2q²'),
      child: Text('p² = 2q²'),
    ),
    ElevatedButton(
      onPressed: () => checkAnswer('p = 2q'),
      child: Text('p = 2q'),
    ),
  ],
)

// 答案验证函数
void checkAnswer(String answer) {
  if (answer == 'p² = 2q²') {
    // 正确逻辑
  } else {
    // 错误提示
  }
}
  • 把证明步骤拆成选择题,用户需要选择正确的推导结果
  • 答案验证函数可以给出即时反馈
  • 核心优势:用户主动思考推导过程,而非被动阅读

10.3 关键步骤填空

// 填空交互示例
TextField(
  hintText: '请输入第1步的假设:√2是____',
  onSubmitted: (value) {
    if (value == '有理数') {
      // 正确反馈
    }
  },
)
  • 关键步骤设置填空,用户需要输入正确内容
  • 提交后验证答案,给出反馈
  • 核心优势:强化关键步骤的记忆,理解更深刻

例如:

  • 第 1 步假设:用户填写“有理数”
  • 第 2 步写出 p/q:用户选择“互质”选项
  • 第 3 步推到 p² = 2q²:用户选择正确的代数变换

这样用户会真的走一遍反证法流程,而不只是阅读。


11. 小结:反证法实现的关键点

  • 阅读型页面:StatelessWidget + ListView
    // 阅读型页面核心结构总结
    class ProofByContradictionPage extends StatelessWidget {
      const ProofByContradictionPage({super.key});
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('反证法')),
          body: Padding(
            padding: EdgeInsets.all(16.w),
            child: ListView(children: [...]), // 可滚动内容
          ),
        );
      }
    }
    
  • 信息组织清晰:标题 + 卡片承载步骤
    • 标题层级分明:24.sp(页面)→ 18.sp(例题)→ 16.sp(步骤)
    • 卡片封装核心内容,视觉边界清晰
  • 关键动作明确:从“假设相反命题”开始
    • 第一步明确标注假设,逻辑起点清晰
    • 假设必须是原命题的否命题
  • 矛盾点落地:互质 vs 同为偶数
    • 矛盾点明确、无歧义
    • 矛盾是“最强矛盾”,说服力强
  • 结论强调:绿色高亮最后一步
    • 颜色编码强化结论
    • 结论直接回应初始假设

12. 这页为什么是 ListView 而不是 Column

反证法的内容天然是“多段文字步骤”。在小屏设备上,哪怕只有 7 步,也可能超过一屏高度。

12.1 ListView vs Column 对比

// ListView 实现
ListView(
  children: [/* 所有内容 */],
)

// Column + SingleChildScrollView 实现(不推荐)
SingleChildScrollView(
  child: Column(
    children: [/* 所有内容 */],
  ),
)

12.2 核心差异分析

  1. 代码简洁性:
    • ListView 只需一层嵌套,代码更简洁
    • Column 方案需要两层嵌套,代码冗余
  2. 性能表现:
    • ListView 按需构建子组件,性能更优
    • Column 方案一次性构建所有子组件,内存占用更高
  3. 滚动体验:
    • ListView 天然支持滚动物理特性
    • Column 方案需要额外配置滚动行为
  4. 适配性:
    • ListView 适配鸿蒙多终端更友好
    • Column 方案在某些设备上可能出现滚动异常

你用 ListView 的好处是:

  • 不会出现溢出,适配所有屏幕尺寸
  • 用户可以按自己的节奏滚动阅读
  • 性能更优,内存占用更低
  • 代码更简洁,维护成本更低

对于方法论训练页,这种“可滚动阅读”比固定布局更稳。


13. 为什么把 1~7 步写成多条 Text,而不是一条长 Text

你把每一步都写成独立的 Text(...)

// 推荐:分步 Text 组件
Column(
  children: [
    Text('1. 假设√2是有理数'),
    SizedBox(height: 8.h),
    Text('2. 则√2 = p/q (p,q互质)'),
    // 其他步骤...
  ],
)

// 不推荐:单条长 Text
Text('1. 假设√2是有理数 2. 则√2 = p/q (p,q互质) ...'),

13.3 分步 Text 的核心价值

  1. 每一步是一个清晰的“推理单元”
    • 每个 Text 对应一个逻辑步骤,边界清晰
    • 符合“一步一推导”的逻辑思维习惯
  2. 用户阅读时更容易停顿
    • 每个步骤独立成行,便于逐句理解
    • 避免长文本“一扫而过”的阅读习惯
  3. 你也更容易在某一步插入额外解释
    • 可以在任意步骤后添加注释或说明
    • 可以为不同步骤设置不同的样式
  4. 样式定制更灵活
    • 不同步骤可以设置不同的颜色、字体、间距
    • 关键步骤可以特殊标注,提升可读性
  5. 响应式适配更友好
    • 分步 Text 更容易适配不同屏幕宽度
    • 避免长文本在小屏上的换行混乱

如果你把 7 步写成一大段文本,用户会更容易“扫过去”,训练效果会弱。


14. 反证法训练的关键:矛盾必须落在“无法同时成立”的条件上

你的矛盾点是:

// 矛盾点高亮展示
Text('6. q也是偶数,与p,q互质矛盾', 
  style: TextStyle(
    fontSize: 16.sp,
    color: Colors.redAccent,
    fontWeight: FontWeight.bold
  )
),
  • 新增红色标注,突出矛盾点
  • 核心矛盾拆解:
    1. 假设 √2 = p/q 且 p,q 互质
      • 互质:p 和 q 没有除1以外的公因子
    2. 推导出 p 与 q 都是偶数
      • 偶数:能被 2 整除,即有公因子 2
    3. 两者无法同时成立
      • 公因子 2 与“无公因子”直接冲突
      • 矛盾是“逻辑上不可能同时成立”

反证法最容易写坏的地方就在这里:

  1. 如果矛盾点不够硬
    • 比如“推导复杂”而非“逻辑矛盾”
    • 用户会觉得你只是“换个说法”
  2. 你的矛盾点非常硬,因此作为训练例题很合适
    • 矛盾是数论中的基本逻辑矛盾
    • 无需额外解释,用户容易理解

15. 颜色高亮最后一步:在训练页里是必要的“收束”

你把最后一步写成绿色:

Text('7. 因此假设错误,√2是无理数', 
  style: TextStyle(
    fontSize: 16.sp, 
    color: Colors.green,
    fontWeight: FontWeight.bold
  )
),

15.1 视觉收束的核心价值

  1. 这相当于给用户一个明确的结束信号:
    • 前面都是推导(黑色/蓝色)
    • 这里是结论(绿色)
    • 视觉上形成“推导→结论”的闭环
  2. 方法论训练页如果缺少这种“收束”,用户会读完但记不住框架
    • 颜色编码帮助用户快速定位结论
    • 强化“假设→矛盾→结论”的反证法框架
  3. 符合认知心理学的“收尾效应”
    • 用户对最后出现的信息记忆更深刻
    • 绿色结论能强化反证法的核心结论

15.2 扩展颜色编码方案

// 完整颜色编码示例
Column(
  children: [
    Text('1. 假设√2是有理数', style: TextStyle(color: Colors.blueAccent)),
    Text('2. 则√2 = p/q (p,q互质)', style: TextStyle(color: Colors.black87)),
    // ... 推导步骤都是黑色 ...
    Text('6. q也是偶数,与p,q互质矛盾', style: TextStyle(color: Colors.redAccent)),
    Text('7. 因此假设错误,√2是无理数', style: TextStyle(color: Colors.green)),
  ],
)
  • 假设(蓝色)→ 推导(黑色)→ 矛盾(红色)→ 结论(绿色)
  • 颜色编码符合用户的直觉认知
  • 强化反证法的完整逻辑链

16. 如何把例题拆成“互动训练”:让用户补全某一步

你现在是阅读型。如果要做互动训练,最小的做法是:

16.1 基础互动:步骤填空

// 互动填空组件示例
class StepFillBlank extends StatefulWidget {
  final String hint; // 填空提示
  final String correctAnswer; // 正确答案

  const StepFillBlank({
    super.key,
    required this.hint,
    required this.correctAnswer,
  });

  
  State<StepFillBlank> createState() => _StepFillBlankState();
}

class _StepFillBlankState extends State<StepFillBlank> {
  String userAnswer = '';

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          hintText: widget.hint,
          onChanged: (value) => setState(() => userAnswer = value),
        ),
        ElevatedButton(
          onPressed: () {
            if (userAnswer == widget.correctAnswer) {
              // 正确反馈
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('回答正确!')),
              );
            }
          },
          child: Text('验证答案'),
        ),
      ],
    );
  }
}
  • 封装可复用的填空组件
  • 用户输入答案后验证,给出即时反馈
  • 状态管理清晰,交互逻辑简单

16.2 使用示例

// 填空组件使用示例
StepFillBlank(
  hint: '第1步:假设√2是____',
  correctAnswer: '有理数',
),
StepFillBlank(
  hint: '第2步:√2 = p/q,其中p和q____',
  correctAnswer: '互质',
),
  • 保留 1~7 的结构,把其中 1~2 步挖空
  • 让用户选填,验证后再显示下一步
  • 核心优势:用户主动参与推理,而非被动阅读

这样用户会真正参与一次反证法推理,而不是只读结论。


17. 如何扩展题库:从数学例题迁移到生活例题

反证法不只在数学里有用。你可以把题库扩展成:

17.1 生活例题代码示例

// 生活例题卡片
Card(
  child: Padding(
    padding: EdgeInsets.all(16.w),
    child: Column(
      children: [
        Text('生活例题:证明"这个系统没有任何bug"不成立', 
          style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
        SizedBox(height: 16.h),
        Text('1. 假设这个系统没有任何bug', style: TextStyle(fontSize: 16.sp)),
        Text('2. 则系统运行时不会产生错误日志', style: TextStyle(fontSize: 16.sp)),
        Text('3. 但监控显示有错误日志产生', style: TextStyle(fontSize: 16.sp)),
        Text('4. 与假设矛盾,因此系统存在bug', 
          style: TextStyle(fontSize: 16.sp, color: Colors.green)),
      ],
    ),
  ),
)

17.2 题库扩展方向

  1. 证明某个说法不可能
    • 数学:证明“不存在最大的质数”
    • 生活:证明“这个方案永远不会出问题”
  2. 从“不可能”推出矛盾
    • 工程:证明“这个接口响应时间不可能小于100ms”
    • 逻辑:证明“这个推理过程不可能出错”

例如:

  • “这个系统没有任何 bug”
    1. 假设没有 bug,那么某个错误日志从何而来?
    2. 错误日志的存在与“无bug”矛盾
    3. 因此系统必然存在bug

把反证法迁移到生活/工程例子,会让用户更愿意使用这种方法:

  1. 贴近实际场景,更容易理解
  2. 能直接应用到工作和生活中
  3. 强化“反证法是通用思维方法”的认知

18. 小结补充:反证法在你的 App 里的定位

你的 App 很多页面都在训练“逆向思维”。反证法是其中最标准的套路之一。

// 逆向思维训练页面入口列表
ListView(
  children: [
    ListTile(
      title: Text('反证法训练'),
      onTap: () => Navigator.push(context, MaterialPageRoute(
        builder: (context) => ProofByContradictionPage(),
      )),
    ),
    ListTile(
      title: Text('逆向推理训练'),
      // 其他逆向思维训练页面...
    ),
  ],
)

当用户掌握:

  • 假设相反
    // 反证法核心:假设相反命题
    Text('假设原命题不成立', style: TextStyle(color: Colors.blueAccent)),
    
  • 推到矛盾
    // 反证法核心:推出矛盾
    Text('推导出与前提矛盾的结论', style: TextStyle(color: Colors.redAccent)),
    

他们在面对很多推理题时,会更有策略,而不是凭直觉猜。


19. 常见踩坑点:忘记 ListView 导致溢出

如果没有 ListView,长文本在小屏上会溢出报错:

// 错误示例:无 ListView 导致溢出
Scaffold(
  body: Padding(
    padding: EdgeInsets.all(16.w),
    child: Column( // 小屏会溢出
      children: [/* 大量文本 */],
    ),
  ),
)

// 正确示例:使用 ListView 避免溢出
Scaffold(
  body: Padding(
    padding: EdgeInsets.all(16.w),
    child: ListView( // 不会溢出
      children: [/* 大量文本 */],
    ),
  ),
)

你用了 ListView,保证页面在各种屏幕尺寸下都能正常显示。这是"响应式布局"的基本保障。


20. 常见踩坑点:Card 的 padding 为 0

如果 Card 没有 padding,内容会贴边,视觉拥挤:

// 错误示例:Card 无 padding
Card(
  child: Column( // 内容贴边,阅读体验差
    children: [/* 文本内容 */],
  ),
)

// 正确示例:Card 有 padding
Card(
  child: Padding(
    padding: EdgeInsets.all(16.w), // 内边距舒适
    child: Column(
      children: [/* 文本内容 */],
    ),
  ),
)

你用了 EdgeInsets.all(16.w),保证内边距舒适。这是"视觉层次"的体现。


21. 常见踩坑点:SizedBox 高度为 0

// 错误示例:高度为0,无间距
SizedBox(height: 0.h),

// 正确示例:明确高度,间距生效
SizedBox(height: 8.h),

你给了 .h 值,保证间距生效。这是"间距明确"的体现。

22. 常见踩坑点:EdgeInsets.all 的参数为 0

// 错误示例:无内边距
EdgeInsets.all(0.w),

// 正确示例:合理内边距
EdgeInsets.all(16.w),

你用了 16.w,保证内容不贴边。这是"间距合理"的体现。

23. 常见踩坑点:AppBar 的 title 为空

// 错误示例:title 为空
AppBar(title: const Text('')),

// 正确示例:明确标题
AppBar(title: const Text('反证法')),

你给了明确标题,保证导航栏正常。这是"导航完整性"的体现。

24. 常见踩坑点:Scaffold 的 body 为 null

// 错误示例:body 为空
Scaffold(appBar: AppBar(title: Text('反证法')), body: null),

// 正确示例:完整 body
Scaffold(
  appBar: AppBar(title: Text('反证法')),
  body: Padding(...),
),

你给了完整 body,保证页面有内容。这是"页面完整性"的体现。

25. 常见踩坑点:StatelessWidget 的 key 为 null

// 不推荐:key 为 null
class ProofByContradictionPage extends StatelessWidget {
  const ProofByContradictionPage(); // 无 key

  // ...
}

// 推荐:使用 const key
class ProofByContradictionPage extends StatelessWidget {
  const ProofByContradictionPage({super.key}); // 有 key

  // ...
}

这说明你对 Flutter 的基础组件和布局系统有扎实理解。即使未来扩展功能,这些稳健的写法也会减少维护成本。

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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐