亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Android進階NestedScroll嵌套滑動機制實現(xiàn)吸頂效果詳解

 更新時間:2023年01月29日 09:40:29   作者:layz4android  
這篇文章主要為大家介紹了Android進階NestedScroll嵌套滑動機制實現(xiàn)吸頂效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

在上一篇文章Android進階寶典 -- 事件沖突怎么解決?先從Android事件分發(fā)機制開始說起中,我們詳細地介紹了Android事件分發(fā)機制,其實只要頁面結構復雜,聯(lián)動眾多就會產(chǎn)生事件沖突,處理不得當就是bug,e.g. 我畫了一張很丑的圖

其實這種交互形式在很多電商、支付平臺都非常常見,頁面整體是可滑動的(scrollable),當頁面整體往上滑時,是外部滑動組件,e.g. NestedScrollView,當TabBar滑動到頂部的時候吸頂,緊接著ListView自身特性繼續(xù)往上滑。

其實這種效果,系統(tǒng)已經(jīng)幫我們實現(xiàn)好了,尤其是像NestScrollView;如果我們在自定義View的時候,沒有系統(tǒng)能力的加持,會有問題嗎?如果熟悉Android事件分發(fā)機制,因為整體上滑的時候,外部組件消費了DOWM事件和MOVE事件,等到Tabbar吸頂之后,再次滑動ListView的時候,因為事件都在外部攔截,此時 mFirstTouchTarget還是父容器,沒有機會讓父容器取消事件再轉(zhuǎn)換到ListView,導致ListView不可滑動。

那么我們只有松開手,再次滑動ListView,讓DOWN事件傳遞到ListView當中,這樣列表會繼續(xù)滑動,顯得沒有那么順滑,從用戶體驗上來說是不可接受的。

1 自定義滑動布局,實現(xiàn)吸頂效果

首先我們?nèi)绻胍獙崿F(xiàn)這個效果,其實辦法有很多,CoordinateLayout就是其中之一,但是如果我們想要自定義一個可滑動的布局,而且還需要實現(xiàn)Tabbar的吸頂效果,我們需要注意兩點:

1)在頭部沒有被移出屏幕的時候,事件需要被外部攔截,只能滑動外部布局,ListView不可滑動;

2)當頭部被移出到屏幕之外時,事件需要被ListView消費(繼續(xù)上滑時),如果下滑時則是同樣會先把頭部拉出來然后才可以滑動ListView

1.1 滑動容器實現(xiàn)

因為我們知道,要控制view移動,可以調(diào)用scrollBy或者scrollTo兩個方法,其中兩個方法的區(qū)別在于,前者是滑動的相對上一次的距離,而后者是滑動到具體位置。

class MyNestScrollView @JvmOverloads constructor(
    val mContext: Context,
    val attributeSet: AttributeSet? = null,
    val flag: Int = 0
) : LinearLayout(mContext, attributeSet, flag) {
    private var mTouchSlop = 0
    private var startY = 0f
    init {
        mTouchSlop = ViewConfiguration.get(context).scaledPagingTouchSlop
    }
    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        /**什么時候攔截事件呢,當頭部還沒有消失的時候*/
        return super.onInterceptTouchEvent(ev)
    }
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                Log.e("TAG", "MyNestScrollView ACTION_DOWN")
                startY = event.y
            }
            MotionEvent.ACTION_MOVE -> {
                Log.e("TAG", "MyNestScrollView ACTION_MOVE")
                val endY = event.y
                if (abs(endY - startY) > mTouchSlop) {
                    //滑動了
                    scrollBy(0, (startY - endY).toInt())
                }
                startY = endY
            }
        }
        return super.onTouchEvent(event)
    }
    override fun scrollTo(x: Int, y: Int) {
        var finalY = 0
        if (y < 0) {
        } else {
            finalY = y
        }
        super.scrollTo(x, finalY)
    }
}

所以在事件消費的時候,會調(diào)用scrollBy,來進行頁面的滑動,如果我們看scrollBy的源碼,會明白最終調(diào)用就是通過scrollTo實現(xiàn)的,只不過是在上次pos的基礎上進行累計。

