1. 程式人生 > >Recyclerview滑動設定標題欄漸變效果

Recyclerview滑動設定標題欄漸變效果

 

前言

最近有這麼個需求,通過Recyclerview滑動監聽來設定標題欄漸變,整個介面是一個RecyclerView,一開始是沒有標題欄的,向上滑動到一定程度標題欄漸變。需求是不難,但是我想記錄一下這個基本的過程,有人需要了可以快速拿走,如果幫到你了,點個贊留個言都行,認可一下。

先上幾張圖醒醒腦,哈哈哈~

one:

two:

three:

那麼,整體思路就是往上滑動第一個Item的一半高度的時候,顯示標題欄,然後從剩下一半的高度開始,透明度從0漸變到1,一直到第一個Item完全滾動到外面,完全顯示白色的標題欄。

1、之前寫過ScrollView的漸變,監聽滑動那個方法api23,android 6.0以上才能用,還得自定義一個ScrollView,然後重寫ScrollView的onScrollChanged方法自定義一個回撥,完後通過這個y座標,去監聽就可以搞定,網上資源不少,寫就ok啦

// 自定義的ScrollView的回撥
svMain.setScrollViewListener(new ObservableScrollView.ScrollViewListener() {
            @Override
            public void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy) {
                
            }
            
            // 這個方法自定義的監聽回撥,判斷是否是滑動停止
            @Override
            public void onScrollFinish(ObservableScrollView scrollView, int x, int y) {

            }
        });

2、那麼好,RecyclerView也實現滑動監聽呢,也不想自定義重寫了,RecyclerView有這麼一個方法,提供dx,dy兩個偏移量,雖然沒有提供座標用起來那麼方便,但是也可以實現。

rvMain.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                
            }
        });

看了很多網友寫的方法,我自己總結一下,寫了兩種:

1、宣告一個全域性變數,用來記錄座標,然後去取第一個可見的Item的位置,如果是下標為0的也就是說是RecyclerView的第一項,去設定漸變,直接上程式碼:

// topbar是自定義的標題欄
rvMain.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                // 記錄滑動的座標
                distanceY += dy;
                LinearLayoutManager layoutManager = (LinearLayoutManager) rvMain.getLayoutManager();
                // 第一個可見Item的位置
                int position = layoutManager.findFirstVisibleItemPosition();
                // 是第一項才去漸變
                if (position == 0) {
                    // 注意此操作如果第一項劃出螢幕外,拿到的是空的,所以必須是position是0的時候才能呼叫
                    View firstView = layoutManager.findViewByPosition(position);
                    // 第一項Item的高度
                    int firstHeight = firstView.getHeight();
                    // 要在它滑到二分之一的時候去漸變
                    int changeHeight = firstHeight / 2;
                    // 小於頭部高度一半隱藏標題欄
                    if (distanceY <= changeHeight) {
                        topbar.setVisibility(View.GONE);
                    // 漸變的區域,頭部從中間到底部的距離
                    } else {
                        topbar.setVisibility(View.VISIBLE);
                        // 設定了一條分割線,漸變的時候分割線先GONE掉,要不不好看
                        topbar.getViewGrayLine().setVisibility(View.GONE);
                        // 從高度的一半開始算透明度,也就是說移動到頭部Item的中部,透明度從0開始計算
                        float scale = (float) (distanceY - changeHeight) / changeHeight;
                        topbar.setAlpha(scale);
                    }
                // 其他的時候就設定都可見,透明度是1
                } else {
                    topbar.setVisibility(View.VISIBLE);
                    topbar.getViewGrayLine().setVisibility(View.VISIBLE);
                    topbar.setAlpha(1);
                }
            }
        });


// 有幾個坑要踩,因為我們全域性用一個distanceY記錄了座標
1、如果你的recyclerView會有程式碼呼叫的scrollTo,scrollToPosition類似的方法,記得處理好你的distanceY
2、我這裡是有一個點選底部的導航欄去回到頂部並重新整理介面的操作:rvMain.scrollToPosition(0),所以回到頂部必須把distanceY置為0,並且topbar要GONE掉

2、通過getTop()方法,得到距離頂部的距離,然後去設定漸變,直接上程式碼:

// topbar是自定義的標題欄
rvDynamic.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {

            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                LinearLayoutManager layoutManager = (LinearLayoutManager) rvDynamic.getLayoutManager();
                // 第一個可見Item的位置
                int position = layoutManager.findFirstVisibleItemPosition();
                // 是第一項才去漸變
                if (position == 0) {
                    // 注意此操作如果第一項劃出螢幕外,拿到的是空的,所以必須是position是0的時候才能呼叫
                    View firstView = layoutManager.findViewByPosition(position);
                    // 第一項Item的高度
                    int firstHeight = firstView.getHeight();
                    // 距離頂部的距離,是負數,也就是說-top就是它向上滑動的距離
                    int scrollY = -firstView.getTop();
                    // 要在它滑到二分之一的時候去漸變
                    int changeHeight = firstHeight / 2;
                    // 小於頭部高度一半隱藏標題欄
                    if (scrollY <= changeHeight) {
                        topbar.setVisibility(View.GONE);
                    } else {
                        topbar.setVisibility(View.VISIBLE);
                        // 設定了一條分割線,漸變的時候分割線先GONE掉,要不不好看
                        topbar.getViewGrayLine().setVisibility(View.GONE);
                        // 從高度的一半開始算透明度,也就是說移動到頭部Item的中部,透明度從0開始計算
                        float alpha = (float)(scrollY - changeHeight) / changeHeight;
                        topbar.setAlpha(alpha);
                    }
                // 其他的時候就設定都可見,透明度是1
                } else {
                    topbar.setVisibility(View.VISIBLE);
                    topbar.getViewGrayLine().setVisibility(View.VISIBLE);
                    topbar.setAlpha(1);
                }
            }
        });

// 這個也是上面說的那個坑:
如果你的recyclerView會有程式碼呼叫的scrollTo(),scrollToPosition()類似的方法,記得把標題欄GONE掉

相比來說我更想用第二種方法,不過兩個方法都可以的,哈哈哈說點題外話:

// 我發現有的網友在用setVisibility()和setAlpha()的時候會先去判斷一下,害怕重複呼叫方法,類似這種操作
if (topbar.getVisibility() == View.GONE) {
        topbar.setVisibility(View.VISIBLE);
}
    // 其實是沒必要的,我們拉一下原始碼,google這麼強大,都是有做判斷的,如果相同就直接return了
    @RemotableViewMethod
    public void setVisibility(@Visibility int visibility) {
        setFlags(visibility, VISIBILITY_MASK);
    }


    void setFlags(int flags, int mask) {
        final boolean accessibilityEnabled =
                AccessibilityManager.getInstance(mContext).isEnabled();
        final boolean oldIncludeForAccessibility = accessibilityEnabled && includeForAccessibility();

        int old = mViewFlags;
        mViewFlags = (mViewFlags & ~mask) | (flags & mask);

        int changed = mViewFlags ^ old;
        if (changed == 0) {
            return;
        }
        ...
        ...
        ...
    }


    public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {
        ensureTransformationInfo();
        if (mTransformationInfo.mAlpha != alpha) {
            // Report visibility changes, which can affect children, to accessibility
            if ((alpha == 0) ^ (mTransformationInfo.mAlpha == 0)) {
                notifySubtreeAccessibilityStateChangedIfNeeded();
            }
            mTransformationInfo.mAlpha = alpha;
            if (onSetAlpha((int) (alpha * 255))) {
                mPrivateFlags |= PFLAG_ALPHA_SET;
                // subclass is handling alpha - don't optimize rendering cache invalidation
                invalidateParentCaches();
                invalidate(true);
            } else {
                mPrivateFlags &= ~PFLAG_ALPHA_SET;
                invalidateViewProperty(true, false);
                mRenderNode.setAlpha(getFinalAlpha());
            }
        }
    }

最後

最近一直都好忙好忙,終於抽出點時間寫了一篇部落格。

我寫的挺詳細的,註釋也寫的很認真,到最後來我又梳理了一下程式碼。

希望後面有相同問題的人,能少走彎路,儘快的完成任務。

如果你們有什麼疑問或者問題,也可以私聊我,很樂意為你們解答,小弟水平不高,盡我所能。

如果這篇部落格對你有幫助,希望你不要吝嗇你的點贊和留言,你們的鼓勵也是我創作的動力!

另外,我有Android基本常用控制元件的原始碼,一共1G多,如果有需要可以聯絡我。無私奉獻。哈哈哈~