2020征文-TV10分鐘鴻蒙應(yīng)用實(shí)戰(zhàn)開發(fā):鴻蒙手繪板 (含源代碼)
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
https://harmonyos.51cto.com/#zz
前言:

今天是鴻蒙的手機(jī)beta發(fā)布活動,很榮幸受邀來到現(xiàn)場,一會兒可以給大家上個(gè)靚照~。
本篇旨在通過實(shí)踐一些樣例,讓開發(fā)者們快速提高腎上腺素,歡樂的加入鴻蒙應(yīng)用開發(fā)之旅。整篇就是一個(gè)完整的實(shí)操樣例,我也盡量在一片中把內(nèi)容都講清楚。
基礎(chǔ)的一些知識點(diǎn),可以訪問我另一個(gè)系列:《鴻蒙OS應(yīng)用開發(fā)實(shí)踐》
正文
(一)創(chuàng)建項(xiàng)目
1.創(chuàng)建一個(gè)新的TV項(xiàng)目:

2.創(chuàng)建一個(gè)新的Java類:

命名為Draw:

這個(gè)作為我們的繪畫的核心組件,所以我們讓他繼承Component,方便后面的調(diào)用。需要注意的是,這里導(dǎo)入包名的時(shí)候,我們選擇第一個(gè):ohos.agp.components包。

完成后,依然會報(bào)錯,提示我們需要創(chuàng)建構(gòu)造函數(shù):

同樣默認(rèn)會有很多構(gòu)造方法,我們選擇第一個(gè)(單個(gè)參數(shù))即可。

(二)實(shí)現(xiàn)繪畫工具
這樣一個(gè)基礎(chǔ)的組件類就創(chuàng)建好了,接著我們構(gòu)思下一畫板工具里需要哪些元素:

畫筆:用于畫出各種點(diǎn)和線。
畫板:用于展現(xiàn)我們到底花了什么,它是內(nèi)容的載體。
所以,根據(jù)以上這些元素,在接下來我們需要在代碼里定義和創(chuàng)建一些內(nèi)容:
- 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 : 表示一個(gè)點(diǎn),通常由二維坐標(biāo)(x,y)組成,需要import ohos.agp.utils.Point;
所以上面的代碼,我們先定義了一些等待使用的工具變量。
現(xiàn)在我們?nèi)鄙倭艘粋€(gè)東西,那就是如何交互?一般的,繪圖這樣的,我們要么鼠標(biāo),要么觸屏,要么就是電子繪筆等。這里我們使用鴻蒙觸摸組件來實(shí)現(xiàn)。
在代碼中去實(shí)現(xiàn)Component.TouchEventListener方法:

實(shí)現(xiàn)onTouchEvent()方法:

onTouchEvent包含兩個(gè)參數(shù):Component表示當(dāng)前接收的組件,TouchEvent表示當(dāng)前的觸摸事件。

通過getAction實(shí)例方法可以獲取TouchEvent的狀態(tài):
- TouchEvent.PRIMARY_POINT_DOWN : 按下狀態(tài)
- TouchEvent.PRIMARY_POINT_UP :點(diǎn)按狀態(tài)抬起
- TouchEvent.POINT_MOVE: 點(diǎn)按拖動
我們需要在按下的時(shí)候開始記錄點(diǎn)的位置,拖動的時(shí)候記錄下整個(gè)軌跡,而抬起的時(shí)候則不做任何事情。
所以,在onTouchEvent事件函數(shù)中,我們的代碼這樣寫:
- 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 :表示是人機(jī)交互接口的一個(gè)Point,這里用來接收點(diǎn)擊事件的點(diǎn),需要import ohos.multimodalinput.event.MmiPoint;
然后在點(diǎn)擊下去的這一下,指定路徑mPath的moveTo目標(biāo)為當(dāng)前事件點(diǎn)擊獲得的點(diǎn)。
同時(shí)也設(shè)置了兩個(gè)預(yù)制緩存點(diǎn)的坐標(biāo)為當(dāng)前點(diǎn)擊的點(diǎn)。
抬起的操作,我們這里暫時(shí)不做處理。
直接來處理下移動分支下的操作:
- 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來接收點(diǎn)擊輸入,然后先說下mPath.cubicTo:使用path的cubicTo方法來實(shí)現(xiàn)三次貝塞爾曲線,就是說兩個(gè)點(diǎn)之間的線有兩個(gè)控制點(diǎn)。這樣可以讓曲線更加的平滑,它需要輸入三個(gè)點(diǎn)的參數(shù),所以,我們之前定義了兩個(gè)Point變量,這里就需要用上了,整體上的原理就是,先把點(diǎn)擊獲得第一個(gè)點(diǎn)傳入到曲線函數(shù)中,然后計(jì)算當(dāng)前點(diǎn)擊的位置加上第一個(gè)點(diǎn)的二分之一偏移量來細(xì)化得到一個(gè)更小的值來作為第三個(gè)參數(shù),而第二個(gè)參數(shù),我們讓緩存的另一個(gè)點(diǎn)直接接收當(dāng)前點(diǎn)擊的點(diǎn)的值,然后傳入到第二個(gè)參數(shù)中,最后又更新當(dāng)前位置給第一個(gè)點(diǎn),這樣第一個(gè)點(diǎn)傳入(舊的點(diǎn)),加上拖動后的當(dāng)前點(diǎn)(新點(diǎn)),在當(dāng)前點(diǎn)的二分偏移量的點(diǎn),構(gòu)成了三點(diǎn)傳給了曲線函數(shù),最后重新更新舊的點(diǎn),讓舊點(diǎn)變成一個(gè)新的位置,再次拖動的時(shí)候,就全部有了新的值,形成一個(gè)閉環(huán)。
invalidate()函數(shù)表示申請重新繪制(刷新UI)。
至此,我們就完成了點(diǎn)繪制(畫筆)的計(jì)算方法。
下一步,我們要實(shí)現(xiàn)讓畫筆的這些點(diǎn)和線呈現(xiàn)到畫板(Canvas)上:

追加實(shí)現(xiàn)Component.DrawTask的方法:
然后根據(jù)提示實(shí)現(xiàn)onDraw方法
在onDraw方法中添加如下實(shí)現(xiàn):
- canvas.drawPath(mPath, mPaint);
使用canvas的實(shí)例方法drawPath來將畫筆和路徑傳入實(shí)現(xiàn)繪制。
到這里還沒完,我們還需要做一些初始化的工作,我們寫一個(gè)Init函數(shù):
- 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);
- }
這里將畫筆實(shí)例化,并設(shè)置顏色、粗細(xì)及樣式。然后添加繪制任務(wù)addDrawTask、設(shè)置點(diǎn)擊事件的監(jiān)聽setTouchEventListener,這倆函數(shù)分別是畫布和事件監(jiān)聽內(nèi)置的函數(shù),并非自定義的。
最后,我們需要在Draw的構(gòu)造函數(shù)中調(diào)用這個(gè) Init()方法,這樣就可以在使用new創(chuàng)建這個(gè)Draw組件實(shí)例時(shí)自動初始化。
(三)調(diào)用工具
最后時(shí)調(diào)用我們寫的這個(gè)繪畫工具。
回到slice目錄,并打開MainAbilitySlice文件

定義各一個(gè)方向布局:
- private DirectionalLayout directionalLayout = new DirectionalLayout(this);
在onStart方法中,創(chuàng)建一個(gè)布局配置,并將配置指定給方向布局:
- LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT);
- directionalLayout.setLayoutConfig(config);
接著創(chuàng)建剛才寫好的Draw組件,需要添加import com.qibiao.drawdemo.Draw;
- Draw draw = new Draw(this);
設(shè)置布局配置
- draw.setLayoutConfig(config);
創(chuàng)建背景元素(這里設(shè)置為黑色,黑板嚒~)
- ShapeElement element = new ShapeElement();
- element.setRgbColor(new RgbColor(0, 0, 0));
設(shè)置背景元素
- draw.setBackground(element);
將組件添加到布局中
- directionalLayout.addComponent(draw);
設(shè)置UI內(nèi)容:
- super.setUIContent(directionalLayout);
完整代碼如下:
(四)運(yùn)行效果
運(yùn)行一個(gè)TV的遠(yuǎn)程模擬器,然后run:
手寫一個(gè):你好,鴻蒙
看起來還不錯(別在意細(xì)節(jié)~)。
不過,還不夠完美,我們再給他弄個(gè)擦除的功能。
回到Draw
添加一個(gè)重置的方法:

然后再到MainAbilitySlice中添加一個(gè)按鈕,并調(diào)用clear方法:

再次運(yùn)行已經(jīng)支持擦除。
(五)完整代碼
ok,本篇已經(jīng)務(wù)必盡量精簡了,最后放上代碼鏈接(已開源):
https://gitee.com/doufx/draw-component
©著作權(quán)歸作者和HarmonyOS技術(shù)社區(qū)共同所有,如需轉(zhuǎn)載,請注明出處,否則將追究法律責(zé)任。
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
https://harmonyos.51cto.com/#zz