public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}

所以這里重寫了scrollTo方法,來判斷y(縱向)滑動的位置,因為當y小于0的時候,按照Android的坐標系,我們知道如果一直往下滑,那么△Y(豎直方向滑動距離) < 0,如果一直向下滑,最終totalY也會小于0,所以這里也是做一次邊界的處理。

接下來我們需要處理下吸頂效果,所以我們需要知道,頂部View的高度,以便控制滑動的距離,也是一次邊界處理。

override fun scrollTo(x: Int, y: Int) {
    var finalY = 0
    if (y &lt; 0) {
    } else {
        finalY = y
    }
    if (y &gt; mTopViewHeight) {
        finalY = mTopViewHeight
    }
    super.scrollTo(x, finalY)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    //頂部view是第一個View
    mTopViewHeight = getChildAt(0).measuredHeight
}

所以這里需要和我們寫的布局相對應,頂部view是容器中第一個子View,通過在onSizeChanged或者onMeasure中獲取第一個子View的高度,在滑動時,如果滑動的距離超過 mTopViewHeight(頂部View的高度),那么滑動時也就不會再繼續(xù)滑動了,這樣就實現(xiàn)了TabBar的吸頂效果。

基礎工作完成了,接下來我們完成需要注意的第一點,先看下面的圖:

當我們上滑的時候,頭部是準備逐漸隱藏的,所以這里會有幾個條件,首先 mStartX - nowX > 0 而且 scrollY < mTopViewHeight,而且此時scrollY是大于0的

/**
 * 頭部View逐漸消失
 * @param dy 手指滑動的相對距離 dy >0 上滑 dy < 0 下滑
 */
private fun isViewHidden(dy: Int): Boolean {
    return dy > 0 && scrollY < mTopViewHeight
}

當我們向下滑動的時候,此時 mStartX - nowX < 0,因為此時頭部隱藏了,所以ScrollY > 0,而且此時是能夠滑動的,如果到了下面這個邊界條件(不會有這種情況發(fā)生,因此在滑動時做了邊界處理),此時scrollY < 0

private fun isViewShow(dy: Int):Boolean{
    return dy < 0 && scrollY > 0 && !canScrollVertically(-1)
}

此時還有一個條件,就是canScrollVertically,這個相信伙伴們也很熟悉,意味著當前View是能夠往下滑動的,如果返回了false,那么就是不能繼續(xù)往下滑動了。

override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
    var intercepted = false
    /**什么時候攔截事件呢,當頭部還沒有消失的時候*/
    when (ev?.action) {
        MotionEvent.ACTION_DOWN -> {
            startY = ev.rawY
        }
        MotionEvent.ACTION_MOVE -> {
            val endY = ev.rawY
            if (abs(startY - endY) > mTouchSlop) {
                if (isViewHidden((startY - endY).toInt())
                    || isViewShow((startY - endY).toInt())
                ) {
                    Log.e("TAG","此時就需要攔截,外部進行消費事件")
                    //此時就需要攔截,外部進行消費事件
                    intercepted = true
                }
            }
            startY = endY
        }
    }
    return intercepted
}

所以在外部攔截的時候,通過判斷這兩種狀態(tài),如果滿足其中一個條件就會攔截事件完全由外部容器處理,這樣就完成了吸頂效果的處理。

1.2 嵌套滑動機制完成交互優(yōu)化

通過上面的gif,我們看效果貌似還可以,但是有一個問題就是,當完成吸頂之后,ListView并不能跟隨手指繼續(xù)向上滑動,而是需要松開手指之后,再次滑動即可,其實我們從Android事件分發(fā)機制中就能夠知道,此時mFirstTouchTarget == 父容器,此時再次上滑并沒有給父容器Cancel的機會,所以才導致事件沒有被ListView接收。

