我这阵子在整理数学课件,发现好多学生把 (a + b)² 顺手就写成 a² + b²,这不是粗心,而是脑子里缺少“展开过程”的具象画面。于是我用 DevEco Studio 做了一个 HarmonyOS 应用实例:完全平方公式拼图。页面结构很直白:上方是待补全的公式,中间是几个虚线槽位,底部是一堆代数卡片,玩家通过拖拽把卡片安放到正确槽位,从而亲手把 a² + 2ab + b² 拼出来。
这个项目体量不大,但很适合作为学习 ArkUI 交互、状态管理和多关卡数据配置的练手案例,也能给教育类应用提供一种“数学公式可视化”的产品思路。全文从玩法设计、数据建模、页面布局、拖拽判定,到 HOS 4.2 真机调试的实操坑点都会讲到,适合刚入门 HarmonyOS 开发、又想做点有意思 Demo 的读者参考。
1. 为什么选择“拼图”来完成公式推导:产品设计层面的取舍
1.1 直接背公式和动手拼公式,差距在哪
很多公式练习 App 其实是“选择题/填空题”的变体:题干给出 (a + b)² = ?,玩家从若干选项里点选一个答案。这种交互的效率高,但对理解公式结构帮助有限。选择题答对了,很可能只是记住了 a² + 2ab + b² 这一行字符,并没有真正搞清楚“首项平方 + 二倍乘积 + 尾项平方”这三个部分为什么存在、顺序为什么不能乱。
完全平方公式拼图这类的“拼合推理”式交互就是为了补上这个缺口。它不直接把答案呈现在一个选项里,而是要求玩家把离散的卡片,比如 a²、2ab、b²,分别移动到对应槽位。这个过程模拟了公式从左到右的展开路径,让玩家每一次拖拽都必须思考:这个碎片属于“首平方”、“二倍积”还是“尾平方”?
学习心理上有一个“生成效应”:通过自己操作生成的信息,比被动阅读的信息记得更牢。拖拽拼图正是利用了这一点。玩家不只是“看见”结果,而是“做”出了结果。
1.2 给这个拼图项目设计的三个关卡阶段
我建议不要只做一个单关卡,那样太单薄。把完全平方公式拆成三个难度梯度,交互不需要大改,只需要更换数据配置,这对代码架构也是很好的锻炼:
- 第一关,单缺填空:展示
(a + b)² = a² + __?__ + b²,底部混着2ab、ab、2a²等卡片,要求拖出正确的2ab。这关侧重“二倍乘积”这个最容易写漏的部分。 - 第二关,完整展开:三个空槽全部为空,玩家需要按顺序把
a²、2ab、b²放好。这一关开始要求玩家理解公式整体的结构。 - 第三关,系数变化:把公式换成
(2x + 3)²,展开式对应4x² + 12x + 9,备选卡里会出现2x²、6x、12x这种干扰项。到这里就不仅仅是背公式,而是真正在套用公式计算了。
这种关卡设置还有一个开发上的好处:游戏逻辑完全一样,变的只是“题目数据”,所以我会在下面把数据模型单独拎出来做,所有关卡都由配置驱动。这也是这个项目最有复用价值的部分,以后想加入平方差公式、完全立方公式,只需要新增配置,不用再改 UI 代码。
1.3 适用范围与内容边界
这个 Demo 的目标用户主要是初中学生,或者用来课堂上做公式辅导的教师演示。开发层面则适合已经跑通过一个 Hello World 工程、对 Column、Row、Stack 有基本概念的初学者。不需要引入第三方库,也不用复杂的 Canvas 绘图,核心代码全部使用 ArkUI 声明式组件加少量手势处理即可完成。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数据建模:把公式拆成可以自由拼装的卡片片元
2.1 卡片的身份不是一段文字,而是一组属性
很多人写拼图类应用,第一个念头是给每个拼图块存一个字符串,比如“a²”,然后判断它该去哪。这在单关卡里能跑通,但到第二关、第三关就会很痛苦,因为你没办法通过字符串去可靠识别“这其实是一张首平方卡片”。
我在项目中设计了这样一个基础数据结构。为了便于理解,我把代码缩写成核心字段:
typescript复制interface FormulaPiece {
id: string; // 卡片唯一标识,例如 "a2"
display: string; // 卡片上显示的内容,例如 "a²"
acceptGroup: string; // 应归属的槽位组,例如 "first-square"
status: 'idle' | 'placed' | 'wrong'; // 当前状态
x: number; // 当前在拼图板中的 x 坐标
y: number; // 当前在拼图板中的 y 坐标
originX: number; // 初始牌堆的 x 坐标,弹回时使用
originY: number; // 初始牌堆的 y 坐标,弹回时使用
}
这里最关键的不是 display,而是 acceptGroup。
判断一张卡片是否能放入某个槽位,绝不能靠显示文本的字符匹配,因为 4x² 和 x² 从视觉上都带“平方”,但从公式角色看完全不同。用一组逻辑标识来判断,后续调整关卡内容时才不会被文本格式变化干扰。比如 x² 在第一关作为“首平方”可以出现,到第三关展开 (2x + 3)² 时,正确卡片是 4x²,它的 acceptGroup 依然是 first-square,但 display 已经变了,卡片本身承担的“槽位组”没有变。
2.2 目标槽位的配置应如何设计
目标槽位和卡片类似,也要做逻辑抽象。我把它定义为:
typescript复制interface FormulaSlot {
id: string; // 槽位唯一标识,例如 "slot_2"
acceptGroup: string; // 当前槽位接受哪一组卡片
label: string; // 槽位展示的提示文字,例如 "二次乘积"
x: number; // 槽位在拼图板中的 x 坐标
y: number; // 槽位在拼图板中的 y 坐标
width: number;
height: number;
}
有些新手会想:既然槽位是固定的,为什么要单独建模?直接在页面写死三个虚框不就行了?如果你只做一个关卡,确实可以写死。但当你通过 @State 维护一个 currentLevel 变量时,不同关卡有不同数量的槽位,题目的排版也会变,此时最稳妥的方案,就是让“关卡对象”一次性提供所有 UI 需要的结构化数据。
2.3 关卡配置数据如何组织
我会用一个 LevelConfig 对象来描述三关差异:
typescript复制class LevelConfig {
levelIndex: number;
title: string;
slots: FormulaSlot[];
pieces: FormulaPiece[];
// 过关后用于演示动画的完整等式
fullExpression: string[];
}
初始化关卡时,根据 levelIndex 构造三个不同的 LevelConfig 实例。这样做的好处非常明显:调难度的过程只需要调整卡片数组里加入哪几个干扰项、槽位坐标怎么摆,不需要动拖拽和校验的代码。
我在实际测试中还发现,卡片和槽位的 acceptGroup 如果设置成同一个字符串,比如都用 "first-square",还需要注意命名唯一性。一个项目中有多个关卡时,容易出现复制粘贴关卡配置后忘记改命名的问题。建议所有 acceptGroup 加关卡前缀,例如 l3_first_square,避免关卡切换时出现老关卡残留数据误匹配的情况。
3. 拼图板页面布局:如何让式子、槽位和牌堆在同一屏内协作
3.1 页面整体层级
这个页面不复杂,三层结构就够了:
- 顶部区域:显示当前关卡标题、公式残缺提示、进度说明。
- 中部拼图区:承载所有目标槽位,也就是玩家需要放置卡片的“答案位”。
- 底部候选牌堆区:展示当前关卡全部可拖拽卡片,包括干扰项。
在 ArkUI 里我选择用一个大的 Stack 作为整个拼图板的根容器,而不是使用普通的纵向 Column 写到底。原因是:卡片在拖拽过程中需要“浮”在页面上层,并且能跨区域移动。如果用普通流式布局,卡片受到父容器约束,很难做出跨越候选牌堆区与拼图区的视觉层叠效果。
简化后的布局结构如下:
typescript复制Stack() {
// 拼图工作区,包含题目行与目标槽
Column() {
Text(this.levelConfig.title)
// 槽位层
ForEach(this.levelConfig.slots, (slot: FormulaSlot) => {
SlotView({ slot: slot })
}, (slot: FormulaSlot) => slot.id)
}
// 候选卡片层
ForEach(this.levelConfig.pieces, (piece: FormulaPiece) => {
PieceCard({ piece: piece })
.position({ x: piece.x, y: piece.y })
}, (piece: FormulaPiece) => piece.id)
// 遮罩提示、完成动画弹出层
if (this.isLevelComplete) {
LevelCompleteView()
}
}
使用 Stack 根的另一个原因是,卡片坐标本质上是在根容器内部的绝对坐标,拖拽时直接修改 piece.x、piece.y,卡片组件就会通过 position 出现在新位置上,定位逻辑会非常简单。
3.2 三个槽位的视觉暗示与心理引导
完全平方公式拼图不应该是“盲拖试错”。为了降低玩家的认知负担,我会给槽位和卡片都加上“类别色”。
比如第一关有三个槽位,分别对应:
| 槽位含义 | 提示文字 | 边框颜色 | 对应卡片的卡片底色 |
|---|---|---|---|
| 首项平方 | a² 的落点 |
浅红 | 浅红 |
| 二倍乘积 | 2ab 的落点 |
浅黄 | 浅黄 |
| 尾项平方 | b² 的落点 |
浅蓝 | 浅蓝 |
这看起来像“作弊”,但在教育类场景里是刻意设计。颜色提示帮助学生建立“位置与角色”的对应关系,而不是一上来就直接卡在识别困扰上。对于第三关的高阶玩家,可以在设置里关闭颜色提示,增加难度。
实现上,每种颜色不需要存一串元组,只需要在槽位模型里增加一个 accentColor 字段,卡片组件根据自身的 acceptGroup 去匹配当前关卡的槽位组,从而取得同一个颜色。初始配色我先用了较淡的 Pastel 色,避免和卡片上的黑色文字对比度过低,实测在阳光下也能看清。
3.3 候选牌堆要保留足够的“呼吸空间”
很多同类项目喜欢把候选卡片横向排得非常紧密,但拖拽时手指会遮挡卡片本身,卡片挨得太近容易误触。我的经验是卡片宽度不要低于 100vp,高度不要低于 80vp,间距至少 12vp,同时给候选区底部预留安全距离,避免全面屏手势条挡住卡片。
还有一个细节容易被忽略:干扰卡片和正确卡片的形状、大小应当保持一致。如果正确卡片比干扰卡片大,玩家会下意识去抓大的,这违背了“理解公式”的初衷。
4. 卡片拖拽与槽位吸附:松手之后的判定链路
4.1 为什么不用系统级拖拽组件,而是自己处理手势
ArkUI 自带拖拽事件,例如 onDragStart 与 onDrop,使用起来很便捷,但有个体感问题:默认拖拽行为往往是先长按激活,或者在松手前拖动视觉是系统生成的半透明浮层,不完全是卡片本身跟随手指。这对普通列表排序很友好,却不太符合“拼图”的真实手感。
我在这个示例里选择了 PanGesture 手势方案。每一张卡片在拼图板中都有明确坐标,手势触发时通过手指偏移量实时更新卡片坐标,视觉上就是完整的一块卡片被手指“推着走”。这种做法反馈直接、延迟低,也是做桌面级拼图类应用最常用的方式。
ArkTS 核心写法参考如下(这不是完整可编译代码,重点是手势阶段的处理节奏):
typescript复制PieceCard({ piece: piece })
.gesture(
PanGesture({ fingers: 1, distance: 1 })
.onActionStart(() => {
this.startX = piece.x;
this.startY = piece.y;
})
.onActionUpdate((event: GestureEvent) => {
piece.x = this.startX + event.offsetX;
piece.y = this.startY + event.offsetY;
})
.onActionEnd(() => {
this.trySettlePiece(piece);
})
)
这里有个关键决策:distance: 1 表示手指移动 1vp 就会触发手势,也就是“即拿即走”,不需要长按。这个值不能太小,否则点击时有轻微抖动也会被当成拖动;实测用 3~5vp 比较合适,我最终选择 3vp,既保持灵敏,也能区分普通点击和拖拽。
4.2 卡片坐标与槽位坐标的统一基准
很多第一次写这种拖拽的人会遇到一个诡异问题:卡片明明拖到了槽位视觉上,判定却永远不命中。原因通常是坐标基准不一致。
槽位坐标可能是相对某个父组件的局部坐标,而卡片坐标是另一个父组件中的局部坐标,两者在数值上没有可比性。所以在这个项目中,所有目标槽、所有可拖动卡片,必须放在同一个 Stack 坐标系下,坐标数值都以拼图板左上角为原点。
如果槽位坐标和卡片坐标来自不同来源,比如你通过 onAreaChange 动态获取槽位在窗口中的坐标,而卡片坐标是 Stack 内相对坐标,就必须统一换算成同一基准后再计算距离。否则你会看到卡片有时能放上,有时却顽固地弹回,这种问题调试起来相当费神。
4.3 松手后的三层判定
每当玩家松手,我会按三个层级依次处理,而不是把所有判断都挤到一个函数里:
第一层,是否进入某个槽位的吸附范围。计算卡片中心与每个槽位中心的欧几里得距离,如果距离小于吸附半径(我用 60vp),就认为用户希望把卡片放进该槽位。为了实现这一点,槽位需要有自己的宽高,卡片也需要自己的宽高。
第二层,判断该卡片与槽位的匹配关系。如果卡片 acceptGroup 与槽位 acceptGroup 相等,执行成功放下的逻辑:把卡片坐标直接写入槽位中心点,而不是留在松手位置。这样视觉上会有“咔哒”一下自动落入的效果。
第三层,如果不匹配,则执行失败反馈。把卡片移回 originX、originY 初始点,同时触发一次轻微抖动动画。抖动幅度不宜太大,我用的是水平方向上下几 vp 的快速位移,300ms 内结束。
伪代码逻辑如下:
typescript复制trySettlePiece(piece: FormulaPiece) {
const hitSlot = this.findNearbySlot(piece);
if (!hitSlot) {
// 卡片既没放在正确槽位,也不在任何槽位范围内
this.resetPiece(piece);
return;
}
if (hitSlot.acceptGroup === piece.acceptGroup) {
// 命中正确归属,吸附到槽位中心
this.placePieceToSlot(piece, hitSlot);
} else {
// 卡片放进了错误的槽,给出错误反馈后弹回
this.playWrongShake(piece);
this.resetPiece(piece);
}
}
4.4 已有卡片占位时的处理
如果正确槽位已经被一张卡片占着,怎么办?两种思路都可行,但我最终选了“后放上去的把先放上去的挤回牌堆”。这样做的交互逻辑类似真的拼图:你可以反复调整,直到所有碎片各就各位。坏处是实现略微复杂,好处是玩家有更强的掌控感,不会因为放错一步就卡死。
具体实现是在 placePieceToSlot 之前,检查目标槽位中是否已经存在一张 status === 'placed' 的卡片。如果存在,先把这张旧卡片状态改回 idle,坐标重置到初始位置,再让新卡片落位。
4.5 松手反馈的节奏感
这一步很影响体验。我在调这个项目时一开始让卡片“嗖”地瞬移到槽位中心,用户根本来不及看清发生了什么。后来改成两步动画:先让卡片透明度降到 0.7,再做一个 200ms 的缩放放大,最后落到槽位时加上一个约 100ms 的过冲效果。全部用 animateTo 包裹,整体手感会柔和很多。
需要注意:坐标更新和 animateTo 动画不要冲突。如果卡片坐标在手势 onActionUpdate 阶段已经不断更新,到了松手吸附时再直接修改坐标到槽位中心,就不应该继续套用长动画。正确做法是:位置修正这一条路径关闭位置动画,只播放缩放和颜色反馈;如果位置修正也使用动画,容易从视觉上看出卡片“先飘了一段又瞬移一次”的割裂感。
5. 过关判定与推导动画的状态联动
5.1 判定“完成”的条件不只检查卡片数量
关卡到最后往往出现一个低级 bug:玩家把三张垃圾卡片放到错误位置后,卡槽全部被占满,程序误判过关。为防止这个问题,我在统一状态区维护一个 settledMap,键是槽位 id,值是卡片 id。
每次卡片成功落位后更新 settledMap,同时判断:
typescript复制isLevelComplete(): boolean {
return this.levelConfig.slots.every(slot => {
const placedCardId = this.settledMap[slot.id];
if (!placedCardId) return false;
const card = this.findCardById(placedCardId);
return card && card.acceptGroup === slot.acceptGroup;
});
}
不要写“所有卡片都为 placed”就算过关,必须遍历槽位确认每张卡都待在该去的位置。
5.2 过关动画如何编排,以及为什么不应立刻弹窗
第一次跑通成功判定后,我马上弹了个 AlertDialog “恭喜过关”,效果非常生硬。因为公式学习需要的是“看懂展开过程”,而不是“得到一个成功提示”。
所以我把完整公式出现的过程拆成三步:
- 第一步,先让三张已经放好的卡片在槽位中依次高亮,顺序从左到右,间隔 200ms。
- 第二步,隐藏“问号/下划线”的占位符号,把整个等式从左往右用 Text 组件重新渲染,让玩家看到完整结果
(a + b)² = a² + 2ab + b²。 - 第三步,关卡按钮区域从“确认放置”变为“继续下一关”。
这类动画的实现不需要复杂转场,最重要的是控制节奏。把高亮操作放进定时器或者 setTimeout 链里,时间间隔统一管理。如果每个步骤之间都用独立 if 分支快速更新,反而看不出推导顺序。
5.3 关卡是顺序推进,还是自由选择?
从学习角度讲,建议强制顺序解锁:完成第一关才能进入第二关。但在开发测试时,强制顺序很烦,每测一个 Level 都要从第一关玩过去。
我是用 @State currentLevel 控制当前关卡,在“设置”里预留一个 debug 面板,允许我输入 1、2、3 直接跳转。发布时再通过构建标记去除。这种做法很方便,也不难实现。
5.4 重置操作的边界
页面右上角的“重置”按钮不是简单的 resetAllPieces()。它要分清场景:
- 当前关卡还没完成时,重置回本关初始状态。
- 当前关卡已经触发完成动画后,用户点击重置,通常目的是想重新体验一次本关。我会把动画标志位一并清掉,恢复到关卡初始状态。
如果只清卡片位置,不清动画标志位,用户重置后再过关,动画可能完全不触发,因为标志位还是 true。这类状态“串位”问题,最有效的解决方式是先在模型层全部重置,再在 UI 层重置标志位。
6. 进阶视觉玩法:让 a、b 可变,把 (a+b)² 变成一张面积拼图
6.1 面积模型的数学内涵
用面积方式证明完全平方公式,是课堂里非常经典的做法。
设想一个大正方形,边长是 a + b,面积自然是 (a + b)²。把这个大正方形分成四块:边长 a 的正方形面积 a²,边长 b 的正方形面积 b²,还有两块长 a、宽 b 的长方形,每块面积都是 ab。四块面积总和是 a² + ab + ab + b² = a² + 2ab + b²。这就把代数公式和几何面积统一起来了。
6.2 如何在这个 Demo 中增加面积拼图关卡
加入这个关卡需要多一点自定义绘制或者矩形定位逻辑,并不算复杂。我建议在工程里新增一个 AreaPuzzleView 组件:
- 设置 a 和 b 的数值,通过两个 Slider 调节,默认 a=3、b=2。
- 拼图底板上绘制一个大正方形轮廓,内部用浅色辅助线画出分割区域。
- 底部给出四个不规则矩形碎片,尺寸与 a、b 数值挂钩。
- 玩家把碎片拖到大正方形的对应分区,正确后会填上对应颜色,顶部实时刷新面积等式。
这里最大的实现难点是矩形碎片的尺寸会随着 a、b 改变,不像文字卡片是统一大小的方块。坐标映射要额外乘上视觉比例系数。比如画布总宽度 360vp,而 a+b = 5,那么每一单位边长约 72vp,a² 正方形视觉边长就是 216vp,b² 正方形视觉边长是 144vp。
从技术验证角度,可以先用固定小数值(例如 a=1,b=1)跑通逻辑,再去改滑动调参,调试效率会高很多。
6.3 为什么这个扩展能最大化“完全平方公式拼图”的价值
文字卡片的拖拽解决的是“公式结构识别”问题,面积拼图解决的是“公式为什么成立”的问题。两层并在一起,拼图这个载体才真正发挥了优势。我在实际准备第二版时就是打算这样扩展的。
对于工程实现来说,这个扩展也验证了 PanGesture + 统一坐标系 这套方案的扩展性:卡片从等宽的正方形变成长宽可变的矩形,只要数据模型里补充宽高字段,判定逻辑基本不用动。这就回到了第 2 节反复强调的问题:无论简单还是复杂的拼图,底层都必须坚持数据驱动。
7. HOS 4.2 真机联调:从 USB 到无线 WiFi 调试的踩坑记录
7.1 为什么跑真机比预览器更适合这个项目
手势拖拽的流畅度、坐标换算、动画反馈这些能力,光看 Previewer 是达不到真实体验的。模拟器或预览器可以验证布局对不对,但判断不了 60fps 跟手性和吸附半径是否合理。所以建议从开发第一天就用真机测试,尤其当你在写 PanGesture 时,真机上的触摸采样和手指落点与鼠标操作完全不同。
7.2 在 HarmonyOS 4.2 上打开无线调试的步骤
我在这台 HarmonyOS 4.2 设备上,关闭 USB 线也可以安装、抓取日志。步骤如下:
- 进入设置,找到“关于本机”,连续点击“版本号”若干次,直到提示开启开发者模式。
- 回到“系统和更新”,进入“开发人员选项”。
- 打开“USB 调试”,并打开“无线调试”。
- 使用与设备同一局域网的电脑,在终端里执行设备配对或连接命令。
无线调试面板通常会显示一个 IP 和端口,但要注意这可能是“自动分配的临时端口”,每次重新开启无线调试,端口号可能变化。
命令行操作的核心参考如下:
bash复制# 连接同一局域网内的 HarmonyOS 设备,IP 换成设备显示值
hdb connect 192.168.1.100:5555
# 查看当前连接设备
hdb devices
# 进入设备 shell
hdb shell
# 安装 HAP 包到设备
hdb install entry-default-signed.hap
# 查看指定进程日志
hdb log -p com.example.puzzle
如果你习惯用 DevEco Studio 的界面操作,连接成功后设备列表会直接出现。开发调试阶段我更推荐直接用 hdb 命令行,安装 HAP 之后不管工程界面是否卡住,都能快速重新部署。
7.3 我实际遇到并规避的三个坑
第一个坑是 hdb connect 连不上。多数情况是开发人员选项里的“无线调试”端口与设备锁屏状态冲突。一定保证设备在同一局域网、亮屏状态,并且首次连接弹窗授权时点击“允许”。开发者选项里如果开了“仅充电模式下允许 ADB 调试”这类限制,也要确认不受影响。
第二个坑是开发工具里默认的签名包部署失败。这个项目第一次安装到 HarmonyOS 4.2 真机时,提示 “signature verification failed”,原因是默认自动签名未能正确关联设备。解决办法是在 DevEco Studio 的 Project Structure 中重新配置自动签名,让工程绑定到当前华为账号和设备,然后重新构建。
第三个坑是关于拖拽性能:真机上普通卡片拖拽没问题,但面积拼图关卡如果每帧都刷新大面积的 Canvas,某些老设备会掉帧。后来我把“被拖动碎片”的实时刷新路径限定为单卡片局部更新,背景底板不参与手势刷新,帧率明显稳定。
7.4 无线调试阶段遇到崩溃的高效排查节奏
在这个项目里遇到过一次经典崩溃:把卡片放到槽位后,再重置关卡时,某个数组元素越界,界面白屏。无线调试排查时不需要反复插拔数据线,直接在终端里抓日志会更快。
我的排查顺序是:
bash复制hdb shell hilog -r # 清理旧日志
# 操作 App 复现问题
hdb shell hilog | grep "FATAL\|Exception\|puzzle"
如果崩溃信息明确指向某一行,回去检查重置函数里是否还在尝试访问“已经清空”的卡片列表。最终发现是因为物理返回键没有正确处理,用户重置时页面状态被销毁而底层数据残留。这类状态生命周期问题,在白屏类崩溃中占比很高。
8. 从能跑到好用:下一步还能增加的体验细节
8.1 音效与震动反馈
拼图成功那一刻,除了缩放动画,还可以加一个短促的高频音效,失败时用一个低沉短音。如果不想引入音频资源,也可以在 HarmonyOS 中调用系统震动能力来模拟反馈,简单又有效。注意震动时长不能太长,成功约 30ms,失败约 80ms,太长会打扰人。
8.2 卡片答案背后的“公式推导回忆录”
当玩家完成第二关全部三个槽位后,下面会出现一条“可展开”的记录面板,把中间过程写成文字:
- 首项平方:
a² - 二倍乘积:
2 × a × b - 尾项平方:
b² - 合并结果:
a² + 2ab + b²
这可以帮学生把“图形拼图交互”与“书面推导步骤”做一次显性对应。我在测试时发现,这个面板虽然只是一段静态文字,但它对公式的记忆锚点作用非常明显。
8.3 把公式题库抽象成可扩展配置
把数据模型抽象得足够清晰之后,加入新的公式题目只是增加资源,而不是重写功能。我已经测试过这类配置化改造:
typescript复制// 新的关卡配置,例如平方差公式
new LevelConfig()
.title("平方差拼图")
.slots(...)
.pieces(...)
这种扩展思路适合想把示例升级成“公式拼图小工具”的读者。
8.4 给教育类小工具的一点个人体会
这个项目让我最受用的不是拖拽代码本身,而是产品逻辑和技术结构的高度同步:只有把每个公式碎片真正建模成“有身份的对象”,游戏反馈才能做得到位;只有把坐标系统一设计,面积模型这类扩展才能顺利加入。如果你也准备做数学可视化互动应用,建议先把一个公式做到“教材讲不透、但交互能讲透”的程度,再扩充题库,而不是一口气做几十个公式的填空题。用户真正记住的,往往是他亲手拖拽过的那一次。
