在上一篇文章中,總結了MotionEvent以及多點觸控相關的基礎理論知識和常用的函數。本節将通過實際案例來進行練習,以及實作一些效果,來了解前面的理論知識。
前言
轉載請聲明,轉自【https://www.cnblogs.com/andy-songwei/p/11158972.html】謝謝!
在上一篇文章中,已經總結了MotionEvent以及多點觸控相關的基礎理論知識和常用的函數。本篇将通過實作單指拖動圖檔,多指拖動圖檔的實際案例來進行練習并實作一些效果,來了解前面的理論知識。要了解本文的代碼,需要先掌握上一篇的理論知識,事件處理基礎,以及一定的自定義View基礎,這些我也在本系列文章的前幾篇中講過,有興趣的可以按照本系列的順序依次閱讀學習,相信您一定會有不小的收獲。
本文的主要内容如下:
一、實作單指拖動圖檔
要實作單指拖動圖檔,大緻思路就是監控手指的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的布局如下:
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事件中,一會兒以第一根手指的觸摸點為坐标,一會兒又以第二根手指的觸摸點為坐标,這就導緻圖檔頻繁跳躍。
二、實作多指操作時隻有第一根手指可以拖動圖檔
這一節我們在上述代碼基礎上,實作第一根手指在拖動圖檔時,另一根手指繼續按下并拖動時無效,也就是第二根手指無法拖動,對第一根手指沒有幹擾。由于是多點觸控,需要使用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,因為擷取指定手指事件坐标的函數傳入的參數都是它。結合代碼中的注釋,剩下的邏輯應該就比較容易看懂了。
效果圖如下,用兩根手指來依次按下并拖動圖檔:
我們發現,隻有第一根手指在滑動時,圖檔才會跟着移動,第二根手指(右邊的手指)的滑動無效,完美!!!
三、實作兩根手指共同拖動圖檔
上面實作的效果還不夠,使用者在使用中,第二根手指滑動時也能接替第一根手指繼續滑動。基本思路大緻是,記錄目前活動手指的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、54、65行所示。依然用兩根手指依次拖動圖檔,效果如下所示:
現在可以看到,兩根手指輪流正常拖動圖檔了,毫無違和感。
結語
到目前為止,多點觸控相關的内容,我想講的已經講完了,上一篇講理論,這一篇講案例,難點其實主要就是pointerIndex和pointerId的了解和使用。希望通過這兩篇文章,對讀者了解多點觸控有所幫助。由于文中代碼結構比較簡單,就沒有必要提供源碼了,讀者自己建立好項目,把這些代碼依次拷貝過去就可以了,非常簡單。還有就是筆者比較窮,使用的免費軟體,是以文中gif圖上都打了水印,以後掙錢了也去享受一下付費服務,把水印給去掉。文中如果有描述不準确或不妥的地方,歡迎來拍磚,萬分感謝,Bye!!!
參考文章
【Android多點觸控最佳實踐】
【安卓自定義View進階-多點觸控詳解】