本篇旨在通过实践一些样例,让开发者们快速提高肾上腺素,欢乐的加入鸿蒙应用开发之旅。整篇就是一个完整的实操样例,我也尽量在一片中把内容都讲清楚。
基础的一些知识点,可以访问我另一个系列 《鸿蒙OS应用开发实践》:https://harmonyos.51cto.com/posts/1898
01
创建项目
① 创建一个新的 TV 项目:![1d1aabd3a307ad9fb64471ba8900ebdc.png](https://i-blog.csdnimg.cn/blog_migrate/563eefa5e03c6cf2e236ffd5892bafba.png)
![d63713d9ae0400713684a18c0983a18c.png](https://i-blog.csdnimg.cn/blog_migrate/57f0c747ee4d150457fdab912ce8b535.png)
![8c9109e267402ce5c8bcb4ce5be66e36.png](https://i-blog.csdnimg.cn/blog_migrate/e5d07766117928206607633d80d9781d.png)
这个作为我们的绘画的核心组件,所以我们让他继承 Component,方便后面的调用。
需要注意的是, 这里导入包名的时候,我们选择第一个:ohos.agp.components 包。![65e0ef3ece2d0610d22de8e149309b4c.png](https://i-blog.csdnimg.cn/blog_migrate/d3195f14dc108ca0a1f29a28838d8f54.png)
![8bc7aaf9afbab7e73ab53b5faec7d954.png](https://i-blog.csdnimg.cn/blog_migrate/7b9d365fb52b47ec686b975d96ce9b00.png)
02
实现绘画工具
这样一个基础的组件类就创建好了,接着我们构思下一画板工具里需要哪些元素:![5ef898143910b0ed588b49dda2aa914d.png](https://i-blog.csdnimg.cn/blog_migrate/d699bbcece028c2656088d9d1f72640d.png)
画笔:用于画出各种点和线。画板:用于展现我们到底花了什么,它是内容的载体。
所以,根据以上这些元素,在接下来我们需要在代码里定义和创建一些内容:Path mPath = new Path();
Paint mPaint;
Point mPrePoint = new Point();
Point mPreCtrlPoint = new Point();
Canvas:画布的意思,属于渲染组件,一般用于渲染各种界面元素,这里需要 import ohos.agp.render.Canvas;
Path:路径的意思,也属于渲染组件,用于描述绘制的路径。需要 import ohos.agp.render.Path;
Paint:表示绘制,属于渲染组件,用于一些绘制操作,需要 import ohos.agp.render.Paint;
Point:表示一个点,通常由二维坐标(x,y)组成,需要 import ohos.agp.utils.Point;
所以上面的代码,我们先定义了一些等待使用的工具变量。
现在我们缺少了一个东西,那就是如何交互?一般的,绘图这样的,我们要么鼠标,要么触屏,要么就是电子绘笔等。这里我们使用鸿蒙触摸组件来实现。
在代码中去实现 Component.TouchEventListener 方法:![4d2842b3c432aab5707e745b68c8b1a8.png](https://i-blog.csdnimg.cn/blog_migrate/5a1f7eb01d34c303c266ee392e5ce8f1.png)
![98b67c2961a2f1f7875f4c90c50884ce.png](https://i-blog.csdnimg.cn/blog_migrate/7dd7bbf8a1352d380f90ddf2b25d5379.png)
![496d885cf6b83e22194a491a13f0175f.png](https://i-blog.csdnimg.cn/blog_migrate/8ef4a130c9dc8634d33f07b7c79eb6a9.png)
通过 getAction 实例方法可以获取 TouchEvent 的状态:
TouchEvent.PRIMARY_POINT_DOWN:按下状态
TouchEvent.PRIMARY_POINT_UP:点按状态抬起
TouchEvent.POINT_MOVE:点按拖动
我们需要在按下的时候开始记录点的位置,拖动的时候记录下整个轨迹,而抬起的时候则不做任何事情。
所以,在 onTouchEvent 事件函数中,我们的代码这样写:switch (touchEvent.getAction()) {
case TouchEvent.PRIMARY_POINT_DOWN: {
MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
mPath.moveTo(point.getX(), point.getY());
mPrePoint.position[0] = point.getX();
mPrePoint.position[1] = point.getY();
mPreCtrlPoint.position[0] = point.getX();
mPreCtrlPoint.position[1] = point.getY();
return true;
}
case TouchEvent.PRIMARY_POINT_UP:
break;
case TouchEvent.POINT_MOVE: {
break;
}
MmiPoint:表示是人机交互接口的一个 Point,这里用来接收点击事件的点,需要 import ohos.multimodalinput.event.MmiPoint;
然后在点击下去的这一下,指定路径 mPath 的 moveTo 目标为当前事件点击获得的点。
同时也设置了两个预制缓存点的坐标为当前点击的点。抬起的操作,我们这里暂时不做处理。
直接来处理下移动分支下的操作:case TouchEvent.POINT_MOVE: {
MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
Point currCtrlPoint = new Point((point.getX() + mPrePoint.position[0]) / 2,
(point.getY() + mPrePoint.position[1]) / 2);
mPath.cubicTo(mPrePoint, mPreCtrlPoint, currCtrlPoint);
mPreCtrlPoint.position[0] = currCtrlPoint.position[0];
mPreCtrlPoint.position[1] = currCtrlPoint.position[1];
mPrePoint.position[0] = point.getX();
mPrePoint.position[1] = point.getY();
invalidate();
break;
解析:同样用 MmiPoint 来接收点击输入,然后先说下 mPath.cubicTo:使用 path 的 cubicTo 方法来实现三次贝塞尔曲线,就是说两个点之间的线有两个控制点。
这样可以让曲线更加的平滑,它需要输入三个点的参数,所以,我们之前定义了两个 Point 变量,这里就需要用上了。
整体上的原理就是,先把点击获得第一个点传入到曲线函数中,然后计算当前点击的位置加上第一个点的二分之一偏移量来细化得到一个更小的值来作为第三个参数。
而第二个参数,我们让缓存的另一个点直接接收当前点击的点的值,然后传入到第二个参数中,最后又更新当前位置给第一个点,这样第一个点传入(旧的点),加上拖动后的当前点(新点)。
在当前点的二分偏移量的点,构成了三点传给了曲线函数,最后重新更新旧的点,让旧点变成一个新的位置,再次拖动的时候,就全部有了新的值,形成一个闭环。
invalidate() 函数表示申请重新绘制(刷新 UI)。至此,我们就完成了点绘制(画笔)的计算方法。
下一步,我们要实现让画笔的这些点和线呈现到画板(Canvas)上:![ff50261aa38f9370ef4de9b2c6cf1c9e.png](https://i-blog.csdnimg.cn/blog_migrate/4175657d92c56cc2c891eddd822120d9.png)
追加实现 Component.DrawTask 的方法:然后根据提示实现 onDraw 方法
在 onDraw 方法中添加如下实现:canvas.drawPath(mPath, mPaint);![113bc8d2929093c59c390069644d36a5.png](https://i-blog.csdnimg.cn/blog_migrate/21a8fe67f0855bb00ddd8bad1cd538ce.png)
使用 canvas 的实例方法 drawPath 来将画笔和路径传入实现绘制。
到这里还没完,我们还需要做一些初始化的工作,我们写一个 Init 函数:private void Init(){
mPaint = new Paint();
mPaint.setColor(Color.WHITE);
mPaint.setStrokeWidth(5f);
mPaint.setStyle(Paint.Style.STROKE_STYLE);
addDrawTask(this::onDraw);
setTouchEventListener(this::onTouchEvent);
}
这里将画笔实例化,并设置颜色、粗细及样式。然后添加绘制任务 addDrawTask、设置点击事件的监听 setTouchEventListener,这俩函数分别是画布和事件监听内置的函数,并非自定义的。
最后,我们需要在 Draw 的构造函数中调用这个 Init() 方法,这样就可以在使用 new 创建这个 Draw 组件实例时自动初始化。
03
调用工具
最后时调用我们写的这个绘画工具。回到 slice 目录,并打开 MainAbilitySlice 文件:![91a994c957f6f2d9f715df9252e60f0a.png](https://i-blog.csdnimg.cn/blog_migrate/0b8a6d7aba5598753d6b374afe68f0ed.png)
private DirectionalLayout directionalLayout = new DirectionalLayout(this);
在 onStart 方法中,创建一个布局配置,并将配置指定给方向布局:
LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT);
directionalLayout.setLayoutConfig(config);
接着创建刚才写好的 Draw 组件,需要添加:
import com.qibiao.drawdemo.Draw;
Draw draw = new Draw(this);
设置布局配置:
draw.setLayoutConfig(config);
创建背景元素(这里设置为黑色,黑板嚒~):
ShapeElement element = new ShapeElement();
element.setRgbColor(new RgbColor(0, 0, 0));
设置背景元素:
draw.setBackground(element);
将组件添加到布局中:
directionalLayout.addComponent(draw);
设置 UI 内容:
super.setUIContent(directionalLayout);
完整代码如下:
04
运行效果
运行一个 TV 的远程模拟器,然后 run:
手写一个:你好,鸿蒙![a5b5477ca8bb5040323bd1079ecde13d.png](https://i-blog.csdnimg.cn/blog_migrate/082e124d7b46ecef16678876cbce725d.png)
看起来还不错(别在意细节~)。不过,还不够完美,我们再给他弄个擦除的功能。
回到 Draw,添加一个重置的方法:
![4098201fc996c7fa01e21843178b6767.png](https://i-blog.csdnimg.cn/blog_migrate/6aabe5ebefa0af51cb4f4d8938b34d01.png)
![91ee51d26c4faad6eea0cd035705cf6f.png](https://i-blog.csdnimg.cn/blog_migrate/ef78d0bdc225e92c1bde65b12e8561e0.png)
再次运行已经支持擦除。
05
完整代码
OK,本篇已经务必尽量精简了,最后放上代码链接(已开源):https://gitee.com/doufx/draw-component
![0d9165e0913e6825659747f9b995b246.gif](https://i-blog.csdnimg.cn/blog_migrate/f11f18fabaf3ac1701b053972416cecb.gif)
点“阅读原文”了解更多