android滑动冲突解决方案

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

2017年大家新年好,这还是我第一次在csdn上写博客,主要是自己最近正好在看这方面的知识,一作为存根之用,二也是希望对大家有所帮助,如讲解有不妥之处,欢迎大家指出。废话不多说了,进入今天的主题----Android滑动冲突。


在讲滑动冲突前,有一点是必须要提的,就是View的事件分发机制。View的事件分发机制可以看任玉刚大神写的《android开发艺术探索》,里面关于这方面讲解的十分透彻。下面我简单讲一下这方面的知识。


所谓事件分发,其实就是对View中MotionEvent事件的分发,当一个MotionEvent事件产生时,系统需要把这个事件传递给一个具体的View,这个传递的过程就是分发过程。点击事件的分发过程由三个方法来共同完成:dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent。其中dispatchTouchEvent用于传递事件;onInterceptTouchEvent用来拦截事件(只有ViewGroup有,View不存在此方法),如果返回true,即代表拦截事件,在同一事件序列中此方法不会被再次调用;onTouchEvent用来处理事件,返回true代表消耗此次事件,返回false表示不消耗,并且在同一事件序列中将不再接收到事件。


事件分发的规则:首先我们手指触碰屏幕产生的点击事件先传递给根ViewGroup,此时根ViewGroup的dispatchTouchEvent方法将执行,如果此ViewGroup想拦截该事件,那么此ViewGroup的onInterceptTouchEvent将返回true(ViewGroup的onInterceptTouchEvent默认返回都是false,不拦截),该点击事件交由ViewGroup处理,然后此ViewGroup的onTouchEvent方法将被执行,如果根ViewGroup不拦截事件,那么此事件将传递给ViewGroup的子元素,并且子元素的dispatchTouchEvent方法会被调用,如果子元素也是个ViewGroup,那么子元素也可以决定是否拦截该事件,如果是一个View,那么接下来View的onTouchEvent方法将被执行,如果返回false,即不消耗事件,那么事件将返回给根ViewGroup的onTouchEvent执行,如果返回true,那么事件到此就交由该View完成了。(其实也很好理解,boss将任务交给手下,手下能完成最好,完不成还得boss出马来解决)


理解了事件分发机制,那么对于滑动冲突也很好理解了。滑动冲突分为三种情况:

(1)不同方向冲突,比如HorizontalScrollView内嵌ListView

(2)同方向冲突,比如纵向ScrollView嵌套ListView,两个滑动方向相同

(3)包含1、2中情况的冲突。


解决方案其实就是基于事件分发机制的。

(1)对于第一种情况,一个是横向滑动,一个是纵向滑动,那么我们就让它在横向滑动时将事件交由HorizontalScrollView全权处理,在纵向滑动时将事件交由ListView全权处理,也就是说,当横向滑动时,HorizontalScrollView将拦截事件,此时产生的点击事件就由HorizontalScrollView的onTouchEvent来完成;当纵向滑动时,HorizontalScrollView不拦截事件,此时产生的点击事件就由ListView的onTouchEvent来完成。

(2)对于第二种情况,解决方案也是类似,视具体的业务需求来解决,比如ListView上头还有一个View,当滑动距离在那个View的高度之间时,ScrollView拦截事件,由ScrollView处理上下滑动的事件,当滑动距离到达ListView时,就不拦截事件,让ListView继续处理上下滑动事件,当然,这只是其中的一个例子。

(3)对于第三种情况也是类似的,就是要考虑是否拦截的判定条件变得更为复杂,我下面要讲的例子就涉及了这方面的内容。


既然知道了滑动冲突的解决思路,那么该如何解决滑动冲突呢,有2种方法:(1)外部拦截法,顾名思义,就是从父元素着手来拦截,具体实现就是在父元素的onInterceptTouchEvent方法中根据解决思路来拦截事件(2)内部拦截法,类似的,就是从子元素着手,主动告诉父元素是否要拦截,具体实现就是在子元素的dispatchTouchEvent方法中使用

getParent().requestDisallowInterceptTouchEvent(false);

false的意思就是告诉父元素此时需要拦截事件了,true就是不让父元素拦截,至于getParent()方法,视你的布局而定,如果只有一个ViewGroup和一个View(或者是Viewgroup),那么用一个getParent()就可以了,如果有两个ViewGroup,那么对于最底层的那个View(或者ViewGroup)就要使用两次getParent(),即
getParent().getParent().requestDisallowInterceptTouchEvent(false);


下面上今天的实例,需求:

(1)根布局为自定义的ScrollView,模仿ViewPager的功能(至于为什么不用ViewPager,主要是ViewPager中已经处理了和Listview上下冲突的问题)

(2)ScrollView中有3个fragment

(3)第一个fragment包含一个Listview,第二个fragment包含一个自定义ScrollView(也是模仿ViewPager的功能)

这样整个布局既包含了不同方向滑动冲突,也包含了同方向滑动冲突。


