【朝花夕拾】Android自定義View篇之(九)多點觸控(下)實踐出真知——實現多指拖動圖片

前言css

       轉載請聲明,轉自【https://www.cnblogs.com/andy-songwei/p/11158972.html】謝謝!html

       在上一篇文章中,已經總結了MotionEvent以及多點觸控相關的基礎理論知識和經常使用的函數。本篇將經過實現單指拖動圖片,多指拖動圖片的實際案例來進行練習並實現一些效果,來理解前面的理論知識。要理解本文的代碼,須要先掌握上一篇的理論知識,事件處理基礎,以及必定的自定義View基礎,這些我也在本系列文章的前幾篇中講過,有興趣的能夠按照本系列的順序依次閱讀學習,相信您必定會有不小的收穫。android

       本文的主要內容以下:canvas

 

1、實現單指拖動圖片ide

       要實現單指拖動圖片,大體思路就是監控手指的ACTION_MOVE事件。手指移動過程當中,獲取事件的座標,讓圖片根據座標的變化來進行移動。具體代碼實現以下,先自定義一個View,在其中處理單指拖動邏輯。函數

 1 public class SingleTouchDragView extends View {  2     private static final String TAG = "songzheweiwang";  3     private Bitmap mBitmap;  4     private RectF mRectF;  5     private Matrix mMatrix;  6     private Paint mPaint;  7     private PointF mLstPointF;  8     private boolean mCanDrag = false;  9 
10     public SingleTouchDragView(Context context, @Nullable AttributeSet attrs) { 11         super(context, attrs); 12  init(); 13  } 14 
15     private void init() { 16         mPaint = new Paint(); 17         mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog); 18         mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 19         mMatrix = new Matrix(); 20         mLstPointF = new PointF(); 21  } 22     
23  @Override 24     public boolean onTouchEvent(MotionEvent event) { 25         switch (event.getAction()) { 26             case MotionEvent.ACTION_DOWN: 27                 //判斷按下位置是否在圖片區域內
28                 if (mRectF.contains(event.getX(), event.getY())) { 29                     mCanDrag = true; 30  mLstPointF.set(event.getX(), event.getY()); 31  } 32                 break; 33             case MotionEvent.ACTION_UP: 34                 mCanDrag = false; 35             case MotionEvent.ACTION_MOVE: 36                 if (mCanDrag) { 37                     //移動圖片
38                     mMatrix.postTranslate(event.getX() - mLstPointF.x, event.getY() - mLstPointF.y); 39                     //更新觸摸位置
40  mLstPointF.set(event.getX(), event.getY()); 41                     // 更新圖片區域
42                     mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 43  mMatrix.mapRect(mRectF); 44                     //刷新
45  invalidate(); 46  } 47                 break; 48  } 49         //注意這裏須要返回true,由於當前自定義view繼承自基類View,默認是沒法消費觸摸事件的
50         return true; 51  } 52 
53  @Override 54     protected void onDraw(Canvas canvas) { 55         super.onDraw(canvas); 56  canvas.drawBitmap(mBitmap, mMatrix, mPaint); 57  } 58 }

代碼邏輯比較簡單,關鍵處也有這注釋說明,這裏就很少說了。使用該自定義View的佈局以下:oop

1 <?xml version="1.0" encoding="utf-8"?>
2 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
3  android:layout_width="match_parent"
4  android:layout_height="match_parent">
5 
6     <com.example.demos.customviewdemo.SingleTouchDragView 7         android:layout_width="match_parent"
8  android:layout_height="match_parent" />
9 </RelativeLayout>

用一根手指在圖片上進行拖動,效果以下左圖所示,圖片隨着手指在滑動:佈局

                

       效果很完美,但上述代碼存在一個問題,就是在單指操做的狀況下,能夠正常被拖動,可是若是是多指操做的時候,就會混亂了。右圖爲兩根手指滑動的圖片的效果,由於兩根手指都在移動, 致使ACTION_MOVE事件中,一下子以第一根手指的觸摸點爲座標,一下子又以第二根手指的觸摸點爲座標,這就致使圖片頻繁跳躍。post

 

