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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

缩放手势 ScaleGestureDetector 源码解析,这一篇就够了

發布時間:2023/12/9 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 缩放手势 ScaleGestureDetector 源码解析,这一篇就够了 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

其實在我們日常的編程中,對于縮放手勢的使用并不是很經常,這一手勢主要是用在圖片瀏覽方面,比如下方例子。但是(敲重點),作為 Android 入門的基礎來說,學習 ScaleGestureDetector 的使用,算是不得不過的一道坎,好在 ScaleGestureDetector 使用起來非常簡單,就是源碼分析上得花些功夫。

本文首先將簡單的介紹下 ScaleGestureDetector 的使用,在重點給大家分析下源碼(由于源碼方面是我自己的理解,可能有偏差,希望各位大佬能在評論區指出,萬分感謝~)


ScaleGestureDetector 使用

ScaleGestureDetector 包括一個監聽器,以及它所有方法的空實現:

名稱用途
ScaleGestureDetector縮放手勢的監聽器
SimpleOnScaleGestureListener該監聽器的空實現,在其中重寫方法

ScaleGestureDetector 方法

名稱用途
onScaleBegin當 >= 2 個手指碰觸屏幕時調用,若返回 false 則忽略改事件調用
onScale滑動(縮放)過程中調用,若成功處理,則用戶返回 true,監聽器繼續記錄下一個縮放等動作,若為 false 表明數據未處理,則監聽器繼續積累
onScaleEnd全部手指離開屏幕,結束監聽

通常情況下,手勢監聽會結合自定義 View 來講,這里我給出一個最簡單的使用,具體的使用實例,以后再結合自定義 View 講講。

private void iniScaleGestureListener(){mListener = new ScaleGestureDetector.SimpleOnScaleGestureListener(){@Overridepublic boolean onScaleBegin(ScaleGestureDetector detector) {return super.onScaleBegin(detector);}@Overridepublic boolean onScale(ScaleGestureDetector detector) {MyLog.d("X:" + detector.getFocusX());MyLog.d("Y:" + detector.getFocusY());MyLog.d("scale:" + detector.getScaleFactor());return super.onScale(detector);}@Overridepublic void onScaleEnd(ScaleGestureDetector detector) {super.onScaleEnd(detector);}};detector = new ScaleGestureDetector(getContext(), mListener);}@Overridepublic boolean onTouchEvent(MotionEvent event) {detector.onTouchEvent(event);return true;}

ScaleGestureDetector 的使用

ScaleGestureDetector 在具體項目的使用有點復雜,我打算過段時間結合自定義 View 寫一篇用來總結,所以這篇我們就先了解下 ScaleGestureDetector 的基本使用。


ScaleGestureDetector 源碼分析

好了,現在我們進入本章重點,ScaleGestureDetector 源碼分析,敲黑板敲黑板。首先,我們打開 ScaleGestureDetector 的源碼可以看到,幾乎所有的代碼都集中在了 onTouchEvent 這個方法上,所以在這里,我就主要給大家介紹這個方法的實現。

第一部分:前期準備

if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}mCurrTime = event.getEventTime();final int action = event.getActionMasked();// Forward the event to check for double tap gestureif (mQuickScaleEnabled) {mGestureDetector.onTouchEvent(event);}final int count = event.getPointerCount();final boolean isStylusButtonDown =(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;

mInputEventConsistencyVerifier

  • 輸入事件一致性驗證器 @有道
  • 根據名字以及前面的定義
  • 我們可以猜測這個對象應該是手勢監聽 Event 是否注冊(連接到硬件)
  • 所以,如果他為空,那么我們在這里調用 onTouchEvent 進行注冊
if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(event, 0);}

mCurrTime

  • 獲得事件發生時的時間
mCurrTime = event.getEventTime();

action

  • 獲得事件類型
final int action = event.getActionMasked();

