Flutter 通過拖拽交互調整曲線控制點繪製圖形

前言

上一篇我們通過Listener獲取觸控點的位置作爲貝塞爾曲線的控制點,實現曲線的交互式繪製。不過,上一篇有個缺陷,控制點繪製完成後只能撤銷,沒法修改,如果要調整繪製的圖形的話會非常麻煩,這一篇我們來實現控制點的拖拽式移動,動態調整位置來調整繪製的圖形。

實現邏輯

上一篇的主要代碼我們不做更改,主要是需要實現控制點的拖拽式移動,移動過程中動態繪製新的曲線。不過由於繪製過程中不能同時移動點,因此需要有個完成繪製的控制,完成繪製後才支持拖拽控制點。拖拽控制點實現這裏有兩個主要邏輯:

  1. 控制點的命中判斷:即拖拽開始時判斷哪個控制點被命中,需要移動。
  2. 監聽觸控位置的移動過程:移動過程中動態繪製新的圖形,以便直接看到對應的效果。

控制點的命中判斷在完成繪製後,首先需要監聽觸控按下事件,看看觸控點是否覆蓋了某個控制點的觸控響應範圍,同時對於距離較近的點,可能會同時命中多個點的觸控響應範圍,這個時候需要取距離最近的那個點。對於觸控範圍,我們定義爲每個觸控點的周邊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(() {});
  }
}),

應用

邏輯完成了,我們就來做一個繪製應用吧。我們嘗試來繪製一個糉子的線條畫看看。下面是調整前後的對比效果以及調整過程的動圖,可以看到,調整後的還是更像糉子一些。


總結

本篇介紹瞭如何通過拖拽調整貝塞爾曲線繪製的控制點來調整圖形的繪製,實際上很多繪圖都可能用到拖拽式的控制點位的調整,比如電子圍欄的設置。實際上主要的代碼是判斷觸控位置命中了具體哪個控制點。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章