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

Android深入探究自定義View之嵌套滑動的實現

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

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

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

嵌套滑動設計目的

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

嵌套滑動的實現

假設布局如下

在這里插入圖片描述


RecyclerView 實現了 NestedScrollingChild 接口,NestedScrollView 實現了 NestedScrollingParent,這是實現嵌套布局的基礎

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

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

//	RecyclerView.java  onTouchEvent函數
 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,然后回調 onStartNestedScroll 和 onNestedScrollAccepted 。onStartNestedScroll 決定了當前控件是否能接收到其內部View(非并非是直接子View)滑動時的參數;按下時確定其嵌套的父布局以及是否能收到后續(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 中調用了 dispatchNestedPreScroll 。dispatchNestedPreScroll 中會回調 onNestedPreScroll 方法,內部的 scrollByInternal 中還會回調 onNestedScroll 方法

整個流程如下

在這里插入圖片描述

onNestedPreScroll中,我們判斷,如果是上滑且頂部控件未完全隱藏,則消耗掉dy,即consumed[1]=dy;如果是下滑且內部View已經無法繼續(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);
         //  這個是被消費的距離,如果沒有會被重復消費現象是父布局與子布局同時滑動,滑動的距離被消費兩次
         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) {
        // 調整contentView的高度為父容器高度,使之填充布局,避免父容器滾動后出現空白
        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);
            //  這個是被消費的距離,如果沒有會被重復消費,現象是父布局與子布局同時滑動
            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(本次手勢內)。
  • NestedScrolling 滑動機制:內部View在滾動的時候,首先將dx,dy交給NestedScrollingParent,NestedScrollingParent可對其進行部分消耗,剩余的部分還給內部View。

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

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

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

相關文章

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

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

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

    android實現raw文件夾導入數據庫代碼

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

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

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

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

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

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

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

    Android實現全屏截圖或長截屏功能

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

    Android 10 啟動之servicemanager源碼解析

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

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

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

    Android生成隨機數的方法實例

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

    Android常用的intent action匯總

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

最新評論