在这里插入图片描述

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

句子倒装在逆向思维训练里很实用,因为它迫使你:

  • 不按习惯顺序说话
  • 把本来放在后面的成分提前
  • 在“同一组词”下,重排结构并观察语义变化

你当前的实现是一个很清爽的演示型训练:

  • 上方展示原句
  • 点击按钮切换显示“倒装句”
  • 点击“下一句”切换到下一条,并重置展示状态

本文涉及文件

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

1. 入口在哪里:从“文字推理”进入

句子倒装属于 WordProblemsPage(文字推理)里的一个入口。
入口在 lib/feature_pages.dart

_buildFeatureCard(context, '句子倒装', Icons.swap_vert, 
  const SentenceInversionPage()),

核心设计要点:

  1. 入口聚合设计:文字推理页作为“训练主题聚合页”,统一管理所有文字类训练入口,符合“单一入口、分类管理”的设计逻辑。
  2. 组件化跳转:通过_buildFeatureCard封装卡片样式,传入图标、标题和目标页面,实现入口样式统一,降低维护成本。
  3. 路由简化:直接通过push方式进入子页,无额外路由配置,适合轻量级训练页的快速跳转需求。

也就是说:

  • 文字推理页负责聚合入口并 push 进入子页
  • 句子倒装页只专注一个训练主题

2. SentenceInversionPage 的真实代码(保持原样引用)

下面这段实现来自你项目 lib/feature_pages.dart
为了保证“代码真实可运行”,我保持核心逻辑不变,补充细节注释和扩展说明。

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

  
  State<SentenceInversionPage> createState() => 
    _SentenceInversionPageState();
}

组件类型选择核心依据:

  1. 状态驱动需求:页面包含“当前句子索引”“是否显示倒装句”两个可变状态,必须使用StatefulWidget
  2. 状态隔离原则:StatefulWidget的状态类_SentenceInversionPageState私有化,避免外部修改状态,保证数据安全。
  3. 一致性设计:与项目中其他训练页(如数字推理、文字重组)保持相同的组件结构,便于团队统一维护。
  4. Key的合理使用:super.key传递父级Key,保证组件在列表/重建场景下的身份唯一性,避免状态错乱。
class _SentenceInversionPageState extends State<SentenceInversionPage> {
  // 原句题库:基础训练语料,覆盖日常简单句式
  final List<String> sentences = [
    '我喜欢苹果',
    '他跑得很快',
    '天空是蓝色的',
    '鸟儿在飞翔',
    '花儿开得正艳', // 新增:扩展题库,增加训练多样性
  ];

题库设计细节:

  1. 数据结构选择:使用List<String>存储原句,结构简单易维护,适合小规模固定题库。
  2. 语料选择原则:选取主谓宾、主谓补等基础句式,避免复杂从句,符合“逆向思维入门训练”的定位。
  3. 扩展性预留:列表形式便于后续通过接口动态加载题库,只需替换静态列表为网络请求数据。
  4. 新增语料考量:补充“花儿开得正艳”,覆盖“名词+动词+补语”结构,丰富训练维度。
  final List<String> inverted = [
    '苹果喜欢我',
    '很快跑得他',
    '蓝色的是天空',
    '飞翔在鸟儿',
    '正艳开得花儿',
  ];

倒装句设计规则:

  1. 索引强关联:倒装句列表与原句列表长度、索引完全对应,通过currentIndex统一关联,避免匹配错误。
  2. 倒装逻辑:遵循“核心成分后置变前置”原则,如“我喜欢苹果”→“苹果喜欢我”(宾语前置),“他跑得很快”→“很快跑得他”(补语前置)。
  3. 格式统一:所有倒装句保持相同的变形规则,让用户形成固定的逆向思维模式。
  int currentIndex = 0;
  bool showInverted = false;

状态字段设计:

