android中view手勢(shì)滑動(dòng)沖突的解決方法
Android手勢(shì)事件的沖突跟點(diǎn)擊事件的分發(fā)過程息息相關(guān),由三個(gè)重要的方法來共同完成,分別是:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。
public boolean dispatchTouchEvent(MotionEvent ev)
這個(gè)方法用來進(jìn)行事件的分發(fā)。如果事件傳遞到view,那么這個(gè)方法一定會(huì)被調(diào)用,返回結(jié)果受當(dāng)前View的onTouchEvent和下級(jí)View的dispatchTouchEvent方法的影響,表示是否消耗當(dāng)前事件。
public boolean onInterceptTouchEvent(MotionEvent event)
在上述方法內(nèi)部調(diào)用,用來判斷是攔截某個(gè)事件,如果當(dāng)前View攔截了某個(gè)事件,那么在同一個(gè)事件序列當(dāng)中,此方法不會(huì)被再次調(diào)用,返回結(jié)果表示是否攔截當(dāng)前事件。
public boolean onTouchEvent(MotionEvent event)
在dispathcTouchEvent方法中調(diào)用,用來處理點(diǎn)擊事件,返回結(jié)果表示是否消耗當(dāng)前事件,如果不消耗,則在同一個(gè)事件序列中,當(dāng)前View無法再次接到事件。
例:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume = false; if(onInterceptTouchEvent(ev)){ consume = onTouchEvent(ev); } else { consum = child.dispathcTouchEvent(ev); } return consume; }
手勢(shì)沖突的解決方法就是用上面的三個(gè)方法;主要分為兩種解決方法:·1外部攔截法 2內(nèi)部攔截法
1.常見的滑動(dòng)沖突場(chǎng)景
1.1 外部滑動(dòng)方向和內(nèi)部滑動(dòng)的方向不一致
這種情況我們經(jīng)常遇見,比如使用viewpaper+listview時(shí),在這種效果中,可以通過左右滑動(dòng)切換頁面,而每一個(gè)頁面往往又是一個(gè)listview,本來在這種情況下是有沖突的,但是Viewpaper內(nèi)部處理了這個(gè)滑動(dòng)沖突,因此采用viewpaper我們無需關(guān)注這個(gè)問題,如果我們采用的不是Viewpaper而是ScrollView等,那么必須手動(dòng)處理滑動(dòng)沖突,否則內(nèi)外兩層只能有一層滑動(dòng),那就是滑動(dòng)沖突。另外內(nèi)部左右滑動(dòng),外部上下滑動(dòng)也同樣屬于該類。
1.2 外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向一致
這種情況就比較復(fù)雜,當(dāng)內(nèi)外兩層都在同一個(gè)方向可以滑動(dòng)的時(shí)候,顯然存在邏輯問題,因?yàn)楫?dāng)手指開始滑動(dòng)的時(shí)候,系統(tǒng)無法知道用戶到底是想讓那一層動(dòng),所以當(dāng)手指滑動(dòng)的時(shí)候就會(huì)出現(xiàn)問題,要么只能一層動(dòng),要么內(nèi)外兩成動(dòng)的都很卡頓。
2.給出解決方案
2.1 外部攔截法
針對(duì)場(chǎng)景1,我們可以發(fā)現(xiàn)外部和內(nèi)部的滑動(dòng)方向不一樣也就是說只要判斷當(dāng)前dy和dx的大小,如果dy>dx,那么當(dāng)前就是豎直滑動(dòng),否則就是水平滑動(dòng)。明確了這個(gè)我就就可以根據(jù)當(dāng)前的手勢(shì)開始攔截了。
從上一節(jié)中我們分析了view的事件分發(fā),我們知道點(diǎn)擊事件的分發(fā)順序是 通過父布局分發(fā),如果父布局沒有攔截,即onInterceptTouchEvent返回false,才會(huì)傳遞給子View。所以我們就可以利用onInterceptTouchEvent()這個(gè)方法來進(jìn)行事件的攔截。來看一下代碼:
public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: { if(父容器攔截的規(guī)則){ intercepted=true; }else{ intercepted=false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept=x; mLastYIntercept=y; return intercepted; }
上面的代碼差多就是外部攔截的通用模板了,在onInterceptTouchEvent方法中,
首先是ACTION_DOWN這個(gè)事件,父容器必須返回false,即不攔截事件,因?yàn)橐坏└溉萜鲾r截了ACTION_DOWN這個(gè)事件,那么后續(xù)的ACTION_MOVE和ACTION_UP事件將直接交給父容器處理,這個(gè)時(shí)候事件沒法繼續(xù)傳遞給子元素了;
然后是ACTION_MOVE這個(gè)事件,這個(gè)事件可以根據(jù)需要決定是否攔截,如果父容器需要攔截就返回true,否則返回false;
最后是ACTION_UP這個(gè)事件,這里必須返回false,因?yàn)檫@個(gè)事件本身也沒有太多意義。
下面我們來具體做一下攔截的操作,我們需要在水平滑動(dòng)的時(shí)候父容器攔截事件。
public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: { int deltaX=x-mLastXIntercept; int deltaY=y=mLastYIntercept; if(Math.abs(deltaX)>Math.abs(deltaY)){ intercepted=true; }else{ intercepted=false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept=x; mLastYIntercept=y; return intercepted; }
從上面的代碼來看,我們只是修改了一下攔截條件而已,所以說外部攔截還是很簡(jiǎn)單方便的。在滑動(dòng)的過程中,當(dāng)水平方向的距離大時(shí)就判定水平滑動(dòng)。
還是一貫我們做實(shí)驗(yàn)來證明理論的風(fēng)格,我們來自定義一個(gè)HorizontalScrollView來體現(xiàn)一下用外部攔截法解決沖突的快感。
先上一下代碼:
package com.gxl.viewtest; import android.animation.Animator; import android.animation.ObjectAnimator; import android.content.Context; import android.text.LoginFilter; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewGroup; import android.widget.Scroller; /** * s * Created by GXL on 2016/7/25 0025. */ public class HorizontalScrollView extends ViewGroup { private final String TAG = "HorizontalScrollView"; private VelocityTracker mVelocityTracker; private Scroller mScroller; private int mChildrenSize; private int mChildWidth; private int mChildIndex; //上次滑動(dòng)的坐標(biāo) private int mLastX = 0; private int mLastY = 0; //上次上次攔截滑動(dòng)的坐標(biāo) private int mLastXIntercept = 0; private int mLastYIntercept = 0; public HorizontalScrollView(Context context) { super(context); init(context); } public HorizontalScrollView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public void init(Context context) { mVelocityTracker = VelocityTracker.obtain(); mScroller = new Scroller(context); } public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: break; case MotionEvent.ACTION_MOVE: int deltaX = x - mLastX; if((getScrollX()-deltaX)>=0&&(getScrollX()-deltaX)<=(getMeasuredWidth()-ScreenUtils.getScreenWidth(getContext()))) { scrollBy(-deltaX, 0); } break; case MotionEvent.ACTION_UP: mVelocityTracker.computeCurrentVelocity(1000); float xVelocityTracker = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocityTracker) > 50) { if (xVelocityTracker > 0) { Log.i(TAG, "快速向右劃"); } else { Log.i(TAG, "快速向左劃"); } } mVelocityTracker.clear(); break; } mLastX = x; mLastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measureHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpaceMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpaceMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (heightSpaceMode == MeasureSpec.AT_MOST && widthSpaceMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; measureHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measureHeight); } else if (heightSpaceMode == MeasureSpec.AT_MOST) { measureHeight = getChildAt(0).getMeasuredHeight(); setMeasuredDimension(widthSpaceSize, measureHeight); } else if (widthSpaceMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measuredWidth, heightSpaceSize); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { Log.i(TAG, "onLayout: " + getMeasuredWidth()); int childleft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < mChildrenSize; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childleft, 0, childleft + mChildWidth, childView.getMeasuredHeight()); childleft += childWidth; } } } private void smoothScrollTo(int destX,int destY) { int scrollX=getScrollX(); int delta=destX-scrollX; mScroller.startScroll(scrollX,0,delta,0,1000); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } }
再來看一下布局文件
<com.gxl.viewtest.HorizontalScrollView android:layout_width="wrap_content" android:layout_height="match_parent" android:background="#00ff00" > <ListView android:id="@+id/listview1" android:layout_width="600dp" android:layout_height="match_parent" android:background="@color/colorPrimary" > </ListView> <ListView android:id="@+id/listview2" android:layout_width="600dp" android:layout_height="match_parent" android:background="@color/colorAccent" > </ListView> <ListView android:id="@+id/listview3" android:layout_width="600dp" android:layout_height="match_parent" android:background="#ff0000" > </ListView> </com.gxl.viewtest.HorizontalScrollView>
以上就是外部處理滑動(dòng)沖突的代碼,認(rèn)真看一下,思路還是很清晰的。里面還涉及了一些自定義View的知識(shí),我會(huì)在后面的博文中認(rèn)真分析一下代碼,你先看一下onInterceptTouchEvent處理滑動(dòng)沖突的部分。
看一下效果圖哈。
2.2 內(nèi)部攔截法
內(nèi)部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器去處理,這種方法和Android中的事件分發(fā)機(jī)制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,這個(gè)方法的大體解釋就是:
requestDisallowInterceptTouchEvent是ViewGroup類中的一個(gè)公用方法,參數(shù)是一個(gè)boolean值,官方介紹如下
Called when a child does not want this parent and its ancestors to intercept touch events with ViewGroup.onInterceptTouchEvent(MotionEvent).
This parent should pass this call onto its parents. This parent must obey this request for the duration of the touch (that is, only clear the flag after this parent has received an up or a cancel.
android系統(tǒng)中,一次點(diǎn)擊事件是從父view傳遞到子view中,每一層的view可以決定是否攔截并處理點(diǎn)擊事件或者傳遞到下一層,如果子view不處理點(diǎn)擊事件,則該事件會(huì)傳遞會(huì)父view,由父view去決定是否處理該點(diǎn)擊事件。在子view可以通過設(shè)置此方法去告訴父view不要攔截并處理點(diǎn)擊事件,父view應(yīng)該接受這個(gè)請(qǐng)求直到此次點(diǎn)擊事件結(jié)束。
使用起來外部攔截事件略顯復(fù)雜一點(diǎn)。下面我也先來看一下它的通用模板(注意下面的代碼是定義在子View中的):
public boolean onInterceptTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { parent.requestDisallowInterceptTouchEvent(true); //父布局不要攔截此事件 break; } case MotionEvent.ACTION_MOVE: { int deltaX=x-mLastXIntercept; int deltaY=y=mLastYIntercept; if(父容器需要攔截的事件){ parent.requestDisallowInterceptTouchEvent(false); //父布局需要要攔截此事件 } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept=x; mLastYIntercept=y; return super.dispathTouchEvent(event); }
上述代碼是內(nèi)部攔截法的典型代碼,當(dāng)面對(duì)不同的滑動(dòng)策略時(shí)只需要修改里面的條件即可,其他不需要修改做改動(dòng)而且也不能改動(dòng)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android 高仿微信朋友圈動(dòng)態(tài)支持雙擊手勢(shì)放大并滑動(dòng)查看圖片效果
- Android實(shí)現(xiàn)圖片自動(dòng)輪播并且支持手勢(shì)左右無限滑動(dòng)
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)和簡(jiǎn)單動(dòng)畫效果
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸放大縮小圖片效果
- Android手勢(shì)滑動(dòng)實(shí)現(xiàn)ImageView縮放圖片大小
- Android GestureDetector手勢(shì)滑動(dòng)使用實(shí)例講解
- Android自定義View實(shí)現(xiàn)隨手勢(shì)滑動(dòng)控件
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果
- Android實(shí)現(xiàn)手勢(shì)滑動(dòng)多點(diǎn)觸摸縮放平移圖片效果(二)
- Android獲取觸摸手勢(shì)實(shí)現(xiàn)左右滑動(dòng)
相關(guān)文章
Android仿QQ復(fù)制昵稱效果的實(shí)現(xiàn)方法
這篇文章主要介紹了Android仿QQ復(fù)制昵稱效果的實(shí)現(xiàn)方法,主要依賴的是一個(gè)開源項(xiàng)目,需要的朋友可以參考下2019-05-05android如何添加桌面圖標(biāo)和卸載程序后自動(dòng)刪除圖標(biāo)
android如何添加桌面圖標(biāo)和卸載程序后自動(dòng)刪除桌面圖標(biāo),這是一個(gè)應(yīng)用的安裝與卸載過程對(duì)桌面圖標(biāo)的操作,下面與大家分享下具體是如何實(shí)現(xiàn)的,感興趣的朋友可以參考下哈2013-06-06Android編程單元測(cè)試實(shí)例詳解(附源碼)
這篇文章主要介紹了Android編程單元測(cè)試,結(jié)合完整實(shí)例形式詳細(xì)分析了Android單元測(cè)試的具體步驟與相關(guān)技巧,并附帶完整實(shí)例代碼供讀者下載參考,需要的朋友可以參考下2015-11-11Android源碼中final關(guān)鍵字的用法及final,finally,finalize的區(qū)別
Android的源碼中很多地方對(duì)final關(guān)鍵字的用法很是“別出心裁”,之所以這么說是因?yàn)槲覐臎]看過是這么使用final關(guān)鍵字的,通過本文給大家分享Android源碼中final關(guān)鍵字的用法及final,finally,finalize的區(qū)別,感興趣的朋友一起學(xué)習(xí)吧2015-12-12Android實(shí)現(xiàn)簡(jiǎn)易的計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)易的計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10Android Studio preview 不固定及常見問題的解決辦法
preview 可以幫助您預(yù)覽您的布局文件將如何在用戶的設(shè)備上呈現(xiàn)。這篇文章主要介紹了Android Studio preview 不固定及常見問題的解決辦法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-05-05Flutter開發(fā)之Shortcuts快捷鍵組件的用法詳解
在桌面端的開發(fā)中,鍵盤快捷鍵是非常常見而必要的,F(xiàn)lutter?既然可以開發(fā)桌面端應(yīng)用,那必然要提供自定義快捷鍵,所以本文就來和大家講講Shortcuts組件的簡(jiǎn)單使用吧2023-05-05Android 多線程的實(shí)現(xiàn)方法總結(jié)
這篇文章主要介紹了Android 多線程的實(shí)現(xiàn)方法總結(jié)的相關(guān)資料,這里提供三種方法,幫助大家掌握這部分內(nèi)容,需要的朋友可以參考下2017-08-08