android多种滑动冲突的解决方案

作者:blueberry_mu 时间:2021-11-16 11:20:10 

一、前言

Android 中解决滑动的方案有2种:外部拦截法 和内部拦截法。

滑动冲突也存在2种场景: 横竖滑动冲突、同向滑动冲突。

所以我就写了4个例子来学习如何解决滑动冲突的,这四个例子分别为: 外部拦截法解决横竖冲突、外部拦截法解决同向冲突、内部拦截法解决横竖冲突、内部拦截法解决同向冲突。

先上效果图:

android多种滑动冲突的解决方案

二、实战

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
  */
 @Override
 public 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;
 }
}
}

来源:http://blog.csdn.net/a992036795/article/details/51735501

标签:android,滑动冲突
0
投稿

猜你喜欢

  • C#自定义处理xml数据类实例

    2022-07-02 11:00:39
  • MybatisPlus使用代码生成器遇到的小问题(推荐)

    2021-07-17 09:15:17
  • 关于Java中的try-with-resources语句

    2022-10-21 16:49:54
  • android 封装抓取网页信息的实例代码

    2021-11-28 09:40:32
  • java基础之字符串编码知识点总结

    2023-06-20 19:05:09
  • Java以命令模式设计模式

    2023-11-24 21:27:52
  • java开发分布式服务框架Dubbo调用过程

    2022-04-13 14:29:55
  • Java 方法(方法的定义,可变参数,参数的传递问题,方法重载,方法签名)

    2021-07-29 20:17:19
  • C# WinForm制作异形窗体与控件的方法

    2023-11-07 11:09:28
  • java实现分布式项目搭建的方法

    2022-10-13 10:42:36
  • Java接口中尽量避免使用数组

    2022-07-06 23:24:14
  • 基于Java8实现提高Excel读写效率

    2023-11-25 10:01:37
  • Android Dialog对话框用法实例详解

    2022-11-04 02:38:47
  • SpringBoot深入了解日志的使用

    2023-01-06 15:40:34
  • MyBatis框架之mybatis逆向工程自动生成代码

    2023-01-11 06:19:18
  • Struts2配置文件中使用通配符的方法(三种形式)

    2022-08-21 01:53:40
  • C#实现IP摄像头的方法

    2023-12-09 03:42:51
  • C#特性 匿名类型与隐式类型局部变量使用介绍

    2023-09-29 12:42:50
  • gateway网关与前端请求跨域问题的解决方案

    2022-09-20 01:30:44
  • Spring实战之SpEl语法实例详解

    2023-09-18 07:56:03
  • asp之家 软件编程 m.aspxhome.com