前言
最近有这么个需求,通过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多,如果有需要可以联系我。无私奉献。哈哈哈~