Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。先上效果图:
二、实战1、外部拦截法,解决横竖冲突思路是,重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。关于为什么返回true就代表拦截事件 。 如果父控件拦截了事件,则在父控件的onTouchEvent进行相应的事件处理。我的这个例子,是一个横向滑动的ViewGroup里面包含了3个竖向滑动的ListView。下面我附上代码:HorizontalEx.java
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 * Created by blueberry on 2016/6/20. * * 解决交错的滑动冲突 * * 外部拦截法 */ public class HorizontalEx extends ViewGroup { private static final String TAG = "HorizontalEx" ; private boolean isFirstTouch = true ; private int childIndex; private int childCount; private int lastXIntercept, lastYIntercept, lastX, lastY; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalEx(Context context) { super (context); init(); } public HorizontalEx(Context context, AttributeSet attrs) { super (context, attrs); init(); } public HorizontalEx(Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); if (childCount == 0 ) { setMeasuredDimension( 0 , 0 ); } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { width = childCount * getChildAt( 0 ).getMeasuredWidth(); height = getChildAt( 0 ).getMeasuredHeight(); setMeasuredDimension(width, height); } else if (widthMode == MeasureSpec.AT_MOST) { width = childCount * getChildAt( 0 ).getMeasuredWidth(); setMeasuredDimension(width, height); } else { height = getChildAt( 0 ).getMeasuredHeight(); setMeasuredDimension(width, height); } } @Override protected void onLayout( boolean changed, int l, int t, int r, int b) { int left = 0 ; for ( int i = 0 ; i < getChildCount(); i++) { final View child = getChildAt(i); child.layout(left + l, t, r + left, b); left += child.getMeasuredWidth(); } } /** * 拦截事件 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false ; int x = ( int ) ev.getX(); int y = ( int ) ev.getY(); switch (ev.getAction()) { /*如果拦截了Down事件,则子类不会拿到这个事件序列*/ case MotionEvent.ACTION_DOWN: lastXIntercept = x; lastYIntercept = y; intercepted = false ; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true ; } break ; case MotionEvent.ACTION_MOVE: final int deltaX = x - lastXIntercept; final int deltaY = y - lastYIntercept; /*根据条件判断是否拦截该事件*/ if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true ; } else { intercepted = false ; } break ; case MotionEvent.ACTION_UP: intercepted = false ; break ; } lastXIntercept = x; lastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { int x = ( int ) event.getX(); int y = ( int ) event.getY(); mVelocityTracker.addMovement(event); ViewConfiguration configuration = ViewConfiguration.get(getContext()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break ; case MotionEvent.ACTION_MOVE: /*因为这里父控件拿不到Down事件,所以使用一个布尔值, 当事件第一次来到父控件时,对lastX,lastY赋值*/ if (isFirstTouch) { lastX = x; lastY = y; isFirstTouch = false ; } final int deltaX = x - lastX; scrollBy(-deltaX, 0 ); break ; case MotionEvent.ACTION_UP: int scrollX = getScrollX(); final int childWidth = getChildAt( 0 ).getWidth(); mVelocityTracker.computeCurrentVelocity( 1000 , configuration.getScaledMaximumFlingVelocity()); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) > configuration.getScaledMinimumFlingVelocity()) { childIndex = xVelocity < 0 ? childIndex + 1 : childIndex - 1 ; } else { childIndex = (scrollX + childWidth / 2 ) / childWidth; } childIndex = Math.min(getChildCount() - 1 , Math.max(childIndex, 0 )); smoothScrollBy(childIndex * childWidth - scrollX, 0 ); mVelocityTracker.clear(); isFirstTouch = true ; break ; } lastX = x; lastY = y; return true ; } void smoothScrollBy( int dx, int dy) { mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500 ); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); } } @Override protected void onDetachedFromWindow() { super .onDetachedFromWindow(); mVelocityTracker.recycle(); } }调用代码:
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <code class = "hljs lasso" > @Override public void showOutHVData(List<string> data1, List<string> data2, List<string> data3) { ListView listView1 = new ListView(getContext()); ArrayAdapter<string> adapter1 = new ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data1); listView1.setAdapter(adapter1); ListView listView2 = new ListView(getContext()); ArrayAdapter<string> adapter2 = new ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data2); listView2.setAdapter(adapter2); ListView listView3 = new ListView(getContext()); ArrayAdapter<string> adapter3 = new ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data3); listView3.setAdapter(adapter3); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mHorizontalEx.addView(listView1, params); mHorizontalEx.addView(listView2, params); mHorizontalEx.addView(listView3, params); }其实外部拦截的主要思想都在于对onInterceptTouchEvent的重写。
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <code class = "hljs java" > @Override public boolean onInterceptTouchEvent(MotionEvent ev) { boolean intercepted = false ; int x = ( int ) ev.getX(); int y = ( int ) ev.getY(); switch (ev.getAction()) { /*如果拦截了Down事件,则子类不会拿到这个事件序列*/ case MotionEvent.ACTION_DOWN: lastXIntercept = x; lastYIntercept = y; intercepted = false ; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true ; } break ; case MotionEvent.ACTION_MOVE: final int deltaX = x - lastXIntercept; final int deltaY = y - lastYIntercept; /*根据条件判断是否拦截该事件*/ if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true ; } else { intercepted = false ; } break ; case MotionEvent.ACTION_UP: intercepted = false ; break ; } lastXIntercept = x; lastYIntercept = y; return intercepted; }</code>这几乎是一个实现外部拦截事件的模板,这里一定不要在ACTION_DOWN 中返回 true,否则会让子VIew没有机会得到事件,因为如果在ACTION_DOWN的时候返回了 true,同一个事件序列ViewGroup的disPatchTouchEvent就不会在调用onInterceptTouchEvent方法了,如果不明白可以参考我的上一遍文章。还有就是 在ACTION_UP中返回false,因为如果父控件拦截了ACTION_UP,那么子View将得不到UP事件,那么将会影响子View的 Onclick方法等。但这对父控件是没有影响的,因为如果是父控件子ACITON_MOVE中 就拦截了事件,他们UP事件必定也会交给它处理,因为有那么一条定律叫做:父控件一但拦截了事件,那么同一个事件序列的所有事件都将交给他处理。这条结论在我的上一篇文章中已经分析过。最后就是在 ACTION_MOVE中根据需求决定是否拦截。
2、内部拦截法,解决横竖冲突内部拦截主要依赖于父控件的 requestDisallowInterceptTouchEvent方法,关于这个方法我的上篇文章其实已经分析过。他设置父控件的一个标志(FLAG_DISALLOW_INTERCEPT)这个标志可以决定父控件是否拦截事件,如果设置了这个标志则不拦截,如果没设这个标志,它就会调用父控件的onInterceptTouchEvent()来询问父控件是否拦截。但这个标志对Down事件无效。可以参考一下源码:ViewGroup#dispatchTouchEvent
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <code class = "hljs coffeescript" > // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // Throw away all previous state when starting a new touch gesture. // The framework may have dropped the up or cancel event for the previous gesture // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); //清楚标志 resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null ) { //标志 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0 ; if (!disallowIntercept) { intercepted = onInterceptTouchEvent(ev); ev.setAction(action); // restore action in case it was changed } else { intercepted = false ; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true ; } </code>那么我们如果想使用 内部拦截法拦截事件。第一步:a、我们要重写父控件的onInterceptTouchEvent,在ACTION_DOWN的时候返回false,负责的话子View调用requestDisallowInterceptTouchEvent也将无能为力。b、还有就是其他事件的话都返回true,这样就把能否拦截事件的权利交给了子View。
第二步:在子View的dispatchTouchEvent中 来决定是否让父控件拦截事件。a. 先要在MotionEvent.ACTION_DOWN:的时候使用mHorizontalEx2.requestDisallowInterceptTouchEvent(true);,负责的话,下一个事件到来时,就交给父控件了。b. 然后在MotionEvent.ACTION_MOVE: 根据业务逻辑决定是否调用mHorizontalEx2.requestDisallowInterceptTouchEvent(false);来决定父控件是否拦截事件。上代码:HorizontalEx2.java
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 /** * Created by blueberry on 2016/6/20. * <p/> * 内部拦截 * 和 ListViewEx配合使用 */ public class HorizontalEx2 extends ViewGroup { private int lastX, lastY; private int childIndex; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalEx2(Context context) { super (context); init(); } public HorizontalEx2(Context context, AttributeSet attrs) { super (context, attrs); init(); } public HorizontalEx2(Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int childCount = getChildCount(); measureChildren(widthMeasureSpec, heightMeasureSpec); if (childCount == 0 ) { setMeasuredDimension( 0 , 0 ); } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { height = getChildAt( 0 ).getMeasuredHeight(); width = childCount * getChildAt( 0 ).getMeasuredWidth(); setMeasuredDimension(width, height); } else if (widthMode == MeasureSpec.AT_MOST) { width = childCount * getChildAt( 0 ).getMeasuredWidth(); setMeasuredDimension(width, height); } else { height = getChildAt( 0 ).getMeasuredHeight(); setMeasuredDimension(width, height); } } @Override protected void onLayout( boolean changed, int l, int t, int r, int b) { int leftOffset = 0 ; for ( int i = 0 ; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(l + leftOffset, t, r + leftOffset, b); leftOffset += child.getMeasuredWidth(); } } /** * 不拦截Down事件,其他一律拦截 * @param ev * @return */ @Override public boolean onInterceptTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { if (!mScroller.isFinished()) { mScroller.abortAnimation(); return true ; } return false ; } else { return true ; } } private boolean isFirstTouch = true ; @Override public boolean onTouchEvent(MotionEvent event) { int x = ( int ) event.getX(); int y = ( int ) event.getY(); mVelocityTracker.addMovement(event); ViewConfiguration configuration = ViewConfiguration.get(getContext()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break ; case MotionEvent.ACTION_MOVE: if (isFirstTouch) { isFirstTouch = false ; lastY = y; lastX = x; } final int deltaX = x - lastX; scrollBy(-deltaX, 0 ); break ; case MotionEvent.ACTION_UP: isFirstTouch = true ; int scrollX = getScrollX(); mVelocityTracker.computeCurrentVelocity( 1000 , configuration.getScaledMaximumFlingVelocity()); float mVelocityX = mVelocityTracker.getXVelocity(); if (Math.abs(mVelocityX) > configuration.getScaledMinimumFlingVelocity()) { childIndex = mVelocityX < 0 ? childIndex + 1 : childIndex - 1 ; } else { childIndex = (scrollX + getChildAt( 0 ).getWidth() / 2 ) / getChildAt( 0 ).getWidth(); } childIndex = Math.min(getChildCount() - 1 , Math.max( 0 , childIndex)); smoothScrollBy(childIndex*getChildAt( 0 ).getWidth()-scrollX, 0 ); mVelocityTracker.clear(); break ; } lastX = x; lastY = y; return true ; } private void smoothScrollBy( int dx, int dy) { mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500 ); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { super .onDetachedFromWindow(); mVelocityTracker.recycle(); } }ListViewEx.java
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 <code class = "hljs java" > /** * Created by blueberry on 2016/6/20. * 内部拦截事件 */ public class ListViewEx extends ListView { private int lastXIntercepted, lastYIntercepted; private HorizontalEx2 mHorizontalEx2; public ListViewEx(Context context) { super (context); } public ListViewEx(Context context, AttributeSet attrs) { super (context, attrs); } public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super (context, attrs, defStyleAttr); } public HorizontalEx2 getmHorizontalEx2() { return mHorizontalEx2; } public void setmHorizontalEx2(HorizontalEx2 mHorizontalEx2) { this .mHorizontalEx2 = mHorizontalEx2; } /** * 使用 outter.requestDisallowInterceptTouchEvent(); * 来决定父控件是否对事件进行拦截 * @param ev * @return */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x = ( int ) ev.getX(); int y = ( int ) ev.getY(); switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: mHorizontalEx2.requestDisallowInterceptTouchEvent( true ); break ; case MotionEvent.ACTION_MOVE: final int deltaX = x-lastYIntercepted; final int deltaY = y-lastYIntercepted; if (Math.abs(deltaX)>Math.abs(deltaY)){ mHorizontalEx2.requestDisallowInterceptTouchEvent( false ); } break ; case MotionEvent.ACTION_UP: break ; } lastXIntercepted = x; lastYIntercepted = y; return super .dispatchTouchEvent(ev); } } </code>调用代码:
? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <code class = "hljs lasso" > @Override public void showInnerHVData(List<string> data1, List<string> data2, List<string> data3) { ListViewEx listView1 = new ListViewEx(getContext()); ArrayAdapter<string> adapter1 = new ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data1); listView1.setAdapter(adapter1); listView1.setmHorizontalEx2(mHorizontalEx2); ListViewEx listView2 = new ListViewEx(getContext()); ArrayAdapter<string> adapter2 = new ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data2); listView2.setAdapter(adapter2); listView2.setmHorizontalEx2(mHorizontalEx2); ListViewEx listView3 = new ListViewEx(getContext()); ArrayAdapter<string> adapter3 = new ArrayAdapter<string>(getContext(), android.R.layout.simple_list_item_1, data3); listView3.setAdapter(adapter3); listView3.setmHorizontalEx2(mHorizontalEx2); ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); mHorizontalEx2.addView(listView1, params); mHorizontalEx2.addView(listView2, params); mHorizontalEx2.addView(listView3, params); }