Android滑动冲突解决方式(下拉刷新上拉加载更多,适配RecyclerView/ListView/ScrollView)

一、Android事件的分发机制

    这里需要了解下Andorid事件的分发机制。事件分发一般是针对一组事件,即ACTION_DOWN > ACTION_UP 或 ACTION_DOWN > ACTION_MOVE... >ACTION_UP,其中涉及事件分发的主要方法有 dispatchTouchEvent(MotionEvent event)、onInterceptTouchEvent(MotionEvent event) (ViewGroup有,View没有)、onTouchEvent(MotionEvent event),而且事件分发是由上向下传递的,即先到parent,再到child,这里简单以 ViewGroup内包裹一个View为例,大致分析下其事件的分发流程(忽略Activity,Window的传递)

    事件首先会传递到ViewGroup.dispatchTouchEvent(MotionEvent event),然后会判断ViewGroup.onInterceptTouchEvent(MotionEvent event)的返回值:

    1.如果返回为false,即不拦截,事件则会传递给View.dispatchTouchEvent(MotionEvent event),由于这里View没有子View了,事件则传递给该View的View.onTouchEvent(MotionEvent event)处理,如果View.onTouchEvent(MotionEvent event)没有消耗该事件,则该事件会返回给ViewGroup.onTouchEvent(MotionEvent event)处理,如果View.onTouchEvent(MotionEvent event)消耗了该事件,则该事件不会再返回给ViewGroup,本次事件分发结束。

    2.如果ViewGroup.onInterceptTouchEvent(MotionEvent event)返回值为ture,即拦截事件,则事件将由ViewGroup.onTouchEvent(MotionEvent event)处理,本次事件分发结束。

    3.即使事件被ViewGroup拦截了,View也可以阻止ViewGroup对事件的拦截。可以通过getParent().requestDisallowInterceptTouchEvent(true)。

大致流程图

    前面说到View可以阻止ViewGroup对事件的拦截,但除了ACTION_DOWN,也就是说,对一组事件,除了ACTION_DOWN,子View可以在ViewGroup.onInterceptTouchEvent(MotionEvent event)返回ture的情况下,获取事件的处理权,下面截图android25的源代码。


@Override

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

        }

    }

当actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget !=null时,会进行disallowIntercept的判断,而disallowIntercept取决于mGroupFlags,因为FLAG_DISALLOW_INTERCEPT是一个常量0x80000,而mGroupFlags的赋值是可以通过requestDisallowInterceptTouchEvent来改变的。当requestDisallowInterceptTouchEvent(true),disallowIntercept=true,此时不走onInterceptTouchEvent(ev)的判断,intercepted=false从而达到阻止ViewGroup拦截的效果。

二、以RecyclerView下拉刷新上拉加载更多为例分析滑动冲突及解决

以recyclerView为例,也可以换listView。默认状态时红色部分为可视部分,也就是顶部和底部隐藏看不见,这里我们选择FrameLayout作为容器,因此,FrameLayout和RecyclerView就会产生同向滑动冲突。只有recyclerView内容滑动到顶部并且手势为下滑时,header才会慢慢下滑到可视范围内,或recyclerView内容滑动到底部时,并且手势为上滑,footer才会慢慢上滑到可视范围内。

    private boolean intercept;

    private float lastInterceptY;

    @Override

    public boolean onInterceptTouchEvent(MotionEvent ev) {

        float curInterceptY = ev.getY();

        switch (ev.getAction()) {

            case MotionEvent.ACTION_DOWN:

                intercept = false;

                break;

            case MotionEvent.ACTION_MOVE:

                if (isRefreshing || isLoadMore) {

                    intercept = false;

                } else {

                    boolean isHeaderShow = headerParams.topMargin > -headerHeight;

                    boolean isFooterShow = footerParams.topMargin < height;

                    intercept = touchHelper != null && touchHelper.judgeIntercept(curInterceptY, lastInterceptY, isHeaderShow, isFooterShow, allowLoadMore);

                }

                break;

            case MotionEvent.ACTION_UP:

                intercept = false;

                break;

        }

        lastInterceptY = curInterceptY;

        return intercept;

    }

    @Override

    public boolean judgeIntercept(float curInterceptY, float lastInterceptY, boolean isHeaderShow, boolean isFooterShow, boolean allowLoadMore) {

        boolean intercept;

        int firstVisiblePos = layoutManager.findFirstVisibleItemPosition();

        View firstView = rv.getChildAt(firstVisiblePos);

        if (firstVisiblePos == 0 && firstView.getTop() == 0) {

            intercept = curInterceptY > lastInterceptY || isHeaderShow;

        } else {

            if (allowLoadMore && layoutManager.findLastVisibleItemPosition() == layoutManager.getItemCount() - 1) {

                intercept = curInterceptY < lastInterceptY || isFooterShow;

            } else {

                intercept = false;

            }

        }

        return intercept;

    }

1.MotionEvent.ACTION_DOWN中,必须返回false,不拦截,一旦拦截,后续的ACTION_MOVE和ACTION_UP将直接交由FrameLayout的onTouchEvent(ev)处理

2.MotionEvent.ACTION_UP中也必须返回false,因为一组事件以ACTION_UP结尾,则ACTION_UP这个事件必定会经过FrameLayout.onTouchEvent(ev),如果拦截了,则子View针对ACTION_UP需要处理的事情就无法完成。