因為傳統(tǒng)的事件沖突解決方案,會導致滑動不流暢,此時就需要嵌套滑動機制解決這個問題。在前面我們提到過,NestedScrollView其實就是已經(jīng)處理過嵌套滑動了,所以我們前面去看一下NestedScrollView到底干了什么事?

public class NestedScrollView extends FrameLayout implements NestedScrollingParent3,
        NestedScrollingChild3, ScrollingView 

我們看到,NestedScrollView是實現(xiàn)了NestedScrollingParent3、NestedScrollingChild3等接口,挺有意思的,這幾個接口貌似都是根據(jù)數(shù)字做了升級,既然有3,那么必然有1和2,所以我們看下這幾個接口的作用。

1.2.1 NestedScrollingParent接口和NestedScrollingChild接口

對于NestedScrollingParent接口,如果可滑動的ViewGroup,e.g. 我們在1.1中定義的容器作為父View,那么就需要實現(xiàn)這個接口;如果是作為可滑動的子View,那么就需要實現(xiàn)NestedScrollingChild接口,因為我們在自定義控件的時候,它既可能作為子View也可能作為父View,因此這倆接口都需要實現(xiàn)。

public interface NestedScrollingChild {
    /**
     * Enable or disable nested scrolling for this view.
     *
     * 啟動或者禁用嵌套滑動,如果返回ture,那么說明當前布局存在嵌套滑動的場景,反之沒有
     * 使用場景:NestedScrollingParent嵌套NestedScrollingChild
     * 在此接口中的方法,都是交給NestedScrollingChildHelper代理類實現(xiàn)
     */
    void setNestedScrollingEnabled(boolean enabled);
    /**
     * Returns true if nested scrolling is enabled for this view.
     * 其實就是返回setNestedScrollingEnabled中設置的值
     */
    boolean isNestedScrollingEnabled();
    /**
     * Begin a nestable scroll operation along the given axes.
     * 表示view開始滾動了,一般是在ACTION_DOWN中調(diào)用,如果返回true則表示父布局支持嵌套滾動。
     * 一般也是直接代理給NestedScrollingChildHelper的同名方法即可。這個時候正常情況會觸發(fā)Parent的onStartNestedScroll()方法
     */
    boolean startNestedScroll(@ScrollAxis int axes);
    /**
     * Stop a nested scroll in progress.
     * 停止嵌套滾動,一般在UP或者CANCEL事件中執(zhí)行,告訴父容器已經(jīng)停止了嵌套滑動
     */
    void stopNestedScroll();
    /**
     * Returns true if this view has a nested scrolling parent.
     * 判斷當前View是否存在嵌套滑動的Parent
     */
    boolean hasNestedScrollingParent();
    /**
    * 當前View消費滑動事件之后,滾動一段距離之后,把剩余的距離回調(diào)給父容器,父容器知道當前剩余距離
    * dxConsumed:x軸滾動的距離
    * dyConsumed:y軸滾動的距離
    * dxUnconsumed:x軸未消費的距離
    * dyUnconsumed:y軸未消費的距離
    * 這個方法是嵌套滑動的時候調(diào)用才有用,返回值 true分發(fā)成功;false 分發(fā)失敗
    */
    boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);
    /**
     * Dispatch one step of a nested scroll in progress before this view consumes any portion of it.
     * 在子View消費滑動距離之前,將滑動距離傳遞給父容器,相當于把消費權交給parent
     * dx:當前水平方向滑動的距離
     * dy:當前垂直方向滑動的距離
     * consumed:輸出參數(shù),會將Parent消費掉的距離封裝進該參數(shù)consumed[0]代表水平方向,consumed[1]代表垂直方向
    * @return true:代表Parent消費了滾動距離
     */
    boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow);
    /**
     * Dispatch one step of a nested scroll in progress.
     * 處理慣性事件,與dispatchNestedScroll類似,也是在消費事件之后,將消費和未消費的距離都傳遞給父容器
     */
    boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
    /**
     * Dispatch a fling to a nested scrolling parent before it is processed by this view.
     * 與dispatchNestedPreScroll類似,在消費之前首先會傳遞給父容器,把優(yōu)先處理權交給父容器
     */
    boolean dispatchNestedPreFling(float velocityX, float velocityY);
}
public interface NestedScrollingParent {
    /**
     * React to a descendant view initiating a nestable scroll operation, claiming the
     * nested scroll operation if appropriate.
     * 當子View調(diào)用startNestedScroll方法的時候,父容器會在這個方法中獲取回調(diào)
     */
    boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
    /**
     * React to the successful claiming of a nested scroll operation.
     * 在onStartNestedScroll調(diào)用之后,就緊接著調(diào)用這個方法
     */
    void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes);
    /**
     * React to a nested scroll operation ending.
     * 當子View調(diào)用 stopNestedScroll方法的時候回調(diào)
     */
    void onStopNestedScroll(@NonNull View target);
    /**
     * React to a nested scroll in progress.
     * 
     */
    void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);
    /**
     * React to a nested scroll in progress before the target view consumes a portion of the scroll.
     * 在子View調(diào)用dispatchNestedPreScroll之后,這個方法拿到了回調(diào)
     * 
     */
    void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed);
    /**
     * Request a fling from a nested scroll.
     *
     */
    boolean onNestedFling(@NonNull View target, float velocityX, float velocityY, boolean consumed);
    /**
     * React to a nested fling before the target view consumes it.
     *
     */
    boolean onNestedPreFling(@NonNull View target, float velocityX, float velocityY);
    /**
     * Return the current axes of nested scrolling for this NestedScrollingParent.
     * 返回當前滑動的方向
     */
    @ScrollAxis
    int getNestedScrollAxes();
}

