一、首先把橫向的listview的代碼放上來 二、我們來瞭解一下屏幕的觸摸監聽事件的分發機制 Touch事件分發中只有兩個主角:ViewGroup和View。Activity的Touch事件事實上是調用它內部的ViewGroup的Touch事件,可以直接當成ViewGroup處理。 View在Vie ...
一、首先把橫向的listview的代碼放上來
HorizontalListView:
package com.common.cklibrary.utils.myview; import java.util.LinkedList; import java.util.Queue; import android.content.Context; import android.database.DataSetObserver; import android.graphics.Rect; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.GestureDetector.OnGestureListener; import android.view.MotionEvent; import android.view.View; import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.Scroller; /** * Created by Admin on 2017/9/7. */ @SuppressWarnings("deprecation") public class HorizontalListView extends AdapterView<ListAdapter> { public boolean mAlwaysOverrideTouch = true; protected ListAdapter mAdapter; private int mLeftViewIndex = -1; private int mRightViewIndex = 0; protected int mCurrentX; protected int mNextX; private int mMaxX = Integer.MAX_VALUE; private int mDisplayOffset = 0; protected Scroller mScroller; private GestureDetector mGesture; private Queue<View> mRemovedViewQueue = new LinkedList<View>(); private OnItemSelectedListener mOnItemSelected; private OnItemClickListener mOnItemClicked; private OnItemLongClickListener mOnItemLongClicked; private boolean mDataChanged = false; public HorizontalListView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } private synchronized void initView() { mLeftViewIndex = -1; mRightViewIndex = 0; mDisplayOffset = 0; mCurrentX = 0; mNextX = 0; mMaxX = Integer.MAX_VALUE; mScroller = new Scroller(getContext()); mGesture = new GestureDetector(getContext(), mOnGesture); } @Override public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener listener) { mOnItemSelected = listener; } @Override public void setOnItemClickListener(AdapterView.OnItemClickListener listener) { mOnItemClicked = listener; } @Override public void setOnItemLongClickListener(AdapterView.OnItemLongClickListener listener) { mOnItemLongClicked = listener; } private DataSetObserver mDataObserver = new DataSetObserver() { @Override public void onChanged() { synchronized (HorizontalListView.this) { mDataChanged = true; } invalidate(); requestLayout(); } @Override public void onInvalidated() { reset(); invalidate(); requestLayout(); } }; @Override public ListAdapter getAdapter() { return mAdapter; } @Override public View getSelectedView() { //TODO: implement return null; } @Override public void setAdapter(ListAdapter adapter) { if (mAdapter != null) { mAdapter.unregisterDataSetObserver(mDataObserver); } mAdapter = adapter; mAdapter.registerDataSetObserver(mDataObserver); reset(); } private synchronized void reset() { initView(); removeAllViewsInLayout(); requestLayout(); } @Override public void setSelection(int position) { //TODO: implement } private void addAndMeasureChild(final View child, int viewPos) { LayoutParams params = child.getLayoutParams(); if (params == null) { params = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); } addViewInLayout(child, viewPos, params, true); child.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(getHeight(), MeasureSpec.AT_MOST)); } @Override protected synchronized void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mAdapter == null) { return; } if (mDataChanged) { int oldCurrentX = mCurrentX; initView(); removeAllViewsInLayout(); mNextX = oldCurrentX; mDataChanged = false; } if (mScroller.computeScrollOffset()) { int scrollx = mScroller.getCurrX(); mNextX = scrollx; } if (mNextX <= 0) { mNextX = 0; mScroller.forceFinished(true); } if (mNextX >= mMaxX) { mNextX = mMaxX; mScroller.forceFinished(true); } int dx = mCurrentX - mNextX; removeNonVisibleItems(dx); fillList(dx); positionItems(dx); mCurrentX = mNextX; if (!mScroller.isFinished()) { post(new Runnable() { @Override public void run() { requestLayout(); } }); } } private void fillList(final int dx) { int edge = 0; View child = getChildAt(getChildCount() - 1); if (child != null) { edge = child.getRight(); } fillListRight(edge, dx); edge = 0; child = getChildAt(0); if (child != null) { edge = child.getLeft(); } fillListLeft(edge, dx); } private void fillListRight(int rightEdge, final int dx) { while (rightEdge + dx < getWidth() && mRightViewIndex < mAdapter.getCount()) { View child = mAdapter.getView(mRightViewIndex, mRemovedViewQueue.poll(), this); addAndMeasureChild(child, -1); rightEdge += child.getMeasuredWidth(); if (mRightViewIndex == mAdapter.getCount() - 1) { mMaxX = mCurrentX + rightEdge - getWidth(); } if (mMaxX < 0) { mMaxX = 0; } mRightViewIndex++; } } private void fillListLeft(int leftEdge, final int dx) { while (leftEdge + dx > 0 && mLeftViewIndex >= 0) { View child = mAdapter.getView(mLeftViewIndex, mRemovedViewQueue.poll(), this); addAndMeasureChild(child, 0); leftEdge -= child.getMeasuredWidth(); mLeftViewIndex--; mDisplayOffset -= child.getMeasuredWidth(); } } private void removeNonVisibleItems(final int dx) { View child = getChildAt(0); while (child != null && child.getRight() + dx <= 0) { mDisplayOffset += child.getMeasuredWidth(); mRemovedViewQueue.offer(child); removeViewInLayout(child); mLeftViewIndex++; child = getChildAt(0); } child = getChildAt(getChildCount() - 1); while (child != null && child.getLeft() + dx >= getWidth()) { mRemovedViewQueue.offer(child); removeViewInLayout(child); mRightViewIndex--; child = getChildAt(getChildCount() - 1); } } private void positionItems(final int dx) { if (getChildCount() > 0) { mDisplayOffset += dx; int left = mDisplayOffset; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); int childWidth = child.getMeasuredWidth(); child.layout(left, 0, left + childWidth, child.getMeasuredHeight()); left += childWidth + child.getPaddingRight(); } } } public synchronized void scrollTo(int x) { mScroller.startScroll(mNextX, 0, x - mNextX, 0); requestLayout(); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = super.dispatchTouchEvent(ev); handled |= mGesture.onTouchEvent(ev); return handled; } private OnGestureListener mOnGesture = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { getParent().requestDisallowInterceptTouchEvent(false); return false; } @Override public boolean onDown(MotionEvent e) { mScroller.forceFinished(true); return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { synchronized (HorizontalListView.this) { mScroller.fling(mNextX, 0, (int) -velocityX, 0, 0, mMaxX, 0, 0); } requestLayout(); if (Math.abs(velocityX)>Math.abs(velocityY)){ return true; }else{ getParent().requestDisallowInterceptTouchEvent(false); return false; } } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { if (Math.abs(distanceY)>20&&Math.abs(distanceY)>Math.abs(distanceX)){ getParent().requestDisallowInterceptTouchEvent(false); return false; }else{ synchronized (HorizontalListView.this) { mNextX += (int) distanceX; } requestLayout(); return true; } } @Override public boolean onSingleTapConfirmed(MotionEvent e) { for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); if (isEventWithinView(e, child)) { if (mOnItemClicked != null) { mOnItemClicked.onItemClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); } if (mOnItemSelected != null) { mOnItemSelected.onItemSelected(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); } break; } } getParent().requestDisallowInterceptTouchEvent(false); return false; // return super.onSingleTapConfirmed(e); } @Override public void onLongPress(MotionEvent e) { int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); if (isEventWithinView(e, child)) { if (mOnItemLongClicked != null) { mOnItemLongClicked.onItemLongClick(HorizontalListView.this, child, mLeftViewIndex + 1 + i, mAdapter.getItemId(mLeftViewIndex + 1 + i)); } break; } } getParent().requestDisallowInterceptTouchEvent(false); } private boolean isEventWithinView(MotionEvent e, View child) { Rect viewRect = new Rect(); int[] childPosition = new int[2]; child.getLocationOnScreen(childPosition); int left = childPosition[0]; int right = left + child.getWidth(); int top = childPosition[1]; int bottom = top + child.getHeight(); viewRect.set(left, top, right, bottom); return viewRect.contains((int) e.getRawX(), (int) e.getRawY()); } }; }
二、我們來瞭解一下屏幕的觸摸監聽事件的分發機制
Touch事件分發中只有兩個主角:ViewGroup和View。Activity的Touch事件事實上是調用它內部的ViewGroup的Touch事件,可以直接當成ViewGroup處理。
View在ViewGroup內,ViewGroup也可以在其他ViewGroup內,這時候把內部的ViewGroup當成View來分析。
ViewGroup的相關事件有三個:onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent。View的相關事件只有兩個:dispatchTouchEvent、onTouchEvent。
先分析ViewGroup的處理流程:首先得有個結構模型概念:ViewGroup和View組成了一棵樹形結構,最頂層為Activity的ViewGroup,下麵有若幹的ViewGroup節點,每個節點之下又有若幹的ViewGroup節點或者View節點,依次類推。如圖:
當一個Touch事件(觸摸事件為例)到達根節點,即Acitivty的ViewGroup時,它會依次下發,下發的過程是調用子View(ViewGroup)的dispatchTouchEvent方法實現的。簡單來說,就是ViewGroup遍歷它包含著的子View,調用每個View的dispatchTouchEvent方法,而當子View為ViewGroup時,又會通過調用ViwGroup的dispatchTouchEvent方法繼續調用其內部的View的dispatchTouchEvent方法。上述例子中的消息下發順序是這樣的:①-②-⑤-⑥-⑦-③-④。dispatchTouchEvent方法只負責事件的分發,它擁有boolean類型的返回值,當返回為true時,順序下發會中斷。在上述例子中如果⑤的dispatchTouchEvent返回結果為true,那麼⑥-⑦-③-④將都接收不到本次Touch事件。來個簡單版的代碼加深理解:
/** * ViewGroup * @param ev * @return */ public boolean dispatchTouchEvent(MotionEvent ev){ ....//其他處理,在此不管 View[] views=getChildView(); for(int i=0;i<views.length;i++){
//判斷下Touch到屏幕上的點在該子View上面
if(...){ if(views[i].dispatchTouchEvent(ev)) return true;
} } ...//其他處理,在此不管 } /** * View * @param ev * @return */ public boolean dispatchTouchEvent(MotionEvent ev){ ....//其他處理,在此不管 return false; }
在此可以看出,ViewGroup的dispatchTouchEvent是真正在執行“分發”工作,而View的dispatchTouchEvent方法,並不執行分發工作,或者說它分發的對象就是自己,決定是否把touch事件交給自己處理,而處理的方法,便是onTouchEvent事件,事實上子View的dispatchTouchEvent方法真正執行的代碼是這樣的
/** * View * @param ev * @return */ public boolean dispatchTouchEvent(MotionEvent ev){ ....//其他處理,在此不管 return onTouchEvent(event); }
一般情況下,我們不該在普通View內重寫dispatchTouchEvent方法,因為它並不執行分發邏輯。當Touch事件到達View時,我們該做的就是是否在onTouchEvent事件中處理它。
那麼,ViewGroup的onTouchEvent事件是什麼時候處理的呢?當ViewGroup所有的子View都返回false時,onTouchEvent事件便會執行。由於ViewGroup是繼承於View的,它其實也是通過調用View的dispatchTouchEvent方法來執行onTouchEvent事件。
在目前的情況看來,似乎只要我們把所有的onTouchEvent都返回false,就能保證所有的子控制項都響應本次Touch事件了。但必須要說明的是,這裡的Touch事件,只限於Acition_Down事件,即觸摸按下事件,而Aciton_UP和Action_MOVE卻不會執行。事實上,一次完整的Touch事件,應該是由一個Down、一個Up和若幹個Move組成的。Down方式通過dispatchTouchEvent分發,分發的目的是為了找到真正需要處理完整Touch請求的View。當某個View或者ViewGroup的onTouchEvent事件返回true時,便表示它是真正要處理這次請求的View,之後的Aciton_UP和Action_MOVE將由它處理。當所有子View的onTouchEvent都返回false時,這次的Touch請求就由根ViewGroup,即Activity自己處理了。
看看改進後的ViewGroup的dispatchTouchEvent方法
View mTarget=null;//保存捕獲Touch事件處理的View public boolean dispatchTouchEvent(MotionEvent ev) { //....其他處理,在此不管 if(ev.getAction()==KeyEvent.ACTION_DOWN){ //每次Down事件,都置為Null
if(!onInterceptTouchEvent()){
mTarget=null; View[] views=getChildView(); for(int i=0;i<views.length;i++){ if(views[i].dispatchTouchEvent(ev)) mTarget=views[i]; return true; }
} } //當子View沒有捕獲down事件時,ViewGroup自身處理。這裡處理的Touch事件包含Down、Up和Move if(mTarget==null){ return super.dispatchTouchEvent(ev); } //...其他處理,在此不管 if(onInterceptTouchEvent()){
//...其他處理,在此不管
}
//這一步在Action_Down中是不會執行到的,只有Move和UP才會執行到。 return mTarget.dispatchTouchEvent(ev); }
ViewGroup還有個onInterceptTouchEvent,看名字便知道這是個攔截事件。這個攔截事件需要分兩種情況來說明:
1.假如我們在某個ViewGroup的onInterceptTouchEvent中,將Action為Down的Touch事件返回true,那便表示將該ViewGroup的所有下發操作攔截掉,這種情況下,mTarget會一直為null,因為mTarget是在Down事件中賦值的。由於mTarge為null,該ViewGroup的onTouchEvent事件被執行。這種情況下可以把這個ViewGroup直接當成View來對待。
2.假如我們在某個ViewGroup的onInterceptTouchEvent中,將Acion為Down的Touch事件都返回false,其他的都返回True,這種情況下,Down事件能正常分發,若子View都返回false,那mTarget還是為空,無影響。若某個子View返回了true,mTarget被賦值了,在Action_Move和Aciton_UP分發到該ViewGroup時,便會給mTarget分發一個Action_Delete的MotionEvent,同時清空mTarget的值,使得接下去的Action_Move(如果上一個操作不是UP)將由ViewGroup的onTouchEvent處理。
情況一用到的比較多,情況二個人還未找到使用場景。
從頭到尾總結一下:
1.Touch事件分發中只有兩個主角:ViewGroup和View。ViewGroup包含onInterceptTouchEvent、dispatchTouchEvent、onTouchEvent三個相關事件。View包含dispatchTouchEvent、onTouchEvent兩個相關事件。其中ViewGroup又繼承於View。
2.ViewGroup和View組成了一個樹狀結構,根節點為Activity內部包含的一個ViwGroup。
3.觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若幹個,可以為0個。
4.當Acitivty接收到Touch事件時,將遍歷子View進行Down事件的分發。ViewGroup的遍歷可以看成是遞歸的。分發的目的是為了找到真正要處理本次完整觸摸事件的View,這個View會在onTouchuEvent結果返回true。
5.當某個子View返回true時,會中止Down事件的分發,同時在ViewGroup中記錄該子View。接下去的Move和Up事件將由該子View直接進行處理。由於子View是保存在ViewGroup中的,多層ViewGroup的節點結構時,上級ViewGroup保存的會是真實處理事件的View所在的ViewGroup對象:如ViewGroup0-ViewGroup1-TextView的結構中,TextView返回了true,它將被保存在ViewGroup1中,而ViewGroup1也會返回true,被保存在ViewGroup0中。當Move和UP事件來時,會先從ViewGroup0傳遞至ViewGroup1,再由ViewGroup1傳遞至TextView。
6.當ViewGroup中所有子View都不捕獲Down事件時,將觸發ViewGroup自身的onTouch事件。觸發的方式是調用super.dispatchTouchEvent函數,即父類View的dispatchTouchEvent方法。在所有子View都不處理的情況下,觸發Acitivity的onTouchEvent方法。
7.onInterceptTouchEvent有兩個作用:1.攔截Down事件的分發。2.中止Up和Move事件向目標View傳遞,使得目標View所在的ViewGroup捕獲Up和Move事件。
另外,上文所列出的代碼並非真正的源碼,只是概括了源碼在事件分發處理中的核心處理流程,真正源碼各位可以自己去看,包含了更豐富的內容。
補充:
“觸摸事件由Action_Down、Action_Move、Aciton_UP組成,其中一次完整的觸摸事件中,Down和Up都只有一個,Move有若幹個,可以為0個。”,這裡補充下其實UP事件是可能為0個的。
三、用戶手勢檢測-GestureDetector使用詳解
一、概述
當用戶觸摸屏幕的時候,會產生許多手勢,例如down,up,scroll,filing等等。
一般情況下,我們知道View類有個View.OnTouchListener內部介面,通過重寫他的onTouch(View v, MotionEvent event)方法,我們可以處理一些touch事件,但是這個方法太過簡單,如果需要處理一些複雜的手勢,用這個介面就會很麻煩(因為我們要自己根據用戶觸摸的軌跡去判斷是什麼手勢)。
Android sdk給我們提供了GestureDetector(Gesture:手勢Detector:識別)類,通過這個類我們可以識別很多的手勢,主要是通過他的onTouchEvent(event)方法完成了不同手勢的識別。雖然他能識別手勢,但是不同的手勢要怎麼處理,應該是提供給程式員實現的。
GestureDetector這個類對外提供了兩個介面和一個外部類
介面:OnGestureListener,OnDoubleTapListener
內部類:SimpleOnGestureListener
這個外部類,其實是兩個介面中所有函數的集成,它包含了這兩個介面里所有必須要實現的函數而且都已經重寫,但所有方法體都是空的;不同點在於:該類是static class,程式員可以在外部繼承這個類,重寫裡面的手勢處理方法。
下麵我們先看OnGestureListener介面;
二、GestureDetector.OnGestureListener---介面
1、基本講解
如果我們寫一個類並implements OnGestureListener,會提示有幾個必須重寫的函數,加上之後是這個樣子的:
[java] view plain copy
- private class gesturelistener implements GestureDetector.OnGestureListener{
- public boolean onDown(MotionEvent e) {
- // TODO Auto-generated method stub
- return false;
- }
- public void onShowPress(MotionEvent e) {
- // TODO Auto-generated method stub
- }
- public boolean onSingleTapUp(MotionEvent e) {
- // TODO Auto-generated method stub
- return false;
- }
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- // TODO Auto-generated method stub
- return false;
- }
- public void onLongPress(MotionEvent e) {
- // TODO Auto-generated method stub
- }
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- // TODO Auto-generated method stub
- return false;
- }
- }
可見,這裡總共重寫了六個函數,這些函數都在什麼情況下才會觸發呢,下麵講一下:
OnDown(MotionEvent e):用戶按下屏幕就會觸發;
onShowPress(MotionEvent e):如果是按下的時間超過瞬間,而且在按下的時候沒有鬆開或者是拖動的,那麼onShowPress就會執行,具體這個瞬間是多久,我也不清楚呃……
onLongPress(MotionEvent e):長按觸摸屏,超過一定時長,就會觸發這個事件
觸發順序:
onDown->onShowPress->onLongPress
onSingleTapUp(MotionEvent e):從名子也可以看出,一次單獨的輕擊抬起操作,也就是輕擊一下屏幕,立刻抬起來,才會有這個觸發,當然,如果除了Down以外還有其它操作,那就不再算是Single操作了,所以也就不會觸發這個事件
觸發順序:
點擊一下非常快的(不滑動)Touchup:
onDown->onSingleTapUp->onSingleTapConfirmed
點擊一下稍微慢點的(不滑動)Touchup:
onDown->onShowPress->onSingleTapUp->onSingleTapConfirmed
onFling(MotionEvent e1, MotionEvent e2, float velocityX,float velocityY) :滑屏,用戶按下觸摸屏、快速移動後鬆開,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE, 1個ACTION_UP觸發
參數解釋:
e1:第1個ACTION_DOWN MotionEvent
e2:最後一個ACTION_MOVE MotionEvent
velocityX:X軸上的移動速度,像素/秒
velocityY:Y軸上的移動速度,像素/秒
onScroll(MotionEvent e1, MotionEvent e2,float distanceX, float distanceY):在屏幕上拖動事件。無論是用手拖動view,或者是以拋的動作滾動,都會多次觸發,這個方法 在ACTION_MOVE動作發生時就會觸發
滑屏:手指觸動屏幕後,稍微滑動後立即鬆開
onDown-----》onScroll----》onScroll----》onScroll----》………----->onFling
拖動
onDown------》onScroll----》onScroll------》onFiling
可見,無論是滑屏,還是拖動,影響的只是中間OnScroll觸發的數量多少而已,最終都會觸發onFling事件!
2、實例
要使用GestureDetector,有三步要走:
1、創建OnGestureListener監聽函數:
可以使用構造實例:
[java] view plain copy
- GestureDetector.OnGestureListener listener = new GestureDetector.OnGestureListener(){
- };
也可以構造類:
[java] view plain copy
- private class gestureListener implements GestureDetector.OnGestureListener{
- }
2、創建GestureDetector實例mGestureDetector:
構造函數有下麵三個,根據需要選擇:
[java] view plain copy- GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener);
- GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
- GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
3、onTouch(View v, MotionEvent event)中攔截:
[java] view plain copy- public boolean onTouch(View v, MotionEvent event) {
- return mGestureDetector.onTouchEvent(event);
- }
4、控制項綁定
[java] view plain copy
- TextView tv = (TextView)findViewById(R.id.tv);
- tv.setOnTouchListener(this);
現在進入實例階段:
首先,在主佈局頁面添加一個textView,並將其放大到整屏,方便在其上的手勢識別,代碼為:
[java] view plain copy
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- tools:context="com.example.gesturedetectorinterface.MainActivity" >
- <TextView
- android:id="@+id/tv"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- android:layout_margin="50dip"
- android:background="#ff00ff"
- android:text="@string/hello_world" />
- </RelativeLayout>
然後在JAVA代碼中,依據上面的三步走原則,寫出代碼,併在所有的手勢下添加上Toast提示並寫上Log
[java] view plain copy
- public class MainActivity extends Activity implements OnTouchListener{
- private GestureDetector mGestureDetector;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mGestureDetector = new GestureDetector(new gestureListener()); //使用派生自OnGestureListener
- TextView tv = (TextView)findViewById(R.id.tv);
- tv.setOnTouchListener(this);
- tv.setFocusable(true);
- tv.setClickable(true);
- tv.setLongClickable(true);
- }
- /*
- * 在onTouch()方法中,我們調用GestureDetector的onTouchEvent()方法,將捕捉到的MotionEvent交給GestureDetector
- * 來分析是否有合適的callback函數來處理用戶的手勢
- */
- public boolean onTouch(View v, MotionEvent event) {
- return mGestureDetector.onTouchEvent(event);
- }
- private class gestureListener implements GestureDetector.OnGestureListener{
- // 用戶輕觸觸摸屏,由1個MotionEvent ACTION_DOWN觸發
- public boolean onDown(MotionEvent e) {
- Log.i("MyGesture", "onDown");
- Toast.makeText(MainActivity.this, "onDown", Toast.LENGTH_SHORT).show();
- return false;
- }
- /*
- * 用戶輕觸觸摸屏,尚未鬆開或拖動,由一個1個MotionEvent ACTION_DOWN觸發
- * 註意和onDown()的區別,強調的是沒有鬆開或者拖動的狀態
- *
- * 而onDown也是由一個MotionEventACTION_DOWN觸發的,但是他沒有任何限制,
- * 也就是說當用戶點擊的時候,首先MotionEventACTION_DOWN,onDown就會執行,
- * 如果在按下的瞬間沒有鬆開或者是拖動的時候onShowPress就會執行,如果是按下的時間超過瞬間
- * (這塊我也不太清楚瞬間的時間差是多少,一般情況下都會執行onShowPress),拖動了,就不執行onShowPress。
- */
- public void onShowPress(MotionEvent e) {
- Log.i("MyGesture", "onShowPress");
- Toast.makeText(MainActivity.this, "onShowPress", Toast.LENGTH_SHORT).show();
- }
- // 用戶(輕觸觸摸屏後)鬆開,由一個1個MotionEvent ACTION_UP觸發
- ///輕擊一下屏幕,立刻抬起來,才會有這個觸發
- //從名子也可以看出,一次單獨的輕擊抬起操作,當然,如果除了Down以外還有其它操作,那就不再算是Single操作了,所以這個事件 就不再響應
- public boolean onSingleTapUp(MotionEvent e) {
- Log.i("MyGesture", "onSingleTapUp");
- Toast.makeText(MainActivity.this, "onSingleTapUp", Toast.LENGTH_SHORT).show();
- return true;
- }
- // 用戶按下觸摸屏,並拖動,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE觸發
- public boolean onScroll(MotionEvent e1, MotionEvent e2,
- float distanceX, float distanceY) {
- Log.i("MyGesture22", "onScroll:"+(e2.getX()-e1.getX()) +" "+distanceX);
- Toast.makeText(MainActivity.this, "onScroll", Toast.LENGTH_LONG).show();
- return true;
- }
- // 用戶長按觸摸屏,由多個MotionEvent ACTION_DOWN觸發
- public void onLongPress(MotionEvent e) {
- Log.i("MyGesture", "onLongPress");
- Toast.makeText(MainActivity.this, "onLongPress", Toast.LENGTH_LONG).show();
- }
- // 用戶按下觸摸屏、快速移動後鬆開,由1個MotionEvent ACTION_DOWN, 多個ACTION_MOVE, 1個ACTION_UP觸發
- public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
- float velocityY) {
- Log.i("MyGesture", "onFling");
- Toast.makeText(MainActivity.this, "onFling", Toast.LENGTH_LONG).show();
- return true;
- }
- };
- }
源碼在博客底部給出。
三、GestureDetector.OnDoubleTapListener---介面
1、構建
有兩種方式設置雙擊監聽:
方法一:新建一個類同時派生自OnGestureListener和OnDoubleTapListener:
[java] view plain copy
- private class gestureListener implements GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener{
- }
方法二:使用GestureDetector::setOnDoubleTapListener();函數設置監聽:
[java] view plain copy
- //構建GestureDetector實例
- mGestureDetector = new GestureDetector(new gestureListener()); //使用派生自OnGestureListener
- private class gestureListener implements GestureDetector.OnGestureListener{
- }
- //設置雙擊監聽器
- mGestureDetector.setOnDoubleTapListener(new doubleTapListener());
- private class doubleTapListener implements GestureDetector.OnDoubleTapListener{
- }
註意:大家可以看到無論在方法一還是在方法二中,都需要派生自GestureDetector.OnGestureListener,前面我們說過GestureDetector 的構造函數,如下:
[java] view plain copy- GestureDetector gestureDetector=new GestureDetector(GestureDetector.OnGestureListener listener);
- GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.OnGestureListener listener);
- GestureDetector gestureDetector=new GestureDetector(Context context,GestureDetector.SimpleOnGestureListener listener);
可以看到,在構造函數中,除了後面要講的SimpleOnGestureListener 以外的其它兩個構造函數都必須是OnGestureListener的實例。所以要想使用OnDoubleTapListener的幾個函數,就必須先實現OnGestureListener。
2、函數講解:
首先看一下OnDoubleTapListener介面必須重寫的三個函數:
[java] view plain copy- private class doubleTapListener implements GestureDetector.OnDoubleTapListener{
- public boolean onSingleTapConfirmed(MotionEvent e) {
- // TODO Auto-generated method stub
- return false;
- }
- public boolean onDoubleTap(MotionEvent e) {
- // TODO Auto-generated method stub
- return false;
- }
- public boolean onDoubleTapEvent(MotionEvent e) {
- // TODO Auto-generated method stub
- return false;
- }
- }
onSingleTapConfirmed(MotionEvent e):單擊事件。用來判定該次點擊是SingleTap而不是DoubleTap,如果連續點擊兩次就是DoubleTap手勢,如果只點擊一次,系統等待一段時間後沒有收到第二次點擊則判定該次點擊為SingleTap而不是DoubleTap,然後觸發SingleTapConfirmed事件。觸發順序是:OnDown->OnsingleTapUp->OnsingleTapConfirmed
關於onSingleTapConfirmed和onSingleTapUp的一點區別: OnGestureListener有這樣的一個方法onSingleTapUp,和onSingleTapConfirmed容易混淆。二者的區別是:onSingleTapUp,只要手抬起就會執行,而對於onSingleTapConfirmed來說,如果雙擊的話,則onSingleTapConfirmed不會執行。
onDoubleTap(MotionEvent e):雙擊事件
onDoubleTapEvent(MotionEvent e):雙擊間隔中發生的動作。指觸發onDoubleTap以後,在雙擊之間發生的其它動作,包含down、up和move事件;下圖是雙擊一下的Log輸出:
兩點總結:
1、從上圖可以看出,在第二下點擊時,先觸發OnDoubleTap,然後再觸發OnDown(第二次點擊)
2、其次在觸發OnDoubleTap以後,就開始觸發onDoubleTapEvent了,onDoubleTapEvent後面的數字代表了當前的事件,0指ACTION_DOWN,1指ACTION_UP,2 指ACTION_MOVE
在上一個例子的基礎上,我們再添加一個雙擊監聽類,實現如下:
[java] view plain copy
- public class MainActivity extends Activity implements OnTouchListener{
- private GestureDetector mGestureDetector;
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mGestureDetector = new GestureDetector(new gestureListener()); //使用派生自OnGestureListener
- mGestureDetector.setOnDoubleTapListener(new doubleTapListener());
- TextView tv = (TextView)findViewById(R.id.tv);
- tv.setOnTouchListener(this);
- tv.setFocusable(true);
- tv.setClickable(true);
- tv.setLongClickable(true);
- }
- /*
- * 在onTouch()方法中,我們調用GestureDetector的onTouchEvent()方法,將捕捉到的MotionEvent交給GestureDetector
- * 來分析是否有合適的callback函數來處理用戶的手勢
- */
- public boolean onTouch(View v, MotionEvent event) {
- return mGestureDetector.onTouchEvent(event);
- }
- //OnGestureListener監聽
- private class gestureListener implements GestureDetector.OnGestureListener{
- public boolean onDown(MotionEvent e) {
- Log.i("MyGesture", "onDown");
- Toast.makeText(MainActivity.this, "onDown", Toast.LENGTH_SHORT).show();
- &nbs