android滑动冲突的解决方案

转载请注明出处: http://blog.csdn.net/a992036795/article/details/51735501

一、前言

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

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

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

先上效果图:

二、实战

1、外部拦截法,解决横竖冲突

思路是,重写父控件的onInterceptTouchEvent方法,然后根据具体的需求,来决定父控件是否拦截事件。如果拦截返回返回true,不拦截返回false。关于为什么返回true就代表拦截事件。可以参考我的上一篇博客:http://blog.csdn.net/a992036795/article/details/51698023 。 如果父控件拦截了事件,则在父控件的onTouchEvent进行相应的事件处理。

我的这个例子,是一个横向滑动的ViewGroup里面包含了3个竖向滑动的ListView。下面我附上代码:

HorizontalEx.java

/**
 * 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事件无效。

可以参考一下源码:

ViewGroup#dispatchTouchEvent

   // 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.
 * <p/>
 * 内部拦截
 * 和 ListViewEx配合使用
 */
public class HorizontalEx2 extends ViewGroup {

    private int lastX, lastY;
    private int childIndex;
    private Scroller mScroller;
    private VelocityTracker mVelocityTracker;

    public HorizontalEx2(Context context) {
        super(context);
        init();
    }

    public HorizontalEx2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public HorizontalEx2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mScroller = new Scroller(getContext());
        mVelocityTracker = VelocityTracker.obtain();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int childCount = getChildCount();
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        if (childCount == 0) {
            setMeasuredDimension(0, 0);
        } else if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            height = getChildAt(0).getMeasuredHeight();
            width = childCount * getChildAt(0).getMeasuredWidth();
            setMeasuredDimension(width, height);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            width = childCount * getChildAt(0).getMeasuredWidth();
            setMeasuredDimension(width, height);
        } else {
            height = getChildAt(0).getMeasuredHeight();
            setMeasuredDimension(width, height);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int leftOffset = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            child.layout(l + leftOffset, t, r + leftOffset, b);
            leftOffset += child.getMeasuredWidth();
        }
    }

    /**
     * 不拦截Down事件,其他一律拦截
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
                return true;
            }
            return false;
        } else {
            return true;
        }
    }

    private boolean isFirstTouch = true;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        mVelocityTracker.addMovement(event);
        ViewConfiguration configuration = ViewConfiguration.get(getContext());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (!mScroller.isFinished()) {
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                if (isFirstTouch) {
                    isFirstTouch = false;
                    lastY = y;
                    lastX = x;
                }
                final int deltaX = x - lastX;
                scrollBy(-deltaX, 0);
                break;
            case MotionEvent.ACTION_UP:
                isFirstTouch = true;
                int scrollX = getScrollX();
                mVelocityTracker.computeCurrentVelocity(1000, configuration.getScaledMaximumFlingVelocity());
                float mVelocityX = mVelocityTracker.getXVelocity();
                if (Math.abs(mVelocityX) > configuration.getScaledMinimumFlingVelocity()) {
                    childIndex = mVelocityX < 0 ? childIndex + 1 : childIndex - 1;
                } else {
                    childIndex = (scrollX + getChildAt(0).getWidth() / 2) / getChildAt(0).getWidth();
                }
                childIndex = Math.min(getChildCount() - 1, Math.max(0, childIndex));
                smoothScrollBy(childIndex*getChildAt(0).getWidth()-scrollX,0);
                mVelocityTracker.clear();
                break;
        }

        lastX = x;
        lastY = y;
        return true;
    }

    private void smoothScrollBy(int dx, int dy) {
        mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,500);
        invalidate();
    }

    @Override
    public void computeScroll() {
        if(mScroller.computeScrollOffset()){
            scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
            postInvalidate();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        mVelocityTracker.recycle();
    }
}

ListViewEx.java


/**
 * Created by blueberry on 2016/6/20.
 * 内部拦截事件
 */
public class ListViewEx extends ListView {

    private int lastXIntercepted, lastYIntercepted;

    private HorizontalEx2 mHorizontalEx2;

    public ListViewEx(Context context) {
        super(context);
    }

    public ListViewEx(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ListViewEx(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public HorizontalEx2 getmHorizontalEx2() {
        return mHorizontalEx2;
    }

    public void setmHorizontalEx2(HorizontalEx2 mHorizontalEx2) {
        this.mHorizontalEx2 = mHorizontalEx2;
    }

    /**
     * 使用 outter.requestDisallowInterceptTouchEvent();
     * 来决定父控件是否对事件进行拦截
     * @param ev
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int x = (int) ev.getX();
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mHorizontalEx2.requestDisallowInterceptTouchEvent(true);
                break;
            case MotionEvent.ACTION_MOVE:
                final int deltaX = x-lastYIntercepted;
                final int deltaY = y-lastYIntercepted;
                if(Math.abs(deltaX)>Math.abs(deltaY)){
                    mHorizontalEx2.requestDisallowInterceptTouchEvent(false);
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        lastXIntercepted = x;
        lastYIntercepted = y;
        return super.dispatchTouchEvent(ev);
    }
}

调用代码:

 @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;

/**
 * Created by blueberry on 2016/6/21.
 *
 *外部拦截(同向)
 *
 */
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;
        }
    }
}
时间: 2024-10-16 01:42:34

