前言
成都創(chuàng)新互聯(lián)公司自2013年起,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都做網(wǎng)站、網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元武山做網(wǎng)站,已為上家服務(wù),為武山各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18982081108
上一篇講了Android觸摸事件的傳遞機(jī)制,具體可以看這里初識(shí)Android觸摸事件傳遞機(jī)制。既然知道Android中觸摸事件的傳遞分發(fā),那么它能解決什么樣的問題,在我們實(shí)際開發(fā)中如何應(yīng)用,這點(diǎn)很重要,知道原理是為了解決問題而準(zhǔn)備的。這篇文章的核心講的如何解決View的滑動(dòng)沖突,這個(gè)問題在日常開發(fā)中很常見,比如內(nèi)部嵌套Fragment視圖是左右滑動(dòng),外部用一個(gè)ScrollView來包含,可以上下滑動(dòng),如果不進(jìn)行滑動(dòng)沖突處理的話,就會(huì)造成外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致。
目錄
常見的滑動(dòng)沖突場(chǎng)景
滑動(dòng)沖突的處理規(guī)則
外部攔截法
內(nèi)部攔截法
小結(jié)
常見的滑動(dòng)沖突場(chǎng)景
常見的滑動(dòng)沖突場(chǎng)景可以簡(jiǎn)單分為以下三種:
場(chǎng)景1:外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向不一致
場(chǎng)景2:外部滑動(dòng)方向和內(nèi)部滑動(dòng)方向一致
場(chǎng)景3:上面兩種情況的嵌套
如圖:
場(chǎng)景1,主要是將ViewPager和Fragment配合使用所組成的頁面滑動(dòng)效果,主流應(yīng)用幾乎都會(huì)使用這個(gè)效果。在這個(gè)效果中可以通過左右滑動(dòng)來切換頁面,而每個(gè)頁面內(nèi)部往往又是一個(gè)ListView,所以就造成了滑動(dòng)沖突,但是在ViewPager內(nèi)部處理了這種滑動(dòng)沖突,因此在采用ViewPager時(shí)我們就無須關(guān)注這個(gè)問題,而如果把ViewPager換成ScrollView,那就必須自己手動(dòng)處理,不然造成的結(jié)果就是內(nèi)外兩層只能一層能夠滑動(dòng)。
場(chǎng)景2,就復(fù)雜一點(diǎn),當(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)但很卡頓。
場(chǎng)景3,是場(chǎng)景1和場(chǎng)景2兩種情況的嵌套,顯得更復(fù)雜了。比如外部有一個(gè)SlideMenu效果,內(nèi)部有一個(gè)ViewPager,ViewPager的每一個(gè)頁面中又是一個(gè)ListView。雖然場(chǎng)景3滑動(dòng)沖突看起來很復(fù)雜,但都是幾個(gè)單一的滑動(dòng)沖突的疊加,因此需要一一拆解開來即可。
滑動(dòng)沖突的處理規(guī)則
一般來說,不管滑動(dòng)沖突有多么復(fù)雜,它都有既定的規(guī)則,根據(jù)這些規(guī)則我們就可以選擇合適的方法去處理。
對(duì)于場(chǎng)景1,它的處理規(guī)則就是:當(dāng)用戶左右滑動(dòng)時(shí),需要讓外部的View攔截點(diǎn)擊事件,當(dāng)用戶上下滑動(dòng),需要讓內(nèi)部View攔截點(diǎn)擊事件。具體來說就是根據(jù)滑動(dòng)是水平滑動(dòng)還是豎直滑動(dòng)來判斷到底是由誰來攔截事件。
如圖:
簡(jiǎn)單來說,就是根據(jù)水平方向和豎直方向的距離差來判斷,如果是Dx>Dy,那么則是水平滑動(dòng),如果是Dy>Dx,那么則是豎直滑動(dòng)。
場(chǎng)景2,則是比較特殊,它無法根據(jù)滑動(dòng)的角度,距離差以及速度差來做判斷。這個(gè)時(shí)候就需要從業(yè)務(wù)上找到突破點(diǎn),比如,當(dāng)處于某種狀態(tài)時(shí)需要外部View響應(yīng)用戶的滑動(dòng),而處于另外一種狀態(tài)時(shí)需要內(nèi)部View來響應(yīng)View的滑動(dòng)
對(duì)于場(chǎng)景3的話,它的滑動(dòng)規(guī)則也更復(fù)雜,和場(chǎng)景2一樣,同樣是從業(yè)務(wù)上找到突破點(diǎn)。
外部攔截法
外部攔截法是指點(diǎn)擊事件都是先經(jīng)過父容器的攔截處理,如果父容器需要此事件就攔截,如果不需要此事件,就不攔截了,這樣就可以解決滑動(dòng)沖突的問題,外部攔截法需要重寫父容器的onInterceptTouchEvent方法,在內(nèi)部做相應(yīng)的攔截即可,偽代碼如下:
@Override 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 (父容器需要點(diǎn)擊當(dāng)前事件) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; }
首先ACTION_DOWN這個(gè)事件,父容器必須返回false,這樣保證后續(xù)move和up的事件可以傳遞給子View,根據(jù)move事件來決定是否攔截,如果父容器攔截就返回true,否則返回false。
實(shí)現(xiàn)一個(gè)自定義類似ViewPager的控件,嵌套ListView的效果,源代碼如下:
public class HorizontalScrollViewEx extends ViewGroup { private static final String TAG = "HorizontalScrollViewEx"; 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)(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; //彈性滑動(dòng)對(duì)象 private VelocityTracker mVelocityTracker; //追蹤滑動(dòng)速度 public HorizontalScrollViewEx(Context context) { super(context); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override 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; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } 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; } Log.d(TAG, "intercepted=" + intercepted); 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: { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; scrollBy(-deltaX, 0); break; } case MotionEvent.ACTION_UP: { int scrollX = getScrollX(); mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= 50) { mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; } else { mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 0); mVelocityTracker.clear(); break; } default: break; } mLastX = x; mLastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight()); } else if (widthSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measuredWidth, heightSpaceSize); } else { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measuredHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); } }
這個(gè)情況的攔截條件就是父容器在滑動(dòng)過程中水平距離差比垂直距離差大,那么就進(jìn)行攔截,否則就不攔截,繼續(xù)傳遞事件。
內(nèi)部攔截法
內(nèi)部攔截法是指父容器不攔截任何事件,所有的事件都傳遞給子元素,如果子元素需要此事件就直接消耗掉,否則就交給父容器進(jìn)行處理,這種方法和Android中的事件分發(fā)機(jī)制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起來較外部攔截法復(fù)雜。偽代碼如下:
@Override public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; if (父容器需要此類點(diǎn)擊事件) { mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); }
當(dāng)子元素調(diào)用requestDisallowInterceptTouchEvent(false)方法時(shí),父元素才能繼續(xù)攔截所需的事件。
前面是用自定義類似的ViewPager,現(xiàn)在重寫一個(gè)ListView,我們可以自定義一個(gè)ListView,叫做ListViewEx,然后對(duì)內(nèi)部攔截法的模板代碼進(jìn)行修改即可。
public class ListViewEx extends ListView { private static final String TAG = "ListViewEx"; private HorizontalScrollViewEx2 mHorizontalScrollViewEx2; // 分別記錄上次滑動(dòng)的坐標(biāo) private int mLastX = 0; private int mLastY = 0; public ListViewEx(Context context) { super(context); } public ListViewEx(Context context, AttributeSet attrs) { super(context, attrs); } public ListViewEx(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } public void setHorizontalScrollViewEx2( HorizontalScrollViewEx2 horizontalScrollViewEx2) { mHorizontalScrollViewEx2 = horizontalScrollViewEx2; } @Override public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(true); break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; Log.d(TAG, "dx:" + deltaX + " dy:" + deltaY); if (Math.abs(deltaX) > Math.abs(deltaY)) { mHorizontalScrollViewEx2.requestDisallowInterceptTouchEvent(false); } break; } case MotionEvent.ACTION_UP: { break; } default: break; } mLastX = x; mLastY = y; return super.dispatchTouchEvent(event); } }
同時(shí)對(duì)于包含ListViewEx外部布局進(jìn)行修改,在onInterceptTouchEvent事件上不進(jìn)行攔截
public class HorizontalScrollViewEx2 extends ViewGroup { private static final String TAG = "HorizontalScrollViewEx2"; 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)(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalScrollViewEx2(Context context) { super(context); init(); } public HorizontalScrollViewEx2(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalScrollViewEx2(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); int action = event.getAction(); if (action == MotionEvent.ACTION_DOWN) { mLastX = x; mLastY = y; if (!mScroller.isFinished()) { mScroller.abortAnimation(); return true; } return false; } else { return true; } } @Override public boolean onTouchEvent(MotionEvent event) { Log.d(TAG, "onTouchEvent action:" + event.getAction()); mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; Log.d(TAG, "move, deltaX:" + deltaX + " deltaY:" + deltaY); scrollBy(-deltaX, 0); break; } case MotionEvent.ACTION_UP: { int scrollX = getScrollX(); int scrollToChildIndex = scrollX / mChildWidth; Log.d(TAG, "current index:" + scrollToChildIndex); mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= 50) { mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; } else { mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 0); mVelocityTracker.clear(); Log.d(TAG, "index:" + scrollToChildIndex + " dx:" + dx); break; } default: break; } mLastX = x; mLastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (heightSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight()); } else if (widthSpecMode == MeasureSpec.AT_MOST) { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measuredWidth, heightSpaceSize); } else { final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measuredHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { Log.d(TAG, "width:" + getWidth()); int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); } }
這個(gè)攔截規(guī)則也是父容器在滑動(dòng)過程中水平距離差與垂直距離差相比。
小結(jié)
總的來說,滑動(dòng)沖突的場(chǎng)景可以分為三種,內(nèi)外部方向不一致、內(nèi)外部方向一致、嵌套前面兩種情況。如何解決,不管多么復(fù)雜的滑動(dòng)沖突,可以進(jìn)行拆分,根據(jù)的一定的規(guī)則,第一種情況可根據(jù)滑動(dòng)距離差、速度差和角度差來解決,第二種和第三種情況,可根據(jù)業(yè)務(wù)上找到突破點(diǎn),業(yè)務(wù)上一種狀態(tài)需要響應(yīng),切換到另外一種狀態(tài)時(shí)則不響應(yīng),根據(jù)業(yè)務(wù)需求得出相應(yīng)的處理規(guī)則,有了處理規(guī)則可以進(jìn)行下一步處理。
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。
網(wǎng)站題目:Android觸摸事件的應(yīng)用詳解
轉(zhuǎn)載來于:http://aaarwkj.com/article22/igiscc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)頁設(shè)計(jì)公司、搜索引擎優(yōu)化、小程序開發(fā)、移動(dòng)網(wǎng)站建設(shè)、面包屑導(dǎo)航、網(wǎng)站內(nèi)鏈
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)