2、實現多指操做時只有第一根手指能夠拖動圖片學習

       這一節咱們在上述代碼基礎上,實現第一根手指在拖動圖片時,另外一根手指繼續按下並拖動時無效,也就是第二根手指沒法拖動,對第一根手指沒有干擾。因爲是多點觸控,須要使用getActionMasked()來獲取事件,並監聽ACTION_POINTER_DOWN和ACTION_POINTER_UP事件。

 1 public class MultiTouchDragView extends View {  2     private static final String TAG = "songzheweiwang";  3     private Bitmap mBitmap;  4     private RectF mRectF;  5     private Matrix mMatrix;  6     private Paint mPaint;  7     private PointF mLstPointF;  8     private boolean mCanDrag = false;  9 
10     public MultiTouchDragView(Context context, @Nullable AttributeSet attrs) { 11         super(context, attrs); 12  init(); 13  } 14 
15     private void init() { 16         mPaint = new Paint(); 17         mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog); 18         mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 19         mMatrix = new Matrix(); 20         mLstPointF = new PointF(); 21  } 22     
23  @Override 24     public boolean onTouchEvent(MotionEvent event) { 25         switch (event.getActionMasked()) { 26             case MotionEvent.ACTION_DOWN: 27             case MotionEvent.ACTION_POINTER_DOWN: 28                 //pointerId爲0的手指(即咱們定義的第一根手指)按下在指定區域內
29                 if (event.getPointerId(event.getActionIndex()) == 0 && mRectF.contains(event.getX(), event.getY())) { 30                     mCanDrag = true; 31                     //getX()和getY()沒有傳入參數時,默認傳入的0
32  mLstPointF.set(event.getX(), event.getY()); 33  } 34                 break; 35             case MotionEvent.ACTION_MOVE: 36                 if (mCanDrag) { 37                     int pointerIndex = event.findPointerIndex(0);//第一根手指的pointerId爲0 38                     //這裏須要注意,多手指頻繁按下和擡起時可能會出現pointerIndex爲-1的狀況,如不處理,後面會報錯
39                     if (pointerIndex == -1) { 40                         break; 41  } 42                     mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x, 43                                              event.getY(pointerIndex) - mLstPointF.y); 44  mLstPointF.set(event.getX(pointerIndex), event.getY(pointerIndex)); 45                     mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 46  mMatrix.mapRect(mRectF); 47  invalidate(); 48  } 49                 break; 50             case MotionEvent.ACTION_POINTER_UP: 51             case MotionEvent.ACTION_UP: 52                 if (event.getPointerId(event.getActionIndex()) == 0) { 53                     mCanDrag = false; 54  } 55                 break; 56  } 57         return true; 58  } 59 
60  @Override 61     protected void onDraw(Canvas canvas) { 62         super.onDraw(canvas); 63  canvas.drawBitmap(mBitmap, mMatrix, mPaint); 64  } 65 }

       因爲咱們要實現的效果是隻有第一根手指能夠拖動圖片,因此在第29行和52行中,根據pointerId是否爲0來判斷是否須要更新界面。上一篇文章中說過,在處理多點觸控事件時,要用pointerId來跟蹤手指事件。因爲第一根手指的pointerId爲0,因此經過pointerId是否爲0來判斷是否爲第一根手指。當有多根手指在屏幕上時,第一根手指擡起再按下,它仍然被認爲是第一跟手指,此時觸發的是ACTION_POINTER_DOWN事件,因此第一根手指按下,ACTION_POINTER_DOWN和ACTION_DOWN都有可能觸發。若是判斷是第一根手指按下了,就記錄下它按下時的座標,並設置mCanDrag爲true,表示能夠滑動。而手指擡起時,多是最後一根擡起的手指,也可能不是,因此ACTION_POINTER_UP和ACTION_UP也均可能觸發。若是檢測到第一根手指擡起了,就設置mCanDrag爲false,表示圖片不可以再滑動了。在ACTION_MOVE事件中,第37行是固定使用,都須要根據findPointerIndex(int pinterId)來獲得pointerIndex,由於獲取指定手指事件座標的函數傳入的參數都是它。結合代碼中的註釋,剩下的邏輯應該就比較容易看懂了。

       效果圖以下,用兩根手指來依次按下並拖動圖片:

       咱們發現,只有第一根手指在滑動時,圖片纔會跟着移動,第二根手指(右邊的手指)的滑動無效,完美!!!

 