通過這兩個接口,我們大概就能夠明白,其實嵌套滑動機制完全是子View在做主導,通過子View能夠決定Parent是否能夠優(yōu)先消費事件(dispatchNestedPreScroll),所以我們先從子View開始,開啟嵌套滑動之旅。

1.2.2 預滾動階段實現(xiàn)

在這個示例中,需要與parent嵌套滑動的就是RecyclerView,所以RecyclerView就需要實現(xiàn)child接口。前面我們看到child接口好多方法,該怎么調(diào)用呢?其實這個接口中大部分的方法都可以交給一個helper代理類實現(xiàn),e.g. NestedScrollingChildHelper.

因為所有的嵌套滑動都是由子View主導,所以我們先看子View消費事件,也就是onTouchEvent中,如果當手指按下的時候,首先獲取滑動的是x軸還是y軸,這里我們就認為是豎向滑動,然后調(diào)用NestedScrollingChild的startNestedScroll方法,這個方法就代表開始滑動了。

override fun onTouchEvent(e: MotionEvent?): Boolean {
    when(e?.action){
        MotionEvent.ACTION_DOWN->{
            mStartX = e.y.toInt()
            //子View開始嵌套滑動
            var axis = ViewCompat.SCROLL_AXIS_NONE
            axis = axis or ViewCompat.SCROLL_AXIS_VERTICAL
            nestedScrollingChildHelper.startNestedScroll(axis)
        }
        MotionEvent.ACTION_MOVE->{
        }
    }
    return super.onTouchEvent(e)
}

我們看下startNestedScroll內(nèi)部的源碼:

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

從源碼中 我們可以看到,首先如果有嵌套滑動的父容器,直接返回true,此時代表嵌套滑動成功;

