欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android深入探究自定義View之嵌套滑動的實現(xiàn)

 更新時間:2021年11月02日 08:36:16   作者:臨木小屋  
什么是嵌套滑動?當我們向下滑動時,首先是外部的布局向下滑動,然后才是內(nèi)部的RecyclerView滑動,向上滑動也是如此。這就是嵌套滑動的效果

本文主要探討以下幾個問題:

  • 嵌套滑動設計目的
  • 嵌套滑動的實現(xiàn)
  • 嵌套滑動與事件分發(fā)機制

嵌套滑動設計目的

不知道大家有沒有注意過淘寶APP首頁的二級聯(lián)動,滑動的商品的時候上面類別也會滑動,滑動過程中類別模塊停了商品還能繼續(xù)滑動。也就是說滑動的是view,ViewGroup也會跟著滑動。如果用事件分發(fā)機制處理也能處理,但會及其麻煩。那用NestedScroll會咋樣?

嵌套滑動的實現(xiàn)

假設布局如下

在這里插入圖片描述


RecyclerView 實現(xiàn)了 NestedScrollingChild 接口,NestedScrollView 實現(xiàn)了 NestedScrollingParent,這是實現(xiàn)嵌套布局的基礎

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2, NestedScrollingChild3
public class NestedScrollView extends FrameLayout implements NestedScrollingParent3, NestedScrollingChild3, ScrollingView

滑動屏幕時 RecyclerView 收到滑動事件,在 ACTION_DOWN 時

//	RecyclerView.java  onTouchEvent函數(shù)
 case MotionEvent.ACTION_DOWN: {
      mScrollPointerId = e.getPointerId(0);
        mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
        mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);

        int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE;
        if (canScrollHorizontally) {
            nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
        }
        if (canScrollVertically) {
            nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
        }
        //	
        startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
    } 
    break;

繼續(xù)深入

    public boolean startNestedScroll(@ScrollAxis int axes, @NestedScrollType int type) {
        if (hasNestedScrollingParent(type)) {
            // Already in progress
            return true;
        }
        if (isNestedScrollingEnabled()) {
            ViewParent p = mView.getParent();
            View child = mView;
            while (p != null) {
                if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes, type)) {
                    setNestedScrollingParentForType(type, p);
                    ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes, type);
                    return true;
                }
                if (p instanceof View) {
                    child = (View) p;
                }
                p = p.getParent();
            }
        }
        return false;
    }

遞歸尋找NestedScrollingParent,然后回調(diào) onStartNestedScroll 和 onNestedScrollAccepted 。onStartNestedScroll 決定了當前控件是否能接收到其內(nèi)部View(非并非是直接子View)滑動時的參數(shù);按下時確定其嵌套的父布局以及是否能收到后續(xù)事件。再看ACTION_MOVE事件

case MotionEvent.ACTION_MOVE: {
    if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
         dx -= mScrollConsumed[0];
         dy -= mScrollConsumed[1];
         vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
     }
 } break;

ACTION_MOVE 中調(diào)用了 dispatchNestedPreScroll 。dispatchNestedPreScroll 中會回調(diào) onNestedPreScroll 方法,內(nèi)部的 scrollByInternal 中還會回調(diào) onNestedScroll 方法

整個流程如下

在這里插入圖片描述

onNestedPreScroll中,我們判斷,如果是上滑且頂部控件未完全隱藏,則消耗掉dy,即consumed[1]=dy;如果是下滑且內(nèi)部View已經(jīng)無法繼續(xù)下拉,則消耗掉dy,即consumed[1]=dy,消耗掉的意思,就是自己去執(zhí)行scrollBy,實際上就是我們的NestedScrollView 滑動。

public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
     // 向上滑動。若當前topview可見,需要將topview滑動至不可見
     boolean hideTop = dy > 0 && getScrollY() < topView.getMeasuredHeight();
     if (hideTop) {
         scrollBy(0, dy);
         //  這個是被消費的距離,如果沒有會被重復消費現(xiàn)象是父布局與子布局同時滑動,滑動的距離被消費兩次
         consumed[1] = dy;
     }
 }

