日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

手绘板的制作——画布缩放(4)

發布時間:2024/3/26 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 手绘板的制作——画布缩放(4) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

在這一篇中,我們講解下畫布的縮放,也就是做一個根據手勢縮放進行畫布縮放的功能。

我們先來梳理下邏輯:

  • 監聽手勢,當為一根手指的時候,就延續之前的操作,執行手繪操作,當操作為兩根手指的時候,則執行縮放功能。
  • 對畫布進行縮放

好了,正文開始!

手勢縮放

看了下,GestureDetector 里面有 onScaleStart、onScaleUpdate、onScaleEnd 參數,這…這不是縮放開始、縮放過程中、縮放結束的回調嗎?Flutter 真方便,都給封裝好了。趕緊試下:

@overrideWidget build(BuildContext context) {return GestureDetector(onPanStart: (details) {print("onPanStart:準備開始移動");_paintedBoardProvider.onStart(details);},onPanUpdate: (details) {print("onPanUpdate:正在移動");_paintedBoardProvider.onUpdate(details);},onPanEnd: (details) {print("onPanDown:移動結束");widget._invoker.execute(PaintedCommand(_paintedBoardProvider, _paintedBoardProvider.strokes.last));},onScaleStart: (details) { // <- 新增print("onScaleStart:縮放開始");},onScaleUpdate: (details) { // <- 新增print("onScaleStart:縮放進行中");},onScaleEnd: (details) { // <- 新增print("onScaleStart:縮放結束");},child: CustomPaint(painter: MyPainter(_paintedBoardProvider),size: Size.infinite,),);}

運行…

======== Exception caught by widgets library ======================================================= The following assertion was thrown building HandPaintedBoard(dirty, state: _HandPaintedBoardState#7df2e): Incorrect GestureDetector arguments.Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.Just use the scale gesture recognizer.

em…咋報錯了…

大意就是,縮放手勢包含了平移手勢,所以,同時賦值縮放手勢和平移手勢是多余,直接使用縮放手勢即可。

具體報錯源碼就是:

final bool havePan = onPanStart != null || onPanUpdate != null || onPanEnd != null;final bool haveScale = onScaleStart != null || onScaleUpdate != null || onScaleEnd != null;if (havePan || haveScale) {if (havePan && haveScale) {throw FlutterError.fromParts(<DiagnosticsNode>[ErrorSummary('Incorrect GestureDetector arguments.'),ErrorDescription('Having both a pan gesture recognizer and a scale gesture recognizer is redundant; scale is a superset of pan.',),ErrorHint('Just use the scale gesture recognizer.'),]);}

這能怎么辦?解決!

所以,我們需要把 onPanStart、onPanUpdate、onPanEnd 去掉,只保留 onScaleStart、onScaleUpdate、onScaleEnd,然后在縮放的方法里面進行縮放和平移的區分,所以,我們先定義一個枚舉:

enum GestureType {translate, // 平移scale, // 縮放 }

具體區分代碼:

class _HandPaintedBoardState extends State<HandPaintedBoard> {PaintedBoardProvider get _paintedBoardProvider =>widget._paintedBoardProvider;// 標識手勢GestureType _gestureType = GestureType.translate; // <- 新增// 記錄縮放開始的縮放double _startScale = 1; // <- 新增 class PaintedBoardProvider extends ChangeNotifier {// 縮放比例double scale = 1; // <- 新增 onScaleStart: (details) {if (details.pointerCount > 1) { // 雙指_gestureType = GestureType.scale;_startScale = _paintedBoardProvider.scale;} else { // 單指_gestureType = GestureType.translate;_paintedBoardProvider.onStart(details.localFocalPoint);}},onScaleUpdate: (details) {switch (_gestureType) {case GestureType.translate:_paintedBoardProvider.onUpdate(details.localFocalPoint);break;case GestureType.scale:setState(() {_paintedBoardProvider.scale = _startScale + details.scale - 1;});break;}},onScaleEnd: (details) {switch (_gestureType) {case GestureType.translate:widget._invoker.execute(PaintedCommand(_paintedBoardProvider, _paintedBoardProvider.strokes.last));break;case GestureType.scale:print("onScaleEnd:縮放結束");break;}},

主要的思路其實就是:

  • 在 onScaleStart 的時候,判斷是單指還是雙指,并且進行記錄該狀態,后續的 onScaleUpdate、onScaleEnd 都是基于這個單指或者雙指進行操作的。
  • 在 onScaleStart 中進行數據記錄:
    • 單指:創建 stroke 存儲當前繪畫信息,便于后續手繪。
    • 雙指:記錄當前的縮放系數。
  • 在 onScaleUpdate 中進行狀態更改:
    • 單指:更新 path 數據,進行手繪刷新。
    • 雙指:手勢過程的縮放系數 details.scale 是基于 1 進行不斷增大的,直至縮放過程結束,所以通過 _startScale + details.scale - 1 就能拿到當前 Widget 正確的縮放系數,_startScale 為在 onScaleStart 中存儲的當前縮放系數。
  • 在 onScaleEnd 進行事件的收尾處理:
    • 單指:提交命令。
    • 雙指:無需操作。

視圖縮放

經過以上步驟,我們可以獲取得到手勢縮放的系數,但是這個系數如何用于放大視圖?

目前一般有兩種步驟:

  • 對于 canvas 進行縮放,并且對于 canvas 的繪制內容進行全部縮放,例如畫筆原有起點為 (1,1),放大后,需要將畫筆原有起點進行更改,可能就要變為 (2,2) 了,這種方式需要更改的比較多,所以我就不在這里實踐了,有興趣的同學可以自己試下。
  • 使用 Transform 進行縮放,也就是把整個 Widget 進行放大,所以 canvas 的坐標系是沒有改動的,之前繪制的內容不需要重新繪制,目前我采取的是這種方案。這里有個重點,canvas 的坐標系是沒有改動的

所以,使用以下代碼即可完成縮放功能:

child: Transform.scale(scale: _paintedBoardProvider.scale,child: CustomPaint(painter: MyPainter(_paintedBoardProvider),size: Size.infinite,),),

所以,這縮放功能就結束了嗎?

當然沒有這么簡單,這后面才是難點。

我們在進行手繪板制作的時候,使用的坐標點是 details.localFocalPoint,它是基于當前視圖的坐標點,但是它的 (0,0) 坐標并不是固定為視圖的左上角,當視圖大于屏幕的時候,它的 (0,0) 是視圖與屏幕的交接處,所以,無論使用 Transform 進行如何縮放,對于同一個點擊點,其 details.localFocalPoint 的值都是一樣的。(這話可能不夠嚴謹,但是對于我當前的 demo 而言,它原有視圖就是鋪滿整個屏幕,無論它使用 Transform 進行縮放多少倍,同個點擊點的 details.localFocalPoint 值都是一樣的。)

但是,我們特別強調了,在進行縮放后,canvas 的坐標系是沒有改動的,只是視圖效果放大而已,所以,即使點擊的是同一個位置,在 canvas 的坐標系上的位置也是不相同的,所以,我們要對于后續繪畫的點進行處理,將 details.localFocalPoint 其轉換為基于視圖 (0,0) 點的坐標。

圖片說明:

  • 藍框為原圖,x、y 為原圖的坐標系。
  • Transform 默認是基于中心僅放大的,所以,黃框是實際上放大的效果。
  • 若放大前的 details.localFocalPoint 為 (10,10),那么放大后同個點擊處的 details.localFocalPoint 仍然為 (10,10)
  • 由于手繪繪制是基于畫布 (0,0) 位置的,也就是黃框的左上角,所以,我們需要把 details.localFocalPoint 加上兩條綠邊距離,才是真正的手繪坐標點。
  • 那兩條綠邊怎么計算?我們先算 x 坐標的,假設原圖大小為 w1,放大后的大小為 w2,那綠邊 x = (w2-w1) / 2,而 w2 其實就是 w1 乘以 scale,所以 x = (scale-1) * w1 /2

實際的代碼實操:

首先,我們需要存儲原有的畫布大小:

class PaintedBoardProvider extends ChangeNotifier {// 畫布原有尺寸Size realCanvasSize = Size.zero;

具體的賦值在 MyPainter:

class MyPainter extends CustomPainter {@overridevoid paint(Canvas canvas, Size size) {paintedBoardProvider.realCanvasSize = size;

剩下的就是換算了:

/// 移動開始時void onStart(Offset localPosition) {double startX = localPosition.dx;double startY = localPosition.dy;final newStroke = Stroke(color: isClear ? Colors.transparent : color, width: paintWidth,isClear: isClear, );newStroke.path.moveTo((startX + (scale - 1) * realCanvasSize.width / 2 ) /scale,(startY + (scale - 1) * realCanvasSize.height / 2 ) /scale);_strokes.add(newStroke);}/// 移動void onUpdate(Offset localPosition) {_strokes.last.path.lineTo((localPosition.dx +(scale - 1) * realCanvasSize.width / 2 ) /scale,(localPosition.dy +(scale - 1) * realCanvasSize.height / 2 ) /scale);notifyListeners();}

可能會有人有疑問,為什么換算后的值還要除以 scale,em…這還是因為 canvas 的坐標系沒有更改過,我們的換算都是基于真正進行放大后的換算,但是實際上坐標系沒有放大,所以還要除以 scale 轉換回來。

清除誤差點

在具體的實操上,其實人點擊屏幕的時候,由于手指接觸屏幕面積較大,所以,經常會出現縮放結束后,還會觸發繪制的效果,所以,我們在手指抬起之后,對于繪制數據進行初步清理,也就是單點的誤差的全部清除,當然,我這種方式還不夠嚴謹,剩下的大家可以根據具體需求進行調整:

onScaleEnd: (details) {switch (_gestureType) {case GestureType.translate:// 移除由于誤操作導致的小點出現final lastBounds = _paintedBoardProvider.strokes.last.path.getBounds();if (lastBounds.width < 0.5 && lastBounds.height < 0.5) {_paintedBoardProvider.strokes.removeLast();_paintedBoardProvider.refreshPaintedBoard();} else {widget._invoker.execute(PaintedCommand(_paintedBoardProvider, _paintedBoardProvider.strokes.last));}break;case GestureType.scale:print("onScaleEnd:縮放結束");break;}},

總結

以上是生活随笔為你收集整理的手绘板的制作——画布缩放(4)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。