public boolean hasNestedScrollingParent(@NestedScrollType int type) {
    return getNestedScrollingParentForType(type) != null;
private ViewParent getNestedScrollingParentForType(@NestedScrollType int type) {
    switch (type) {
        case TYPE_TOUCH:
            return mNestedScrollingParentTouch;
        case TYPE_NON_TOUCH:
            return mNestedScrollingParentNonTouch;
    }
    return null;
}

在判斷的時候,會判斷mNestedScrollingParentTouch是否為空,因為第一次進來的時候肯定是空的,所以會繼續(xù)往下走;如果支持嵌套滑動,那么就會進入到while循環(huán)中。

核心代碼1:

while (p != null) {
    //---------- 判斷條件1 -------------//
    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();
}

首先調(diào)用ViewParentCompat的onStartNestedScroll方法如下:

public static boolean onStartNestedScroll(@NonNull ViewParent parent, @NonNull View child,
        @NonNull View target, int nestedScrollAxes, int type) {
    if (parent instanceof NestedScrollingParent2) {
        // First try the NestedScrollingParent2 API
        return ((NestedScrollingParent2) parent).onStartNestedScroll(child, target,
                nestedScrollAxes, type);
    } else if (type == ViewCompat.TYPE_TOUCH) {
        // Else if the type is the default (touch), try the NestedScrollingParent API
        if (Build.VERSION.SDK_INT &gt;= 21) {
            try {
                return Api21Impl.onStartNestedScroll(parent, child, target, nestedScrollAxes);
            } catch (AbstractMethodError e) {
                Log.e(TAG, "ViewParent " + parent + " does not implement interface "
                        + "method onStartNestedScroll", e);
            }
        } else if (parent instanceof NestedScrollingParent) {
            return ((NestedScrollingParent) parent).onStartNestedScroll(child, target,
                    nestedScrollAxes);
        }
    }
    return false;
}

其實在這個方法中,就是判斷parent是否實現(xiàn)了NestedScrollingParent(2 3)接口,如果實現(xiàn)了此接口,那么返回值就是parent中onStartNestedScroll的返回值。

這里需要注意的是,如果parent中onStartNestedScroll的返回值為false,那么就不會進入代碼塊的條件判斷,所以在實現(xiàn)parent接口的時候,onStartNestedScroll需要返回true。進入代碼塊中調(diào)用setNestedScrollingParentForType方法,將父容器給mNestedScrollingParentTouch賦值,那么此時hasNestedScrollingParent方法就返回true,不需要遍歷View層級了。

private void setNestedScrollingParentForType(@NestedScrollType int type, ViewParent p) {
    switch (type) {
        case TYPE_TOUCH:
            mNestedScrollingParentTouch = p;
            break;
        case TYPE_NON_TOUCH:
            mNestedScrollingParentNonTouch = p;
            break;
    }
}

然后又緊接著調(diào)用了parent的onNestedScrollAccepted方法,這兩者一前一后,這樣預滾動階段就算是完成了。

在父容器中,預滾動節(jié)點就需要處理這兩個回調(diào)即可,關鍵在于onStartNestedScroll的返回值。

override fun onStartNestedScroll(child: View, target: View, axes: Int): Boolean {
    Log.e("TAG","onStartNestedScroll")
    //這里需要return true,否則在子View中分發(fā)事件就不會成功
    return true
}
override fun onNestedScrollAccepted(child: View, target: View, axes: Int) {
    Log.e("TAG","onNestedScrollAccepted")
}

1.2.3 滾動階段實現(xiàn)

然后MOVE事件來了,這個時候我們需要記住,即便是滑動了子View,但是子View依然是需要將事件扔給父類,這里就需要調(diào)用dispatchNestedPreScroll方法,這里在1.2.1中介紹過,需要跟dispatchNestedScroll區(qū)分,dispatchNestedPreScroll是在子View消費事件之前就交給父類優(yōu)先處理。

public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
        @Nullable int[] offsetInWindow, @NestedScrollType int type) {
    if (isNestedScrollingEnabled()) {
        //這里不為空了
        final ViewParent parent = getNestedScrollingParentForType(type);
        if (parent == null) {
            return false;
        }
        if (dx != 0 || dy != 0) {
            int startX = 0;
            int startY = 0;
            if (offsetInWindow != null) {
                mView.getLocationInWindow(offsetInWindow);
                startX = offsetInWindow[0];
                startY = offsetInWindow[1];
            }
            if (consumed == null) {
                consumed = getTempNestedScrollConsumed();
            }
            consumed[0] = 0;
            consumed[1] = 0;
            ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
            if (offsetInWindow != null) {
                mView.getLocationInWindow(offsetInWindow);
                offsetInWindow[0] -= startX;
                offsetInWindow[1] -= startY;
            }
            //-------- 由父容器是否消費決定返回值 -------//
            return consumed[0] != 0 || consumed[1] != 0;
        } else if (offsetInWindow != null) {
            offsetInWindow[0] = 0;
            offsetInWindow[1] = 0;
        }
    }
    return false;
}