整體代碼如下

public class NestedScrollLayout extends NestedScrollView {
    private View topView;
    private ViewGroup contentView;
    private static final String TAG = "NestedScrollLayout";

    public NestedScrollLayout(Context context) {
        this(context, null);
        init();
    }

    public NestedScrollLayout(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
        init();
    }

    public NestedScrollLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
        init();
    }

    public NestedScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private FlingHelper mFlingHelper;

    int totalDy = 0;
    /**
     * 用于判斷RecyclerView是否在fling
     */
    boolean isStartFling = false;
    /**
     * 記錄當前滑動的y軸加速度
     */
    private int velocityY = 0;

    private void init() {
        mFlingHelper = new FlingHelper(getContext());
        setOnScrollChangeListener(new View.OnScrollChangeListener() {
            @Override
            public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
                if (isStartFling) {
                    totalDy = 0;
                    isStartFling = false;
                }
                if (scrollY == 0) {
                    Log.e(TAG, "TOP SCROLL");
                   // refreshLayout.setEnabled(true);
                }
                if (scrollY == (getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight())) {
                    Log.e(TAG, "BOTTOM SCROLL");
                    dispatchChildFling();
                }
                //在RecyclerView fling情況下,記錄當前RecyclerView在y軸的偏移
                totalDy += scrollY - oldScrollY;
            }
        });
    }

    private void dispatchChildFling() {
        if (velocityY != 0) {
            Double splineFlingDistance = mFlingHelper.getSplineFlingDistance(velocityY);
            if (splineFlingDistance > totalDy) {
                childFling(mFlingHelper.getVelocityByDistance(splineFlingDistance - Double.valueOf(totalDy)));
            }
        }
        totalDy = 0;
        velocityY = 0;
    }

    private void childFling(int velY) {
        RecyclerView childRecyclerView = getChildRecyclerView(contentView);
        if (childRecyclerView != null) {
            childRecyclerView.fling(0, velY);
        }
    }

    @Override
    public void fling(int velocityY) {
        super.fling(velocityY);
        if (velocityY <= 0) {
            this.velocityY = 0;
        } else {
            isStartFling = true;
            this.velocityY = velocityY;
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        topView = ((ViewGroup) getChildAt(0)).getChildAt(0);
        contentView = (ViewGroup) ((ViewGroup) getChildAt(0)).getChildAt(1);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 調(diào)整contentView的高度為父容器高度,使之填充布局,避免父容器滾動后出現(xiàn)空白
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        ViewGroup.LayoutParams lp = contentView.getLayoutParams();
        lp.height = getMeasuredHeight();
        contentView.setLayoutParams(lp);
    }

    /**
     *          解決滑動沖突:RecyclerView在滑動之前會問下父布局是否需要攔截,父布局使用此方法
     */
    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        Log.e("NestedScrollLayout", getScrollY()+"::onNestedPreScroll::"+topView.getMeasuredHeight()+"::dy::"+dy);
        // 向上滑動。若當前topview可見,需要將topview滑動至不可見
        boolean hideTop = dy > 0 && getScrollY() < topView.getMeasuredHeight();
        if (hideTop) {
            scrollBy(0, dy);
            //  這個是被消費的距離,如果沒有會被重復消費,現(xiàn)象是父布局與子布局同時滑動
            consumed[1] = dy;
        }
    }

    private RecyclerView getChildRecyclerView(ViewGroup viewGroup) {
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
            View view = viewGroup.getChildAt(i);
            if (view instanceof RecyclerView && view.getClass() == NestedLogRecyclerView.class) {
                return (RecyclerView) viewGroup.getChildAt(i);
            } else if (viewGroup.getChildAt(i) instanceof ViewGroup) {
                ViewGroup childRecyclerView = getChildRecyclerView((ViewGroup) viewGroup.getChildAt(i));
                if (childRecyclerView instanceof RecyclerView) {
                    return (RecyclerView) childRecyclerView;
                }
            }
            continue;
        }
        return null;
    }
}