  1. 最小状态原则:仅定义必要的两个状态字段,避免冗余状态导致的逻辑混乱。
  2. 初始值合理性:currentIndex初始为0,保证页面加载后显示第一题;showInverted初始为false,符合“先看原句,再看倒装”的训练节奏。
  3. 状态可变性:所有状态修改都通过setState触发,保证UI与状态同步。
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('句子倒装')),
      body: Padding(
        padding: EdgeInsets.all(16.w),
        child: Column(
          children: [

页面基础布局设计:

  1. 骨架结构:Scaffold + AppBar + Padding + Column是Flutter页面的标准基础结构,保证页面的完整性和兼容性。
  2. 响应式间距:使用16.w(屏幕宽度适配单位)而非固定像素,适配不同尺寸的鸿蒙设备,符合跨平台适配要求。
  3. 纵向布局逻辑:Column作为核心布局容器,按“标题→原句→按钮→倒装句→下一句按钮”的逻辑排列,符合用户视觉流和操作流。
            Text('句子倒装训练', 
              style: TextStyle(
                fontSize: 20.sp,
                fontWeight: FontWeight.bold
              )
            ),
            SizedBox(height: 24.h),

标题样式设计:

  1. 字号与权重:20.sp的字号+粗体,保证标题醒目,与正文形成视觉层级;sp为屏幕适配字号单位,适配不同分辨率。
  2. 间距控制:SizedBox(height: 24.h)作为标题与下方内容的间距,24.h符合移动端UI设计的“8px倍数”原则,视觉更舒适。
  3. 无额外装饰:标题仅用样式区分,无多余边框/背景,保持页面简洁性。
            Card(
              child: Padding(
                padding: EdgeInsets.all(16.w),
                child: Text(
                  sentences[currentIndex],
                  style: TextStyle(fontSize: 18.sp),
                ),
              ),
            ),

原句展示区域设计:

  1. 容器选择:Card组件提供天然的视觉边界,让原句区域与其他内容区分开,用户能快速定位核心内容。
  2. 内边距设计:16.w的内边距保证文字不贴边,阅读舒适度提升;内外边距统一使用w单位,保证适配一致性。
  3. 字号选择:18.sp的正文字号,介于标题(20.sp)和辅助文字之间,符合移动端阅读的最佳字号范围(16-20sp)。
            SizedBox(height: 24.h),
            ElevatedButton(
              onPressed: () => setState(() => 
                showInverted = !showInverted),
              child: Text(showInverted ? '显示原句' : '显示倒装'),
            ),

切换按钮交互设计:

  1. 状态联动:按钮文案通过三元运算符动态切换,直接反馈当前状态,用户无需记忆操作逻辑,降低认知成本。
  2. 极简回调:setState中直接修改showInverted状态,无多余逻辑,保证交互响应速度。
  3. 按钮类型选择:使用ElevatedButton(带背景的按钮),突出可点击性,符合移动端“主要操作按钮”的设计规范。
            SizedBox(height: 24.h),
            if (showInverted) Card(
              color: Colors.orange[50],
              child: Padding(
                padding: EdgeInsets.all(16.w),
                child: Text(
                  inverted[currentIndex],
                  style: TextStyle(
                    fontSize: 18.sp,
                    fontStyle: FontStyle.italic
                  ),
                ),
              ),
            ),

倒装句展示区域设计:

  1. 条件渲染:if (showInverted)保证只有用户主动点击按钮后才显示倒装句,避免提前泄露答案,符合训练逻辑。
  2. 视觉区分:
    • 背景色:Colors.orange[50]浅橙色背景,与原句的白色卡片形成明显区分,视觉上提示“这是变形后的内容”;
    • 字体样式:斜体(FontStyle.italic)进一步强化“变形”的视觉感知,让用户快速识别倒装句。
  3. 样式一致性:字号、内边距与原句保持一致,保证视觉统一,仅通过颜色和样式区分内容类型。
            Spacer(),
            ElevatedButton(
              onPressed: () => setState(() {

布局优化设计:

  1. Spacer的使用:Spacer会占据Column中剩余的所有空间,将“下一句”按钮推至页面底部,符合移动端“操作按钮在底部”的交互习惯。
  2. 按钮位置合理性:核心操作按钮(下一句)放在页面底部,用户无需滚动即可点击,提升操作便捷性。
                currentIndex = (currentIndex + 1) % sentences.length;
                showInverted = false;
              }),
              child: const Text('下一句'),
            ),
          ],
        ),
      ),
    );
  }
}

下一句按钮逻辑设计:

  1. 循环索引实现:(currentIndex + 1) % sentences.length通过取模运算实现题库的循环遍历,用户可以反复训练,无需担心“做完就结束”的问题。
  2. 状态重置:切换索引的同时重置showInverted为false,保证每道新题都从“只显示原句”开始,维持统一的训练节奏。
  3. 无边界设计:取模运算避免了索引越界的风险,即使题库长度变化,逻辑依然有效,提升代码健壮性。

3. 为什么用 StatefulWidget:currentIndex 与 showInverted 都会变化

这一页至少有两个状态:

  • currentIndex:当前句子
  • showInverted:是否显示倒装句

