首页 > 代码库 > Android——滑动事件冲突解决
Android——滑动事件冲突解决
android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件。
android系统中的每个View的子类都具有下面三个与TouchEvent处理密切相关的方法:
(1)public boolean dispatchTouchEvent(MotionEvent ev)这个方法用来分发TouchEvent
(2)public boolean onInterceptTouchEvent(MotionEvent ev)这个方法用来拦截TouchEvent
(3)public boolean onTouchEvent(MotionEvent ev)这个方法用来处理TouchEvent
当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View,TouchEvent最先到达最顶层View的dispatchTouchEvent,然后由dispatchTouchEvent方法进行分发。
如果dispatchTouchEvent返回true,则交给这个view的onTouchEvent处理;如果返回false,则交给这个view的interceptTouchEvent方法来决定是否要拦截这个事件。
如果interceptTouchEvent返回true,也就是拦截了,则交给它的onTouchEvent来处理,如果interceptTouchEvent返回false,则传递给子view,由子view的dispatchTouchEvent再开始这个事件分发。
如果事件传递到某一层的子view的onTouchEvent上了,这个方法返回了false,那么这个事件会从这个view往上传递,都是onTouchEvent来接收。而如果传递到最上面的onTouchEvent也返回false的话,这个事件就好消失,而且接收不到下一次事件。
让子view先处理的方法是重写父view的onInterceptTouchEvent事件并返回false
1 public boolean onInterceptTouchEvent(MotionEvent ev){ 2 return false; 3 }
转自:http://blog.csdn.net/spt110/article/details/7919870
转自: http://blog.csdn.net/a992036795/article/details/51735501
一、前言
Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。
滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。
先上效果图:
二、实战
1、外部拦截法,解决横竖冲突
思路是,重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。关于为什么返回true就代表拦截事件。可以参考我的上一篇博客:http://blog.csdn.net/a992036795/article/details/51698023 。 如果父控件拦截了事件,则在父控件的onTouchEvent进行相应的事件处理。
我的这个例子,是一个横向滑动的ViewGroup里面包含了3个竖向滑动的ListView。下面我附上代码:
HorizontalEx.java
1 /** 2 * Created by blueberry on 2016/6/20. 3 * 4 * 解决交错的滑动冲突 5 * 6 * 外部拦截法 7 */ 8 public class HorizontalEx extends ViewGroup { 9 10 private static final String TAG = "HorizontalEx"; 11 12 private boolean isFirstTouch = true; 13 private int childIndex; 14 private int childCount; 15 private int lastXIntercept, lastYIntercept, lastX, lastY; 16 17 private Scroller mScroller; 18 private VelocityTracker mVelocityTracker; 19 20 public HorizontalEx(Context context) { 21 super(context); 22 init(); 23 } 24 25 public HorizontalEx(Context context, AttributeSet attrs) { 26 super(context, attrs); 27 init(); 28 } 29 30 public HorizontalEx(Context context, AttributeSet attrs, int defStyleAttr) { 31 super(context, attrs, defStyleAttr); 32 init(); 33 } 34 35 private void init() { 36 mScroller = new Scroller(getContext()); 37 mVelocityTracker = VelocityTracker.obtain(); 38 } 39 40 @Override 41 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 42 int width = MeasureSpec.getSize(widthMeasureSpec); 43 int height = MeasureSpec.getSize(heightMeasureSpec); 44 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 45 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 46 47 childCount = getChildCount(); 48 measureChildren(widthMeasureSpec, heightMeasureSpec); 49 50 if (childCount == 0) { 51 setMeasuredDimension(0, 0); 52 } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { 53 width = childCount * getChildAt(0).getMeasuredWidth(); 54 height = getChildAt(0).getMeasuredHeight(); 55 setMeasuredDimension(width, height); 56 } else if (widthMode == MeasureSpec.AT_MOST) { 57 width = childCount * getChildAt(0).getMeasuredWidth(); 58 setMeasuredDimension(width, height); 59 } else { 60 height = getChildAt(0).getMeasuredHeight(); 61 setMeasuredDimension(width, height); 62 } 63 } 64 65 @Override 66 protected void onLayout(boolean changed, int l, int t, int r, int b) { 67 int left = 0; 68 for (int i = 0; i < getChildCount(); i++) { 69 final View child = getChildAt(i); 70 child.layout(left + l, t, r + left, b); 71 left += child.getMeasuredWidth(); 72 } 73 } 74 75 /** 76 * 拦截事件 77 * @param ev 78 * @return 79 */ 80 @Override 81 public boolean onInterceptTouchEvent(MotionEvent ev) { 82 boolean intercepted = false; 83 int x = (int) ev.getX(); 84 int y = (int) ev.getY(); 85 86 switch (ev.getAction()) { 87 /*如果拦截了Down事件,则子类不会拿到这个事件序列*/ 88 case MotionEvent.ACTION_DOWN: 89 lastXIntercept = x; 90 lastYIntercept = y; 91 intercepted = false; 92 if (!mScroller.isFinished()) { 93 mScroller.abortAnimation(); 94 intercepted = true; 95 } 96 break; 97 case MotionEvent.ACTION_MOVE: 98 final int deltaX = x - lastXIntercept; 99 final int deltaY = y - lastYIntercept; 100 /*根据条件判断是否拦截该事件*/ 101 if (Math.abs(deltaX) > Math.abs(deltaY)) { 102 intercepted = true; 103 } else { 104 intercepted = false; 105 } 106 break; 107 case MotionEvent.ACTION_UP: 108 intercepted = false; 109 break; 110 111 } 112 lastXIntercept = x; 113 lastYIntercept = y; 114 return intercepted; 115 } 116 117 118 @Override 119 public boolean onTouchEvent(MotionEvent event) { 120 int x = (int) event.getX(); 121 int y = (int) event.getY(); 122 mVelocityTracker.addMovement(event); 123 ViewConfiguration configuration = ViewConfiguration.get(getContext()); 124 switch (event.getAction()) { 125 case MotionEvent.ACTION_DOWN: 126 if (!mScroller.isFinished()) { 127 mScroller.abortAnimation(); 128 } 129 break; 130 case MotionEvent.ACTION_MOVE: 131 /*因为这里父控件拿不到Down事件,所以使用一个布尔值, 132 当事件第一次来到父控件时,对lastX,lastY赋值*/ 133 if (isFirstTouch) { 134 lastX = x; 135 lastY = y; 136 isFirstTouch = false; 137 } 138 final int deltaX = x - lastX; 139 scrollBy(-deltaX, 0); 140 break; 141 case MotionEvent.ACTION_UP: 142 int scrollX = getScrollX(); 143 final int childWidth = getChildAt(0).getWidth(); 144 mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity()); 145 float xVelocity = mVelocityTracker.getXVelocity(); 146 if (Math.abs(xVelocity) > configuration.getScaledMinimumFlingVelocity()) { 147 childIndex = xVelocity < 0 ? childIndex + 1 : childIndex - 1; 148 } else { 149 childIndex = (scrollX + childWidth / 2) / childWidth; 150 } 151 childIndex = Math.min(getChildCount() - 1, Math.max(childIndex, 0)); 152 smoothScrollBy(childIndex * childWidth - scrollX, 0); 153 mVelocityTracker.clear(); 154 isFirstTouch = true; 155 break; 156 } 157 158 lastX = x; 159 lastY = y; 160 return true; 161 } 162 163 void smoothScrollBy(int dx, int dy) { 164 mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500); 165 invalidate(); 166 } 167 168 @Override 169 public void computeScroll() { 170 if (mScroller.computeScrollOffset()) { 171 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 172 invalidate(); 173 } 174 } 175 176 @Override 177 protected void onDetachedFromWindow() { 178 super.onDetachedFromWindow(); 179 mVelocityTracker.recycle(); 180 } 181 }
调用代码:
1 @Override 2 public void showOutHVData(List<String> data1, List<String> data2, List<String> data3) { 3 ListView listView1 = new ListView(getContext()); 4 ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data1); 5 listView1.setAdapter(adapter1); 6 7 ListView listView2 = new ListView(getContext()); 8 ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data2); 9 listView2.setAdapter(adapter2); 10 11 ListView listView3 = new ListView(getContext()); 12 ArrayAdapter<String> adapter3 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data3); 13 listView3.setAdapter(adapter3); 14 15 ViewGroup.LayoutParams params 16 = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 17 ViewGroup.LayoutParams.MATCH_PARENT); 18 19 mHorizontalEx.addView(listView1, params); 20 mHorizontalEx.addView(listView2, params); 21 mHorizontalEx.addView(listView3, params); 22 }
其实外部拦截的主要思想都在于对onInterceptTouchEvent的重写。
1 @Override 2 public boolean onInterceptTouchEvent(MotionEvent ev) { 3 boolean intercepted = false; 4 int x = (int) ev.getX(); 5 int y = (int) ev.getY(); 6 7 switch (ev.getAction()) { 8 /*如果拦截了Down事件,则子类不会拿到这个事件序列*/ 9 case MotionEvent.ACTION_DOWN: 10 lastXIntercept = x; 11 lastYIntercept = y; 12 intercepted = false; 13 if (!mScroller.isFinished()) { 14 mScroller.abortAnimation(); 15 intercepted = true; 16 } 17 break; 18 case MotionEvent.ACTION_MOVE: 19 final int deltaX = x - lastXIntercept; 20 final int deltaY = y - lastYIntercept; 21 /*根据条件判断是否拦截该事件*/ 22 if (Math.abs(deltaX) > Math.abs(deltaY)) { 23 intercepted = true; 24 } else { 25 intercepted = false; 26 } 27 break; 28 case MotionEvent.ACTION_UP: 29 intercepted = false; 30 break; 31 32 } 33 lastXIntercept = x; 34 lastYIntercept = y; 35 return intercepted; 36 }
这几乎是一个实现外部拦截事件的模板,这里一定不要在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 // Handle an initial down. 2 if (actionMasked == MotionEvent.ACTION_DOWN) { 3 // Throw away all previous state when starting a new touch gesture. 4 // The framework may have dropped the up or cancel event for the previous gesture 5 // due to an app switch, ANR, or some other state change. 6 cancelAndClearTouchTargets(ev); 7 //清楚标志 8 resetTouchState(); 9 } 10 11 // Check for interception. 12 final boolean intercepted; 13 if (actionMasked == MotionEvent.ACTION_DOWN 14 || mFirstTouchTarget != null) { 15 //标志 16 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 17 if (!disallowIntercept) { 18 intercepted = onInterceptTouchEvent(ev); 19 ev.setAction(action); // restore action in case it was changed 20 } else { 21 intercepted = false; 22 } 23 } else { 24 // There are no touch targets and this action is not an initial down 25 // so this view group continues to intercept touches. 26 intercepted = true; 27 }
那么我们如果想使用 内部拦截法拦截事件。
第一步:
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 * Created by blueberry on 2016/6/20. 3 * <p/> 4 * 内部拦截 5 * 和 ListViewEx配合使用 6 */ 7 public class HorizontalEx2 extends ViewGroup { 8 9 private int lastX, lastY; 10 private int childIndex; 11 private Scroller mScroller; 12 private VelocityTracker mVelocityTracker; 13 14 public HorizontalEx2(Context context) { 15 super(context); 16 init(); 17 } 18 19 public HorizontalEx2(Context context, AttributeSet attrs) { 20 super(context, attrs); 21 init(); 22 } 23 24 public HorizontalEx2(Context context, AttributeSet attrs, int defStyleAttr) { 25 super(context, attrs, defStyleAttr); 26 init(); 27 } 28 29 private void init() { 30 mScroller = new Scroller(getContext()); 31 mVelocityTracker = VelocityTracker.obtain(); 32 } 33 34 @Override 35 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 36 int width = MeasureSpec.getSize(widthMeasureSpec); 37 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 38 int height = MeasureSpec.getSize(heightMeasureSpec); 39 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 40 41 int childCount = getChildCount(); 42 measureChildren(widthMeasureSpec, heightMeasureSpec); 43 44 if (childCount == 0) { 45 setMeasuredDimension(0, 0); 46 } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { 47 height = getChildAt(0).getMeasuredHeight(); 48 width = childCount * getChildAt(0).getMeasuredWidth(); 49 setMeasuredDimension(width, height); 50 } else if (widthMode == MeasureSpec.AT_MOST) { 51 width = childCount * getChildAt(0).getMeasuredWidth(); 52 setMeasuredDimension(width, height); 53 } else { 54 height = getChildAt(0).getMeasuredHeight(); 55 setMeasuredDimension(width, height); 56 } 57 } 58 59 @Override 60 protected void onLayout(boolean changed, int l, int t, int r, int b) { 61 int leftOffset = 0; 62 for (int i = 0; i < getChildCount(); i++) { 63 View child = getChildAt(i); 64 child.layout(l + leftOffset, t, r + leftOffset, b); 65 leftOffset += child.getMeasuredWidth(); 66 } 67 } 68 69 /** 70 * 不拦截Down事件,其他一律拦截 71 * @param ev 72 * @return 73 */ 74 @Override 75 public boolean onInterceptTouchEvent(MotionEvent ev) { 76 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 77 if (!mScroller.isFinished()) { 78 mScroller.abortAnimation(); 79 return true; 80 } 81 return false; 82 } else { 83 return true; 84 } 85 } 86 87 private boolean isFirstTouch = true; 88 89 @Override 90 public boolean onTouchEvent(MotionEvent event) { 91 int x = (int) event.getX(); 92 int y = (int) event.getY(); 93 mVelocityTracker.addMovement(event); 94 ViewConfiguration configuration = ViewConfiguration.get(getContext()); 95 switch (event.getAction()) { 96 case MotionEvent.ACTION_DOWN: 97 if (!mScroller.isFinished()) { 98 mScroller.abortAnimation(); 99 } 100 break; 101 case MotionEvent.ACTION_MOVE: 102 if (isFirstTouch) { 103 isFirstTouch = false; 104 lastY = y; 105 lastX = x; 106 } 107 final int deltaX = x - lastX; 108 scrollBy(-deltaX, 0); 109 break; 110 case MotionEvent.ACTION_UP: 111 isFirstTouch = true; 112 int scrollX = getScrollX(); 113 mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity()); 114 float mVelocityX = mVelocityTracker.getXVelocity(); 115 if (Math.abs(mVelocityX) > configuration.getScaledMinimumFlingVelocity()) { 116 childIndex = mVelocityX < 0 ? childIndex + 1 : childIndex - 1; 117 } else { 118 childIndex = (scrollX + getChildAt(0).getWidth() / 2) / getChildAt(0).getWidth(); 119 } 120 childIndex = Math.min(getChildCount() - 1, Math.max(0, childIndex)); 121 smoothScrollBy(childIndex*getChildAt(0).getWidth()-scrollX,0); 122 mVelocityTracker.clear(); 123 break; 124 } 125 126 lastX = x; 127 lastY = y; 128 return true; 129 } 130 131 private void smoothScrollBy(int dx, int dy) { 132 mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,500); 133 invalidate(); 134 } 135 136 @Override 137 public void computeScroll() { 138 if(mScroller.computeScrollOffset()){ 139 scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); 140 postInvalidate(); 141 } 142 } 143 144 @Override 145 protected void onDetachedFromWindow() { 146 super.onDetachedFromWindow(); 147 mVelocityTracker.recycle(); 148 } 149 }
ListViewEx.java
1 /** 2 * Created by blueberry on 2016/6/20. 3 * 内部拦截事件 4 */ 5 public class ListViewEx extends ListView { 6 7 private int lastXIntercepted, lastYIntercepted; 8 9 private HorizontalEx2 mHorizontalEx2; 10 11 public ListViewEx(Context context) { 12 super(context); 13 } 14 15 public ListViewEx(Context context, AttributeSet attrs) { 16 super(context, attrs); 17 } 18 19 public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { 20 super(context, attrs, defStyleAttr); 21 } 22 23 public HorizontalEx2 getmHorizontalEx2() { 24 return mHorizontalEx2; 25 } 26 27 public void setmHorizontalEx2(HorizontalEx2 mHorizontalEx2) { 28 this.mHorizontalEx2 = mHorizontalEx2; 29 } 30 31 /** 32 * 使用 outter.requestDisallowInterceptTouchEvent(); 33 * 来决定父控件是否对事件进行拦截 34 * @param ev 35 * @return 36 */ 37 @Override 38 public boolean dispatchTouchEvent(MotionEvent ev) { 39 int x = (int) ev.getX(); 40 int y = (int) ev.getY(); 41 switch (ev.getAction()) { 42 case MotionEvent.ACTION_DOWN: 43 mHorizontalEx2.requestDisallowInterceptTouchEvent(true); 44 break; 45 case MotionEvent.ACTION_MOVE: 46 final int deltaX = x-lastYIntercepted; 47 final int deltaY = y-lastYIntercepted; 48 if(Math.abs(deltaX)>Math.abs(deltaY)){ 49 mHorizontalEx2.requestDisallowInterceptTouchEvent(false); 50 } 51 break; 52 case MotionEvent.ACTION_UP: 53 break; 54 } 55 lastXIntercepted = x; 56 lastYIntercepted = y; 57 return super.dispatchTouchEvent(ev); 58 } 59 }
调用代码:
1 @Override 2 public void showInnerHVData(List<String> data1, List<String> data2, List<String> data3) { 3 4 ListViewEx listView1 = new ListViewEx(getContext()); 5 ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data1); 6 listView1.setAdapter(adapter1); 7 listView1.setmHorizontalEx2(mHorizontalEx2); 8 9 ListViewEx listView2 = new ListViewEx(getContext()); 10 ArrayAdapter<String> adapter2 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data2); 11 listView2.setAdapter(adapter2); 12 listView2.setmHorizontalEx2(mHorizontalEx2); 13 14 ListViewEx listView3 = new ListViewEx(getContext()); 15 ArrayAdapter<String> adapter3 = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, data3); 16 listView3.setAdapter(adapter3); 17 listView3.setmHorizontalEx2(mHorizontalEx2); 18 19 ViewGroup.LayoutParams params 20 = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 21 ViewGroup.LayoutParams.MATCH_PARENT); 22 23 mHorizontalEx2.addView(listView1, params); 24 mHorizontalEx2.addView(listView2, params); 25 mHorizontalEx2.addView(listView3, params); 26 }
至此,2种拦截方法已经学习完毕,下面我们来学习如何解决同向滑动冲突。
其实和上面的2个例子思路是一样的,只是用来判断是否拦截的那块逻辑不同而已。
下面的例子,是一个下拉刷新的一个控件。
3、外部拦截 解决同向滑动冲突
RefreshLayoutBase.java
1 package com.blueberry.sample.widget.refresh; 2 3 import android.content.Context; 4 import android.graphics.Color; 5 import android.util.AttributeSet; 6 import android.util.DisplayMetrics; 7 import android.util.Log; 8 import android.util.TypedValue; 9 import android.view.MotionEvent; 10 import android.view.View; 11 import android.view.ViewConfiguration; 12 import android.view.ViewGroup; 13 import android.view.WindowManager; 14 import android.widget.ProgressBar; 15 import android.widget.Scroller; 16 import android.widget.TextView; 17 18 import com.blueberry.sample.R; 19 20 /** 21 * Created by blueberry on 2016/6/21. 22 * 23 *外部拦截(同向) 24 * 25 */ 26 public abstract class RefreshLayoutBase<T extends View> extends ViewGroup { 27 28 private static final String TAG = "RefreshLayoutBase"; 29 30 public static final int STATUS_LOADING = 1; 31 public static final int STATUS_RELEASE_TO_REFRESH = 2; 32 public static final int STATUS_PULL_TO_REFRESH = 3; 33 public static final int STATUS_IDLE = 4; 34 public static final int STATUS_LOAD_MORE =5; 35 private static int SCROLL_DURATION =500; 36 37 protected ViewGroup mHeadView; 38 protected ViewGroup mFootView; 39 private T contentView; 40 private ProgressBar headProgressBar; 41 private TextView headTv; 42 private ProgressBar footProgressBar; 43 private TextView footTv; 44 45 private boolean isFistTouch = true; 46 47 protected int currentStatus = STATUS_IDLE; 48 private int mScreenWidth; 49 private int mScreenHeight; 50 private int mLastXIntercepted; 51 private int mLastYIntercepted; 52 private int mLastX; 53 private int mLastY; 54 protected int mInitScrollY = 0; 55 private int mTouchSlop; 56 57 protected Scroller mScoller; 58 59 private OnRefreshListener mOnRefreshListener; 60 61 public RefreshLayoutBase(Context context) { 62 this(context, null); 63 } 64 65 public RefreshLayoutBase(Context context, AttributeSet attrs) { 66 this(context, attrs, 0); 67 } 68 69 public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyleAttr) { 70 super(context, attrs, defStyleAttr); 71 getScreenSize(); 72 initView(); 73 mScoller = new Scroller(context); 74 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 75 setPadding(0, 0, 0, 0); 76 } 77 78 public void setContentView(T view) { 79 addView(view, 1); 80 } 81 82 public OnRefreshListener getOnRefreshListener() { 83 return mOnRefreshListener; 84 } 85 86 public void setOnRefreshListener(OnRefreshListener mOnRefreshListener) { 87 this.mOnRefreshListener = mOnRefreshListener; 88 } 89 90 private void initView() { 91 setupHeadView(); 92 setupFootView(); 93 } 94 95 private void getScreenSize() { 96 WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 97 DisplayMetrics metrics = new DisplayMetrics(); 98 wm.getDefaultDisplay().getMetrics(metrics); 99 mScreenWidth = metrics.widthPixels; 100 mScreenHeight = metrics.heightPixels; 101 } 102 103 private int dp2px(int dp) { 104 WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 105 DisplayMetrics metrics = new DisplayMetrics(); 106 wm.getDefaultDisplay().getMetrics(metrics); 107 return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); 108 } 109 110 /** 111 * 设置头布局 112 */ 113 private void setupHeadView() { 114 mHeadView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_head_view, null); 115 mHeadView.setBackgroundColor(Color.RED); 116 headProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressbar); 117 headTv = (TextView) mHeadView.findViewById(R.id.head_tv); 118 /*设置 实际高度为 1/4 ,但内容区域只有 100dp*/ 119 ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mScreenHeight / 4); 120 mHeadView.setLayoutParams(layoutParams); 121 mHeadView.setPadding(0, mScreenHeight / 4 - dp2px(100), 0, 0); 122 addView(mHeadView); 123 } 124 125 /** 126 * 设置尾布局 127 */ 128 private void setupFootView() { 129 mFootView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_foot_view, null); 130 mFootView.setBackgroundColor(Color.BLUE); 131 footProgressBar = (ProgressBar) mFootView.findViewById(R.id.fresh_foot_progressbar); 132 footTv = (TextView) mFootView.findViewById(R.id.fresh_foot_tv); 133 addView(mFootView); 134 } 135 136 @Override 137 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 138 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 139 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 140 int height = MeasureSpec.getSize(heightMeasureSpec); 141 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 142 143 int finalHeight = 0; 144 for (int i = 0; i < getChildCount(); i++) { 145 View child = getChildAt(i); 146 measureChild(child, widthMeasureSpec, heightMeasureSpec); 147 finalHeight += child.getMeasuredHeight(); 148 } 149 150 if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { 151 widthSize = getChildAt(0).getMeasuredWidth(); 152 setMeasuredDimension(widthSize, finalHeight); 153 } else if (widthMode == MeasureSpec.AT_MOST) { 154 widthSize = getChildAt(0).getMeasuredWidth(); 155 setMeasuredDimension(widthSize, height); 156 } else { 157 setMeasuredDimension(widthSize, finalHeight); 158 } 159 160 } 161 162 163 @Override 164 protected void onLayout(boolean changed, int l, int t, int r, int b) { 165 int topOffset = 0; 166 for (int i = 0; i < getChildCount(); i++) { 167 View child = getChildAt(i); 168 child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); 169 topOffset += child.getMeasuredHeight(); 170 } 171 mInitScrollY = mHeadView.getMeasuredHeight() + getPaddingTop(); 172 scrollTo(0, mInitScrollY); 173 174 } 175 176 @Override 177 public boolean onInterceptTouchEvent(MotionEvent ev) { 178 boolean intercepted = false; 179 int x = (int) ev.getX(); 180 int y = (int) ev.getY(); 181 switch (ev.getAction()) { 182 case MotionEvent.ACTION_DOWN: 183 mLastXIntercepted = x; 184 mLastYIntercepted = y; 185 break; 186 case MotionEvent.ACTION_MOVE: 187 final int deltaY = x - mLastYIntercepted; 188 if (isTop() && deltaY > 0 && Math.abs(deltaY) > mTouchSlop) { 189 /*下拉*/ 190 intercepted = true; 191 } 192 break; 193 case MotionEvent.ACTION_UP: 194 break; 195 } 196 mLastXIntercepted = x; 197 mLastYIntercepted = y; 198 return intercepted; 199 } 200 201 private void doRefresh() { 202 Log.i(TAG, "doRefresh: "); 203 if (currentStatus == STATUS_RELEASE_TO_REFRESH) { 204 mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); 205 currentStatus = STATUS_IDLE; 206 } else if (currentStatus == STATUS_PULL_TO_REFRESH) { 207 mScoller.startScroll(0,getScrollY(),0,0-getScrollY(),SCROLL_DURATION); 208 if (null != mOnRefreshListener) { 209 currentStatus = STATUS_LOADING; 210 mOnRefreshListener.refresh(); 211 } 212 } 213 invalidate(); 214 } 215 216 @Override 217 public boolean onTouchEvent(MotionEvent event) { 218 int x = (int) event.getX(); 219 int y = (int) event.getY(); 220 switch (event.getAction()) { 221 case MotionEvent.ACTION_DOWN: 222 if (!mScoller.isFinished()) { 223 mScoller.abortAnimation(); 224 } 225 mLastX = x; 226 mLastY = y; 227 break; 228 case MotionEvent.ACTION_MOVE: 229 if (isFistTouch) { 230 isFistTouch = false; 231 mLastX = x; 232 mLastY = y; 233 } 234 final int deltaY = y - mLastY; 235 if (currentStatus != STATUS_LOADING) { 236 changeScrollY(deltaY); 237 } 238 break; 239 case MotionEvent.ACTION_UP: 240 isFistTouch = true; 241 doRefresh(); 242 break; 243 } 244 245 mLastX = x; 246 mLastY = y; 247 return true; 248 } 249 250 private void changeScrollY(int deltaY) { 251 Log.i(TAG, "changeScrollY: "); 252 int curY = getScrollY(); 253 if (deltaY > 0) { 254 /*下拉*/ 255 if (curY - deltaY > getPaddingTop()) { 256 scrollBy(0, -deltaY); 257 } 258 } else { 259 /*上拉*/ 260 if (curY - deltaY <= mInitScrollY) { 261 scrollBy(0, -deltaY); 262 } 263 } 264 265 curY = getScrollY(); 266 int slop = mInitScrollY / 2; 267 if (curY > 0 && curY <=slop) { 268 currentStatus = STATUS_PULL_TO_REFRESH; 269 } else if (curY > 0 && curY >= slop) { 270 currentStatus = STATUS_RELEASE_TO_REFRESH; 271 } 272 } 273 274 @Override 275 public void computeScroll() { 276 if (mScoller.computeScrollOffset()) { 277 scrollTo(mScoller.getCurrX(), mScoller.getCurrY()); 278 postInvalidate(); 279 } 280 } 281 282 /** 283 * 加载完成调用这个方法 284 */ 285 public void refreshComplete() { 286 mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); 287 currentStatus = STATUS_IDLE; 288 invalidate(); 289 } 290 291 /** 292 * 显示 Footer 293 */ 294 public void showFooter() { 295 if(currentStatus==STATUS_LOAD_MORE) return ; 296 currentStatus = STATUS_LOAD_MORE ; 297 mScoller.startScroll(0, getScrollY(), 0, mFootView.getMeasuredHeight() 298 , SCROLL_DURATION); 299 invalidate(); 300 301 } 302 303 304 /** 305 * loadMore完成之后调用 306 */ 307 public void footerComplete() { 308 mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); 309 invalidate(); 310 currentStatus = STATUS_IDLE; 311 } 312 313 public interface OnRefreshListener { 314 void refresh(); 315 } 316 317 abstract boolean isTop(); 318 319 abstract boolean isBottom(); 320 321 }
它是一个抽象类,需要编写子类继承isTop()和 isBottom()方法、
下面给出它的一个实现类:
1 package com.blueberry.sample.widget.refresh; 2 3 import android.content.Context; 4 import android.util.AttributeSet; 5 import android.widget.AbsListView; 6 import android.widget.ListView; 7 8 /** 9 * Created by blueberry on 2016/6/21. 10 * 11 * RefreshLayoutBase 的一个实现类 12 */ 13 public class RefreshListView extends RefreshLayoutBase<ListView> { 14 15 private static final String TAG = "RefreshListView"; 16 17 private ListView listView; 18 private onl oadListener loadListener; 19 20 public RefreshListView(Context context) { 21 super(context); 22 } 23 24 public RefreshListView(Context context, AttributeSet attrs) { 25 super(context, attrs); 26 } 27 28 public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) { 29 super(context, attrs, defStyleAttr); 30 } 31 32 public ListView getListView() { 33 return listView; 34 } 35 36 public void setListView(final ListView listView) { 37 this.listView = listView; 38 setContentView(listView); 39 40 this.listView.setOnScrollListener(new AbsListView.OnScrollListener() { 41 @Override 42 public void onScrollStateChanged(AbsListView view, int scrollState) { 43 } 44 45 @Override 46 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 47 48 /*这里存在一个bug: 当listView滑动到底部的时候,如果下拉也会出现footer 49 * 这是因为,暂时还没有想到如何判断是下拉还是上拉。 50 * 如果要解决此问题,我觉得应该重写listView 的onTouchEvent来判断手势方向 51 * 次模块主要解决竖向滑动冲突,故现将此问题放下。 52 * */ 53 if (currentStatus == STATUS_IDLE 54 && getScrollY() <= mInitScrollY && isBottom() 55 ) { 56 showFooter(); 57 if (null != loadListener) { 58 loadListener.onLoadMore(); 59 } 60 } 61 62 } 63 }); 64 } 65 66 public onl oadListener getLoadListener() { 67 return loadListener; 68 } 69 70 public void setLoadListener(OnLoadListener loadListener) { 71 this.loadListener = loadListener; 72 } 73 74 @Override 75 boolean isTop() { 76 return listView.getFirstVisiblePosition() == 0 77 && getScrollY() <= mHeadView.getMeasuredHeight(); 78 } 79 80 @Override 81 boolean isBottom() { 82 return listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1; 83 } 84 85 public interface onl oadListener { 86 void onl oadMore(); 87 } 88 }
4、内部拦截法解决同向滑动
同样是一个下拉刷新组件,因为实现原理都一样,所以这个写的比较随意些。主要还是如果解决滑动冲突。
RefreshLayoutBase2.java
1 package com.blueberry.sample.widget.refresh; 2 3 import android.content.Context; 4 import android.graphics.Color; 5 import android.util.AttributeSet; 6 import android.util.Log; 7 import android.view.MotionEvent; 8 import android.view.View; 9 import android.view.ViewGroup; 10 import android.widget.ArrayAdapter; 11 import android.widget.ListView; 12 import android.widget.Scroller; 13 14 import com.blueberry.sample.R; 15 16 import java.util.ArrayList; 17 import java.util.List; 18 19 /** 20 * Created by blueberry on 2016/6/22. 21 * 结合内部类 ListVieEx 22 * 内部拦截法,同向 23 */ 24 public class RefreshLayoutBase2 extends ViewGroup { 25 26 private static final String TAG = "RefreshLayoutBase2"; 27 28 private static List<String> datas; 29 30 static { 31 datas = new ArrayList<>(); 32 for (int i = 0; i < 40; i++) { 33 datas.add("数据—" + i); 34 } 35 } 36 37 private ViewGroup headView; 38 private ListViewEx lv; 39 40 private int lastY; 41 public int mInitScrollY; 42 43 private Scroller mScroller; 44 45 public RefreshLayoutBase2(Context context) { 46 this(context, null); 47 } 48 49 public RefreshLayoutBase2(Context context, AttributeSet attrs) { 50 this(context, attrs, 0); 51 52 } 53 54 public RefreshLayoutBase2(Context context, AttributeSet attrs, int defStyleAttr) { 55 super(context, attrs, defStyleAttr); 56 mScroller = new Scroller(context); 57 setupHeadView(context); 58 setupContentView(context); 59 60 } 61 62 @Override 63 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 64 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 65 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 66 int height = MeasureSpec.getSize(heightMeasureSpec); 67 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 68 69 int finalHeight = 0; 70 for (int i = 0; i < getChildCount(); i++) { 71 View child = getChildAt(i); 72 measureChild(child, widthMeasureSpec, heightMeasureSpec); 73 finalHeight += child.getMeasuredHeight(); 74 } 75 76 if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { 77 widthSize = getChildAt(0).getMeasuredWidth(); 78 setMeasuredDimension(widthSize, finalHeight); 79 } else if (widthMode == MeasureSpec.AT_MOST) { 80 widthSize = getChildAt(0).getMeasuredWidth(); 81 setMeasuredDimension(widthSize, height); 82 } else { 83 setMeasuredDimension(widthSize, finalHeight); 84 } 85 86 } 87 88 @Override 89 protected void onLayout(boolean changed, int l, int t, int r, int b) { 90 int topOffset = 0; 91 for (int i = 0; i < getChildCount(); i++) { 92 View child = getChildAt(i); 93 child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); 94 topOffset += child.getMeasuredHeight(); 95 } 96 mInitScrollY = headView.getMeasuredHeight() + getPaddingTop(); 97 scrollTo(0, mInitScrollY); 98 99 } 100 101 /** 102 * 不拦截Down 其他一律拦截 103 * @param ev 104 * @return 105 */ 106 @Override 107 public boolean onInterceptTouchEvent(MotionEvent ev) { 108 if (ev.getAction() == MotionEvent.ACTION_DOWN) return false; 109 return true; 110 } 111 112 @Override 113 public boolean onTouchEvent(MotionEvent event) { 114 int y = (int) event.getY(); 115 switch (event.getAction()) { 116 case MotionEvent.ACTION_DOWN: 117 break; 118 case MotionEvent.ACTION_MOVE: 119 final int deltaY = y-lastY; 120 Log.i(TAG, "onTouchEvent: deltaY: "+deltaY); 121 if (deltaY >= 0 && lv.isTop() && getScrollY() - deltaY >=getPaddingTop()) { 122 scrollBy(0, -deltaY); 123 } 124 break; 125 case MotionEvent.ACTION_UP: 126 this.postDelayed(new Runnable() { 127 @Override 128 public void run() { 129 mScroller.startScroll(0,getScrollY(),0,mInitScrollY-getScrollY()); 130 invalidate(); 131 } 132 },2000); 133 break; 134 } 135 136 lastY = y ; 137 return true; 138 } 139 140 private void setupHeadView(Context context) { 141 headView = (ViewGroup) View.inflate(context, R.layout.fresh_head_view, null); 142 headView.setBackgroundColor(Color.RED); 143 ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); 144 addView(headView, params); 145 } 146 147 public void setupContentView(Context context) { 148 lv = new ListViewEx(context, this); 149 lv.setBackgroundColor(Color.BLUE); 150 ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, datas); 151 lv.setAdapter(adapter); 152 addView(lv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 153 } 154 155 @Override 156 public void computeScroll() { 157 if(mScroller.computeScrollOffset()){ 158 scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); 159 postInvalidate(); 160 } 161 } 162 163 public static class ListViewEx extends ListView { 164 165 private RefreshLayoutBase2 outter; 166 167 public ListViewEx(Context context, RefreshLayoutBase2 outter) { 168 super(context); 169 this.outter = outter; 170 } 171 172 public ListViewEx(Context context, AttributeSet attrs) { 173 super(context, attrs); 174 } 175 176 public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { 177 super(context, attrs, defStyleAttr); 178 } 179 180 /** 181 * 使用 outter.requestDisallowInterceptTouchEvent(); 182 * 来决定父控件是否对事件进行拦截 183 * @param ev 184 * @return 185 */ 186 @Override 187 public boolean dispatchTouchEvent(MotionEvent ev) { 188 switch (ev.getAction()) { 189 case MotionEvent.ACTION_DOWN: 190 outter.requestDisallowInterceptTouchEvent(true); 191 break; 192 case MotionEvent.ACTION_MOVE: 193 194 if ( isTop() && outter.getScrollY() <= outter.mInitScrollY) { 195 outter.requestDisallowInterceptTouchEvent(false); 196 } 197 break; 198 199 } 200 return super.dispatchTouchEvent(ev); 201 } 202 203 public boolean isTop() { 204 return getFirstVisiblePosition() ==0; 205 } 206 } 207 }
Android——滑动事件冲突解决