mQuickScaleEnabled

  • Forward the event to check for double tap gesture
  • @有道 轉發事件以檢查雙擊手勢
  • 首先是 mQuickScaleEnabled 這個對象
  • 翻譯過來是: @有道 啟用快速擴展
  • 作用大概就是調用雙擊監聽事件,比如雙擊最大化
if (mQuickScaleEnabled) {mGestureDetector.onTouchEvent(event);}

count

  • 獲得屏幕上手指的數目
final int count = event.getPointerCount();

isStylusButtonDown

這個主要是由于判斷手寫筆是否按下
由于我們很少處理手寫筆,所以這里不做過多說明

final boolean isStylusButtonDown =(event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;

## 第二部分:處理與手勢變化

用戶的縮放手勢不總是一定的,就是說對于用戶而言,隨時可能有手指碰觸或離開屏幕,這就使得縮放中心的(焦點)隨時可能發生變化,這部分主要是用來處理這一變化,并做出響應。

final boolean anchoredScaleCancelled =mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;final boolean streamComplete = action == MotionEvent.ACTION_UP ||action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;// 如果發生了上面這種小動作,或者說有一手指離開了屏幕,進行調用if (action == MotionEvent.ACTION_DOWN || streamComplete) {// Reset any scale in progress with the listener.// If it's an ACTION_DOWN we're beginning a new event stream.// This means the app probably didn't give us all the events. Shame on it.if (mInProgress) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;} else if (inAnchoredScaleMode() && streamComplete) {mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}if (streamComplete) {return true;}}

### anchoredScaleCancelled

  • @Google 錨定規模取消
  • 我的理解是:用于判斷滑動事件是否被取消
final boolean anchoredScaleCancelled =mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;

streamComplete

  • @Google Translate: 流完成
  • 我的理解是,這個布爾變量用于標記
  • 當前動作是否完成
  • 我這里說的動作有兩種
  • 這里指的是:在大動作如三指觸屏放大過程中,又一個手指離開了屏幕這種
  • 在大動作三指觸屏中發生的一個小動作,離開一指
final boolean streamComplete = action == MotionEvent.ACTION_UP ||action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;

action == MotionEvent.ACTION_DOWN || streamComplete

  • 如果發生了上面這種小動作,或者說有一手指離開了屏幕,就進行調用
if (action == MotionEvent.ACTION_DOWN || streamComplete) {...}

if (mInProgress)

  • @google Translate:重置偵聽器正在進行的任何縮放。
  • 如果是ACTION_DOWN,我們開始一個新的事件流。
  • 這意味著應用程序可能沒有給我們所有的事件。很遺憾。
  • 首先判斷該進程(從第一個手指碰上屏幕,到最后一個手指離開屏幕為止)是否結束
  • 如果仍在運行中,這調用回調方法:onScaleEnd 使其結束
if (mInProgress) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}

else if (inAnchoredScaleMode() && streamComplete)

  • 如果當前進程已經結束
  • 判斷 mAnchoredScaleMode 是否為 ANCHORED_SCALE_MODE_STYLUS 狀態
  • 同時判斷操作流 streamComplete 是否完成
  • 都符合的情況下結束這一手勢變化
else if (inAnchoredScaleMode() && streamComplete) {mInProgress = false;mInitialSpan = 0;mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;}

if (streamComplete)

  • 結束本次 onTouchEvent 方法的調用,等待下一次調用發生
if (streamComplete) {return true;}

總結: 可以看到,當觸發 down 或者觸發 up,cancel 時,如果之前處于縮放計算的狀態,會將其狀態重置, 并調用 onScaleEnd 方法。


進入錨定比例模式

  • 當判斷用戶動作,如果為雙擊這類點擊事件,進入該模式
  • 與正常縮放區分。這個模式功能一般是:雙擊最大化和最小化