状态管理核心逻辑:

  1. 状态触发场景:
    • 点击“显示倒装/显示原句”按钮:修改showInverted状态,触发UI更新,显示/隐藏倒装句;
    • 点击“下一句”按钮:修改currentIndex状态(切换题目)+ 重置showInverted状态,触发UI更新为新题的原句。
  2. setState的必要性:所有状态修改必须包裹在setState中,否则Flutter无法感知状态变化,UI不会更新。
  3. 状态与UI的绑定:UI中的原句、倒装句、按钮文案都直接绑定状态字段,实现“状态驱动UI”的响应式设计。

点击按钮会改变 showInverted
点击“下一句”会改变 currentIndex 并重置 showInverted

因此用 StatefulWidget + setState 非常合适。


4. sentences 与 inverted:平行列表表达“原句/倒装句”

你用两个列表:

  • sentences:原句
  • inverted:倒装句

并用同一个索引关联。

数据结构设计分析:

  1. 优势:
    • 简单直观:平行列表的关联方式符合“一一对应”的业务逻辑,开发和维护成本低;
    • 访问高效:通过索引直接访问,时间复杂度为O(1),性能无损耗;
    • 兼容性好:适合小规模固定题库,无需复杂的模型封装。
  2. 潜在风险:
    • 长度不一致:如果两个列表长度不同,会导致索引越界异常;
    • 维护成本:新增/删除句子时,需要同时修改两个列表,容易遗漏。

这种结构简单直接,但有一个隐含约束:

  • 两个列表长度必须一致

你当前满足。

进阶优化建议:
如果未来题库变大,建议把两者合成一个结构体(例如 map 或模型),减少维护风险。示例如下:

class SentenceModel {
  final String original;
  final String inverted; 

  SentenceModel({required this.original, required this.inverted});
}

final List<SentenceModel> sentenceList = [
  SentenceModel(original: '我喜欢苹果', inverted: '苹果喜欢我'),
  SentenceModel(original: '他跑得很快', inverted: '很快跑得他'),
];

模型化后,只需维护一个列表,且通过类的构造函数保证每个句子都有原句和倒装句,从根源避免数据不一致问题。


5. 原句展示:Card + Padding 把题干“做成一块”

你用 Card 承载原句:

  • Card 提供可见边界
  • Padding 提供阅读舒适度

视觉设计细节:

  1. 边界感:Card组件自带轻微的阴影和圆角,让原句区域形成独立的视觉模块,用户无需在整页中寻找题目文本。
  2. 留白原则:Padding的内边距遵循“呼吸感”设计,文字与边界保持足够距离,避免视觉拥挤。
  3. 一致性:Card的样式与项目中其他训练页的题干容器保持一致,用户形成视觉习惯,提升使用体验。

这让用户不需要在整页里寻找题目文本。


6. 切换按钮:showInverted ? ‘显示原句’ : ‘显示倒装’

按钮文案的关键是:

child: Text(showInverted ? '显示原句' : '显示倒装')

交互文案设计原则:

  1. 状态反馈:文案直接反映“当前操作会触发的结果”,而非“当前状态”,例如:当showInverted为false时,文案是“显示倒装”(点击后会显示倒装句),用户一看就知道点击后的效果。
  2. 简洁性:文案仅用4个字,符合移动端按钮文案“短、准、易理解”的原则。
  3. 无歧义:避免使用“切换”“反转”等模糊文案,直接说明操作结果,降低理解成本。

它把“当前状态”直接反馈给用户:

  • 如果已显示倒装句,按钮就提示可以切回原句
  • 如果当前显示原句,按钮就提示可以显示倒装

这是一种很省事但很有效的交互设计。


7. 倒装句区域:只在 showInverted 时出现

你用条件渲染:

if (showInverted) Card(...)

条件渲染的优势:

  1. 资源节约:未显示时,倒装句的Card组件不会被构建,减少内存占用和渲染开销。
  2. 训练节奏控制:用户必须主动点击按钮才能看到倒装句,强迫用户先思考“如何倒装”,再看参考答案,符合逆向思维训练的核心目标。
  3. 界面简洁:初始状态下页面只有原句和按钮,无多余内容,避免干扰用户注意力。

这能保证:

  • 用户不点击时不会看到倒装句
  • 页面保持“先读原句,再看倒装”的节奏

你给倒装句卡片加了 Colors.orange[50] 的浅底,并且用斜体显示:

fontStyle: FontStyle.italic

视觉区分设计:

  1. 颜色心理学:浅橙色(Colors.orange[50])属于暖色调,既醒目又不刺眼,提示用户这是“变形后的内容”,同时与原句的白色背景形成温和对比。
  2. 字体样式强化:斜体字在视觉上与正体字形成区分,进一步强调“这是倒装后的特殊表达”,防止用户混淆原句和倒装句。
  3. 样式统一性:除了背景色和字体样式,倒装句卡片的内边距、字号、圆角等都与原句卡片一致,保证视觉风格统一。