在子View調(diào)用dispatchNestedPreScroll方法時,需要傳入四個參數(shù),這里我們再次詳細介紹一下:
dx、dy指的是x軸和y軸滑動的距離;
consumed在子View調(diào)用時,其實只需要傳入一個空數(shù)組即可,具體的賦值是需要在父容器中進行,父view消費了多少距離,就傳入多少,consumed[0]代表x軸,consumed[1]代表y軸;

看上面的源碼,當dx或者dy不為0的時候,說明有滑動了,那么此時就會做一些初始化的配置,把consumed數(shù)組清空,然后會調(diào)用父容器的onNestedPreScroll方法,父容器決定是否消費這個事件,因為在父容器中會對consumed數(shù)組進行復制,所以這個方法的返回值代表著父容器是否消費過事件;如果消費過,那么就返回true,沒有消費過,那么就返回false.

所以我們先看父容器的處理:

override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) {
    Log.e("TAG", "onNestedPreScroll")
    //父容器什么時候 消費呢?
    if (isViewShow(dy) || isViewHidden(dy)) {
        //假設這個時候把事件全消費了
        consumed[1] = dy
        scrollBy(0, dy)
    }
}

其實我們這里就是直接將之前在onTouchEvent中的處理邏輯放在了onNestedPreScroll中,如果在上拉或者下滑時,首先頭部優(yōu)先,假設父容器把距離全部消費,這個時候給consumed[1]賦值為dy。

MotionEvent.ACTION_MOVE -> {
    val endY = e.y.toInt()
    val endX = e.x.toInt()
    var dx = mStartX - endX
    var dy = mStartY - endY
    //進行事件分發(fā),優(yōu)先給parent
    if (dispatchNestedPreScroll(dx, dy, cosumed, null)) {
        //如果父容器消費過事件,這個時候,cosumed有值了,我們只關心dy
        dy -= cosumed[1]
        if (dy == 0) {
            //代表父容器全給消費了
            return true
        }
    } else {
        //如果沒有消費事件,那么就子view消費吧
        smoothScrollBy(dx, dy)
    }
}

再來看子View,這里是在MOVE事件中進行事件分發(fā),調(diào)用dispatchNestedPreScroll方法,判斷如果父容器有事件消費,看消費了多少,剩下的就是子View消費;如果父容器沒有消費,dispatchNestedPreScroll返回了false,那么子View自行處理事件。

所以如果子View使用的是RecyclerView,那么在父容器做完處理之后,其實就能夠?qū)崿F(xiàn)嵌套滑動吸頂?shù)耐昝佬Ч?,為什么呢?是因為RecyclerView本來就實現(xiàn)了parent接口,所以如果在自定義子View(可滑動)時,子View處理的這部分代碼就需要特別關心。

1.2.4 滾動結束

在手指抬起之后,調(diào)用stopNestedScroll方法。

MotionEvent.ACTION_UP->{
    nestedScrollingChildHelper.stopNestedScroll()
}

從源碼中看,其實就是回到父容器的onStopNestedScroll方法,然后將滑動的標志位(mNestedScrollingParentTouch)置為空,在下次按下的時候,重新初始化。

public void stopNestedScroll(@NestedScrollType int type) {
    ViewParent parent = getNestedScrollingParentForType(type);
    if (parent != null) {
        ViewParentCompat.onStopNestedScroll(parent, mView, type);
        setNestedScrollingParentForType(type, null);
    }
}

以上就是Android進階NestedScroll嵌套滑動機制實現(xiàn)吸頂效果詳解的詳細內(nèi)容,更多關于Android NestedScroll吸頂?shù)馁Y料請關注腳本之家其它相關文章!

相關文章

最新評論