3、實現兩根手指共同拖動圖片

       上面實現的效果還不夠,用戶在使用中,第二根手指滑動時也能接替第一根手指繼續滑動。基本思路大體是,記錄當前活動手指的pointerId,ACTION_MOVE中以活動手指爲基礎來肯定滑動操做。仍然在上述代碼基礎上修改來實現。

 1 public class MultiTouchDragView2 extends View {  2     private static final String TAG = "songzheweiwang";  3     private Bitmap mBitmap;  4     private RectF mRectF;  5     private Matrix mMatrix;  6     private Paint mPaint;  7     private PointF mLstPointF;  8     private boolean mCanDrag = false;  9     private int mActivePointerId; 10     private final int INVALID_POINTER = -1; 11 
12     public MultiTouchDragView2(Context context, @Nullable AttributeSet attrs) { 13         super(context, attrs); 14  init(); 15  } 16 
17     private void init() { 18         mPaint = new Paint(); 19         mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.dog); 20         mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 21         mMatrix = new Matrix(); 22         mLstPointF = new PointF(); 23  } 24 
25  @Override 26     public boolean onTouchEvent(MotionEvent event) { 27         int actionIndex = event.getActionIndex(); 28         switch (event.getActionMasked()) { 29             case MotionEvent.ACTION_DOWN: 30                 //getX()和getY()沒有傳入參數時,默認傳入的0
31                 if (mRectF.contains(event.getX(), event.getY())) { 32                     mActivePointerId = 0; //第一根手指按下時,pointerId和pointerIndex都爲0
33                     mCanDrag = true; 34  mLstPointF.set(event.getX(), event.getY()); 35  } 36                 break; 37             case MotionEvent.ACTION_POINTER_DOWN: 38                 //有新落下的手指,則將新落下的手指做爲活動手指,保存下活動手指的座標
39                 mActivePointerId = event.getPointerId(actionIndex); 40  mLstPointF.set(event.getX(actionIndex), event.getY(actionIndex)); 41                 break; 42             case MotionEvent.ACTION_MOVE: 43                 if (mActivePointerId == INVALID_POINTER) { 44                     break; 45  } 46                 if (mCanDrag) {
47 //這裏根據活動手指的pointerId來找到pointerIndex,而再也不是固定的手指的pointerId了
48 int pointerIndex = event.findPointerIndex(mActivePointerId); 49 if (pointerIndex == -1) { 50 break; 51 } 52 mMatrix.postTranslate(event.getX(pointerIndex) - mLstPointF.x, 53 event.getY(pointerIndex) - mLstPointF.y); 54 mLstPointF.set(event.getX(pointerIndex), event.getY(pointerIndex)); 55 mRectF = new RectF(0, 0, mBitmap.getWidth(), mBitmap.getHeight()); 56 mMatrix.mapRect(mRectF); 57 invalidate(); 58 } 59 break; 60 case MotionEvent.ACTION_POINTER_UP: 61 //若是當前擡起的手指爲活動手指,那麼活動手指就傳給留下的手指中pointerIndex最前面的一個 62 if (mActivePointerId == event.getPointerId(actionIndex)) { 63 int newPointerIndex = actionIndex == 0 ? 1 : 0; 64 mActivePointerId = event.getPointerId(newPointerIndex); 65 mLstPointF.set(event.getX(newPointerIndex), event.getY(newPointerIndex)); 66 } 67 break; 68 case MotionEvent.ACTION_UP: 69 //最後一根手指也擡起來了 70 mActivePointerId = INVALID_POINTER; 71 mCanDrag = false; 72 break; 73 } 74 return true; 75 } 76 77 @Override 78 protected void onDraw(Canvas canvas) { 79 super.onDraw(canvas); 80 canvas.drawBitmap(mBitmap, mMatrix, mPaint); 81 } 82 }

      因爲須要依據活動的手指來拖動圖片,因此須要實時記錄下活動手指的座標,如第40、5四、65行所示。依然用兩根手指依次拖動圖片,效果以下所示:

如今能夠看到,兩根手指輪流正常拖動圖片了,毫無違和感。

 

結語

      到目前爲止,多點觸控相關的內容,我想講的已經講完了,上一篇講理論,這一篇講案例,難點其實主要就是pointerIndex和pointerId的理解和使用。但願經過這兩篇文章,對讀者理解多點觸控有所幫助。因爲文中代碼結構比較簡單,就沒有必要提供源碼了,讀者本身創建好項目,把這些代碼依次拷貝過去就能夠了,很是簡單。還有就是筆者比較窮,使用的免費軟件,因此文中gif圖上都打了水印,之後掙錢了也去享受一下付費服務,把水印給去掉。文中若是有描述不許確或不妥的地方,歡迎來拍磚,萬分感謝,Bye!!!

 

參考文章

       【Android多點觸控最佳實踐

      【安卓自定義View進階-多點觸控詳解

原文出處:https://www.cnblogs.com/andy-songwei/p/11158972.html

相關文章
相關標籤/搜索