前言
最近有这么个需求,通过Recyclerview滑动监听来设置标题栏渐变,整个界面是一个RecyclerView,一开始是没有标题栏的,向上滑动到一定程度标题栏渐变。需求是不难,但是我想记录一下这个基本的过程,有人需要了可以快速拿走,如果帮到你了,点个赞留个言都行,认可一下。
先上几张图醒醒脑,哈哈哈~
one:
two:
three:
那么,整体思路就是往上滑动第一个Item的一半高度的时候,显示标题栏,然后从剩下一半的高度开始,透明度从0渐变到1,一直到第一个Item完全滚动到外面,完全显示白色的标题栏。
之前写过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) {
}
});
那么好,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());
}
}
}
最后
最近一直都好忙好忙,终于抽出点时间写了一篇博客。
我写的挺详细的,注释也写的很认真,到最后来我又梳理了一下代码。
希望后面有相同问题的人,能少走弯路,尽快的完成任务。
如果你们有什么疑问或者问题,也可以私聊我,很乐意为你们解答,小弟水平不高,尽我所能。
如果这篇博客对你有帮助,希望你不要吝啬你的点赞和留言,你们的鼓励也是我创作的动力!