<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # Android 多點觸控詳解 Android 多點觸控詳解,在前面的幾篇文章中我們大致了解了 Android 中的事件處理流程和一些簡單的處理方案,本次帶大家了解 Android 多點觸控相關的一些知識。 **多點觸控** ( **Multitouch**,也稱 **Multi-touch** ),即同時接受屏幕上多個點的人機交互操作,多點觸控是從 Android 2.0 開始引入的功能,在 Android 2.2 時對這一部分進行了重新設計。 在本文開始之前,先回顧一下 [MotionEvent詳解][motionevent] 中提到過的內容: - Android 將所有的事件都封裝進了 `Motionvent` 中。 - 我們可以通過復寫 `onTouchEvent` 或者設置 `OnTouchListener` 來獲取 View 的事件。 - 多點觸控獲取事件類型請使用 `getActionMasked()` 。 - 追蹤事件流請使用 `PointId`。 **多點觸控相關的事件:** | 事件 | 簡介 | | --------------------------- | ------------------------------ | | ACTION_DOWN | **第一個** 手指 **初次接觸到屏幕** 時觸發。 | | ACTION_MOVE | 手指 **在屏幕上滑動** 時觸發,會多次觸發。 | | ACTION_UP | **最后一個** 手指 **離開屏幕** 時觸發。 | | **ACTION_POINTER_DOWN** | 有非主要的手指按下(**即按下之前已經有手指在屏幕上**)。 | | **ACTION_POINTER_UP** | 有非主要的手指抬起(**即抬起之后仍然有手指在屏幕上**)。 | | 以下事件類型不推薦使用 | ---以下事件在 2.2 版本以上被標記為廢棄--- | | ~~ACTION_POINTER\_1\_DOWN~~ | 第 2 個手指按下,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_2\_DOWN~~ | 第 3 個手指按下,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_3\_DOWN~~ | 第 4 個手指按下,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_1\_UP~~ | 第 2 個手指抬起,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_2\_UP~~ | 第 3 個手指抬起,已廢棄,不推薦使用。 | | ~~ACTION_POINTER\_3\_UP~~ | 第 4 個手指抬起,已廢棄,不推薦使用。 | **多點觸控相關的方法:** | 方法 | 簡介 | | ------------------------------- | ---------------------------------------- | | getActionMasked() | 與 `getAction()` 類似,**多點觸控需要使用這個方法獲取事件類型**。 | | getActionIndex() | 獲取該事件是哪個指針(手指)產生的。 | | getPointerCount() | 獲取在屏幕上手指的個數。 | | getPointerId(int pointerIndex) | 獲取一個指針(手指)的唯一標識符ID,在手指按下和抬起之間ID始終不變。 | | findPointerIndex(int pointerId) | 通過PointerId獲取到當前狀態下PointIndex,之后通過PointIndex獲取其他內容。 | | getX(int pointerIndex) | 獲取某一個指針(手指)的X坐標 | | getY(int pointerIndex) | 獲取某一個指針(手指)的Y坐標 | 回顧完畢,開始正文。 ## 一、多點觸控相關問題 在引入多點觸控之前,事件的類型很少,基本事件類型只有按下(down)、移動(move) 和 抬起(up),即便加上那些特殊的事件類型也只有幾種而已,所以我們可以用幾個常量來標記這些事件,在使用的時候使用 `getAction()` 方法來獲取具體的事件,之后和這些常量進行對比就行了。 在 Android 2.0 版本的時候,開始引入多點觸控技術,由于技術上并不成熟,硬件和驅動也跟不上,多數設備只能支持追蹤兩三個點而已,因此在設計 API 上采取了一種簡單粗暴的方案,添加了幾個常量用于多點觸控的事件類型的判斷。 | 事件 | 簡介 | | ----------------------- | -------------------- | | ACTION_POINTER\_1\_DOWN | 第 2 個手指按下,已廢棄,不推薦使用。 | | ACTION_POINTER\_2\_DOWN | 第 3 個手指按下,已廢棄,不推薦使用。 | | ACTION_POINTER\_3\_DOWN | 第 4 個手指按下,已廢棄,不推薦使用。 | | ACTION_POINTER\_1\_UP | 第 2 個手指抬起,已廢棄,不推薦使用。 | | ACTION_POINTER\_2\_UP | 第 3 個手指抬起,已廢棄,不推薦使用。 | | ACTION_POINTER\_3\_UP | 第 4 個手指抬起,已廢棄,不推薦使用。 | 這些事件類型是用來判斷非主要手指(第一個按下的稱為主要手指)的按下和抬起,使用起來大概是這樣子: ```java switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_UP: break; case MotionEvent.ACTION_MOVE: break; case MotionEvent.ACTION_POINTER_1_DOWN: break; case MotionEvent.ACTION_POINTER_2_DOWN: break; case MotionEvent.ACTION_POINTER_3_DOWN: break; case MotionEvent.ACTION_POINTER_1_UP: break; case MotionEvent.ACTION_POINTER_2_UP: break; case MotionEvent.ACTION_POINTER_3_UP: break; } ``` 看到這里可能會產生以下的一些疑問? ### 1.為什么沒有 ACTION_POINTER_X_MOVE ? 在多指觸控中所有的移動事件都是使用 `ACTION_MOVE`, 并沒有追蹤某一個手指的 move 事件類型,個人猜測主要是因為:**很難無歧義的實現單獨追蹤每一個手指。** 要理解這個,首先要明白設備是如何識別多點觸控的,設備沒有眼睛,不能像我們人一樣看到有幾個手指(或者觸控筆)在屏幕上。 目前大多數 Android 設備都是電容屏,它們感知觸摸是利用手指(觸控筆)與屏幕接觸產生的微小電流變化,之后通過計算這些電流變化來得出具體的觸摸位置,在多點觸控中,當兩個觸摸點足夠靠近時,設備實際上是無法分清這兩個點的。因此當兩個觸摸點靠近(重合)后再分開,設備很可能就無法正確的追蹤兩個點了,所以也很難實現無歧義的追蹤每一個點。 并且從軟件上來說,事件的編號產生和復用也是一個大問題,例如下面的場景: | 事件 | 手指數量 | 編號變化 | | ------------ | :--: | ------------------------- | | 一個手指按下(命名為A) | 1 | A手指的編號為0,id為0 | | 一個手指按下(命名為B) | 2 | B手指的編號為1,id為1 | | A手指抬起 | 1 | B手指編號變更為0,id不變為1 | | 一個手指按下(命名為C) | 2 | C手指編號為0,id為0,B手指編號為1,id為1 | 注意觀察上面編號和id的變化,有兩個問題,**1、B手指的編號變化了。2、A手指和C手指id是相同的(A手指抬起后,C手指按下替代了A手指)。**所以這就引出了一個問題:如果存在 ACTION_POINTER_X_MOVE,那么X應該用什么標志呢?編號會變化,id雖然不會變化,但id會被復用,例如A手指抬起后C手指按下,C手指復用了A手指的id。所以不論使用哪一個都不能保證唯一性。 當然了,解決問題最好的方式就是把問題拋出去,既然從硬件和軟件上都不能保證唯一性和不變性,就不做區分了,因此所有的 move 事件都是 `ACTION_MOVE`, 具體是哪個手指產生的 move 用戶可以結合其他事件(按下和抬起)來綜合判斷。 ### 2.超過4個手指怎么辦? **2.0 兼容版**,在2.2 之前的設計中,其提供的常量最多能判斷四個手指的抬起和落下,當超過四個手指時怎么辦呢? 由于在 2.2 版本之前,由于沒有 `getActionMasked` 方法,我們可以自己自己手動進行計算,例如下面這樣 : ```java String TAG = "Gcs"; int action = event.getAction() & MotionEvent.ACTION_MASK; int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; switch (action) { case MotionEvent.ACTION_DOWN: Log.e(TAG,"第1個手指按下"); break; case MotionEvent.ACTION_UP: Log.e(TAG,"最后1個手指抬起"); break; case MotionEvent.ACTION_POINTER_1_DOWN: // 此時相當于 ACTION_POINTER_DOWN Log.e(TAG,"第"+(index+1)+"個手指按下"); break; case MotionEvent.ACTION_POINTER_1_UP: // 此時相當于 ACTION_POINTER_UP Log.e(TAG,"第"+(index+1)+"個手指抬起"); break; } ``` 在上面的例子中有幾點比較關鍵: #### 2.1、action 與 Index 的獲得 我們在 [MotionEvent詳解][motionevent] 中了解過,Android中的事件一般用最后8位來表示事件類型,再往前8位來表示Index。 例如多指觸控的按下事件,其事件類型是 0x000000**05**, 其Index標志位是 0x0000**00**05,隨著更多的手指按下,其中變化的部分是 Index 標志位,最后兩位是始終不變的,所以我們只要能將這兩個分離開就行了。 **取得事件類型(action)** ```java // 獲取事件類型 int action = event.getAction() & MotionEvent.ACTION_MASK; ``` 這個非常簡單,ACTION_MASK=0x000000ff, 與 getAction() 進行按位與操作后保留最后8位內容(十六進制每一個字符轉化為二進制是4位)。 例如: 0x000001**05** & 0x000000ff = 0x000000**05** **取得事件索引(index)** ```java // 獲取index編號 int index = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; ``` ACTION_POINTER_INDEX_MASK = 0x0000ff00 ACTION_POINTER_INDEX_SHIFT = 8 首先讓 getAction() 與 ACTION_POINTER_INDEX_MASK 按位與之后,只保留 Index 那8位,之后再右移8位,最終就拿到了 Index 的真實數值。 例如: 0x0000**01**05 & 0x0000ff00 = 0x0000**01**00 0x0000**01**00 >> 8 = 0x000000**01** #### 2.2、用 ACTION\_POINTER\_1\_DOWN 代替 ACTION\_POINTER\_DOWN 這是因為在 2.0 版本的時候還沒有 ACTION\_POINTER\_DOWN 的這個常量,但是它們兩個點數值是相同的,都是 0x00000005,這個你可以查看官方文檔或者源碼,甚至你直接寫 `case 0x00000005` 也行,抬起也是同理。 #### 2.3、只考慮兼容 2.2 以上的版本 當然了,如果你不需要兼容 2.0 版本,只需要兼容到 2.2 以上的話就很簡單了,像下面這樣: ```java String TAG = "Gcs"; int index = event.getActionIndex(); switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: Log.e(TAG,"第1個手指按下"); break; case MotionEvent.ACTION_UP: Log.e(TAG,"最后1個手指抬起"); break; case MotionEvent.ACTION_POINTER_DOWN: Log.e(TAG,"第"+(index+1)+"個手指按下"); break; case MotionEvent.ACTION_POINTER_UP: Log.e(TAG,"第"+(index+1)+"個手指抬起"); break; } ``` ### 3. index 和 pointId 的變化規則 在 2.2 版本以上,我們可以通過 getActionIndex() 輕松獲取到事件的索引(Index),但是這個事件索引的變化還是有點意思的,Index 變化有以下幾個特點: 1、從 0 開始,自動增長。 2、如果之前落下的手指抬起,后面手指的 Index 會隨之減小。 3、Index 變化趨向于第一次落下的數值(落下手指時,前面有空缺會優先填補空缺)。 4、對 move 事件無效。 下面我們逐條解釋一下具體含義。 #### 3.1、從 0 開始,自動增長。 這一條非常簡單,也很容易理解,而且在 [MotionEvent詳解][motionevent] 中講解 getAction() 與 getActionMasked() 也簡單說過。 | 手指按下 | 觸發事件(數值) | | :-----: | :--------------------------------------- | | 第1個手指按下 | ACTION_DOWN (0x0000**00**00) | | 第2個手指按下 | ACTION_POINTER_DOWN (0x0000**01**05) | | 第3個手指按下 | ACTION_POINTER_DOWN (0x0000**02**05) | | 第4個手指按下 | ACTION_POINTER_DOWN (0x0000**03**05) | 注意加粗的位置,數值隨著手指按下而不斷變大。 #### 3.2、如果之前落下的手指抬起,后面手指的 Index 會隨之減小。 這個也比較容易理解,像下面這樣: | 手指按下 | 觸發事件(數值) | | :-----: | :--------------------------------------- | | 第1個手指按下 | ACTION_DOWN (0x0000**00**00) | | 第2個手指按下 | ACTION_POINTER_DOWN (0x0000**01**05) | | 第3個手指按下 | ACTION_POINTER_DOWN (0x0000**02**05) | | 第2個手指抬起 | ACTION_POINTER_UP (0x0000**01**06) | | 第3個手指抬起 | ACTION_POINTER_UP (0x0000**01**06) | 注意最后兩次觸發的事件,它的 Index 都是 1,這樣也比較容易解釋,當原本的第 2 個手指抬起后,屏幕上就只剩下兩個手指了,之前的第 3 個手指就變成了第 2 個,于是抬起時觸發事件的 Index 為 1,即之前落下的手指抬起,后面手指的 Index 會隨之減小。 #### 3.3、Index 變化趨向于第一次落下的數值(落下手指時,前面有空缺會優先填補空缺)。 這個就有點神奇了,通過上一條規則,我們知道,某一個手指的 Index 可能會隨著其他手指的抬起而變小,這次我們用 4 個手指測試一下 Index 的變化趨勢。 | 手指按下 | 觸發事件(數值) | | :---------: | :--------------------------------------- | | 第1個手指按下 | ACTION_DOWN (0x0000**00**00) | | 第2個手指按下 | ACTION_POINTER_DOWN (0x0000**01**05) | | **第3個手指按下** | ACTION_POINTER_DOWN (0x0000**02**05) | | 第2個手指抬起 | ACTION_POINTER_UP (0x0000**01**06) | | ~~第3個手指抬起~~ | ~~ACTION_POINTER_UP~~ ~~(0x0000**01**06)~~ | | 第4個手指按下 | ACTION_POINTER_DOWN (0x0000**01**05) | | **第3個手指抬起** | ACTION_POINTER_UP (0x0000**02**06) | 這個要和上一個對比這看,**重點觀察第 3 個手指所觸發事件區別**,在上一個示例中,隨著第 2 個手指的抬起,第 3 個手指變化為第 2(01) 個,所以抬起時觸發的是第 2 根手指的抬起事件(刪除線部分)。 但是,如果第 2 個手指抬起后,落在屏幕上另外一個手指會怎樣?經過測試,發現另外**落下的手指會替代之前第 2 個手指的位置,系統判定為 2(01),而不是順延下去變成 3(02),并且原本第3個手指的index變為原來數值(02)**,但是如果繼續落下其他的手指,數值則會順延。 **即手指抬起時的 Index 會趨向于和按下時相同,雖然在手指數量不足時,Index 會變小,但是當手指變多時,Index 會趨向于保持和按下時一樣。** > PS:由于程序是從0開始計數的,所以 0 就是 1, 1 就是 2 ... #### 3.4、對 move 事件無效。 這個也比較容易理解,我們所取得的 Index 屬性實際上是從事件上分離下來的,但是 move 事件始終為 0x0000**00**02,也就是說,在 move 時不論你移動哪個手指,使用 `getActionIndex()` 獲取到的始終是數值 0。 既然 move 事件無法用事件索引(Index)區別,那么該如何區分 move 是那個手指發出的呢?這就要用到 pointId 了,**pointId 和 index 最大的區別就是 pointId 是不變的,始終為第一次落下時生成的數值,不會受到其他手指抬起和落下的影響。** #### 3.5、pointId 與 index 的異同。 相同點: - 從 0 開始,自動增長。 - 落下手指時優先填補空缺(填補之前抬起手指的編號)。 不同點: - Index 會變化,pointId 始終不變。 ### 4. Move 相關事件 #### 4.1 actionIndex 與 pointerIndex 在 move 中無法取得 actionIndex 的,我們需要使用 pointerIndex 來獲取更多的信息,例如某個手指的坐標: ```java getX(int pointerIndex) getY(int pointerIndex) ``` **但是這個 pointerIndex 又是什么呢?和 actionIndex 有區別么?** 實際上這個 pointerIndex 和 actionIndex 區別并不大,兩者的數值是相同的,你可以認為 pointerIndex 是特地為 move 事件準備的 actionIndex。 #### 4.2 pointerIndex 與 pointerId | 類型 | 簡介 | | ------------ | ----------------------------- | | pointerIndex | 用于獲取具體事件,可能會隨著其他手指的抬起和落下而變化 | | pointerId | 用于識別手指,手指按下時產生,手指抬起時回收,期間始終不變 | 這兩個數值使用以下兩個方法相互轉換。 | 方法 | 簡介 | | ------------------------------- | ---------------------------------------- | | getPointerId(int pointerIndex) | 獲取一個指針(手指)的唯一標識符ID,在手指按下和抬起之間ID始終不變。 | | findPointerIndex(int pointerId) | 通過 pointerId 獲取到當前狀態下 pointIndex,之后通過 pointIndex 獲取其他內容。 | > 通常情況下,pointerIndex 和 pointerId 是相同的,但也可能會因為某些手指的抬起而變得不同。 #### 4.3 遍歷多點觸控 先來一個簡單的,遍歷出多個手指的 move 事件: ```java String TAG = "Gcs"; switch (event.getActionMasked()) { case MotionEvent.ACTION_MOVE: for (int i = 0; i < event.getPointerCount(); i++) { Log.i("TAG", "pointerIndex="+i+", pointerId="+event.getPointerId(i)); // TODO } } ``` 通過遍歷 pointerCount 獲取到所有的 pointerIndex,同時通過 pointerIndex 來獲取 pointerId,可以通過不同手指抬起和按下后移動來觀察 pointerIndex 和 pointerId 的變化。 #### 4.4 在多點觸控中追蹤單個手指 要實現追蹤單個手指還是有些麻煩的,需要同時使用上 actionIndex, pointerId 和 pointerIndex,例如,我們只追蹤第2個手指,并畫出其位置: ```java /** * 繪制出第二個手指第位置 */ public class MultiTouchTest extends CustomView { String TAG = "Gcs"; // 用于判斷第2個手指是否存在 boolean haveSecondPoint = false; // 記錄第2個手指第位置 PointF point = new PointF(0, 0); public MultiTouchTest(Context context) { this(context, null); } public MultiTouchTest(Context context, AttributeSet attrs) { super(context, attrs); mDeafultPaint.setAntiAlias(true); mDeafultPaint.setTextAlign(Paint.Align.CENTER); mDeafultPaint.setTextSize(30); } @Override public boolean onTouchEvent(MotionEvent event) { int index = event.getActionIndex(); switch (event.getActionMasked()) { case MotionEvent.ACTION_POINTER_DOWN: // 判斷是否是第2個手指按下 if (event.getPointerId(index) == 1){ haveSecondPoint = true; point.set(event.getY(), event.getX()); } break; case MotionEvent.ACTION_POINTER_UP: // 判斷抬起的手指是否是第2個 if (event.getPointerId(index) == 1){ haveSecondPoint = false; point.set(0, 0); } break; case MotionEvent.ACTION_MOVE: if (haveSecondPoint) { // 通過 pointerId 來獲取 pointerIndex int pointerIndex = event.findPointerIndex(1); // 通過 pointerIndex 來取出對應的坐標 point.set(event.getX(pointerIndex), event.getY(pointerIndex)); } break; } invalidate(); // 刷新 return true; } @Override protected void onDraw(Canvas canvas) { canvas.save(); canvas.translate(mViewWidth/2, mViewHeight/2); canvas.drawText("追蹤第2個按下手指的位置", 0, 0, mDeafultPaint); canvas.restore(); // 如果屏幕上有第2個手指則繪制出來其位置 if (haveSecondPoint) { canvas.drawCircle(point.x, point.y, 50, mDeafultPaint); } } } ``` 這段代碼也非常短,其核心就是通過判斷數值為 1 的 pointerId 是否存在,如果存在就在 move 的時候取出其坐標,并繪制出來。 ![SecondPoint](http://ww3.sinaimg.cn/large/006y8lVagw1fbs1vkt1mwj30bo0jq3z3.jpg) > 雖然邏輯簡單,但個人感覺寫起來還是有些麻煩,如果有更簡單的方案歡迎告訴我。 ## 二、如何使用多點觸控 多點觸控應用還是比較廣泛的,至少目前大部分的圖片查看都需要用到多點觸控技術(用于拖動和縮放圖片)。 但是在某些看似不需要多觸控的地方也需要對多點觸控進行判斷,只要是多點觸控可能引起錯誤的地方都應該加上多點觸控的判斷。例如使用到 move 事件的時候,由于 move 事件可能由多個手指同時觸發,所以可能會出現同時被多個手指控制的情況,如果不適當的處理,這個 move 就可能由任何一個手指觸發。 舉一個簡單的例子: 如果我們需要一個**可以用單指拖動的圖片**。假如我們不進行多指觸控的判斷,像下面這樣: **沒有針對多指觸控處理版本:** ```java /** * 一個可以拖圖片動的 View */ public class DragView1 extends CustomView { String TAG = "Gcs"; Bitmap mBitmap; // 圖片 RectF mBitmapRectF; // 圖片所在區域 Matrix mBitmapMatrix; // 控制圖片的 matrix boolean canDrag = false; PointF lastPoint = new PointF(0, 0); public DragView1(Context context) { this(context, null); } public DragView1(Context context, AttributeSet attrs) { super(context, attrs); // 調整圖片大小 BitmapFactory.Options options = new BitmapFactory.Options(); options.outWidth = 960/2; options.outHeight = 800/2; mBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.drag_test, options); mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight()); mBitmapMatrix = new Matrix(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // 判斷按下位置是否包含在圖片區域內 if (mBitmapRectF.contains((int)event.getX(), (int)event.getY())){ canDrag = true; lastPoint.set(event.getX(), event.getY()); } break; case MotionEvent.ACTION_UP: canDrag = false; case MotionEvent.ACTION_MOVE: if (canDrag) { // 移動圖片 mBitmapMatrix.postTranslate(event.getX() - lastPoint.x, event.getY() - lastPoint.y); // 更新上一次點位置 lastPoint.set(event.getX(), event.getY()); // 更新圖片區域 mBitmapRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); mBitmapMatrix.mapRect(mBitmapRectF); invalidate(); } break; } return true; } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, mBitmapMatrix, mDeafultPaint); } } ``` 這個版本非常簡單,當然了,如果正常使用(只使用一個手指)的話也不會出問題,但是當使用多個手指,且有抬起和按下的時候就可能出問題,下面用一個典型的場景演示一下: ![dragview1](http://ww2.sinaimg.cn/large/006y8lVagw1fbs1vm2rppg308c0d4kfm.gif) 注意在第二個手指按下,第一個手指抬起時,此時原本的第二個手指會被識別為第一個,所以圖片會直接跳動到第二個手指位置。 為了不出現這種情況,我們可以判斷一下 pointId 并且只獲取第一個手指的數據,這樣就能避免這種情況發生了,如下。 **針對多指觸控處理后版本:** ```java /** * 一個可以拖圖片動的 View */ public class DragView extends CustomView { String TAG = "Gcs"; Bitmap mBitmap; // 圖片 RectF mBitmapRectF; // 圖片所在區域 Matrix mBitmapMatrix; // 控制圖片的 matrix boolean canDrag = false; PointF lastPoint = new PointF(0, 0); public DragView(Context context) { this(context, null); } public DragView(Context context, AttributeSet attrs) { super(context, attrs); BitmapFactory.Options options = new BitmapFactory.Options(); options.outWidth = 960/2; options.outHeight = 800/2; mBitmap = BitmapFactory.decodeResource(this.getResources(), R.drawable.drag_test, options); mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight()); mBitmapMatrix = new Matrix(); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: // ▼ 判斷是否是第一個手指 && 是否包含在圖片區域內 if (event.getPointerId(event.getActionIndex()) == 0 && mBitmapRectF.contains((int)event.getX(), (int)event.getY())){ canDrag = true; lastPoint.set(event.getX(), event.getY()); } break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: // ▼ 判斷是否是第一個手指 if (event.getPointerId(event.getActionIndex()) == 0){ canDrag = false; } break; case MotionEvent.ACTION_MOVE: // 如果存在第一個手指,且這個手指的落點在圖片區域內 if (canDrag) { // ▼ 注意 getX 和 getY int index = event.findPointerIndex(0); // Log.i(TAG, "index="+index); mBitmapMatrix.postTranslate(event.getX(index)-lastPoint.x, event.getY(index)-lastPoint.y); lastPoint.set(event.getX(index), event.getY(index)); mBitmapRectF = new RectF(0,0,mBitmap.getWidth(), mBitmap.getHeight()); mBitmapMatrix.mapRect(mBitmapRectF); invalidate(); } break; } return true; } @Override protected void onDraw(Canvas canvas) { canvas.drawBitmap(mBitmap, mBitmapMatrix, mDeafultPaint); } } ``` 可以看到,比起上一個版本,只添加了少量代碼,就變得更加“智能”了,可以準確識別某一個手指,不會因為手指抬起而認錯手指。 ![dragview2](http://ww2.sinaimg.cn/large/006y8lVagw1fbs1vmwpu4g308c0d4nf9.gif) **重點注意最后,第一個手指抬起之后,圖片并沒有跳躍到第二個手指的位置。** 上面的兩個對比示例都精簡到了極致,其核心依舊是正確的追蹤某一個手指,建議大家自己寫一遍體會一下。 ------ 我感覺很多人看到這里依舊是不明所以的,一些簡單的東西還好弄,但是復雜一些,如同時處理多個手指的數值就有些困難了,**假如說你之前沒有接觸過多點觸控的處理,此時讓你實現用兩個手指來縮放圖片還是有些困難的。** 因為這不僅要追蹤兩個手指的位置,還要根據位置變化來計算縮放比例和縮放中心,單單這兩個非常簡單的數學問題就能難倒一大批人。 ![-510a02a1a3f8d16d](http://ww2.sinaimg.cn/large/006y8lVagw1fbs1vnfni1g307b07ct8m.gif) 當然了,很多麻煩問題都有簡單的解決方案,假如說我們真的要實現一個可以用兩個或者多個手指縮放的控件,何必要自己算呢,可以嘗試一下 Android 自帶的解決方案:**手勢檢測(GestureDetector)**,不僅能自動幫你計算好縮放比例和縮放中心,而且還可以檢測出 單擊、長按、滑屏 等不同的手勢,不過這就不是本篇的事情了,以后有時間會寫一下有關手勢檢測的用法(繼續挖坑)。 ## 三、總結 前段時間因為各種事情比較忙,這篇文章也沒時間去寫,所以就一直拖到了現在,期間收到不少讀者催更,實在是抱歉了。今后在會盡量保證穩定更新的,爭取盡快把自定義View系列這一個大坑填完。 ˊ_>ˋ 關于多點觸控,個人認為還算一個比較重要的知識點。尤其是隨著 Android 的發展,很多炫酷的交互操作可能會需要用戶進行拖拽操作。在進行這類操作的時候進行一下手指的判斷還是相當重要的。 本文中需要注意的幾個知識點: - 如何兼容 2.0 版本的多點觸控(目前大部分都不需要兼容 2.0 了吧)。 - actionIndex、pointIndex 與 pointId 的區別和用法。 - 如何在多點觸控中正確的追蹤一個手指。 ## About Me ### 作者微博: <a href="http://weibo.com/GcsSloop" target="_blank">@GcsSloop</a> <a href="http://www.gcssloop.com/info/about" target="_blank"><img src="http://ww4.sinaimg.cn/large/005Xtdi2gw1f1qn89ihu3j315o0dwwjc.jpg" width="300" style="display:inline;" /></a> ## 參考資料 [MotionEvent ](https://developer.android.com/reference/android/view/MotionEvent.html) [motionevent]: http://www.gcssloop.com/customview/motionevent
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看