android滑动冲突的解决方案的相关文章

教你用两招就完美解决 Android 滑动冲突!

冲突情况 在 Android 开发中,滑动冲突总是我们一个无法避免的话题.而对于解决方案却是众说纷纭.比如 RecyclerView 嵌套 RecyclerView,直接通过相关方法禁掉内部 RecyclerView 的滑动:ScrollView 嵌套 RecyclerView 直接把 ScrollView 替换为 NestedScrollView 等等. 但我们今天要说的是在自定义 View 中遇到滑动冲突时,我们又应该如何处理呢?当然,今天的话题需要 View 的事件分发机制做理论前提. 1

关于Android滑动冲突的解决方法(二)

之前的一遍学习笔记主要就Android滑动冲突中,在不同方向的滑动所造成冲突进行了了解,这样的冲突非常easy理解,当然也非常easy解决.今天,就同方向的滑动所造成的冲突进行一下了解,这里就先以垂直方向的滑动冲突为背景,这也是日常开发中最常见的一种情况. 这里先看一张效果图 由于GIF 图片大小的限制.截图效果不是非常好 上图是在购物软件上常见的上拉查看图文详情,关于这中动画效果的实现.事实上实现总体的效果,办法是有非常多的,网上有非常多相关的样例,可是对某些细节的处理不是非常清晰.比方,下拉

Android 滑动冲突处理

要想解决滑动冲突就必须好好理解 Android 的事件分发机制.不了解 Android 事件分发机制的请先参考资料学习一下. 一般有 2 种方法 1 外部拦截法 这个非常简单,因为事件是从父 view 向子 view 进行分发的,所以我们可以重写父控件的 onInterceptTouchEvent, 如果父容器需要某个事件就拦截,如果不需要就不拦截交给子view处理. 伪代码如下 public boolean onInterceptTouchEvent(MotionEvent event) {

Android 滑动冲突以及如何解决

首先要理解事件分发机制. 关于事件分发机制,ViewGroup的事件分发,有3个关键方法 dispatherTouchEvent,onInterceptTouchEvent,onTouchEvent. 分发,拦截,执行. 滑动冲突的产生,主要是因为 ,存在有多层嵌套的可滑动viewGroup,那么如果真的到了这种场景,到底应该如何响应呢?应该响应哪一层的滑动事件呢? 多层嵌套的可滑动ViewGroup,三种情况(两层嵌套内外可滑动方向一致,两层嵌套内外可滑动方向不一致,内外嵌套超过3层含3层),

Android滑动事件冲突(上)

首先,我们假设这样一个场景:一个ViewPager里面嵌套一个ViewPager,内部滑动方向和外部滑动方向一样时,该怎么解决这一冲突呢? 针对滑动冲突这里给出两种解决方案:外部拦截法,内部拦截法. 外部拦截法 外部拦截法是指点击事件都先经过父容器的拦截处理,如果父容器需要此拦截事件,就拦截,不需要就不拦截,这种方法比较符合点击事件的分发机制.这种方法代码如下: 1 @Override 2 public boolean onInterceptTouchEvent(MotionEvent ev)

解决侧滑中ViewPager和SlidingMenu的滑动冲突

当我们在使用开源框架SlidingMenu时,如果要是使用到ViewPager,就会出现滑动冲突. 解决方案: }/** 解决ViewPager和侧滑冲突 */ public void changeSlidingMenuTOUCHMODE(int arg0) { switch (arg0) { case 0: if (getActivity() instanceof SlidingFragmentActivity) { SlidingFragmentActivity activity = (Sl

Android 解决下拉刷新控件和ScrollVIew的滑动冲突问题。

最近项目要实现ScrollView中嵌套广告轮播图+RecyleView卡片布局,并且RecyleView按照header和内容的排列样式,因为RecyleView的可扩展性很强,所以我毫无疑问的选择了它,而且让RecyleView实现了可拖拽的效果, 最后我再加上了下拉刷新的效果(这里我用的下拉刷新控件是三方的SmartRefreshLayout).记得刚开始实现这个效果的时候还是十分的得心印手.可是当我测试的时候,发现RecyleView的子item的拖拽效果并不流畅,起初我以 为是由于Re

Android——滑动事件冲突解决

android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件. android系统中的每个View的子类都具有下面三个与TouchEvent处理密切相关的方法: (1)public boolean dispatchTouchEvent(MotionEvent ev)这个方法用来分发TouchEvent (2)public boolean onInterceptTouchEvent(MotionEvent ev)这个方法用来拦截TouchEvent (3)publi

view的滑动冲突解决方案

一.常见的滑动冲突场景 1.外部滑动方向和内部滑动方向不一致 2.外部滑动方向和内部滑动方向一致 3.上面两种情况的嵌套 二.滑动冲突处理的原则 场景1的处理原则是:当用户左右滑动时,需要让外部的view拦截点击事件,当用户上下滑动时,需要让内部的view拦截点击事件.场景2和场景3比较特殊,无法如同场景1一样原则的处理冲突,需要在业务上寻找突破点.比如业务上规定:当处于某种状态时需要外部View响应用户的滑动,而处于另一种状态时则需要内部View来响应View的滑动,根据这种业务上的需求我们也