《鸿蒙纪元》 是 张风捷特烈 计划打造的一套 HarmonyOS 开发系列教程合集。致力于创作优质的鸿蒙原生学习资源,帮助开发者进入纯血鸿蒙的开发之中。本系列的所有代码将开源在 HarmonyUnit 项目中:
github
: github.com/toly1994328…
gitee
: gitee.com/toly1994328…
鸿蒙纪元 系列文章列表可在《文章总集》 或 【github 项目首页】 查看。
上一篇,我们完成了线条粗细
和 颜色
的选择。本篇将继续完善画板需求,如下效果,在头部标题栏的左侧添加两个按钮,分别用于 回退上一步 和 撤销回退 :
- 向前回退: 移除当前线列表中的最后一条线。
- 撤销回退: 向当前线列表中添加上次回退的线。
向前回退 | 撤销回退 |
---|---|
1. 增加需求支持的数据
每项功能需求的增加,其背后都需要对应的数据支持。由于需要 "后悔"
,所以需要引入一个线列表作为 "后悔药"
,也就是收集向前回退过程中被抛弃的线。这里在状态类中添加 _historyLines
列表来维护:
---->[pages/painter/bloc/PainterBloc.ets]----
@Track historyLines: Line[] = []; //回退历史线列表
另外,在界面构建逻辑中,需要注意按钮可操作性的限制,比如当线列表为空时,无法向前回退;当界面上有内容时,才允许点击左侧按钮回退。撤销按钮同理,只有回退历史中有元素,才可以操作。
无绘制时 | 有绘制时 |
---|---|
为了追踪两个列表线的个数,这里在业务逻辑层提供了两个计数器;并通过 updateCount 方法更新这两个数值:
---->[pages/painter/bloc/PainterBloc.ets]----
@Track lineCount = 0;
@Track historyLineCount = 0;
private updateCount(){
this.lineCount = this.lines.length;
this.historyLineCount = this.historyLines.length;
}
2. 回退与撤销界面构建
首先看一下两个按钮的构建逻辑,这里封装在 actionsButton
组件构建2方法中。其中 enabled
表示按钮的可用情况,校验状态中对应的计数即可。比如 lineCount
大于 0 时才允许回退,将它作为左侧按钮的 enable 值:
两个按钮的事件,分别触发 PainterBloc#back 和 PainterBloc#revocation 方法,处理具体的数据变化逻辑:
@Builder
actionsButton() {
Row() {
Button() {
SymbolGlyph($r('sys.symbol.undo')).fontSize(24)
}
.width(36).height(36)
.backgroundColor(Color.Transparent)
.enabled(this.model.lineCount > 0)
.onClick(() => this.model.back())
Button() {
SymbolGlyph($r('sys.symbol.redo')).fontSize(24)
}
.width(36).height(36)
.backgroundColor(Color.Transparent)
.enabled(this.model.historyLineCount > 0)
.onClick(() => this.model.revocation())
}
}
3. 状态数据的维护
最后一步就是在 back
方法中处理回退的逻辑;在 revocation
中处理撤销的逻辑。在回退方法中移除 lines
的最后一个元素,然后让 historyLines
列表添加移除的线,再更新界面即可:
back(): void {
let line = this.lines.pop()
if (line) {
this.historyLines.push(line);
this.updateCount();
}
this.paint(this.context);
}
在撤销回退方法中移除 historyLines
的最后一个元素,然后让 lines
列表添加移除的线,再更新界面即可:
revocation(): void {
let line = this.historyLines.pop()
if (line) {
this.lines.push(line);
this.updateCount();
}
this.paint(this.context);
}
到这里, 回退上一步 和 撤销回退 的功能就已经实现了,提交一个小里程碑 v20-回退与撤销回退。
4.拖拽更新的频繁触发
虽然现在画板操作时看起开没什么问题,但内部却危机四伏。如果你缓慢滑动手指,画了很多东西。最后会发现非常卡顿。在拖拽更新的回调 _onPanUpdate
中打印一下日志,会发现它的触发非常频繁。而每触发一次都会像线中添加一个点,就会导致点非常多。
特别是缓慢移动的过程中,会加入很多相近的无用点,不仅占据内存,也会造成绘制的负担。我们可以优化一下收集点的逻辑,根据与前一点的距离决定加不加入该点,这样可以有效降低点的数量,减缓绘制压力。处理逻辑并不复杂,如下所示,只要校验当前点和线的最后一点的距离,是否超过阈值即可。
// 平移更新时,为新线添加点
updateLine(x: number, y: number) {
let lastLine = this.lines[this.lines.length-1];
let lastPoint = lastLine.points[lastLine.points.length-1]
let distance = Math.sqrt(Math.pow((x - lastPoint.x), 2) + Math.pow((y - lastPoint.y), 2))
if (distance < 5) return;
console.log(`==onPanUpdate:(${x},${y})========`);
this.lines[this.lines.length-1].points.push(new Point(x, y));
this.paint(this.context);
}
阈值越大,忽略的点就越多,线条越不精细,相对来说绘制压力也就越低,需要酌情处理。这里用 5 个逻辑像素,在操作体验上没什么影响,也能达到一定的优化效果。
5. 尾声
到这里,白板绘制的基础功能就已经完成了,当前代码位置提交一个小里程碑 v21-简单画板完成。还有些值得优化和改进的地方,比如:
- 现在的线是通过点进行连接的折线,可以通过贝塞尔曲线进行拟合,让点之间的连接更加圆滑;
- 可以提供一些基础图形的绘制操作,让绘制更加丰富。
- 现在每次添加点都会将所有的内容绘制一边,随着绘制内容的增加,会带来频繁的复杂绘制。
- 如何存储绘制的信息到本地,这样即使在退出应用后,也可以在下次开启时恢复绘制的内容。
对这些问题的改进,大家可以在今后的路途中通过自己思考和理解,来尝试解决。接下来,我们将对之前几个小项目进行整合,放入到一个项目中。
更多文章和视频知识资讯,大家可以关注我的公众号、掘金和 B 站 。关注 公众号
并回复 鸿蒙纪元 可领取最新的 xmind 脑图电子版,让我们一起成长,变得更强。我们下次再见~