前言
上一篇我們通過Listener
獲取觸控點的位置作為貝塞爾曲線的控制點,實現曲線的交互式繪制。不過,上一篇有個缺陷,控制點繪制完成后只能撤銷,沒法修改,如果要調整繪制的圖形的話會非常麻煩,這一篇我們來實現控制點的拖拽式移動,動態調整位置來調整繪制的圖形。
實現邏輯
上一篇的主要代碼我們不做更改,主要是需要實現控制點的拖拽式移動,移動過程中動態繪制新的曲線。不過由于繪制過程中不能同時移動點,因此需要有個完成繪制的控制,完成繪制后才支持拖拽控制點。拖拽控制點實現這里有兩個主要邏輯:
- 控制點的命中判斷:即拖拽開始時判斷哪個控制點被命中,需要移動。
- 監聽觸控位置的移動過程:移動過程中動態繪制新的圖形,以便直接看到對應的效果。
控制點的命中判斷在完成繪制后,首先需要監聽觸控按下事件,看看觸控點是否覆蓋了某個控制點的觸控響應范圍,同時對于距離較近的點,可能會同時命中多個點的觸控響應范圍,這個時候需要取距離最近的那個點。對于觸控范圍,我們定義為每個觸控點的周邊10個像素點。命中觸控點的實現代碼如下:
int checkPointToMove(Offset pressedPoint) {
// 控制點非空才查找
if (points.isNotEmpty) {
var pointsToCheck = <Offset>[];
final maxDistance = 10.0;
// 查找觸控響應范圍內的控制點
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) {
// 只有一個點,直接返回
return points.indexOf(pointsToCheck[0]);
} else {
// 有多個點命中,找到距離最近的點返回
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;
}
移動過程的處理就比較簡單了,我們已經找到了命中的控制點,那就在觸控位置移動監聽響應方法onPointerMove
中更新控制點位置,并重新繪制即可,代碼如下,其中indexOfPointToMove
是一個狀態變量,即找到的控制點下標:
onPointerMove: ((event) {
if (indexOfPointToMove != -1) {
points[indexOfPointToMove] = event.localPosition;
setState(() {});
}
}),
應用
邏輯完成了,我們就來做一個繪制應用吧。我們嘗試來繪制一個粽子的線條畫看看。下面是調整前后的對比效果以及調整過程的動圖,可以看到,調整后的還是更像粽子一些。
拖拽調整
總結
本篇介紹了如何通過拖拽調整貝塞爾曲線繪制的控制點來調整圖形的繪制,實際上很多繪圖都可能用到拖拽式的控制點位的調整,比如電子圍欄的設置。實際上主要的代碼是判斷觸控位置命中了具體哪個控制點。