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多,如果有需要可以联系我。无私奉献。哈哈哈~

猜你喜欢

转载自blog.csdn.net/pengbo6665631/article/details/85268062