一、前言 Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。
滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。
所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。
先上效果图:
二、实战
1、外部拦截法,解决横竖冲突
思路是,重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。如果父控件拦截了事件,则在父控件的onTouchEvent进行相应的事件处理。
我的这个例子,是一个横向滑动的ViewGroup里面包含了3个竖向滑动的ListView。下面我附上代码,HorizontalEx.Java:
/** * 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(); }}
调用代码:
@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的重写。
@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; }
这几乎是一个实现外部拦截事件的模板,这里一定不要在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事件无效。
可以参考一下源码:
// 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; }
那么我们如果想使用 内部拦截法拦截事件。
第一步:
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:
/** * Created by blueberry on 2016/6/20. * 内部拦截 * 和 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
/** * 内部拦截事件 */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); }}
调用代码:
@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); }
至此,2种拦截方法已经学习完毕,下面我们来学习如何解决同向滑动冲突。
其实和上面的2个例子思路是一样的,只是用来判断是否拦截的那块逻辑不同而已。
下面的例子,是一个下拉刷新的一个控件。
3、外部拦截 解决同向滑动冲突
RefreshLayoutBase.java
package com.blueberry.sample.widget.refresh;import android.content.Context;import android.graphics.Color;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.util.TypedValue;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;import android.view.ViewGroup;import android.view.WindowManager;import android.widget.ProgressBar;import android.widget.Scroller;import android.widget.TextView;import com.blueberry.sample.R;/** *外部拦截(同向) * */public abstract class RefreshLayoutBase<T extends View> extends ViewGroup { private static final String TAG = "RefreshLayoutBase"; public static final int STATUS_LOADING = 1; public static final int STATUS_RELEASE_TO_REFRESH = 2; public static final int STATUS_PULL_TO_REFRESH = 3; public static final int STATUS_IDLE = 4; public static final int STATUS_LOAD_MORE =5; private static int SCROLL_DURATION =500; protected ViewGroup mHeadView; protected ViewGroup mFootView; private T contentView; private ProgressBar headProgressBar; private TextView headTv; private ProgressBar footProgressBar; private TextView footTv; private boolean isFistTouch = true; protected int currentStatus = STATUS_IDLE; private int mScreenWidth; private int mScreenHeight; private int mLastXIntercepted; private int mLastYIntercepted; private int mLastX; private int mLastY; protected int mInitScrollY = 0; private int mTouchSlop; protected Scroller mScoller; private OnRefreshListener mOnRefreshListener; public RefreshLayoutBase(Context context) {this(context, null); } public RefreshLayoutBase(Context context, AttributeSet attrs) {this(context, attrs, 0); } public RefreshLayoutBase(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);getScreenSize();initView();mScoller = new Scroller(context);mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();setPadding(0, 0, 0, 0); } public void setContentView(T view) {addView(view, 1); } public OnRefreshListener getOnRefreshListener() {return mOnRefreshListener; } public void setOnRefreshListener(OnRefreshListener mOnRefreshListener) {this.mOnRefreshListener = mOnRefreshListener; } private void initView() {setupHeadView();setupFootView(); } private void getScreenSize() {WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);DisplayMetrics metrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(metrics);mScreenWidth = metrics.widthPixels;mScreenHeight = metrics.heightPixels; } private int dp2px(int dp) {WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);DisplayMetrics metrics = new DisplayMetrics();wm.getDefaultDisplay().getMetrics(metrics);return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, metrics); } /*** 设置头布局*/ private void setupHeadView() {mHeadView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_head_view, null);mHeadView.setBackgroundColor(Color.RED);headProgressBar = (ProgressBar) mHeadView.findViewById(R.id.head_progressbar);headTv = (TextView) mHeadView.findViewById(R.id.head_tv);/*设置 实际高度为 1/4 ,但内容区域只有 100dp*/ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, mScreenHeight / 4);mHeadView.setLayoutParams(layoutParams);mHeadView.setPadding(0, mScreenHeight / 4 - dp2px(100), 0, 0);addView(mHeadView); } /*** 设置尾布局*/ private void setupFootView() {mFootView = (ViewGroup) View.inflate(getContext(), R.layout.fresh_foot_view, null);mFootView.setBackgroundColor(Color.BLUE);footProgressBar = (ProgressBar) mFootView.findViewById(R.id.fresh_foot_progressbar);footTv = (TextView) mFootView.findViewById(R.id.fresh_foot_tv);addView(mFootView); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthSize = MeasureSpec.getSize(widthMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int finalHeight = 0;for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); finalHeight += child.getMeasuredHeight();}if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, finalHeight);} else if (widthMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, height);} else { setMeasuredDimension(widthSize, finalHeight);} } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {int topOffset = 0;for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); topOffset += child.getMeasuredHeight();}mInitScrollY = mHeadView.getMeasuredHeight() + getPaddingTop();scrollTo(0, mInitScrollY); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) {boolean intercepted = false;int x = (int) ev.getX();int y = (int) ev.getY();switch (ev.getAction()) { case MotionEvent.ACTION_DOWN:mLastXIntercepted = x;mLastYIntercepted = y;break; case MotionEvent.ACTION_MOVE:final int deltaY = x - mLastYIntercepted;if (isTop() && deltaY > 0 && Math.abs(deltaY) > mTouchSlop) { /*下拉*/ intercepted = true;}break; case MotionEvent.ACTION_UP:break;}mLastXIntercepted = x;mLastYIntercepted = y;return intercepted; } private void doRefresh() {Log.i(TAG, "doRefresh: ");if (currentStatus == STATUS_RELEASE_TO_REFRESH) { mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION); currentStatus = STATUS_IDLE;} else if (currentStatus == STATUS_PULL_TO_REFRESH) { mScoller.startScroll(0,getScrollY(),0,0-getScrollY(),SCROLL_DURATION); if (null != mOnRefreshListener) {currentStatus = STATUS_LOADING;mOnRefreshListener.refresh(); }}invalidate(); } @Override public boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) { case MotionEvent.ACTION_DOWN:if (!mScoller.isFinished()) { mScoller.abortAnimation();}mLastX = x;mLastY = y;break; case MotionEvent.ACTION_MOVE:if (isFistTouch) { isFistTouch = false; mLastX = x; mLastY = y;}final int deltaY = y - mLastY;if (currentStatus != STATUS_LOADING) { changeScrollY(deltaY);}break; case MotionEvent.ACTION_UP:isFistTouch = true;doRefresh();break;}mLastX = x;mLastY = y;return true; } private void changeScrollY(int deltaY) {Log.i(TAG, "changeScrollY: ");int curY = getScrollY();if (deltaY > 0) { /*下拉*/ if (curY - deltaY > getPaddingTop()) {scrollBy(0, -deltaY); }} else { /*上拉*/ if (curY - deltaY <= mInitScrollY) {scrollBy(0, -deltaY); }}curY = getScrollY();int slop = mInitScrollY / 2;if (curY > 0 && curY <=slop) { currentStatus = STATUS_PULL_TO_REFRESH;} else if (curY > 0 && curY >= slop) { currentStatus = STATUS_RELEASE_TO_REFRESH;} } @Override public void computeScroll() {if (mScoller.computeScrollOffset()) { scrollTo(mScoller.getCurrX(), mScoller.getCurrY()); postInvalidate();} } /*** 加载完成调用这个方法*/ public void refreshComplete() {mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION);currentStatus = STATUS_IDLE;invalidate(); } /*** 显示 Footer*/ public void showFooter() {if(currentStatus==STATUS_LOAD_MORE) return ;currentStatus = STATUS_LOAD_MORE ;mScoller.startScroll(0, getScrollY(), 0, mFootView.getMeasuredHeight(), SCROLL_DURATION);invalidate(); } /*** loadMore完成之后调用*/ public void footerComplete() {mScoller.startScroll(0, getScrollY(), 0, mInitScrollY - getScrollY(), SCROLL_DURATION);invalidate();currentStatus = STATUS_IDLE; } public interface OnRefreshListener {void refresh(); } abstract boolean isTop(); abstract boolean isBottom();}
它是一个抽象类,需要编写子类继承isTop()和 isBottom()方法。下面给出它的一个实现类:
package com.blueberry.sample.widget.refresh;import android.content.Context;import android.util.AttributeSet;import android.widget.AbsListView;import android.widget.ListView;/** * Created by blueberry on 2016/6/21. * * RefreshLayoutBase 的一个实现类 */public class RefreshListView extends RefreshLayoutBase<ListView> { private static final String TAG = "RefreshListView"; private ListView listView; private OnLoadListener loadListener; public RefreshListView(Context context) {super(context); } public RefreshListView(Context context, AttributeSet attrs) {super(context, attrs); } public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr); } public ListView getListView() {return listView; } public void setListView(final ListView listView) {this.listView = listView;setContentView(listView);this.listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {/*这里存在一个bug: 当listView滑动到底部的时候,如果下拉也会出现footer* 这是因为,暂时还没有想到如何判断是下拉还是上拉。* 如果要解决此问题,我觉得应该重写listView 的onTouchEvent来判断手势方向* 次模块主要解决竖向滑动冲突,故现将此问题放下。* */if (currentStatus == STATUS_IDLE&& getScrollY() <= mInitScrollY && isBottom()) { showFooter(); if (null != loadListener) {loadListener.onLoadMore(); }} }}); } public OnLoadListener getLoadListener() {return loadListener; } public void setLoadListener(OnLoadListener loadListener) {this.loadListener = loadListener; } @Override boolean isTop() {return listView.getFirstVisiblePosition() == 0&& getScrollY() <= mHeadView.getMeasuredHeight(); } @Override boolean isBottom() {return listView.getLastVisiblePosition() == listView.getAdapter().getCount() - 1; } public interface OnLoadListener {void onLoadMore(); }}
4、内部拦截法解决同向滑动 同样是一个下拉刷新组件,因为实现原理都一样,所以这个写的比较随意些。主要还是如果解决滑动冲突。
RefreshLayoutBase2.java
package com.blueberry.sample.widget.refresh;import android.content.Context;import android.graphics.Color;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.ArrayAdapter;import android.widget.ListView;import android.widget.Scroller;import com.blueberry.sample.R;import java.util.ArrayList;import java.util.List;/** * Created by blueberry on 2016/6/22. * 结合内部类 ListVieEx * 内部拦截法,同向 */public class RefreshLayoutBase2 extends ViewGroup { private static final String TAG = "RefreshLayoutBase2"; private static List<String> datas; static {datas = new ArrayList<>();for (int i = 0; i < 40; i++) { datas.add("数据—" + i);} } private ViewGroup headView; private ListViewEx lv; private int lastY; public int mInitScrollY; private Scroller mScroller; public RefreshLayoutBase2(Context context) {this(context, null); } public RefreshLayoutBase2(Context context, AttributeSet attrs) {this(context, attrs, 0); } public RefreshLayoutBase2(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mScroller = new Scroller(context);setupHeadView(context);setupContentView(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int widthSize = MeasureSpec.getSize(widthMeasureSpec);int widthMode = MeasureSpec.getMode(widthMeasureSpec);int height = MeasureSpec.getSize(heightMeasureSpec);int heightMode = MeasureSpec.getMode(heightMeasureSpec);int finalHeight = 0;for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); measureChild(child, widthMeasureSpec, heightMeasureSpec); finalHeight += child.getMeasuredHeight();}if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, finalHeight);} else if (widthMode == MeasureSpec.AT_MOST) { widthSize = getChildAt(0).getMeasuredWidth(); setMeasuredDimension(widthSize, height);} else { setMeasuredDimension(widthSize, finalHeight);} } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) {int topOffset = 0;for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.layout(getPaddingLeft(), getPaddingTop() + topOffset, r, getPaddingTop() + child.getMeasuredHeight() + topOffset); topOffset += child.getMeasuredHeight();}mInitScrollY = headView.getMeasuredHeight() + getPaddingTop();scrollTo(0, mInitScrollY); } /*** 不拦截Down 其他一律拦截* @param ev* @return*/ @Override public boolean onInterceptTouchEvent(MotionEvent ev) {if (ev.getAction() == MotionEvent.ACTION_DOWN) return false;return true; } @Override public boolean onTouchEvent(MotionEvent event) {int y = (int) event.getY();switch (event.getAction()) { case MotionEvent.ACTION_DOWN:break; case MotionEvent.ACTION_MOVE:final int deltaY = y-lastY;Log.i(TAG, "onTouchEvent: deltaY: "+deltaY);if (deltaY >= 0 && lv.isTop() && getScrollY() - deltaY >=getPaddingTop()) {scrollBy(0, -deltaY);}break; case MotionEvent.ACTION_UP:this.postDelayed(new Runnable() { @Override public void run() {mScroller.startScroll(0,getScrollY(),0,mInitScrollY-getScrollY());invalidate(); }},2000);break;}lastY = y ;return true; } private void setupHeadView(Context context) {headView = (ViewGroup) View.inflate(context, R.layout.fresh_head_view, null);headView.setBackgroundColor(Color.RED);ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300);addView(headView, params); } public void setupContentView(Context context) {lv = new ListViewEx(context, this);lv.setBackgroundColor(Color.BLUE);ArrayAdapter<String> adapter = new ArrayAdapter<String>(getContext(), android.R.layout.simple_list_item_1, datas);lv.setAdapter(adapter);addView(lv, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); } @Override public void computeScroll() {if(mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate();} } public static class ListViewEx extends ListView {private RefreshLayoutBase2 outter;public ListViewEx(Context context, RefreshLayoutBase2 outter) { super(context); this.outter = outter;}public ListViewEx(Context context, AttributeSet attrs) { super(context, attrs);}public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr);}/** * 使用 outter.requestDisallowInterceptTouchEvent(); * 来决定父控件是否对事件进行拦截 * @param ev * @return */@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { switch (ev.getAction()) {case MotionEvent.ACTION_DOWN: outter.requestDisallowInterceptTouchEvent(true); break;case MotionEvent.ACTION_MOVE: if ( isTop() && outter.getScrollY() <= outter.mInitScrollY) {outter.requestDisallowInterceptTouchEvent(false); } break; } return super.dispatchTouchEvent(ev);}public boolean isTop() { return getFirstVisiblePosition() ==0;} }}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。