Android手勢ImageView三部曲 第二部
廢話不多說了,還記得上一節(jié)Android手勢ImageView三部曲(一)最后我們提及的那個框架么?這一節(jié)我們重點了掌握一下GestureDetector這個類相關(guān)的屬性方法。
一、那么GestureDetector是干嘛的呢?
顧名思義,字面意思就是“手勢檢測器“的意思,還記得我們上一節(jié)中實現(xiàn)的GestureImageView么?我們在onTouchEvent中檢測到了各種個樣的手勢(手指按下、抬起、什么時候?qū)儆谕献А⑹裁磿r候?qū)儆诳s放)都是通過我們的計算得到的,但是有了GestureDetector這個類后,我們不需要自己做判斷現(xiàn)在是什么手勢了,GestureDetector會幫我們做好判斷,完了后通過回調(diào)函數(shù)告訴你,就像官網(wǎng)所說的(This class should only be used with MotionEvents reported via touch (don't use for trackball events).)這個類僅僅是通過觸碰檢查事件的,而不是用于跟蹤事件的,我檢測到了事件,然后告訴你,至于你需要怎么處理這個事件,那就是你自己的事了。
GestureDetector的一些具體的api大家可以去查看谷歌官方文檔或啟艦大神的博客:
https://developer.android.google.cn/reference/android/view/GestureDetector.html
Android手勢識別器GestureDetector使用詳解
說了這么多估計你都有點累了,下面讓我們看看具體怎么使用:
偷一下懶,我就直接用 Android手勢ImageView三部曲(一)
中的MatrixImageView類改改代碼了:
public class MatrixImageView extends ImageView {
private static final int MODE_NONE = 190;
private static final int MODE_DRAG = 468;
private static final int MODE_ZOOM = 685;
private int mode;
private float startX, startY;
private float midX, midY;
private Matrix currMatrix, savedMatrix;
private float preRotate, rotate;
private float preSpacing;
private GestureDetector detector;
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
detector=new GestureDetector(context,onGestureListener);
}
private void initView() {
mode = MODE_NONE;
currMatrix = new Matrix();
savedMatrix = new Matrix();
DisplayMetrics dm = getResources().getDisplayMetrics();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
private GestureDetector.SimpleOnGestureListener onGestureListener=new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.e("TAG", "====onSingleTapUp=====");
return super.onSingleTapUp(e);
}
@Override
public void onLongPress(MotionEvent e) {
Log.e("TAG", "====onLongPress=====");
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.e("TAG", "====onScroll=====");
Log.e("TAG", "distanceX===>"+distanceX);
Log.e("TAG", "distanceY===>"+distanceY);
return super.onScroll(e1, e2, distanceX, distanceY);
}
@Override
public void onShowPress(MotionEvent e) {
Log.e("TAG", "====onShowPress=====");
super.onShowPress(e);
}
@Override
public boolean onDown(MotionEvent e) {
Log.e("TAG", "====onDown=====");
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.e("TAG", "====onDoubleTap=====");
return super.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.e("TAG", "====onDoubleTapEvent=====");
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.e("TAG", "====onSingleTapConfirmed=====");
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onContextClick(MotionEvent e) {
Log.e("TAG", "====onContextClick=====");
return super.onContextClick(e);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.e("TAG", "====onFling=====");
Log.e("TAG", "velocityX===>"+velocityX);
Log.e("TAG", "velocityY===>"+velocityY);
return super.onFling(e1, e2, velocityX, velocityY);
}
};
}
首先我們在構(gòu)造方法中創(chuàng)建一個手勢監(jiān)測器的對象GestureDetector:
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
detector=new GestureDetector(context,onGestureListener);
}
GestureDetector既然是監(jiān)聽我們的手勢的工具類,那我們是不是得把我們得手勢交給它呢? 是的?。?于是我們在onTouchEvent中把事件交給GestureDetector:
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
那我們把事件交給了GestureDetector,GestureDetector處理完畢后我們怎么知道呢? 還記得我們創(chuàng)建GestureDetector對象的時候傳遞的參數(shù)嗎?
detector=new GestureDetector(context,onGestureListener);
我們傳遞給了GestureDetector一個onGestureListener對象,GestureDetector檢查完畢手勢后,會調(diào)用onGestureListener中的方法進行回調(diào),我們只需要在onGestureListener對象的相應(yīng)方法中作出處理就可以了:
private GestureDetector.SimpleOnGestureListener onGestureListener=new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onSingleTapUp(MotionEvent e) {
Log.e(“TAG”, “====onSingleTapUp=====”);
return super.onSingleTapUp(e);
}
@Override
public void onLongPress(MotionEvent e) {
Log.e("TAG", "====onLongPress=====");
super.onLongPress(e);
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.e("TAG", "====onScroll=====");
Log.e("TAG", "distanceX===>"+distanceX);
Log.e("TAG", "distanceY===>"+distanceY);
return super.onScroll(e1, e2, distanceX, distanceY);
}
@Override
public void onShowPress(MotionEvent e) {
Log.e("TAG", "====onShowPress=====");
super.onShowPress(e);
}
@Override
public boolean onDown(MotionEvent e) {
Log.e("TAG", "====onDown=====");
return super.onDown(e);
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.e("TAG", "====onDoubleTap=====");
return super.onDoubleTap(e);
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
Log.e("TAG", "====onDoubleTapEvent=====");
return super.onDoubleTapEvent(e);
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
Log.e("TAG", "====onSingleTapConfirmed=====");
return super.onSingleTapConfirmed(e);
}
@Override
public boolean onContextClick(MotionEvent e) {
Log.e("TAG", "====onContextClick=====");
return super.onContextClick(e);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.e("TAG", "====onFling=====");
Log.e("TAG", "velocityX===>"+velocityX);
Log.e("TAG", "velocityY===>"+velocityY);
return super.onFling(e1, e2, velocityX, velocityY);
}
};
我們先不管其中方法啥時候調(diào)用,我們先重寫它的所有方法,然后打上log,看看我們手指操作后相應(yīng)的回調(diào),于是我們運行代碼:

