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再开始这个事件分发。
【Android——滑动事件冲突解决】如果事件传递到某一层的子view的onTouchEvent上了,这个方法返回了false,那么这个事件会从这个view往上传递,都是onTouchEvent来接收。而如果传递到最上面的onTouchEvent也返回false的话,这个事件就好消失,而且接收不到下一次事件。
 
让子view先处理的方法是重写父view的onInterceptTouchEvent事件并返回false

1 public boolean onInterceptTouchEvent(MotionEvent ev){ 2return false; 3 }

 
转自:http://blog.csdn.net/spt110/article/details/7919870
 
 
转自:  http://blog.csdn.net/a992036795/article/details/51735501
一、前言 
Android  中解决滑动的方案有2种:外部拦截法 和内部拦截法。 
滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。 
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。 
先上效果图: 
Android——滑动事件冲突解决

文章图片

二、实战 
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 10private static final String TAG = "HorizontalEx"; 11 12private boolean isFirstTouch = true; 13private int childIndex; 14private int childCount; 15private int lastXIntercept, lastYIntercept, lastX, lastY; 16 17private Scroller mScroller; 18private VelocityTracker mVelocityTracker; 19 20public HorizontalEx(Context context) { 21super(context); 22init(); 23} 24 25public HorizontalEx(Context context, AttributeSet attrs) { 26super(context, attrs); 27init(); 28} 29 30public HorizontalEx(Context context, AttributeSet attrs, int defStyleAttr) { 31super(context, attrs, defStyleAttr); 32init(); 33} 34 35private void init() { 36mScroller = new Scroller(getContext()); 37mVelocityTracker = VelocityTracker.obtain(); 38} 39 40@Override 41protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 42int width = MeasureSpec.getSize(widthMeasureSpec); 43int height = MeasureSpec.getSize(heightMeasureSpec); 44int widthMode = MeasureSpec.getMode(widthMeasureSpec); 45int heightMode = MeasureSpec.getMode(heightMeasureSpec); 46 47childCount = getChildCount(); 48measureChildren(widthMeasureSpec, heightMeasureSpec); 49 50if (childCount == 0) { 51setMeasuredDimension(0, 0); 52} else if (widthMode == MeasureSpec.AT_MOST & & heightMode == MeasureSpec.AT_MOST) { 53width = childCount * getChildAt(0).getMeasuredWidth(); 54height = getChildAt(0).getMeasuredHeight(); 55setMeasuredDimension(width, height); 56} else if (widthMode == MeasureSpec.AT_MOST) { 57width = childCount * getChildAt(0).getMeasuredWidth(); 58setMeasuredDimension(width, height); 59} else { 60height = getChildAt(0).getMeasuredHeight(); 61setMeasuredDimension(width, height); 62} 63} 64 65@Override 66protected void onLayout(boolean changed, int l, int t, int r, int b) { 67int left = 0; 68for (int i = 0; i < getChildCount(); i++) { 69final View child = getChildAt(i); 70child.layout(left + l, t, r + left, b); 71left += child.getMeasuredWidth(); 72} 73} 74 75/** 76* 拦截事件 77* @param ev 78* @return 79*/ 80@Override 81public boolean onInterceptTouchEvent(MotionEvent ev) { 82boolean intercepted = false; 83int x = (int) ev.getX(); 84int y = (int) ev.getY(); 85 86switch (ev.getAction()) { 87/*如果拦截了Down事件,则子类不会拿到这个事件序列*/ 88case MotionEvent.ACTION_DOWN: 89lastXIntercept = x; 90lastYIntercept = y; 91intercepted = false; 92if (!mScroller.isFinished()) { 93mScroller.abortAnimation(); 94intercepted = true; 95} 96break; 97case MotionEvent.ACTION_MOVE: 98final int deltaX = x - lastXIntercept; 99final int deltaY = y - lastYIntercept; 100/*根据条件判断是否拦截该事件*/ 101if (Math.abs(deltaX) > Math.abs(deltaY)) { 102intercepted = true; 103} else { 104intercepted = false; 105} 106break; 107case MotionEvent.ACTION_UP: 108intercepted = false; 109break; 110 111} 112lastXIntercept = x; 113lastYIntercept = y; 114return intercepted; 115} 116 117 118@Override 119public boolean onTouchEvent(MotionEvent event) { 120int x = (int) event.getX(); 121int y = (int) event.getY(); 122mVelocityTracker.addMovement(event); 123ViewConfiguration configuration = ViewConfiguration.get(getContext()); 124switch (event.getAction()) { 125case MotionEvent.ACTION_DOWN: 126if (!mScroller.isFinished()) { 127mScroller.abortAnimation(); 128} 129break; 130case MotionEvent.ACTION_MOVE: 131/*因为这里父控件拿不到Down事件,所以使用一个布尔值, 132当事件第一次来到父控件时,对lastX,lastY赋值*/ 133if (isFirstTouch) { 134lastX = x; 135lastY = y; 136isFirstTouch = false; 137} 138final int deltaX = x - lastX; 139scrollBy(-deltaX, 0); 140break; 141case MotionEvent.ACTION_UP: 142int scrollX = getScrollX(); 143final int childWidth = getChildAt(0).getWidth(); 144mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity()); 145float xVelocity = mVelocityTracker.getXVelocity(); 146if (Math.abs(xVelocity) > configuration.getScaledMinimumFlingVelocity()) { 147childIndex = xVelocity < 0 ? childIndex + 1 : childIndex - 1; 148} else { 149childIndex = (scrollX + childWidth / 2) / childWidth; 150} 151childIndex = Math.min(getChildCount() - 1, Math.max(childIndex, 0)); 152smoothScrollBy(childIndex * childWidth - scrollX, 0); 153mVelocityTracker.clear(); 154isFirstTouch = true; 155break; 156} 157 158lastX = x; 159lastY = y; 160return true; 161} 162 163void smoothScrollBy(int dx, int dy) { 164mScroller.startScroll(getScrollX(), getScrollY(), dx, dy, 500); 165invalidate(); 166} 167 168@Override 169public void computeScroll() { 170if (mScroller.computeScrollOffset()) { 171scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 172invalidate(); 173} 174} 175 176@Override 177protected void onDetachedFromWindow() { 178super.onDetachedFromWindow(); 179mVelocityTracker.recycle(); 180} 181 }

