最近項目中對已有的自定義的圖表控件進行優(yōu)化. 考慮之前的手勢處理都是在控件內的onTouchEvent中處理的. 通過監(jiān)聽每一次的MotionEvent事件, 然后進行判斷不同的時機去決定操作事件的觸發(fā).
其實, Android系統(tǒng)早已提供了針對Touch事件識別的處理, 那就是GestureDetector和ScaleGestureDetector.在它們的onTouchEvent中已經完美識別了我們需要的不同的操作, 并通過Listener回調給使用者.
OnGestureListener, OnDoubleTapListener, OnContextClickListener
回調接口?參見GestureDetector.java:
OnScaleGestureListener
縮放操作的回調接口參見ScaleGestureDetector.java:
從以上接口中, 我們可以非常方便的找到系統(tǒng)所支持的所有手勢操作處理點. 并準確的把握處理時機. 以便完成我們的處理邏輯.
可是, 它仍有一些弊端:
- 語義不夠明確, 有時我們僅需要告訴使用者什么時候真的 單擊, 長按, 縮放了...
- 自定義控件時使用不具靈活,
長按時間不能自定義, 長按時間參見系統(tǒng)設置:Settings.Secure.LONG_PRESS_TIMEOUT 默認為500毫秒
<integer name="def_long_press_timeout_millis">500</integer>
對縮放操作自定義處理. - 與父布局的Scroll事件沖突. 本例中當View的父控件是可滾動時,
View的移動操作會與父控件的Scroll事件發(fā)生沖突.
當然, 這都因需求而定, 目前我的項目主要是針對股票K線及技術指標圖表的操作, 主要有單擊, 雙擊, 長按, 移動, 長按后的移動,
為此我有如下封裝:
/**
* Created by ?kangqiao on 16/9/22.
* e-mail: kangqiao610@gmail.com
*/
public interface GestureOperateListener {
/**
* 只要有 ACTION_UP 或 ACTION_CANCEL
*/
void onUpOrCancel();
/**
* 縮放
* if true zoom in, else zoom out
*
* @param detector
*/
void onZoom(ScaleGestureDetector detector);
/**
* 滑動, 注意: 之前沒有進行長按.
*
* @param downEvent
* @param event
* @param distanceX
* @param distanceY
*/
void onMove(MotionEvent downEvent, MotionEvent event, float distanceX, float distanceY);
/**
* 長按后滑動, 回調多次
*
* @param event
*/
void onMoveAfterLongPress(MotionEvent event);
/**
* 長按 僅回調一次
*
* @param event
*/
void onLongPress(MotionEvent event);
/**
* 快速點擊. 第一次 touch up時, 后續(xù)又沒有任何操作(滑動, 不處理長按中, 縮放)時回調
*
* @param event
*/
void onClick(MotionEvent event);
/**
* 雙擊. 第二次 touch up時, 回調
*
* @param event
*/
void onDoubleClick(MotionEvent event);
/**
* 快速滑動后的最后一個動作
*
* @param downEvent 起點
* @param event 終點
* @param velocityX 水平方向移動的速度,像素/秒
* @param velocityY 垂直方向移動的速度,像素/秒
*/
void onFling(MotionEvent downEvent, MotionEvent event, float velocityX, float velocityY);
/**
* 重繪UI
*/
void postInvalidate();
}
對于TouchEvent事件中那些復雜的操作判斷仍然交由系統(tǒng)提供的GestureDetector和ScaleGestureDetector來偵測, 需要作的僅僅是在其回調中轉換為需要的操作.
依然遵循Detector的使用規(guī)則, 我做了如下的封裝:
public class KLineChartView extends LinearLayout {
......
private GestureOperateListener supportGestureOperate = new GestureDetectHandler.SimpleGestureOperateListener() {
private void init(Context context, AttributeSet attrs) {
initAttrs(attrs);
mGestureDetectHandler = new GestureDetectHandler(this, supportGestureOperate);
//設置長按效果后延時取消的時間.
mGestureDetectHandler.setDelayedCancelTimeoutAfterLongPress(2000);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return mGestureDetectHandler.onTouchEvent(event);
}
......
}
GestureDetectHandler是針對TouchEvent事件的綜合處理類.
在構造時接收被偵測的View, 并給出相應的事件回調.
并覆寫View的onTouchEvent方法, 將事件傳遞給處理者.
public class GestureDetectHandler extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {
public GestureDetectHandler(View detectedView, GestureOperateListener operator) {
assert detectedView != null : Log.e(TAG, "detectedView is null in constructor!");
assert operator != null : Log.e(TAG, "GestureOperateListener is null in constructor!");
mDetectedView = detectedView;
detectedView.setOnTouchListener(this);
mHandler = new GestureHandler(detectedView.getContext().getMainLooper());
mGestureOptListener = operator;
mGestureDetector = new GestureDetector(mDetectedView.getContext(), this);
mScaleGestureDetector = new ScaleGestureDetector(mDetectedView.getContext(), this);
setIsCustomLongPressEnabled(true); //默認使用自定義的長按操作
}
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL) {
loge("onTouch", actionToStr(action));
//當mDetectedView的onTouchEvent事件突然交到父控件處理時, 緊急執(zhí)行清理操作.
dispatchCancel();
}
return false;
}
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
return true;
}
}
在解決與父控件滾動事件沖突時, 可以通過阻止父層的View截獲touch事件,就是調用getParent().requestDisallowInterceptTouchEvent(true);方法。一旦底層View收到touch的action后調用這個方法那么父層View就不會再調用onInterceptTouchEvent了,也無法截獲以后的action。
//在發(fā)生滾動和長按時觸發(fā), 標示著mDetectedView要完全接收事件. 父控件不在干預.
mDetectedView.getParent().requestDisallowInterceptTouchEvent(true);
//當mDetectedView結束了相應的操作后, 要相應的給重置為false, 標示著允許父控件與子控件交互處理事件.
mDetectedView.getParent().requestDisallowInterceptTouchEvent(false);
GestureDetectHandler對于mDetectedView事件的處理時機的判斷已經交由GestureDetector和ScaleGestureDetector來處理, 并在回調OnGestureListener, OnDoubleTapListener和OnScaleGestureListener中轉換為
GestureOperateListener接口中定義的相應的操作, 最后回調給使用者.
固GestureDetectHandler主要是針對GestureOperateListener接口的操作做了相應的轉換操作. 并主要針對
- 長按
長按時間自定義
解決與父控件的沖突
長按效果延長時間的設置. - 長按后的移動
解決正常移動與自定義長按后移動的沖突. - 單擊事件的重新偵測
長按后不會觸發(fā)單擊操作. - 縮放
縮放操作的自定義
完整的代碼如下: ( 注解中已詳盡說明)
/**
* Created by zhaopan on 16/9/24.
* e-mail: kangqiao610@gmail.com
* 手勢識別轉換處理類
*/
public class GestureDetectHandler extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener, View.OnTouchListener {
private static final String TAG = "GestureDetectHandler";
public static final boolean DEBUG = true;
//最小縮放距離, 單位:像素
public static final int MIN_SCALE_DISTANCE = 20;
//最短縮放時間, 單位:毫秒
public static final int MIN_SCALE_TIME_MILLI = 10;
//最小移動距離
public static final int MIN_MOVE_DISTANCE = 5;
//X軸的坐標位移大于FLING_MIN_DISTANCE
public static final int FLING_MIN_DISTANCE = 100;
//移動速度大于FLING_MIN_VELOCITY個像素/秒
public static final int FLING_MIN_VELOCITY = 150;
public static final long CUSTOM_LONG_PRESS_TIMEOUT = 300;
public static final long CUSTOM_SCALE_BEGIN_TIMEOUT = 0;
public static final long CUSTOM_DELAYED_CANCEL_TIMEOUT_AFTER_LONG_PRESS = 100;
private static final int LONG_PRESS = 1;
private static final int SCALE_BEGIN = 2;
private static final int DELAYED_CANCEL = 3;
private long mLongPressTimeout = CUSTOM_LONG_PRESS_TIMEOUT;
private long mScaleBeginTimeout = CUSTOM_SCALE_BEGIN_TIMEOUT;
private long mDelayedCancelTimeoutAfterLongPress = CUSTOM_DELAYED_CANCEL_TIMEOUT_AFTER_LONG_PRESS;
private boolean mInLongPressProgress = false;
private boolean mInScaleProgress = false;
private boolean mInDoubleTapProgress = false;
private boolean mIsCanceled = true;
private boolean mIsCustomLongPressEnabled = true;
private float lastSpan;
private MotionEvent showPressEvent;
private View mDetectedView;
private Handler mHandler;
private GestureOperateListener mGestureOptListener;
private GestureDetector mGestureDetector;//單擊和雙擊事件手勢識別
private ScaleGestureDetector mScaleGestureDetector;//縮放事件手勢識別
public GestureDetectHandler(View detectedView, GestureOperateListener operator) {
assert detectedView != null : Log.e(TAG, "detectedView is null in constructor!");
assert operator != null : Log.e(TAG, "GestureOperateListener is null in constructor!");
mDetectedView = detectedView;
detectedView.setOnTouchListener(this);
mHandler = new GestureHandler(detectedView.getContext().getMainLooper());
mGestureOptListener = operator;
mGestureDetector = new GestureDetector(mDetectedView.getContext(), this);
mScaleGestureDetector = new ScaleGestureDetector(mDetectedView.getContext(), this);
setIsCustomLongPressEnabled(true); //默認使用自定義的長按操作
}
private class GestureHandler extends Handler {
GestureHandler() {
super();
}
GestureHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case LONG_PRESS:
dispatchLongPress();
break;
case SCALE_BEGIN:
dispatchScaleBegin();
break;
case DELAYED_CANCEL:
dispatchCancel();
break;
default:
throw new RuntimeException("Unknown message " + msg); //never
}
}
}
/**
* OnTouchListener 僅僅作為補充操作,
* 防止長按后滑動和長按滑動后onFling, 這兩個操作后續(xù)沒有明確的結束回調, 即Up或Cancel時GestureDetector并不回調操作.
* 不建議使用, 如是mDetectedView有去設置OnTouchListener則會覆蓋此實現. 即會導致此中功能失效.
*
* @param v
* @param event
* @return
*/
@Override
@Deprecated
public boolean onTouch(View v, MotionEvent event) {
final int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL) {
loge("onTouch", actionToStr(action));
//當mDetectedView的onTouchEvent事件突然交到父控件處理時, 緊急執(zhí)行清理操作.
dispatchCancel();
}
//改由GestureDetector的每一步單獨處理, 減少重復的繪制操作.
//mGestureOptListener.postInvalidate();
return false;
}
/**
* 接收系統(tǒng)的onTouchEvent事件, 并返回true表示處理, 不再傳遞
*
* @param event
* @return
*/
public boolean onTouchEvent(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
mScaleGestureDetector.onTouchEvent(event);
//return super.onTouchEvent(event);//不管返回值是什么,都能接收down事件,都能觸發(fā)onDown、onShowPress、onLongPress
return true;//但只有返回true才能繼續(xù)接收move,up等事件,也才能響應ScaleGestureDetector事件及GestureDetector中與move,up相關的事件
}
private void dispatchLongPress() {
mDetectedView.getParent().requestDisallowInterceptTouchEvent(true);
mInLongPressProgress = true;
onLongPress(showPressEvent);
}
private void dispatchScaleBegin() {
mDetectedView.getParent().requestDisallowInterceptTouchEvent(true);
mInScaleProgress = true;
if(mInLongPressProgress) {
mInLongPressProgress = false;
mHandler.removeMessages(LONG_PRESS);
mGestureOptListener.onUpOrCancel();
mGestureOptListener.postInvalidate();
}
}
//ACTION_CANCEL and ACTION_UP
private void dispatchCancel() {
loge("dispatchCancel", "dispatchCancel");
if (!mIsCanceled) {
mIsCanceled = true;
mDetectedView.getParent().requestDisallowInterceptTouchEvent(false);
mHandler.removeMessages(DELAYED_CANCEL);
mHandler.removeMessages(LONG_PRESS);
mHandler.removeMessages(SCALE_BEGIN);
mInDoubleTapProgress = false;
mInLongPressProgress = false;
mInScaleProgress = false;
mGestureOptListener.onUpOrCancel();
}
mGestureOptListener.postInvalidate();
}
/**
* 設置是否使用自己的長按處理.
*
* @param isCustomLongPressEnabled
*/
public void setIsCustomLongPressEnabled(boolean isCustomLongPressEnabled) {
if (isCustomLongPressEnabled) {
//如果自定義自己的長按處理, 先禁用系統(tǒng)的長按觸發(fā).
mGestureDetector.setIsLongpressEnabled(false);
} else {
//如果不自定義自己的長按處理, 默認啟用系統(tǒng)的長按
mGestureDetector.setIsLongpressEnabled(true);
}
//mGestureDetector.setIsLongpressEnabled(!isCustomLongPressEnabled);
mIsCustomLongPressEnabled = isCustomLongPressEnabled;
}
/**
* 設置是否使用系統(tǒng)的長按處理.
*
* @param isSystemLongPressEnabled
*/
public void setIsLongpressEnabled(boolean isSystemLongPressEnabled) {
mGestureDetector.setIsLongpressEnabled(isSystemLongPressEnabled);
mIsCustomLongPressEnabled = false;
}
public void setScaleBeginTimeout(long scaleBeginTimeout) {
if (scaleBeginTimeout >= 0) this.mScaleBeginTimeout = scaleBeginTimeout;
}
public void setLongPressTimeout(long longPressTimeout) {
if (longPressTimeout >= 0) this.mLongPressTimeout = longPressTimeout;
}
/**
* 設置長按后的處理操作 需要延時多長時間執(zhí)行清理操作. 默認大于等于100毫秒. 且會自動開啟自定義的長按操作處理.
*
* @param cancelTimeout
*/
public void setDelayedCancelTimeoutAfterLongPress(long cancelTimeout) {
if (cancelTimeout >= CUSTOM_DELAYED_CANCEL_TIMEOUT_AFTER_LONG_PRESS) {
setIsCustomLongPressEnabled(true);
this.mDelayedCancelTimeoutAfterLongPress = cancelTimeout;
}
}
private void sendDelayedCancelMessage() {
mHandler.removeMessages(DELAYED_CANCEL);
mHandler.sendEmptyMessageDelayed(DELAYED_CANCEL, mDelayedCancelTimeoutAfterLongPress);
}
public GestureDetector getGestureDetector() {
return mGestureDetector;
}
public ScaleGestureDetector getScaleGestureDetector() {
return mScaleGestureDetector;
}
/**
* 注意:
* 1. onSingleTapConfirmed(單擊)和onSingleTapUp都是在down后既沒有滑動onScroll,又沒有長按onLongPress時, up 時觸發(fā)的
* 2. 非??斓狞c擊一下:onDown->onSingleTapUp->onSingleTapConfirmed
* 3. 稍微慢點的點擊一下:onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed(最后一個不一定會觸發(fā))
*/
////////OnGestureListener/////////////////////////////
/**
* Touch down時觸發(fā)
* 按下(onDown): 剛剛手指接觸到觸摸屏的那一剎那,就是觸的那一下
*
* @param e
* @return
*/
@Override
public boolean onDown(MotionEvent e) {
loge("view-手勢", "onDown event.getAction=" + actionToStr(e.getAction()) + ", event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
mInLongPressProgress = false;
mIsCanceled = false;
return super.onDown(e);
}
/**
* onScroll一點距離后,【拋擲時】觸發(fā)(若是輕輕的、慢慢的停止活動,而非拋擲,則很可能不觸發(fā))
* 參數為手指接觸屏幕、離開屏幕一瞬間的動作事件,及手指水平、垂直方向移動的速度,像素/秒
* 拋擲(onFling): 手指在觸摸屏上迅速移動,并松開的動作,onDown -> onScroll ... -> onFling
* 長按后滾動(onScroll): 手指在觸摸屏上滑動,onDown -> onShowPress -> onLongPress -> onScroll ... [onFling]
*
* @param downEvent
* @param event
* @param velocityX
* @param velocityY
* @return
*/
@Override
public boolean onFling(MotionEvent downEvent, MotionEvent event, float velocityX, float velocityY) {
loge("view-手勢", "onFling newAction=" + actionToStr(event.getAction()) + ", downEvent.getRawX()=" + downEvent.getRawX() + ", event.getRawX()=" + event.getRawX() + ", velocityX=" + velocityX + ", velocityY=" + velocityY);
mGestureOptListener.onFling(downEvent, event, velocityX, velocityY);
mGestureOptListener.postInvalidate();
if (mIsCustomLongPressEnabled && mInLongPressProgress) {
sendDelayedCancelMessage(); //長按后滑動, 最后onFling了一下, 執(zhí)行延時清理操作.
} else {
dispatchCancel(); //正常的onFling, 直接執(zhí)行清理操作.
}
/*if ((event.getRawX() - downEvent.getRawX()) > FLING_MIN_DISTANCE *//*&& Math.abs(velocityX) > FLING_MIN_VELOCITY*//*) {
loge("view-手勢", "onFling-從左往右滑");
return true;
} else if (downEvent.getRawX() - event.getRawX() > FLING_MIN_DISTANCE *//*&& Math.abs(velocityX) > FLING_MIN_VELOCITY*//*) {
loge("view-手勢", "onFling-從右往左滑");
return true;
}*/
return super.onFling(downEvent, event, velocityX, velocityY);
}
/**
* Touch了滑動時觸發(fā),e1代表觸摸時的事件,是不變的,e2代表滑動過程中的事件,是時刻變化的
* distance是當前event2與上次回調時的event2之間的距離,代表上次回調之后到這次回調之前移動的距離
* 縮放(onScroll) onDown -> onScaleBegin -> (onScroll ... -> onScale) ... -> onScaleEnd
* 長按后滾動(onScroll): 手指在觸摸屏上滑動,onDown -> onShowPress -> onLongPress -> onScroll ... [onFling]
* 滾動(onScroll): 手指在觸摸屏上滑動,onDown -> onScroll ...
*
* @param downEvent
* @param event
* @param distanceX
* @param distanceY
* @return
*/
@Override
public boolean onScroll(MotionEvent downEvent, MotionEvent event, float distanceX, float distanceY) {
if (mInScaleProgress) {
// TODO: 16/9/27 縮放中的同步滑動
loge("view-手勢", "onScroll 縮放中>>> " + actionToStr(event.getAction()) + ", -X-" + (int) downEvent.getX() + "/" + (int) event.getX() + "/" + (int) distanceX
+ ", >>> -Y-" + (int) downEvent.getY() + "/" + (int) event.getY() + "/" + (int) distanceY);
} else if (mInLongPressProgress) { //長按后的滑動.
loge("view-手勢", "onScroll 長按中>>> " + actionToStr(event.getAction()) + ", -X-" + (int) downEvent.getX() + "/" + (int) event.getX() + "/" + (int) distanceX
+ ", >>> -Y-" + (int) downEvent.getY() + "/" + (int) event.getY() + "/" + (int) distanceY);
mGestureOptListener.onMoveAfterLongPress(event);
mGestureOptListener.postInvalidate(); //后續(xù)仍有移動, 執(zhí)行刷新UI操作
if (mIsCustomLongPressEnabled) {
sendDelayedCancelMessage(); //長按后的滑動操作, 執(zhí)行延時清理操作
} else {
// TODO: 16/9/27 注: 沒有自定義長按操作, 如果沒有后續(xù)操作是不會有Up或Cancel動作, 即沒有執(zhí)行清理操作的機會了.
}
} else { //普通滑動
loge("view-手勢", "onScroll 滑動中>>> " + actionToStr(event.getAction()) + ", -X-" + (int) downEvent.getX() + "/" + (int) event.getX() + "/" + (int) distanceX
+ ", >>> -Y-" + (int) downEvent.getY() + "/" + (int) event.getY() + "/" + (int) distanceY);
mGestureOptListener.onMove(downEvent, event, distanceX, distanceY);
mGestureOptListener.postInvalidate(); //后續(xù)仍有移動, 執(zhí)行刷新UI操作
// TODO: 16/9/27 注: 普通的滑動操作, 如果沒有后續(xù)操作是不會有Up或Cancel動作, 即沒有執(zhí)行清理操作的機會了.
}
return super.onScroll(downEvent, event, distanceX, distanceY);
}
/**
* ouch了不移動一直Touch down時觸發(fā)
* 長按(onLongPress): 手指按在持續(xù)一段時間,并且沒有松開
* 1. onDown -> onShowPress -> onLongPress -> onSingleTapUp -> onSingleTapConfirmed
* 2. onDown -> onShowPress -> onLongPress -> onScroll ...
* 3. onDown -> onShowPress -> onLongPress -> onScroll ... -> onFling
*
* @param event
*/
@Override
public void onLongPress(MotionEvent event) {
loge("view-手勢", "onLongPress event.getAction=" + actionToStr(event.getAction()) + ", event.getX()=" + event.getX() + ", event.getY()=" + event.getY());
mGestureOptListener.onLongPress(event);
mGestureOptListener.postInvalidate();
super.onLongPress(event);
}
/**
* Touch了還沒有滑動時觸發(fā)
* 按?。╫nShowPress): 手指按在觸摸屏上,在按下起效,在長按前失效,
* onDown -> onShowPress -> onLongPress
*
* @param e
*/
@Override
public void onShowPress(MotionEvent e) {
loge("view-手勢", "onShowPress event.getAction=" + actionToStr(e.getAction()) + ", event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
if (mIsCustomLongPressEnabled && !mInDoubleTapProgress) { //如果自定義長按啟用, 并且不是在雙擊的第二下長按的, 即執(zhí)行長按操作.
mHandler.sendEmptyMessageAtTime(LONG_PRESS, e.getDownTime() + mLongPressTimeout);
showPressEvent = e;
} else { //沒有設置自定義長按操作
mDetectedView.getParent().requestDisallowInterceptTouchEvent(true); //參考dispatchLongPress();
mInLongPressProgress = true;
}
super.onShowPress(e);
}
/**
* 在touch down后又沒有滑動(onScroll),又沒有長按(onLongPress),然后Touchup時觸發(fā)。
* 抬起(onSingleTapUp):手指離開觸摸屏的那一剎那
*
* @param e
* @return
*/
@Override
public boolean onSingleTapUp(MotionEvent e) {
loge("view-手勢", "onSingleTapUp event.getAction=" + actionToStr(e.getAction()) + ", event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
return super.onSingleTapUp(e);
}
////////OnDoubleTapListener/////////////////////////////
/**
* 在touch down后又沒有滑動(onScroll),又沒有長按(onLongPress),然后Touchup時觸發(fā)。
* 單擊確認,即很快的按下并抬起,但并不連續(xù)點擊第二下
*
* @param event
* @return
*/
@Override
public boolean onSingleTapConfirmed(MotionEvent event) {
loge("view-手勢", "onSingleTapConfirmed event.getAction=" + actionToStr(event.getAction()) + ", event.getX()=" + event.getX() + ", event.getY()=" + event.getY());
if (mInLongPressProgress) {
//長按進行中... 如果設置了自定義長按操作, 執(zhí)行延時清理操作, 否則直接清理.
if (mIsCustomLongPressEnabled) {
sendDelayedCancelMessage();
} else {
dispatchCancel();
}
} else {
//執(zhí)行單擊操作.
mGestureOptListener.onClick(event);
dispatchCancel(); //后續(xù)沒有操作并已經Up或Cancel了, 所以執(zhí)行清理操作.
}
return super.onSingleTapConfirmed(event);
}
/**
* 雙擊的【第二下】Touch down時觸發(fā)(只執(zhí)行一次)
*
* @param e
* @return
*/
@Override
public boolean onDoubleTap(MotionEvent e) {
loge("view-手勢", "onDoubleTap event.getAction=" + actionToStr(e.getAction()) + ", event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
mInDoubleTapProgress = true;
return super.onDoubleTap(e);
}
/**
* 雙擊的【第二下】Touch down和up都會觸發(fā)(執(zhí)行次數不確定)。
*
* @param event
* @return
*/
@Override
public boolean onDoubleTapEvent(MotionEvent event) {
loge("view-手勢", "onDoubleTapEvent event.getAction=" + actionToStr(event.getAction()) + ", event.getX()=" + event.getX() + ", event.getY()=" + event.getY());
if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
mGestureOptListener.onDoubleClick(event);
dispatchCancel(); //雙擊操作完成并Up或Cancel, 所以執(zhí)行清理操作
}
return super.onDoubleTapEvent(event);
}
////////OnContextClickListener/////////////////////////////
@Override
public boolean onContextClick(MotionEvent e) {
loge("view-手勢", "onContextClick event.getX()=" + e.getX() + ", event.getY()=" + e.getY());
return super.onContextClick(e);
}
////////OnScaleGestureListener/////////////////////////////
/**
* http://www.cnblogs.com/baiqiantao/p/5630506.html
* public float getCurrentSpan () 返回手勢過程中,組成該手勢的兩個觸點的當前距離。
* public long getEventTime () 返回事件被捕捉時的時間。
* public float getFocusX () 返回當前手勢焦點的 X 坐標。 如果手勢正在進行中,焦點位于組成手勢的兩個觸點之間。 如果手勢正在結束,焦點為仍留在屏幕上的觸點的位置。若 isInProgress() 返回 false,該方法的返回值未定義。
* public float getFocusY () 返回當前手勢焦點的 Y 坐標。
* public float getPreviousSpan () 返回手勢過程中,組成該手勢的兩個觸點的前一次距離。
* public float getScaleFactor () 返回從前一個伸縮事件至當前伸縮事件的伸縮比率。該值定義為 getCurrentSpan() / getPreviousSpan()。
* public long getTimeDelta () 返回前一次接收到的伸縮事件距當前伸縮事件的時間差,以毫秒為單位。
* public boolean isInProgress () 如果手勢處于進行過程中,返回 true。否則返回 false。
*/
/**
* 開始縮放
*
* @param detector
* @return
*/
@Override
public boolean onScale(ScaleGestureDetector detector) {
if (mInScaleProgress) { //縮放中...
float distance = detector.getCurrentSpan() - lastSpan;
if (Math.abs(distance) > MIN_SCALE_DISTANCE) {
lastSpan = detector.getCurrentSpan();
if (detector.getScaleFactor() < 1) { //縮小
Log.e("view-縮放", "onScale,縮小" + detector.getScaleFactor() + ", curSpan=" + detector.getCurrentSpan() + ", preSpan=" + detector.getPreviousSpan() + ", curSpan-preSpan=>" + distance + ", timeDelta=" + detector.getTimeDelta());
} else { //放大
Log.e("view-縮放", "onScale,放大" + detector.getScaleFactor() + ", curSpan=" + detector.getCurrentSpan() + ", preSpan=" + detector.getPreviousSpan() + ", curSpan-preSpan=>" + distance + ", timeDelta=" + detector.getTimeDelta());
}
mGestureOptListener.onZoom(detector);
mGestureOptListener.postInvalidate(); //并沒Up或Cancel且后續(xù)仍在Zoom, 執(zhí)行刷新UI操作
}
}
return false;
}
/**
* 縮放開始, 一次縮放僅執(zhí)行一次
*
* @param detector
* @return
*/
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
loge("view-縮放", "onScaleBegin");
mHandler.sendEmptyMessageAtTime(SCALE_BEGIN, detector.getEventTime() + mScaleBeginTimeout);
return true;
}
/**
* 縮放結束, 一次縮放僅執(zhí)行一次
*
* @param detector
*/
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
loge("view-縮放", "onScaleEnd");
dispatchCancel(); //已經停止縮放操作并Up或Cancel, 執(zhí)行清理操作.
}
/**
* @see ScaleGestureDetector#getCurrentSpanX()
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static float getCurrentSpanX(ScaleGestureDetector scaleGestureDetector) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return scaleGestureDetector.getCurrentSpanX();
} else {
return scaleGestureDetector.getCurrentSpan();
}
}
/**
* @see ScaleGestureDetector#getCurrentSpanY()
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public static float getCurrentSpanY(ScaleGestureDetector scaleGestureDetector) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return scaleGestureDetector.getCurrentSpanY();
} else {
return scaleGestureDetector.getCurrentSpan();
}
}
private static void loge(String TAG, String msg) {
if (DEBUG) Log.e("zp>>>" + TAG, msg);
}
private static String actionToStr(int action) {
switch (action) {
case MotionEvent.ACTION_DOWN:
return "DOWN_" + action;
case MotionEvent.ACTION_UP:
return "UP_" + action;
case MotionEvent.ACTION_MOVE:
return "MOVE_" + action;
case MotionEvent.ACTION_CANCEL:
return "CANCEL_" + action;
}
return "<" + action + ">";
}
//省略 GestureOperateListener與SimpleGestureOperateListener的定義.
}
自此, GestureDetectHandler已經完成對于TouchEvent事件的轉換, 并符合項目所支持的所有操作.
對于后續(xù)操作的處理. 接口定義方法中的參數已經足夠給使用者完成處理.
以后如遇到自定義View的事件處理時, 可簡單的使用它便能很好的完成事件操作的處理.
且可自定義操作.
歡迎指出缺陷及不足, 一起成長.