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的源代碼。

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;
            }
@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();
    }
源碼下載:點擊打開鏈接

不完善點還待指正!!!




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章