是的,沒錯!就只是一張圖片,因為我們也只是顯示了一張圖片:
我們輕輕的點擊一下屏幕:
03-02 20:47:41.367 1798-1798/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 20:47:41.466 1798-1798/com.leo.gestureimageview E/TAG: ====onShowPress=====
03-02 20:47:41.967 1798-1798/com.leo.gestureimageview E/TAG: ====onLongPress=====
輕輕的點擊一下屏幕:
我們可以看到log執(zhí)行順序:onDown->onShowPress->onLongPress
我們點擊屏幕按下,然后過一會再放開:
03-02 21:51:27.121 17138-17138/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 21:51:27.222 17138-17138/com.leo.gestureimageview E/TAG: ====onShowPress=====
03-02 21:51:27.722 17138-17138/com.leo.gestureimageview E/TAG: ====onLongPress=====
我們滑動一下手指:
03-02 21:51:27.121 17138-17138/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 21:51:27.222 17138-17138/com.leo.gestureimageview E/TAG: ====onShowPress=====
03-02 21:51:27.722 17138-17138/com.leo.gestureimageview E/TAG: ====onLongPress=====
不管我們怎么樣操作,打印的log總是這三個方法? 這是咋回事呢? 如果看到這里你有疑問的話,那我告訴你,你Android事件傳遞機制掌握的還不是很好,為什么這么說呢?? 下面我們帶著疑問看看源碼:
猜都可以猜到GestureDetector處理手勢的代碼肯定在onTouchEvent方法中,那么我們看一下onTouchEvent方法:
public boolean onTouchEvent(MotionEvent ev) {
if (mDoubleTapListener != null) {
boolean hadTapMessage = mHandler.hasMessages(TAP);
if (hadTapMessage) mHandler.removeMessages(TAP);
if ((mCurrentDownEvent != null) && (mPreviousUpEvent != null) && hadTapMessage &&
isConsideredDoubleTap(mCurrentDownEvent, mPreviousUpEvent, ev)) {
// This is a second tap
mIsDoubleTapping = true;
// Give a callback with the first tap of the double-tap
handled |= mDoubleTapListener.onDoubleTap(mCurrentDownEvent);
// Give a callback with down event of the double-tap
handled |= mDoubleTapListener.onDoubleTapEvent(ev);
} else {
// This is a first tap
mHandler.sendEmptyMessageDelayed(TAP, DOUBLE_TAP_TIMEOUT);
}
}
mDownFocusX = mLastFocusX = focusX;
mDownFocusY = mLastFocusY = focusY;
if (mCurrentDownEvent != null) {
mCurrentDownEvent.recycle();
}
mCurrentDownEvent = MotionEvent.obtain(ev);
mAlwaysInTapRegion = true;
mAlwaysInBiggerTapRegion = true;
mStillDown = true;
mInLongPress = false;
mDeferConfirmSingleTap = false;
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;
}
代碼太多了,那為什么我們只收到了onDown、onShowPress、onLongPress這三個方法的回調(diào)呢?
我們知道,當(dāng)我們手指剛按下屏幕的時候,ACTION_DOWN會執(zhí)行,然后我們看到這么一行代碼:
handled |= mListener.onDown(ev);
mListener是我們傳遞的SimpleOnGestureListener,于是就看到了控制臺的第一個log:
03-02 21:51:29.706 17138-17138/com.leo.gestureimageview E/TAG: ====onDown=====
我們的onDown是打印了,然后handled |= mListener.onDown(ev);看一下我們返回的是什么值:
@Override
public boolean onDown(MotionEvent e) {
Log.e("TAG", "====onDown=====");
return super.onDown(e);
}
我們直接返回了super.onDown(e),接著我們看一下父類返回的是什么:
public boolean onDown(MotionEvent e) {
return false;
}
可以看到,父類直接返回了false,所以handled此時為false,然后當(dāng)ACTION_DOWN執(zhí)行完畢后,就回到了我們的自定義view中的onTouchEvent方法中了:
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
此時我們的view中的onTouchEvent 方法返回的是false,到了這里懂事件傳遞機制的小伙伴都懂,當(dāng)我們的onTouchEvent返回了false的話,后面的事件都將接收不到了,也就是說只能執(zhí)行ACTION_DOWN,那么有些小伙伴可能又要說了,那我把view的clickable或者longclickable設(shè)置成true,事件不就可以傳遞了么?
好的~! 我們試一試:
<com.leo.gestureimageview.MatrixImageView android:clickable="true" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix" />
運行代碼,還是只打印了那三個方法,那這又是怎么回事呢? 還記得我們view的onTouchEvent方法么?我們是這么寫的:
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
如果改成這樣再試試:
@Override
public boolean onTouchEvent(MotionEvent event) {
detector.onTouchEvent(event);
return super.onTouchEvent(event);
}
拖動手指返回結(jié)果:

