android滑动冲突解决方案

来源:互联网 发布:公司网络优化方案 编辑:程序博客网 时间:2024/05/19 11:47

转载请注明出处: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
原创粉丝点击