先看主布局文件,自定义ScrollView中包含三个fragment

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <com.bellnet.slidingconflict.CustomHorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <fragment
            android:id="@+id/fragment1"
            android:name="com.bellnet.slidingconflict.Fragment1"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </fragment>

        <fragment
            android:id="@+id/fragment2"
            android:name="com.bellnet.slidingconflict.Fragment2"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </fragment>

        <fragment
            android:id="@+id/fragment3"
            android:name="com.bellnet.slidingconflict.Fragment3"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </fragment>
    </com.bellnet.slidingconflict.CustomHorizontalScrollView>

</LinearLayout>

下面看这个ScrollView如何来实现,采用的是外部拦截法

/**
 * Created by bellnett on 17/2/5.
 */
public class CustomHorizontalScrollView extends ViewGroup {

    private Scroller mSroller;//用于完成滚动的实例
    private int mTouchSlop;//判定为拖动的最小移动像素数

    private int leftBorder;//界面可滚动的左边界
    private int rightBorder;//界面可滚动的右边界

    private float mFirstX = 0;//第一次触碰屏幕的x坐标
    private float mFirstY = 0;//第一次触碰屏幕的y坐标
    private float mLastX = 0;//滑动屏幕时的x坐标

    private float mLastXIntercept = 0;//用于onInterceptTouchEvent中滑动屏幕的x坐标
    private float mLastYIntercept = 0;//用于onInterceptTouchEvent中滑动屏幕的y坐标


