View的滑动冲突解决-android开发艺术探索笔记

来源:互联网 发布:搜狗上的动态壁纸软件 编辑:程序博客网 时间:2024/05/17 07:52

最近在准本找工作的同时,也在慢慢的看android开发艺术探索这本书感觉写的真是太好了,有空下来就做个笔记,下面进入正题:


滑动冲突的产生:

在页面中要有内外两层勇士滑动,这个时候就会产生滑动冲突了,比如scrollerView嵌套listview这种情况。


常见的冲突场景:

场景一:外部滑动方向和内部滑动方向的不一致(eq: horizontalScrollview和listview的嵌套)
场景二:外部滑动方向和内部滑动方向一致(eq: verticalScrollView和listview的嵌套)
场景三:上面两种情况的嵌套


滑动冲突的处理原则:

根据滑动时水平滑动还是竖直滑动来判断到底是由外部的view拦截触摸事件还是内部的view拦截触摸事件
如何判断水平滑动还是竖直滑动?
1.根据滑动路径和水平方向的夹角进行判断
2.根据水平方向和竖直方向的距离差进行判断
3.特殊时候可以根据水平和竖直方向的速度差来做判断


解决方式:

1.内部拦截法
2.外部拦截法

外部拦截法

指点击事情都先经过父容器的拦截处理,如果父容器需要此事件则拦截,否则下发到子控件中。外部拦截法需要重写父容器的onInterceptTouchEvent方法,做相应的拦截即可
伪代码:

父容器:

 @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        boolean intercept=false;        int x= (int) ev.getX();        int y= (int) ev.getY();        switch (ev.getAction())        {            case MotionEvent.ACTION_DOWN:                intercept=false;                break;            case MotionEvent.ACTION_MOVE:                if (父容器需要当前点击事件)                {                    intercept=true;                }else                 {                    intercept=false;                }                break;            case MotionEvent.ACTION_UP:                intercept=false;                break;         }        return intercept;    }

MotionEvent.ACTION_UP这里必须返回false,考虑一种情况,如果在父容器ACTION_UP返回了true,会导致子元素无法接受到ACTION_UP事件,那么子元素中的onClick事件就无法促发。

内部拦截法

所有事件都传递给子元素,如果子元素需要此事件则就消耗,否则交由给父容器进行处理,这种方法和android中的事件分发不一样,需要配合requestDisallowInterceptTouchEvent(boolean)进行处理,相对于外部拦截法来说,稍显复杂,我们需要重写子元素的dispatchTouchEvent():
伪代码:
子元素:

  @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        int x= (int) ev.getX();        int y= (int) ev.getY();        switch (ev.getAction())        {            case MotionEvent.ACTION_DOWN:                getParent().requestDisallowInterceptTouchEvent(true);                break;            case MotionEvent.ACTION_MOVE:                int deltaX=x-mLastX;                int deltaY=x-mLastY;                if (父容器需要当前点击事件)                {                    getParent().requestDisallowInterceptTouchEvent(false);                }                break;        }        mLastX=x;        mLastY=y;        return super.dispatchTouchEvent(ev);    }}

除了子元素需要修改以外,父容器也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用getParent().requestDisallowInterceptTouchEvent(false)方法时候,父元素才能继续拦截所需的事件。

为什么父容器不能拦截ACTION_DOWN事件呢,因为ACTION_DOWN事件不受FLAG_DISALLOW_INTERCEPT这个标记位控制,所以一旦拦截了ACTION_DOWN事件,则子元素根本接收不到传递的事件,因为父容器把他拦截了。
父容器:

 @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        int action=ev.getAction();        if (action==MotionEvent.ACTION_DOWN)        {            return false;        }else         {            return true;        }    }

这里详细讲一下requestDisallowInterceptTouchEvent(boolean)这个方法:
源码:
View源码

 /**     * Called when a child does not want this parent and its ancestors to     * intercept touch events with     * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}.     *     * <p>This parent should pass this call onto its parents. This parent must obey     * this request for the duration of the touch (that is, only clear the flag     * after this parent has received an up or a cancel.</p>     *      * @param disallowIntercept True if the child does not want the parent to     *            intercept touch events.     */    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept);

我们看到里面写着,如果传入进去的值为true,说明孩子元素不想让父容器去拦截触摸事件,这是一个对外的公开方法。
我们再到Viewgroup里面查看,因为一般而言顶级View都是一个ViewGroup,我们查看Viewgroup的dispatchTouchEvent,可以在里面找到如下的源码:

 // 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;            }/**     * {@inheritDoc}     */    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {        if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {            // We're already in this state, assume our ancestors are too            return;        }        if (disallowIntercept) {            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;        } else {            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;        }        // Pass it up to our parent        if (mParent != null) {            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);        }    }

我们可以清楚地看到里面的逻辑:

if (!disallowIntercept) {                    intercepted = onInterceptTouchEvent(ev);                    ev.setAction(action); // restore action in case it was changed                } else {                    intercepted = false;                }

说明这个标志确实是如我们上述形容的作用,决定父容器是否拦截触摸事件。

0 0
原创粉丝点击