Android利用控制點(diǎn)的拖拽畫(huà)一個(gè)粽子
前言
上一篇我們通過(guò)Listener
獲取觸控點(diǎn)的位置作為貝塞爾曲線(xiàn)的控制點(diǎn),實(shí)現(xiàn)曲線(xiàn)的交互式繪制。不過(guò),上一篇有個(gè)缺陷,控制點(diǎn)繪制完成后只能撤銷(xiāo),沒(méi)法修改,如果要調(diào)整繪制的圖形的話(huà)會(huì)非常麻煩,這一篇我們來(lái)實(shí)現(xiàn)控制點(diǎn)的拖拽式移動(dòng),動(dòng)態(tài)調(diào)整位置來(lái)調(diào)整繪制的圖形。
實(shí)現(xiàn)邏輯
上一篇的主要代碼我們不做更改,主要是需要實(shí)現(xiàn)控制點(diǎn)的拖拽式移動(dòng),移動(dòng)過(guò)程中動(dòng)態(tài)繪制新的曲線(xiàn)。不過(guò)由于繪制過(guò)程中不能同時(shí)移動(dòng)點(diǎn),因此需要有個(gè)完成繪制的控制,完成繪制后才支持拖拽控制點(diǎn)。拖拽控制點(diǎn)實(shí)現(xiàn)這里有兩個(gè)主要邏輯:
- 控制點(diǎn)的命中判斷:即拖拽開(kāi)始時(shí)判斷哪個(gè)控制點(diǎn)被命中,需要移動(dòng)。
- 監(jiān)聽(tīng)觸控位置的移動(dòng)過(guò)程:移動(dòng)過(guò)程中動(dòng)態(tài)繪制新的圖形,以便直接看到對(duì)應(yīng)的效果。
控制點(diǎn)的命中判斷在完成繪制后,首先需要監(jiān)聽(tīng)觸控按下事件,看看觸控點(diǎn)是否覆蓋了某個(gè)控制點(diǎn)的觸控響應(yīng)范圍,同時(shí)對(duì)于距離較近的點(diǎn),可能會(huì)同時(shí)命中多個(gè)點(diǎn)的觸控響應(yīng)范圍,這個(gè)時(shí)候需要取距離最近的那個(gè)點(diǎn)。對(duì)于觸控范圍,我們定義為每個(gè)觸控點(diǎn)的周邊10個(gè)像素點(diǎn)。命中觸控點(diǎn)的實(shí)現(xiàn)代碼如下:
int checkPointToMove(Offset pressedPoint) { // 控制點(diǎn)非空才查找 if (points.isNotEmpty) { var pointsToCheck = <Offset>[]; final maxDistance = 10.0; // 查找觸控響應(yīng)范圍內(nèi)的控制點(diǎn) for (Offset p in points) { if ((p.dx - pressedPoint.dx).abs() < maxDistance && (p.dy - pressedPoint.dy).abs() < maxDistance) { pointsToCheck.add(p); } } // 未找到 if (pointsToCheck.length == 0) { return -1; } else if (pointsToCheck.length == 1) { // 只有一個(gè)點(diǎn),直接返回 return points.indexOf(pointsToCheck[0]); } else { // 有多個(gè)點(diǎn)命中,找到距離最近的點(diǎn)返回 Offset point = pointsToCheck[0]; var distance = distanceBetween(pointsToCheck[0], pressedPoint); for (int i = 1; i < pointsToCheck.length; i++) { var newDistance = distanceBetween(pointsToCheck[i], pressedPoint); if (newDistance < distance) { point = pointsToCheck[i]; distance = newDistance; } } return points.indexOf(point); } } return -1; }
移動(dòng)過(guò)程的處理就比較簡(jiǎn)單了,我們已經(jīng)找到了命中的控制點(diǎn),那就在觸控位置移動(dòng)監(jiān)聽(tīng)響應(yīng)方法onPointerMove
中更新控制點(diǎn)位置,并重新繪制即可,代碼如下,其中indexOfPointToMove
是一個(gè)狀態(tài)變量,即找到的控制點(diǎn)下標(biāo):
onPointerMove: ((event) { if (indexOfPointToMove != -1) { points[indexOfPointToMove] = event.localPosition; setState(() {}); } }),
應(yīng)用
邏輯完成了,我們就來(lái)做一個(gè)繪制應(yīng)用吧??紤]端午節(jié)快到了,我們嘗試來(lái)繪制一個(gè)粽子的線(xiàn)條畫(huà)看看。下面是調(diào)整前后的對(duì)比效果以及調(diào)整過(guò)程的動(dòng)圖,可以看到,調(diào)整后的還是更像粽子一些。
總結(jié)
本篇介紹了如何通過(guò)拖拽調(diào)整貝塞爾曲線(xiàn)繪制的控制點(diǎn)來(lái)調(diào)整圖形的繪制,實(shí)際上很多繪圖都可能用到拖拽式的控制點(diǎn)位的調(diào)整,比如電子圍欄的設(shè)置。實(shí)際上主要的代碼是判斷觸控位置命中了具體哪個(gè)控制點(diǎn)。本篇代碼已經(jīng)上傳至:繪圖相關(guān)代碼。
以上就是Android利用控制點(diǎn)的拖拽畫(huà)一個(gè)粽子的詳細(xì)內(nèi)容,更多關(guān)于Android粽子的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android編程實(shí)現(xiàn)簡(jiǎn)單文件瀏覽器功能
這篇文章主要介紹了Android編程實(shí)現(xiàn)簡(jiǎn)單文件瀏覽器功能,結(jié)合實(shí)例形式分析了Android文件管理器的布局、文件與目錄的遍歷、權(quán)限控制等相關(guān)操作技巧,需要的朋友可以參考下2018-01-01Genymotion模擬器常見(jiàn)問(wèn)題整理與相應(yīng)解決方法
為什么說(shuō)是常見(jiàn)問(wèn)題整合呢,因?yàn)樾【幬揖褪荊enymotion模板器最悲劇的使用者,該見(jiàn)過(guò)的問(wèn)題,我基本都見(jiàn)過(guò)了,在此總結(jié)出這血的教訓(xùn),望大家不要重蹈覆轍2018-03-03Android中SharedPreferences簡(jiǎn)單使用實(shí)例
這篇文章主要介紹了Android中SharedPreferences簡(jiǎn)單使用案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10Android 中兩個(gè)Activity 之間的傳值問(wèn)題
這篇文章主要介紹了Android 中兩個(gè)Activity 之間的傳值問(wèn)題的相關(guān)資料,需要的朋友可以參考下2017-08-08Android開(kāi)發(fā)中使用外部應(yīng)用獲取SD卡狀態(tài)的方法
這篇文章主要介紹了Android開(kāi)發(fā)中使用外部應(yīng)用獲取SD卡狀態(tài)的方法,簡(jiǎn)單分析了Android監(jiān)聽(tīng)SD卡狀態(tài)的方法,并結(jié)合實(shí)例形式分析了Android外部應(yīng)用獲取SD卡狀態(tài)的相關(guān)操作技巧,需要的朋友可以參考下2017-11-11Android中findViewById獲取控件返回為空問(wèn)題怎么解決
這篇文章主要介紹了Android中findViewById獲取控件返回為空問(wèn)題怎么解決的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-06-06Android端權(quán)限隱私的合規(guī)化處理實(shí)戰(zhàn)記錄
大家應(yīng)該都發(fā)現(xiàn)了,現(xiàn)在很多應(yīng)用市場(chǎng)都要求應(yīng)用上架需要用戶(hù)協(xié)議,這篇文章主要給大家介紹了關(guān)于Android端權(quán)限隱私合規(guī)化處理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08Android獲取當(dāng)前運(yùn)行的類(lèi)名或者方法
這篇文章主要介紹了Android獲取當(dāng)前運(yùn)行的類(lèi)名或者方法,涉及Android操作類(lèi)與方法的技巧,需要的朋友可以參考下2015-05-05