    public CustomHorizontalScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        /* 创建scroller的实例 */
        mSroller = new Scroller(context);
        /* 获取判定滑动的最小移动像素数 */
        ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
        mTouchSlop = viewConfiguration.getScaledTouchSlop();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            measureChild(view, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        if (b) {
            int childCount = getChildCount();
            for (int j = 0; j < childCount; j++) {
                View view = getChildAt(j);
                view.layout(j * view.getMeasuredWidth(), 0, (j + 1) * view.getMeasuredWidth(), view
                        .getMeasuredHeight());
            }
            leftBorder = getChildAt(0).getLeft();
            rightBorder = getChildAt(childCount - 1).getRight();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        System.out.println("ev------->" + ev.getAction());
        Boolean intercept = false;
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mFirstX = ev.getRawX();
                mFirstY = ev.getRawY();
                mLastX = ev.getRawX();
                break;
            case MotionEvent.ACTION_MOVE:
                mLastXIntercept = ev.getRawX();
                mLastYIntercept = ev.getRawY();
                float delX = Math.abs(mLastXIntercept - mFirstX);
                float dexY = Math.abs(mLastYIntercept - mFirstY);
                mFirstX = mLastXIntercept;
                if (getScrollX() + leftBorder < getWidth()) {//此时为第一个fragment的页面
                    if (delX > dexY) {
                        intercept = true;//x滑动距离大于y时,此时拦截子控件的事件
                    } else {
                        intercept = false;
                    }
                } else if (getScrollX() + leftBorder > getWidth() && getScrollX() + leftBorder <
                        2 * getWidth()) {//此时为第二个fragment的页面
                    intercept = false;
                } else {//此时是第三个fragment的页面
                    if (delX > dexY) {
                        intercept = true;//x滑动距离大于y时,此时拦截子控件的事件
                    } else {
                        intercept = false;
                    }
                }

                /*
                if (delX < mTouchSlop) {//如果滑动的最小距离小于toushSlop,那么不认为滑动,不拦截子控件的事件
                    intercept = false;
                } else {
                    intercept = true;
                }
                */

                break;
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        System.out.println("event------->" + event.getAction());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_MOVE:
                mLastX = event.getRawX();
                int delX = (int) (mFirstX - mLastX);
                if (getScrollX() + delX < leftBorder) {
                    scrollTo(leftBorder, 0);
                    return true;
                } else if (getScrollX() + getWidth() + delX > rightBorder) {
                    scrollTo(rightBorder - getWidth(), 0);
                    return true;
                }
                scrollBy(delX, 0);
                mFirstX = mLastX;
                break;
            case MotionEvent.ACTION_UP:
                int index = (getScrollX() + getWidth() / 2) / getWidth();
                int dx = index * getWidth() - getScrollX();
                mSroller.startScroll(getScrollX(), 0, dx, 0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

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


主要就是在onInterceptTouchEvent进行判断,当滑动的目标为第一个fragment时,此时fragment中只包含一个ListView,那么根据x、y滑动距离的比较来判定是上下滑动还是左右滑动;当滑动的目标为第二个fragment时,由于第二个fragment也包含一个类似ViewPager的ScrollView,因此就让子元素自己处理事件,父元素不拦截,至于第三个fragment,就只有一个TextView,因此也可以根据x、y滑动的比较来判定拦截条件。至于如何实现类似ViewPager的ScrollView,我就不详解了,自行百度或参照上面代码所写。


下面上子元素的ScrollView的实现,采用的是内部拦截法,主要是外部拦截法我不知道怎么来判定这个子元素的宽度,由于用了fragment,每次获取到的宽度就是手机的屏幕宽,因此从内部着手来判定宽度,方便告诉父元素何时拦截

/**
 * Created by bellnett on 17/2/6.
 */
public class CustomBroadcastScrollView extends ViewGroup {

    private float mFirstX = 0;//第一次触碰屏幕的x坐标
    private float mLastX = 0;//滑动屏幕时的x坐标
    private float mDispatchX = 0;//在dispatchTouchEvent中滑动屏幕时的x坐标

    private int leftBorder;//界面可滚动的左边界
    private int rightBorder;//界面可滚动的右边界

    private Scroller mScroller;//用于完成滚动的实例

    public CustomBroadcastScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        System.out.println("ev2------->" + ev.getAction());
        getParent().getParent().requestDisallowInterceptTouchEvent(true);
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
                mFirstX = ev.getX();
                break;
            case MotionEvent.ACTION_MOVE:
                mDispatchX = ev.getX();
                int delX = (int) (mFirstX - mDispatchX);
                if(getScrollX() + delX < leftBorder){//此时为滑动的第一张图片
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                }else if(getScrollX() + getWidth() + delX > rightBorder){//此时已经滑动到最后一张图片
                    getParent().getParent().requestDisallowInterceptTouchEvent(false);
                }
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        System.out.println("event2------->" + event.getAction());
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                return true;
            case MotionEvent.ACTION_MOVE:
                mLastX = event.getX();
                int delX = (int) (mFirstX - mLastX);
                if(getScrollX() + delX < leftBorder){
                    scrollTo(leftBorder,0);
                    return true;
                }else if(getScrollX() + getWidth() + delX > rightBorder){
                    scrollTo(rightBorder - getWidth(),0);
                    return true;
                }
                scrollBy(delX,0);
                mFirstX = mLastX;
                break;
            case MotionEvent.ACTION_UP:
                int index = (getScrollX() + getWidth() / 2) / getWidth();
                int dx = index * getWidth() - getScrollX();
                mScroller.startScroll(getScrollX(),0,dx,0);
                invalidate();
                break;
        }
        return super.onTouchEvent(event);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            measureChild(view, widthMeasureSpec, heightMeasureSpec);
        }
    }

    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
        if (b) {
            int childCount = getChildCount();
            for (int j = 0; j < childCount; j++) {
                View view = getChildAt(j);
                view.layout(j * view.getMeasuredWidth(), 0, (j + 1) * view.getMeasuredWidth
                        (), view.getMeasuredHeight());
            }
            leftBorder = getChildAt(0).getLeft();
            rightBorder = getChildAt(childCount - 1).getRight();
        }
    }

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


其子元素就是在dispatchTouchEvent中处理拦截事件,当滑动为第一张图片时,此时如果手指往右滑动,那么就告诉父元素拦截事件,父元素将从第二个fragment滑动到第一个fragment;当滑动为最后一张图片时,此时如果手指往左滑动,那么久告诉父元素拦截事件,父元素将从第二个fragment滑动到第三个fragment,这就是两种边界情况时的判定,其余情况就告诉父元素不要拦截事件,让子元素自己处理滑动事件。(这个视具体的业务需求来定,我这里举的例子是根据第一张和最后一张图片来判定)


第一个fragment布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这是第一个fragment"
        android:textSize="20sp"/>

    <ListView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:id="@+id/oneListView"
        android:dividerHeight="2dp"
        android:layout_weight="1">

    </ListView>

</LinearLayout>

第二个fragment布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="这是第二个fragment"
        android:textSize="20sp"/>

    <com.bellnet.slidingconflict.CustomBroadcastScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/a"/>
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/b"/>
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/c"/>
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@drawable/d"/>
    </com.bellnet.slidingconflict.CustomBroadcastScrollView>

</LinearLayout>


第三个就不粘了,只有一个TextView


第一个fragment实现

/**
 * Created by bellnett on 17/2/5.
 */
public class Fragment1 extends Fragment {

    private ListView listView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
            savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_one, container, false);
        listView = (ListView) view.findViewById(R.id.oneListView);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        String[] a = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14",
                "15", "16", "17", "18", "19", "20","21","22","23","24","25","26","27"};
        ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout
                .simple_list_item_1, a);
        listView.setAdapter(adapter);
    }
}

第二个fragment实现

/**
 * Created by bellnett on 17/2/5.
 */
public class Fragment2 extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_two, container, false);
        return view;
    }
}

同样的,第三个fragment的实现和第二个fragment完全一样,只需要改个布局文件就可以了。到此所有的代码都粘贴出来了,如有不妥或者更好的处理方式,欢迎大家指出,下面看看具体的效果。由于上传图片有大小限制,我拆成了两张图。


           



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值