好啦~??! 終于看到我們久違的結(jié)果了,如果我們還是想用以前的寫法,把onTouchEvent的返回結(jié)果交給GestureDetector處理該怎么做呢?
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
我們只需要在回調(diào)方法的onDown中返回true即可:
@Override
public boolean onDown(MotionEvent e) {
Log.e("TAG", "====onDown=====");
return true;
}
我們再次運行代碼并拖動手指:

好啦~! 說了那么多不知道小伙伴們理解了沒?還是不理解的小伙伴可以去看看我前幾篇事件傳遞的博客,嘻嘻~我們還是快點往下走吧….
我們長按一下屏幕然后提起手指:
03-02 22:29:37.361 22104-22104/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 22:29:37.367 22104-22104/com.leo.gestureimageview E/TAG: ====onSingleTapUp=====
03-02 22:29:37.663 22104-22104/com.leo.gestureimageview E/TAG: ====onSingleTapConfirmed=====
執(zhí)行了onDown=>onSingleTapUp=>onSingleTapConfirmed.
我們快速點擊一下屏幕:
03-02 22:31:48.603 22104-22104/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 22:31:48.610 22104-22104/com.leo.gestureimageview E/TAG: ====onSingleTapUp=====
03-02 22:31:48.903 22104-22104/com.leo.gestureimageview E/TAG: ====onSingleTapConfirmed=====
執(zhí)行了onDown=>onSingleTapUp=>onSingleTapConfirmed.
然后我們再次滑動手指:
03-02 22:34:41.820 22104-22104/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 22:34:41.920 22104-22104/com.leo.gestureimageview E/TAG: ====onShowPress=====
03-02 22:34:42.018 22104-22104/com.leo.gestureimageview E/TAG: ====onScroll=====
03-02 22:34:42.018 22104-22104/com.leo.gestureimageview E/TAG: distanceX===>-117.13138
03-02 22:34:42.018 22104-22104/com.leo.gestureimageview E/TAG: distanceY===>75.100464
03-02 22:34:42.036 22104-22104/com.leo.gestureimageview E/TAG: ====onScroll=====
03-02 22:34:42.036 22104-22104/com.leo.gestureimageview E/TAG: distanceX===>-75.859314
執(zhí)行順序:onDown=》onShowPress=》onScroll(很多次)
最后我們手指拖動距離長一點再快一點:
03-02 22:47:42.453 5103-5103/com.leo.gestureimageview E/TAG: distanceX===>-274.69336
03-02 22:47:42.453 5103-5103/com.leo.gestureimageview E/TAG: distanceY===>-0.34838867
03-02 22:47:42.460 5103-5103/com.leo.gestureimageview E/TAG: ====onFling=====
03-02 22:47:42.460 5103-5103/com.leo.gestureimageview E/TAG: velocityX===>27284.943
03-02 22:47:42.460 5103-5103/com.leo.gestureimageview E/TAG: velocityY===>-95.6131
前 面還有一段log沒給出了,調(diào)用方法順序為:onDown=》onShowPress=》onScroll(很多次)=》最后松開手指的時候onFling();
好了~! 到這里,我們的回調(diào)方法中還有幾個沒有被調(diào)用,就是監(jiān)聽雙擊事件的時候,于是我們雙擊屏幕:
03-02 22:50:34.786 5103-5103/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 22:50:34.793 5103-5103/com.leo.gestureimageview E/TAG: ====onSingleTapUp=====
03-02 22:50:34.924 5103-5103/com.leo.gestureimageview E/TAG: ====onDoubleTap=====
03-02 22:50:34.924 5103-5103/com.leo.gestureimageview E/TAG: ====onDoubleTapEvent=====
03-02 22:50:34.924 5103-5103/com.leo.gestureimageview E/TAG: ====onDown=====
03-02 22:50:34.932 5103-5103/com.leo.gestureimageview E/TAG: ====onDoubleTapEvent=====
我們雙擊屏幕執(zhí)行的方法為:
onDown=>onSingleTapUp=>onDoubleTap=>onDoubleTapEvent=>onDoubleTapEvent
可見,執(zhí)行了一次onDoubleTap,兩次onDoubleTapEvent
好啦~! 看完了SimpleOnGestureListener中所有方法的回調(diào),我們反過來再看一遍這些回調(diào)方法:

好啦~!說了那么api內(nèi)容,下面寫個小例子用一下GestureDetector:
package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.ImageView;
public class MatrixImageView extends ImageView {
private Matrix currMatrix;
private GestureDetector detector;
public MatrixImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
detector=new GestureDetector(context,onGestureListener);
}
private void initView() {
currMatrix = new Matrix();
DisplayMetrics dm = getResources().getDisplayMetrics();
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return detector.onTouchEvent(event);
}
private float currX;
private float currY;
private GestureDetector.SimpleOnGestureListener onGestureListener=new GestureDetector.SimpleOnGestureListener(){
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
Log.e("TAG", "====onScroll=====");
Log.e("TAG", "distanceX===>"+distanceX);
Log.e("TAG", "distanceY===>"+distanceY);
currX-=distanceX;
currY-=distanceY;
currMatrix.reset();
currMatrix.postTranslate(currX,currY);
setImageMatrix(currMatrix);
return super.onScroll(e1, e2, distanceX, distanceY);
}
};
}
代碼很短,想必大家都看得懂,就是一個隨著手指移動而移動的圖片:

好啦~~ 這篇有點長,為什么花這么久去研究GestureDetector,這也是為了給下一節(jié)的MoveGestureDetector、RotateGestureDetector、ShoveGestureDetector以及PhotoView這些大牛寫的框架做鋪墊。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android仿新浪微博oauth2.0授權(quán)界面實現(xiàn)代碼(2)
這篇文章主要為大家詳細(xì)介紹了Android仿新浪微博oauth2.0授權(quán)界面實現(xiàn)代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11
Android自定義View onDraw()方法會調(diào)用兩次的問題解決
這篇文章主要介紹了Android自定義View onDraw()方法會調(diào)用兩次的問題解決,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友參考下吧2024-01-01
Android 實現(xiàn)帶進度條的WebView的實例
這篇文章主要介紹了Android 實現(xiàn)帶進度條的WebView的實例的相關(guān)資料,這里介紹了Webview加載網(wǎng)頁的方法及帶進度的Drawable文件view_progress_webview的實現(xiàn),需要的朋友可以參考下2017-07-07
Android實現(xiàn)快速滾動FastScrollView效果
這篇文章主要介紹了Android實現(xiàn)快速滾動FastScrollView效果,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08
Android中pendingIntent與Intent的深入分析
這篇文章主要介紹了Android中pendingIntent的深入分析的相關(guān)資料,需要的朋友可以參考下2017-04-04
Android內(nèi)存優(yōu)化操作方法梳理總結(jié)
這篇文章主要介紹了Android 內(nèi)存優(yōu)化知識點梳理總結(jié),Android 操作系統(tǒng)給每個進程都會分配指定額度的內(nèi)存空間,App 使用內(nèi)存來進行快速的文件訪問交互,長時間如此便需要優(yōu)化策略,文章分享優(yōu)化知識點總結(jié),需要的朋友可以參考一下2022-11-11