3.MotionEvent.ACTION_MOVE中根据实际情况判断是否拦截。

    private float moveDis;

    @Override

    public boolean onTouchEvent(MotionEvent ev) {

        if (touchHelper != null) {

            float curTouchY = ev.getY();

            switch (ev.getAction()) {

                case MotionEvent.ACTION_DOWN:

                    break;

                case MotionEvent.ACTION_MOVE:

                    moveDis = curTouchY - lastInterceptY;

                    if (Math.abs(moveDis) < touchSlop) {

                        break;

                    }

                    if (isRefreshing || isLoadMore) {

                        break;

                    }

                    moveDis = moveDis * kMoveFactor;

                    if (touchHelper.isContentSlideToTop()) {

                        updateHeaderMargin(moveDis);

                    } else if (touchHelper.isContentSlideToBottom()) {

                        updateFooterMargin(moveDis);

                    }

                    break;

                case MotionEvent.ACTION_UP:

                    if (moveDis > 0) {

                        if (touchHelper.isContentSlideToTop()) {

                            if (headerParams.topMargin < 0) {

                                scrollHeaderByAnimator(headerParams.topMargin, -headerHeight);

                                if (header != null) {

                                    header.onPullToRefresh(moveDis);

                                }

                            } else {

                                scrollHeaderByAnimator(headerParams.topMargin, 0);

                                if (header != null) {

                                    header.onRefreshing();

                                }

                                isRefreshing = true;

                                if (listener != null) {

                                    listener.onRefresh();

                                }

                            }

                        }

                    } else {

                        if (touchHelper.isContentSlideToBottom()) {

                            if (footerParams.topMargin > height - footerHeight) {

                                scrollFooterByAnimator(false, footerParams.topMargin, height);

                                if (footer != null) {

                                    footer.onPullToLoadMore(moveDis);

                                }

                            } else {

                                scrollFooterByAnimator(false, footerParams.topMargin, height - footerHeight);

                                if (footer != null) {

                                    footer.onLoadMore();

                                }

                                isLoadMore = true;

                                if (listener != null) {

                                    listener.onLoadMore();

                                }

                            }

                        }

                    }

                    break;

            }

        }

        return true;

    }

private void updateHeaderMargin(float moveDis) {

        moveDis = moveDis < 0 ? 0 : moveDis;

        headerParams.topMargin = (int) (-headerHeight + moveDis);

        headerVG.setLayoutParams(headerParams);

        setChildViewTopMargin((int) moveDis);

        if (header != null) {

            if (moveDis < headerHeight) {

                header.onPullToRefresh(moveDis);

            } else {

                header.onReleaseToRefresh(moveDis);

            }

        }

    }

    private void setChildViewTopMargin(int topMargin) {

        LayoutParams childParams = (LayoutParams) childView.getLayoutParams();

        childParams.topMargin = topMargin;

        childView.setLayoutParams(childParams);

    }

    private void updateFooterMargin(float moveDis) {

        moveDis = moveDis > 0 ? 0 : moveDis;

        footerParams.topMargin = (int) (height + moveDis);

        footerVG.setLayoutParams(footerParams);

        setChildViewBottomMargin((int) Math.abs(moveDis));

        scrollContentToBottom((int) -moveDis);

        if (footer != null) {

            if (Math.abs(moveDis) < footerHeight) {

                footer.onPullToLoadMore(moveDis);

            } else {

                footer.onReleaseToLoadMore(moveDis);

            }

        }

    }

    private void setChildViewBottomMargin(int bottomMargin) {

        LayoutParams childParams = (LayoutParams) childView.getLayoutParams();

        childParams.bottomMargin = bottomMargin;

        childView.setLayoutParams(childParams);

    }

    private void scrollContentToBottom(int deltaY) {

        if (childView instanceof RecyclerView) {

            childView.scrollBy(0, deltaY);

        } else if (childView instanceof ListView) {

            ((ListView) childView).smoothScrollBy(deltaY, 0);

        } else if (childView instanceof ScrollView) {

            childView.scrollBy(0, deltaY);

        }

    }

    private void scrollHeaderByAnimator(float startY, float endY) {

        ValueAnimator animator = ValueAnimator.ofFloat(startY, endY);

        animator.setDuration(kDuration);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override

            public void onAnimationUpdate(ValueAnimator animation) {

                float floatValue = (float) animation.getAnimatedValue();

                headerParams.topMargin = (int) floatValue;

                headerVG.setLayoutParams(headerParams);

                setChildViewTopMargin((int) (headerHeight + floatValue));

            }

        });

        animator.start();

    }

    private void scrollFooterByAnimator(final boolean isAuto, float startY, float endY) {

        ValueAnimator animator = ValueAnimator.ofFloat(startY, endY);

        animator.setDuration(kDuration);

        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override

            public void onAnimationUpdate(ValueAnimator animation) {

                float floatValue = (float) animation.getAnimatedValue();

                footerParams.topMargin = (int) floatValue;

                footerVG.setLayoutParams(footerParams);

                int bottomMargin = (int) (height - floatValue);

                setChildViewBottomMargin(bottomMargin);

                if (isAuto) {

                    scrollContentToBottom(bottomMargin);

                    if (footer != null) {

                        footer.onPullToLoadMore(bottomMargin);

                        if (bottomMargin == footerHeight) {

                            footer.onLoadMore();

                        }

                    }

                }

            }

        });

        animator.start();

    }

不完善点还待指正!!!

源码下载:下拉刷新上拉加载更多 喜欢就star下吧!万分感谢!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342