这会把它明确区分为“变形后的表达”,防止用户混淆。


8. 下一句:切题必须重置 showInverted

你在下一句按钮里做了:

currentIndex = (currentIndex + 1) % sentences.length;
showInverted = false;

切题逻辑设计:

  1. 循环出题:% sentences.length实现题库的循环遍历,用户可以反复练习,无需担心题库耗尽,适合碎片化训练场景。
  2. 状态重置:showInverted = false保证切换到新题后,自动隐藏倒装句,回到“只显示原句”的初始状态,避免上一题的状态影响当前题,保证训练节奏的一致性。
  3. 操作原子性:一次setState中完成两个状态的修改,保证状态更新的原子性,避免部分状态更新导致的UI异常。

这非常关键。

如果不重置,用户切题后仍会看到倒装句区域,训练节奏会被破坏。


9. 为什么 currentIndex 用取模:让训练可循环

你使用:

(currentIndex + 1) % sentences.length

取模运算的优势:

  1. 边界安全:无论currentIndex是多少,取模后结果始终在0 ~ sentences.length-1范围内,避免索引越界异常。
  2. 循环逻辑简洁:无需判断“是否是最后一题”,一行代码实现循环,逻辑清晰且代码量少。
  3. 扩展性强:即使后续修改题库长度,该逻辑无需调整,适配性强。

这会让题库循环。

对这种“短句练习”页来说,循环是很实用的:

  • 用户可以反复练
  • 不需要维护完成度

如果未来要做“完成进度”,可以在最后一题提示完成。示例如下:

onPressed: () => setState(() {
  if (currentIndex == sentences.length - 1) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('已完成一轮训练,继续循环练习!')),
    );
  }
  currentIndex = (currentIndex + 1) % sentences.length;
  showInverted = false;
}),

10. 这页如何升级成更强的训练

目前它更像演示页。
如果你要更训练化,有两个方向。

10.1 增加用户输入

核心升级逻辑:

  1. 输入交互闭环:让用户先输入自己的倒装句,再对比参考答案,强化主动思考,而非被动查看。
  2. 实现思路:
    • 添加TextField组件,用于用户输入;
    • 添加“提交”按钮,点击后对比用户输入与参考答案;
    • 给出反馈(正确/错误/部分正确),提升训练的互动性。

示例代码:

String userInput = '';

TextField(
  decoration: InputDecoration(
    hintText: '请输入你的倒装句',
    border: OutlineInputBorder(),
  ),
  onChanged: (value) => setState(() => userInput = value),
),
ElevatedButton(
  onPressed: () {
    if (userInput == inverted[currentIndex]) {
    } else {
    }
  },
  child: const Text('提交'),
),
  • 给一个输入框
  • 让用户自己写倒装句
  • 点击按钮后再显示参考倒装

这会与“逆向阅读”形成互补。

10.2 增加评分规则

评分规则设计:
对于倒装句,很多时候没有唯一答案。
你可以用“关键词覆盖”作为弱判定:

  1. 关键词提取:拆分原句的核心关键词(如“我、喜欢、苹果”);
  2. 覆盖判定:用户输入的句子必须包含所有核心关键词;
  3. 顺序判定:关键词的顺序必须与原句不同(即实现“倒装”);
  4. 容错性:忽略标点、空格、语气词等非核心内容,提升判定的人性化。
List<String> extractKeywords(String sentence) {
  return sentence.replaceAll(RegExp(r'[,。!?]'), '').split('');
}

bool isInversionValid(String userInput, String original, String answer) {
  final originalKeywords = extractKeywords(original);
  final userKeywords = extractKeywords(userInput);
  final hasAllKeywords = originalKeywords.every(userKeywords.contains);
  final isOrderDifferent = userInput != original;
  return hasAllKeywords && isOrderDifferent;
}
  • 必须包含原句所有关键词
  • 并且顺序发生变化

这样训练更有挑战,但不会被唯一答案限制。


11. 小结:句子倒装实现的关键点

  • 状态最小化:只用 currentIndexshowInverted
  • 节奏清晰:先原句,再倒装
  • 区分明显:倒装句浅底 + 斜体
  • 切题重置到位:避免状态污染

到这里,这篇文章已经把你项目中的“句子倒装”训练页实现讲清楚了。
下一步如果你想把它变成完整关卡,优先加入“用户输入倒装句 + 提示/参考答案”的闭环。

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


Logo

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

更多推荐