调用代码:
1 @Override 2public void showOutHVData(List< String> data1, List< String> data2, List< String> data3) { 3ListView listView1 = new ListView(getContext()); 4ArrayAdapter< String> adapter1 = new ArrayAdapter< String> (getContext(), android.R.layout.simple_list_item_1, data1); 5listView1.setAdapter(adapter1); 6 7ListView listView2 = new ListView(getContext()); 8ArrayAdapter< String> adapter2 = new ArrayAdapter< String> (getContext(), android.R.layout.simple_list_item_1, data2); 9listView2.setAdapter(adapter2); 10 11ListView listView3 = new ListView(getContext()); 12ArrayAdapter< String> adapter3 = new ArrayAdapter< String> (getContext(), android.R.layout.simple_list_item_1, data3); 13listView3.setAdapter(adapter3); 14 15ViewGroup.LayoutParams params 16= new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 17ViewGroup.LayoutParams.MATCH_PARENT); 18 19mHorizontalEx.addView(listView1, params); 20mHorizontalEx.addView(listView2, params); 21mHorizontalEx.addView(listView3, params); 22}

其实外部拦截的主要思想都在于对onInterceptTouchEvent的重写。
1 @Override 2public boolean onInterceptTouchEvent(MotionEvent ev) { 3boolean intercepted = false; 4int x = (int) ev.getX(); 5int y = (int) ev.getY(); 6 7switch (ev.getAction()) { 8/*如果拦截了Down事件,则子类不会拿到这个事件序列*/ 9case MotionEvent.ACTION_DOWN: 10lastXIntercept = x; 11lastYIntercept = y; 12intercepted = false; 13if (!mScroller.isFinished()) { 14mScroller.abortAnimation(); 15intercepted = true; 16} 17break; 18case MotionEvent.ACTION_MOVE: 19final int deltaX = x - lastXIntercept; 20final int deltaY = y - lastYIntercept; 21/*根据条件判断是否拦截该事件*/ 22if (Math.abs(deltaX) > Math.abs(deltaY)) { 23intercepted = true; 24} else { 25intercepted = false; 26} 27break; 28case MotionEvent.ACTION_UP: 29intercepted = false; 30break; 31 32} 33lastXIntercept = x; 34lastYIntercept = y; 35return 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. 2if (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. 6cancelAndClearTouchTargets(ev); 7//清楚标志 8resetTouchState(); 9} 10 11// Check for interception. 12final boolean intercepted; 13if (actionMasked == MotionEvent.ACTION_DOWN 14|| mFirstTouchTarget != null) { 15//标志 16final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 17if (!disallowIntercept) { 18intercepted = onInterceptTouchEvent(ev); 19ev.setAction(action); // restore action in case it was changed 20} else { 21intercepted = 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. 26intercepted = 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 9private int lastX, lastY; 10private int childIndex; 11private Scroller mScroller; 12private VelocityTracker mVelocityTracker; 13 14public HorizontalEx2(Context context) { 15super(context); 16init(); 17} 18 19public HorizontalEx2(Context context, AttributeSet attrs) { 20super(context, attrs); 21init(); 22} 23 24public HorizontalEx2(Context context, AttributeSet attrs, int defStyleAttr) { 25super(context, attrs, defStyleAttr); 26init(); 27} 28 29private void init() { 30mScroller = new Scroller(getContext()); 31mVelocityTracker = VelocityTracker.obtain(); 32} 33 34@Override 35protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 36int width = MeasureSpec.getSize(widthMeasureSpec); 37int widthMode = MeasureSpec.getMode(widthMeasureSpec); 38int height = MeasureSpec.getSize(heightMeasureSpec); 39int heightMode = MeasureSpec.getMode(heightMeasureSpec); 40 41int childCount = getChildCount(); 42measureChildren(widthMeasureSpec, heightMeasureSpec); 43 44if (childCount == 0) { 45setMeasuredDimension(0, 0); 46} else if (widthMode == MeasureSpec.AT_MOST & & heightMode == MeasureSpec.AT_MOST) { 47height = getChildAt(0).getMeasuredHeight(); 48width = childCount * getChildAt(0).getMeasuredWidth(); 49setMeasuredDimension(width, height); 50} else if (widthMode == MeasureSpec.AT_MOST) { 51width = childCount * getChildAt(0).getMeasuredWidth(); 52setMeasuredDimension(width, height); 53} else { 54height = getChildAt(0).getMeasuredHeight(); 55setMeasuredDimension(width, height); 56} 57} 58 59@Override 60protected void onLayout(boolean changed, int l, int t, int r, int b) { 61int leftOffset = 0; 62for (int i = 0; i < getChildCount(); i++) { 63View child = getChildAt(i); 64child.layout(l + leftOffset, t, r + leftOffset, b); 65leftOffset += child.getMeasuredWidth(); 66} 67} 68 69/** 70* 不拦截Down事件,其他一律拦截 71* @param ev 72* @return 73*/ 74@Override 75public boolean onInterceptTouchEvent(MotionEvent ev) { 76if (ev.getAction() == MotionEvent.ACTION_DOWN) { 77if (!mScroller.isFinished()) { 78mScroller.abortAnimation(); 79return true; 80} 81return false; 82} else { 83return true; 84} 85} 86 87private boolean isFirstTouch = true; 88 89@Override 90public boolean onTouchEvent(MotionEvent event) { 91int x = (int) event.getX(); 92int y = (int) event.getY(); 93mVelocityTracker.addMovement(event); 94ViewConfiguration configuration = ViewConfiguration.get(getContext()); 95switch (event.getAction()) { 96case MotionEvent.ACTION_DOWN: 97if (!mScroller.isFinished()) { 98mScroller.abortAnimation(); 99} 100break; 101case MotionEvent.ACTION_MOVE: 102if (isFirstTouch) { 103isFirstTouch = false; 104lastY = y; 105lastX = x; 106} 107final int deltaX = x - lastX; 108scrollBy(-deltaX, 0); 109break; 110case MotionEvent.ACTION_UP: 111isFirstTouch = true; 112int scrollX = getScrollX(); 113mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity()); 114float mVelocityX = mVelocityTracker.getXVelocity(); 115if (Math.abs(mVelocityX) > configuration.getScaledMinimumFlingVelocity()) { 116childIndex = mVelocityX < 0 ? childIndex + 1 : childIndex - 1; 117} else { 118childIndex = (scrollX + getChildAt(0).getWidth() / 2) / getChildAt(0).getWidth(); 119} 120childIndex = Math.min(getChildCount() - 1, Math.max(0, childIndex)); 121smoothScrollBy(childIndex*getChildAt(0).getWidth()-scrollX,0); 122mVelocityTracker.clear(); 123break; 124} 125 126lastX = x; 127lastY = y; 128return true; 129} 130 131private void smoothScrollBy(int dx, int dy) { 132mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,500); 133invalidate(); 134} 135 136@Override 137public void computeScroll() { 138if(mScroller.computeScrollOffset()){ 139scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); 140postInvalidate(); 141} 142} 143 144@Override 145protected void onDetachedFromWindow() { 146super.onDetachedFromWindow(); 147mVelocityTracker.recycle(); 148} 149 }

 
ListViewEx.java
1 /** 2* Created by blueberry on 2016/6/20. 3* 内部拦截事件 4*/ 5 public class ListViewEx extends ListView { 6 7private int lastXIntercepted, lastYIntercepted; 8 9private HorizontalEx2 mHorizontalEx2; 10 11public ListViewEx(Context context) { 12super(context); 13} 14 15public ListViewEx(Context context, AttributeSet attrs) { 16super(context, attrs); 17} 18 19public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { 20super(context, attrs, defStyleAttr); 21} 22 23public HorizontalEx2 getmHorizontalEx2() { 24return mHorizontalEx2; 25} 26 27public void setmHorizontalEx2(HorizontalEx2 mHorizontalEx2) { 28this.mHorizontalEx2 = mHorizontalEx2; 29} 30 31/** 32* 使用 outter.requestDisallowInterceptTouchEvent(); 33* 来决定父控件是否对事件进行拦截 34* @param ev 35* @return 36*/ 37@Override 38public boolean dispatchTouchEvent(MotionEvent ev) { 39int x = (int) ev.getX(); 40int y = (int) ev.getY(); 41switch (ev.getAction()) { 42case MotionEvent.ACTION_DOWN: 43mHorizontalEx2.requestDisallowInterceptTouchEvent(true); 44break; 45case MotionEvent.ACTION_MOVE: 46final int deltaX = x-lastYIntercepted; 47final int deltaY = y-lastYIntercepted; 48if(Math.abs(deltaX)> Math.abs(deltaY)){ 49mHorizontalEx2.requestDisallowInterceptTouchEvent(false); 50} 51break; 52case MotionEvent.ACTION_UP: 53break; 54} 55lastXIntercepted = x; 56lastYIntercepted = y; 57return super.dispatchTouchEvent(ev); 58} 59 }

调用代码:
1 @Override 2public void showInnerHVData(List< String> data1, List< String> data2, List< String> data3) { 3 4ListViewEx listView1 = new ListViewEx(getContext()); 5ArrayAdapter< String> adapter1 = new ArrayAdapter< String> (getContext(), android.R.layout.simple_list_item_1, data1); 6listView1.setAdapter(adapter1); 7listView1.setmHorizontalEx2(mHorizontalEx2); 8 9ListViewEx listView2 = new ListViewEx(getContext()); 10ArrayAdapter< String> adapter2 = new ArrayAdapter< String> (getContext(), android.R.layout.simple_list_item_1, data2); 11listView2.setAdapter(adapter2); 12listView2.setmHorizontalEx2(mHorizontalEx2); 13 14ListViewEx listView3 = new ListViewEx(getContext()); 15ArrayAdapter< String> adapter3 = new ArrayAdapter< String> (getContext(), android.R.layout.simple_list_item_1, data3); 16listView3.setAdapter(adapter3); 17listView3.setmHorizontalEx2(mHorizontalEx2); 18 19ViewGroup.LayoutParams params 20= new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 21ViewGroup.LayoutParams.MATCH_PARENT); 22 23mHorizontalEx2.addView(listView1, params); 24mHorizontalEx2.addView(listView2, params); 25mHorizontalEx2.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 28private static final String TAG = "RefreshLayoutBase"; 29 30public static final int STATUS_LOADING = 1; 31public static final int STATUS_RELEASE_TO_REFRESH = 2; 32public static final int STATUS_PULL_TO_REFRESH = 3; 33public static final int STATUS_IDLE = 4; 34public static final int STATUS_LOAD_MORE =5; 35private static int SCROLL_DURATION =500; 36 37protected ViewGroup mHeadView; 38protected ViewGroup mFootView; 39private T contentView; 40private ProgressBar headProgressBar; 41private TextView headTv; 42private ProgressBar footProgressBar; 43private TextView footTv; 44 45private boolean isFistTouch = true; 46 47protected int currentStatus = STATUS_IDLE; 48private int mScreenWidth; 49private int mScreenHeight; 50private int mLastXIntercepted; 51private int mLastYIntercepted; 52private int mLastX; 53private int mLastY; 54protected int mInitScrollY = 0; 55private int mTouchSlop; 56 57protected Scroller mScoller; 58 59private OnRefreshListener mOnRefreshListener; 60 61public RefreshLayoutBase(Context context) { 62this(context, null); 63} 64 65public RefreshLayoutBase(Context context, AttributeSet attrs) { 66this(context, attrs, 0); 67} 68 69public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyleAttr) { 70super(context, attrs, defStyleAttr); 71getScreenSize(); 72initView(); 73mScoller = new Scroller(context); 74mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 75setPadding(0, 0, 0, 0); 76} 77 78public void setContentView(T view) { 79addView(view, 1); 80} 81 82public OnRefreshListener getOnRefreshListener() { 83return mOnRefreshListener; 84} 85 86public void setOnRefreshListener(OnRefreshListener mOnRefreshListener) { 87this.mOnRefreshListener = mOnRefreshListener; 88} 89 90private void initView() { 91setupHeadView(); 92setupFootView(); 93} 94 95private void getScreenSize() { 96WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 97DisplayMetrics metrics = new DisplayMetrics(); 98wm.getDefaultDisplay().getMetrics(metrics); 99mScreenWidth = metrics.widthPixels; 100mScreenHeight = metrics.heightPixels; 101} 102 103private int dp2px(int dp) { 104WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); 105DisplayMetrics metrics = new DisplayMetrics(); 106wm.getDefaultDisplay().getMetrics(metrics); 107return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); 108} 109 110/** 111* 设置头布局 112*/ 113private void setupHeadView() { 114mHeadView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_head_view, null); 115mHeadView.setBackgroundColor(Color.RED); 116headProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressbar); 117headTv = (TextView) mHeadView.findViewById(R.id.head_tv); 118/*设置 实际高度为 1/4 ,但内容区域只有 100dp*/ 119ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mScreenHeight / 4); 120mHeadView.setLayoutParams(layoutParams); 121mHeadView.setPadding(0, mScreenHeight / 4 - dp2px(100), 0, 0); 122addView(mHeadView); 123} 124 125/** 126* 设置尾布局 127*/ 128private void setupFootView() { 129mFootView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_foot_view, null); 130mFootView.setBackgroundColor(Color.BLUE); 131footProgressBar = (ProgressBar) mFootView.findViewById(R.id.fresh_foot_progressbar); 132footTv = (TextView) mFootView.findViewById(R.id.fresh_foot_tv); 133addView(mFootView); 134} 135 136@Override 137protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 138int widthSize = MeasureSpec.getSize(widthMeasureSpec); 139int widthMode = MeasureSpec.getMode(widthMeasureSpec); 140int height = MeasureSpec.getSize(heightMeasureSpec); 141int heightMode = MeasureSpec.getMode(heightMeasureSpec); 142 143int finalHeight = 0; 144for (int i = 0; i < getChildCount(); i++) { 145View child = getChildAt(i); 146measureChild(child, widthMeasureSpec, heightMeasureSpec); 147finalHeight += child.getMeasuredHeight(); 148} 149 150if (widthMode == MeasureSpec.AT_MOST & & heightMode == MeasureSpec.AT_MOST) { 151widthSize = getChildAt(0).getMeasuredWidth(); 152setMeasuredDimension(widthSize, finalHeight); 153} else if (widthMode == MeasureSpec.AT_MOST) { 154widthSize = getChildAt(0).getMeasuredWidth(); 155setMeasuredDimension(widthSize, height); 156} else { 157setMeasuredDimension(widthSize, finalHeight); 158} 159 160} 161 162 163@Override 164protected void onLayout(boolean changed, int l, int t, int r, int b) { 165int topOffset = 0; 166for (int i = 0; i < getChildCount(); i++) { 167View child = getChildAt(i); 168child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); 169topOffset += child.getMeasuredHeight(); 170} 171mInitScrollY = mHeadView.getMeasuredHeight() + getPaddingTop(); 172scrollTo(0, mInitScrollY); 173 174} 175 176@Override 177public boolean onInterceptTouchEvent(MotionEvent ev) { 178boolean intercepted = false; 179int x = (int) ev.getX(); 180int y = (int) ev.getY(); 181switch (ev.getAction()) { 182case MotionEvent.ACTION_DOWN: 183mLastXIntercepted = x; 184mLastYIntercepted = y; 185break; 186case MotionEvent.ACTION_MOVE: 187final int deltaY = x - mLastYIntercepted; 188if (isTop() & & deltaY > 0 & & Math.abs(deltaY) > mTouchSlop) { 189/*下拉*/ 190intercepted = true; 191} 192break; 193case MotionEvent.ACTION_UP: 194break; 195} 196mLastXIntercepted = x; 197mLastYIntercepted = y; 198return intercepted; 199} 200 201private void doRefresh() { 202Log.i(TAG, "doRefresh: "); 203if (currentStatus == STATUS_RELEASE_TO_REFRESH) { 204mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); 205currentStatus = STATUS_IDLE; 206} else if (currentStatus == STATUS_PULL_TO_REFRESH) { 207mScoller.startScroll(0,getScrollY(),0,0-getScrollY(),SCROLL_DURATION); 208if (null != mOnRefreshListener) { 209currentStatus = STATUS_LOADING; 210mOnRefreshListener.refresh(); 211} 212} 213invalidate(); 214} 215 216@Override 217public boolean onTouchEvent(MotionEvent event) { 218int x = (int) event.getX(); 219int y = (int) event.getY(); 220switch (event.getAction()) { 221case MotionEvent.ACTION_DOWN: 222if (!mScoller.isFinished()) { 223mScoller.abortAnimation(); 224} 225mLastX = x; 226mLastY = y; 227break; 228case MotionEvent.ACTION_MOVE: 229if (isFistTouch) { 230isFistTouch = false; 231mLastX = x; 232mLastY = y; 233} 234final int deltaY = y - mLastY; 235if (currentStatus != STATUS_LOADING) { 236changeScrollY(deltaY); 237} 238break; 239case MotionEvent.ACTION_UP: 240isFistTouch = true; 241doRefresh(); 242break; 243} 244 245mLastX = x; 246mLastY = y; 247return true; 248} 249 250private void changeScrollY(int deltaY) { 251Log.i(TAG, "changeScrollY: "); 252int curY = getScrollY(); 253if (deltaY > 0) { 254/*下拉*/ 255if (curY - deltaY > getPaddingTop()) { 256scrollBy(0, -deltaY); 257} 258} else { 259/*上拉*/ 260if (curY - deltaY < = mInitScrollY) { 261scrollBy(0, -deltaY); 262} 263} 264 265curY = getScrollY(); 266int slop = mInitScrollY / 2; 267if (curY > 0 & & curY < =slop) { 268currentStatus = STATUS_PULL_TO_REFRESH; 269} else if (curY > 0 & & curY > = slop) { 270currentStatus = STATUS_RELEASE_TO_REFRESH; 271} 272} 273 274@Override 275public void computeScroll() { 276if (mScoller.computeScrollOffset()) { 277scrollTo(mScoller.getCurrX(), mScoller.getCurrY()); 278postInvalidate(); 279} 280} 281 282/** 283* 加载完成调用这个方法 284*/ 285public void refreshComplete() { 286mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); 287currentStatus = STATUS_IDLE; 288invalidate(); 289} 290 291/** 292* 显示 Footer 293*/ 294public void showFooter() { 295if(currentStatus==STATUS_LOAD_MORE) return ; 296currentStatus = STATUS_LOAD_MORE ; 297mScoller.startScroll(0, getScrollY(), 0, mFootView.getMeasuredHeight() 298, SCROLL_DURATION); 299invalidate(); 300 301} 302 303 304/** 305* loadMore完成之后调用 306*/ 307public void footerComplete() { 308mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); 309invalidate(); 310currentStatus = STATUS_IDLE; 311} 312 313public interface OnRefreshListener { 314void refresh(); 315} 316 317abstract boolean isTop(); 318 319abstract 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 15private static final String TAG = "RefreshListView"; 16 17private ListView listView; 18private OnLoadListener loadListener; 19 20public RefreshListView(Context context) { 21super(context); 22} 23 24public RefreshListView(Context context, AttributeSet attrs) { 25super(context, attrs); 26} 27 28public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) { 29super(context, attrs, defStyleAttr); 30} 31 32public ListView getListView() { 33return listView; 34} 35 36public void setListView(final ListView listView) { 37this.listView = listView; 38setContentView(listView); 39 40this.listView.setOnScrollListener(new AbsListView.OnScrollListener() { 41@Override 42public void onScrollStateChanged(AbsListView view, int scrollState) { 43} 44 45@Override 46public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 47 48/*这里存在一个bug: 当listView滑动到底部的时候,如果下拉也会出现footer 49* 这是因为,暂时还没有想到如何判断是下拉还是上拉。 50* 如果要解决此问题,我觉得应该重写listView 的onTouchEvent来判断手势方向 51* 次模块主要解决竖向滑动冲突,故现将此问题放下。 52* */ 53if (currentStatus == STATUS_IDLE 54& & getScrollY() < = mInitScrollY & & isBottom() 55) { 56showFooter(); 57if (null != loadListener) { 58loadListener.onLoadMore(); 59} 60} 61 62} 63}); 64} 65 66public OnLoadListener getLoadListener() { 67return loadListener; 68} 69 70public void setLoadListener(OnLoadListener loadListener) { 71this.loadListener = loadListener; 72} 73 74@Override 75boolean isTop() { 76return listView.getFirstVisiblePosition() == 0 77& & getScrollY() < = mHeadView.getMeasuredHeight(); 78} 79 80@Override 81boolean isBottom() { 82return listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1; 83} 84 85public interface OnLoadListener { 86void onLoadMore(); 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 26private static final String TAG = "RefreshLayoutBase2"; 27 28private static List< String> datas; 29 30static { 31datas = new ArrayList< > (); 32for (int i = 0; i < 40; i++) { 33datas.add("数据—" + i); 34} 35} 36 37private ViewGroup headView; 38private ListViewEx lv; 39 40private int lastY; 41public int mInitScrollY; 42 43private Scroller mScroller; 44 45public RefreshLayoutBase2(Context context) { 46this(context, null); 47} 48 49public RefreshLayoutBase2(Context context, AttributeSet attrs) { 50this(context, attrs, 0); 51 52} 53 54public RefreshLayoutBase2(Context context, AttributeSet attrs, int defStyleAttr) { 55super(context, attrs, defStyleAttr); 56mScroller = new Scroller(context); 57setupHeadView(context); 58setupContentView(context); 59 60} 61 62@Override 63protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 64int widthSize = MeasureSpec.getSize(widthMeasureSpec); 65int widthMode = MeasureSpec.getMode(widthMeasureSpec); 66int height = MeasureSpec.getSize(heightMeasureSpec); 67int heightMode = MeasureSpec.getMode(heightMeasureSpec); 68 69int finalHeight = 0; 70for (int i = 0; i < getChildCount(); i++) { 71View child = getChildAt(i); 72measureChild(child, widthMeasureSpec, heightMeasureSpec); 73finalHeight += child.getMeasuredHeight(); 74} 75 76if (widthMode == MeasureSpec.AT_MOST & & heightMode == MeasureSpec.AT_MOST) { 77widthSize = getChildAt(0).getMeasuredWidth(); 78setMeasuredDimension(widthSize, finalHeight); 79} else if (widthMode == MeasureSpec.AT_MOST) { 80widthSize = getChildAt(0).getMeasuredWidth(); 81setMeasuredDimension(widthSize, height); 82} else { 83setMeasuredDimension(widthSize, finalHeight); 84} 85 86} 87 88@Override 89protected void onLayout(boolean changed, int l, int t, int r, int b) { 90int topOffset = 0; 91for (int i = 0; i < getChildCount(); i++) { 92View child = getChildAt(i); 93child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); 94topOffset += child.getMeasuredHeight(); 95} 96mInitScrollY = headView.getMeasuredHeight() + getPaddingTop(); 97scrollTo(0, mInitScrollY); 98 99} 100 101/** 102* 不拦截Down 其他一律拦截 103* @param ev 104* @return 105*/ 106@Override 107public boolean onInterceptTouchEvent(MotionEvent ev) { 108if (ev.getAction() == MotionEvent.ACTION_DOWN) return false; 109return true; 110} 111 112@Override 113public boolean onTouchEvent(MotionEvent event) { 114int y = (int) event.getY(); 115switch (event.getAction()) { 116case MotionEvent.ACTION_DOWN: 117break; 118case MotionEvent.ACTION_MOVE: 119final int deltaY = y-lastY; 120Log.i(TAG, "onTouchEvent: deltaY: "+deltaY); 121if (deltaY > = 0 & & lv.isTop() & & getScrollY() - deltaY > =getPaddingTop()) { 122scrollBy(0, -deltaY); 123} 124break; 125case MotionEvent.ACTION_UP: 126this.postDelayed(new Runnable() { 127@Override 128public void run() { 129mScroller.startScroll(0,getScrollY(),0,mInitScrollY-getScrollY()); 130invalidate(); 131} 132},2000); 133break; 134} 135 136lastY = y ; 137return true; 138} 139 140private void setupHeadView(Context context) { 141headView = (ViewGroup) View.inflate(context, R.layout.fresh_head_view, null); 142headView.setBackgroundColor(Color.RED); 143ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); 144addView(headView, params); 145} 146 147public void setupContentView(Context context) { 148lv = new ListViewEx(context, this); 149lv.setBackgroundColor(Color.BLUE); 150ArrayAdapter< String> adapter = new ArrayAdapter< String> (getContext(), android.R.layout.simple_list_item_1, datas); 151lv.setAdapter(adapter); 152addView(lv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); 153} 154 155@Override 156public void computeScroll() { 157if(mScroller.computeScrollOffset()){ 158scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); 159postInvalidate(); 160} 161} 162 163public static class ListViewEx extends ListView { 164 165private RefreshLayoutBase2 outter; 166 167public ListViewEx(Context context, RefreshLayoutBase2 outter) { 168super(context); 169this.outter = outter; 170} 171 172public ListViewEx(Context context, AttributeSet attrs) { 173super(context, attrs); 174} 175 176public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { 177super(context, attrs, defStyleAttr); 178} 179 180/** 181* 使用 outter.requestDisallowInterceptTouchEvent(); 182* 来决定父控件是否对事件进行拦截 183* @param ev 184* @return 185*/ 186@Override 187public boolean dispatchTouchEvent(MotionEvent ev) { 188switch (ev.getAction()) { 189case MotionEvent.ACTION_DOWN: 190outter.requestDisallowInterceptTouchEvent(true); 191break; 192case MotionEvent.ACTION_MOVE: 193 194if ( isTop() & & outter.getScrollY() < = outter.mInitScrollY) { 195outter.requestDisallowInterceptTouchEvent(false); 196} 197break; 198 199} 200return super.dispatchTouchEvent(ev); 201} 202 203public boolean isTop() { 204return getFirstVisiblePosition() ==0; 205} 206} 207 }

 


































    推荐阅读