嵌套滑動與事件分發(fā)機制

  • 事件分發(fā)機制:子View首先得到事件處理權,處理過程中父View可以對其攔截,但是攔截了以后就無法再還給子View(本次手勢內(nèi))。
  • NestedScrolling 滑動機制:內(nèi)部View在滾動的時候,首先將dx,dy交給NestedScrollingParent,NestedScrollingParent可對其進行部分消耗,剩余的部分還給內(nèi)部View。

總結:嵌套布局要注意的有幾個方面

  • ACTION_DOWN 時子view調(diào)用父布局的onStartNestedScroll,根據(jù)滑動方向判斷父布局是否要收到子view的滑動參數(shù)
  • ACTION_MOVE時子view調(diào)用父布局的onNestedPreScroll函數(shù),父布局是否要滑動已經(jīng)消費掉自身需要的距離
  • ACTION_UP時,手指抬起可能還有加速度,調(diào)用父布局的onPreFling判斷是否需要消費以及消費剩下的再傳給子布局

到此這篇關于Android深入探究自定義View之嵌套滑動的實現(xiàn)的文章就介紹到這了,更多相關Android 嵌套滑動內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 直接應用項目中的Android圖片緩存技術

    直接應用項目中的Android圖片緩存技術

    這篇文章主要為大家詳細介紹了直接應用項目中的Android圖片緩存技術,簡單、方便、高效,感興趣的小伙伴們可以參考一下
    2016-04-04
  • android實現(xiàn)raw文件夾導入數(shù)據(jù)庫代碼

    android實現(xiàn)raw文件夾導入數(shù)據(jù)庫代碼

    這篇文章主要介紹了android實現(xiàn)raw文件夾導入數(shù)據(jù)庫代碼,有需要的朋友可以參考一下
    2013-12-12
  • Android編程實現(xiàn)檢測當前電源狀態(tài)的方法

    Android編程實現(xiàn)檢測當前電源狀態(tài)的方法

    這篇文章主要介紹了Android編程實現(xiàn)檢測當前電源狀態(tài)的方法,涉及Android針對當前電源的電量、容量、伏數(shù)、溫度等的檢測技巧,非常簡單實用,需要的朋友可以參考下
    2015-11-11
  • android開發(fā)去除標題欄的方法

    android開發(fā)去除標題欄的方法

    這篇文章主要介紹了android開發(fā)去除標題欄的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • AndroidManifest.xml中含蓋的安全問題詳解

    AndroidManifest.xml中含蓋的安全問題詳解

    這篇文章主要介紹了AndroidManifest.xml中含蓋的安全問題,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-03-03
  • Android實現(xiàn)全屏截圖或長截屏功能

    Android實現(xiàn)全屏截圖或長截屏功能

    這篇文章主要為大家詳細介紹了Android實現(xiàn)全屏截圖或長截屏功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-05-05
  • Android 10 啟動之servicemanager源碼解析

    Android 10 啟動之servicemanager源碼解析

    這篇文章主要為大家介紹了Android 10 啟動之servicemanager源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-10-10
  • Flutter如何輕松實現(xiàn)動態(tài)更新ListView淺析

    Flutter如何輕松實現(xiàn)動態(tài)更新ListView淺析

    在Android中通常都會用到listview.那么flutter里面怎么用呢?下面這篇文章主要給大家介紹了關于Flutter如何輕松實現(xiàn)動態(tài)更新ListView的相關資料,需要的朋友可以參考下
    2022-02-02
  • Android生成隨機數(shù)的方法實例

    Android生成隨機數(shù)的方法實例

    這篇文章主要為大家詳細介紹了Android生成隨機數(shù)的方法實例,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-03-03
  • Android常用的intent action匯總

    Android常用的intent action匯總

    這篇文章主要介紹了Android常用的intent action功能與用法,分析了intent的原理以及action屬性常用動作名稱、作用與使用方法,需要的朋友可以參考下
    2016-10-10

最新評論