if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()&& !streamComplete && isStylusButtonDown) {// Start of a button scale gesturemAnchoredScaleStartX = event.getX();mAnchoredScaleStartY = event.getY();mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;mInitialSpan = 0;}

mAnchoredScaleStartX & mAnchoredScaleStartY

  • 后文中將用于重新計算焦點
mAnchoredScaleStartX = event.getX();mAnchoredScaleStartY = event.getY();

mAnchoredScaleMode

  • 賦值之后,再次調用 inAnchoredScaleMode() 方法,返回值變為 true
mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;

計算縮放中心

final boolean configChanged = action == MotionEvent.ACTION_DOWN ||action == MotionEvent.ACTION_POINTER_UP ||action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;final int skipIndex = pointerUp ? event.getActionIndex() : -1;// Determine focal pointfloat sumX = 0, sumY = 0;final int div = pointerUp ? count - 1 : count;final float focusX;final float focusY;if (inAnchoredScaleMode()) {// In anchored scale mode, the focal pt is always where the double tap// or button down gesture startedfocusX = mAnchoredScaleStartX;focusY = mAnchoredScaleStartY;if (event.getY() < focusY) {mEventBeforeOrAboveStartingGestureEvent = true;} else {mEventBeforeOrAboveStartingGestureEvent = false;}} else {for (int i = 0; i < count; i++) {if (skipIndex == i) continue;sumX += event.getX(i);sumY += event.getY(i);}focusX = sumX / div;focusY = sumY / div;}

configChanged

  • 布爾類型量,標志著一個操作的完成或者結束(手指離開,手指按下)
final boolean configChanged = action == MotionEvent.ACTION_DOWN ||action == MotionEvent.ACTION_POINTER_UP ||action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;

pointerUp

  • 布爾類型量,用于判斷當前動作,是否為手指離開(抬起動作)
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;

skipIndex

  • 標記量,在是手指離開的情況下,標記離開手指
  • 在后面計算新的焦點代碼中,跳過該手指的標記點坐標,進行計算
final int skipIndex = pointerUp ? event.getActionIndex() : -1;

初始化計算所需臨時變量

// Determine focal pointfloat sumX = 0, sumY = 0;// 如果是抬起手指,則當前手指數減1,否則不變final int div = pointerUp ? count - 1 : count;final float focusX;final float focusY;

判斷是否為錨定比例模式

  • 是的話直接將點擊時記下的點,作為焦點
  • 不是的話,把所有點累加求和,除以總個數,計算平均值
if (inAnchoredScaleMode()) {// In anchored scale mode, the focal pt is always where the double tap// or button down gesture started// 在錨定比例模式中,焦點pt始終是雙擊的位置,或按下手勢開始focusX = mAnchoredScaleStartX;focusY = mAnchoredScaleStartY;if (event.getY() < focusY) {mEventBeforeOrAboveStartingGestureEvent = true;} else {mEventBeforeOrAboveStartingGestureEvent = false;}} else {for (int i = 0; i < count; i++) {if (skipIndex == i) continue;sumX += event.getX(i);sumY += event.getY(i);}focusX = sumX / div;focusY = sumY / div;}

算縮放比例

  • 計算縮放比例也很簡單,就是計算各個手指到焦點的平均距離,在用戶手指移動后用新的平均距離除以舊的平均距離,并以此計算得出縮放比例。
// Determine average deviation from focal point @Google translate float devSumX = 0, devSumY = 0;for (int i = 0; i < count; i++) {if (skipIndex == i) continue;// Convert the resulting diameter into a radius.devSumX += Math.abs(event.getX(i) - focusX);devSumY += Math.abs(event.getY(i) - focusY);}final float devX = devSumX / div;final float devY = devSumY / div;// Span is the average distance between touch points through the focal point;// i.e. the diameter of the circle with a radius of the average deviation from// the focal point.final float spanX = devX * 2;final float spanY = devY * 2;final float span;if (inAnchoredScaleMode()) {span = spanY;} else {span = (float) Math.hypot(spanX, spanY);}

計算平均偏差

  • 確定焦點的平均偏差
float devSumX = 0, devSumY = 0;for (int i = 0; i < count; i++) {if (skipIndex == i) continue;// Convert the resulting diameter into a radius.devSumX += Math.abs(event.getX(i) - focusX);devSumY += Math.abs(event.getY(i) - focusY);}final float devX = devSumX / div;final float devY = devSumY / div;

計算縮放比例

  • 跨度是通過焦點的觸摸點之間的平均距離;
  • 即圓的直徑,其半徑為平均偏差
  • 這里的 Math.hypot(spanX, spanY) 方法,相當于 sqrt(xx + yy)
final float spanX = devX * 2;final float spanY = devY * 2;final float span;if (inAnchoredScaleMode()) {span = spanY;} else {span = (float) Math.hypot(spanX, spanY);}

結束縮放事件

  • @Google Translate:根據需要調度開始/結束事件。
  • 如果配置發生更改,請通過開始通知應用重置其當前狀態
  • 一個新的比例事件流。
  • 這里就不做太多描述,主要就是:
  • 判斷是不是所有手指都離開了屏幕
  • 如果是,那么索命這個縮放進程結束了
  • 則保存當前縮放的數據
  • 調用 onScaleEnd 方法,結束當前操作
final boolean wasInProgress = mInProgress;mFocusX = focusX;mFocusY = focusY;if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {mListener.onScaleEnd(this);mInProgress = false;mInitialSpan = span;}if (configChanged) {mPrevSpanX = mCurrSpanX = spanX;mPrevSpanY = mCurrSpanY = spanY;mInitialSpan = mPrevSpan = mCurrSpan = span;}

觸發 onScaleBegin 開始縮放

  • 當手指移動的距離超過一定數值(數值大小由系統定義)后,會觸發 onScaleBegin 方法
  • 如果用戶在 onScaleBegin 方法里面返回了 true,則接受事件后,就會重置縮放相關數值,并且開始積累縮放因子。
final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;if (!mInProgress && span >= minSpan &&(wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {mPrevSpanX = mCurrSpanX = spanX;mPrevSpanY = mCurrSpanY = spanY;mPrevSpan = mCurrSpan = span;mPrevTime = mCurrTime;mInProgress = mListener.onScaleBegin(this);}

通知用戶進行縮放處理

  • @ Google Translate: 處理動作;焦點和跨度/比例因子正在發生變化。
  • 這塊代碼的功能主要就是通知用戶(編程者)
  • 根據這些數據進行縮放
if (action == MotionEvent.ACTION_MOVE) {mCurrSpanX = spanX;mCurrSpanY = spanY;mCurrSpan = span;boolean updatePrev = true;if (mInProgress) {updatePrev = mListener.onScale(this);}if (updatePrev) {mPrevSpanX = mCurrSpanX;mPrevSpanY = mCurrSpanY;mPrevSpan = mCurrSpan;mPrevTime = mCurrTime;}}

updatePrev

  • 這個用于接收用戶的返回值
  • 只要我們放回 true ,系統就會保存當前數據
  • 重新獲取并計算新的數據和比例
  • 系統默認返回 false 然后進行下一次事件的計算
if (mInProgress) {updatePrev = mListener.onScale(this);}if (updatePrev) {mPrevSpanX = mCurrSpanX;mPrevSpanY = mCurrSpanY;mPrevSpan = mCurrSpan;mPrevTime = mCurrTime;}

結語

我要講的所有內容,到這里就完全結束了

由于源碼是按照我自己的理解來講的,所以難免會有一些出入

希望大家能在評論區中幫我指出,謝謝~ ?

轉載于:https://www.cnblogs.com/yuanhao-1999/p/11102806.html

總結

以上是生活随笔為你收集整理的缩放手势 ScaleGestureDetector 源码解析,这一篇就够了的全部內容,希望文章能夠幫你解決所遇到的問題。

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