参考文章:Android 轻松实现 RecyclerView 悬浮条
本例源码:https://github.com/374901588/RVSuspensionBarTest
在参考文章中,实现的是如下效果:
实现的基本原理就是在一个 FrameLayout 中,设置一个 RV,然后在设置一个和 ItemView 一样布局结构以及样式的悬浮条,然后悬浮条根据条件动态设置位置。
而该文章中博主也说明了这种效果的实现方案,但那是在 RV 的 Item 只有一个层级的情况下,即所有的 ItemView 都是同一类型的,而我是在使用了 drakeet 大佬的 MultiType,实现了 数据扁平化处理
:
引自:Android 复杂的列表视图新写法 MultiType
我需要让下图的 RV 也实现那种效果:
虽然存在 Post 和 Comment 两种类型的 ItemView,但是由于扁平化的处理,两种 ItemView 都处于同一层次结构,而不是嵌套的关系。即两种 ItemView 都 RV 的直接 ItemView,并且其中某些 Post 类型的 ItemView 可能会不存在附属的 Comment ItemView。这种情况下,如果完全按照参考文章的那种实现方法的话,则会得不到预期的效果,下图为预期效果图:
当然,主要的思想根据原参考文章的类似,但是在一些细节方面就需要做一下修改,这本例的实现中,RV 的两种 ItemView 都是一个简答的 TextView,只不过是在 Adapter 中动态设置样式而已,因此悬浮条也是一个简单的 TextView,且高度、样式都要与 Post ItemView 的样式一样。
然后下面是核心代码,说明也依附在代码注释上了,其中 mCurrentPosition
初始值为 0,因为在本次演示中不存在 HraderView,如果存在的话,则 mCurrentPosition
初始值应为第一个 Post ItemView 的 position
:
rv.addOnScrollListener(new RecyclerView.OnScrollListener() {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) rv.getLayoutManager();
int mSuspensionHeight;
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// mSuspensionBar.getHeight()的高度的获取如果是在 onCreate() 或者是
// 在 RecyclerView.OnScrollListener 被初始化的时候去获取,获得的结果
// 会为 0,因此此时 mSuspensionBar 还没有初始化完成
mSuspensionHeight = mSuspensionBar.getHeight();
int firstVisPos = linearLayoutManager.findFirstVisibleItemPosition();
Object firstVisibleItem = items.get(firstVisPos);
Object nextItem = items.get(firstVisPos + 1);
View nextView = linearLayoutManager.findViewByPosition(firstVisPos + 1);
//下滑情况下
if (dy > 0) {
//只有第一个可见的 Item 的下一个 Item 的类型
//为 Post类型时才需要动态设置效果
if (nextItem instanceof Post) {
if (nextView.getTop() <= mSuspensionHeight) {
//被顶掉的效果
mSuspensionBar.setY(-(mSuspensionHeight - nextView.getTop()));
} else {
//否则就直接回到 Y = 0 的位置
mSuspensionBar.setY(0);
}
}
//判断是否需要更新悬浮条
if (mCurrentPosition != firstVisPos && firstVisibleItem instanceof Post) {
mCurrentPosition = firstVisPos;
//根据 mCurrentPosition 的值,更新 mSuspensionBar
updateSuspensionBar();
mSuspensionBar.setY(0);
}
} else {//上滑情况
// 1、nextItem -> Post and firstVisibleItem -> Comment mCurrentPosition = ((Comment) firstVisibleItem).getParentPostPosition()
// 2、nextItem -> Post and firstVisibleItem -> Post mCurrentPosition = firstVisPos
// 3、nextItem -> Comment and firstVisibleItem -> Comment mSuspensionBar 不动
// 4、nextItem -> Comment and firstVisibleItem -> Post mSuspensionBar 不动
if (nextItem instanceof Post) {
mCurrentPosition = firstVisibleItem instanceof Post ? firstVisPos : ((Comment) firstVisibleItem).getParentPostPosition();
updateSuspensionBar();
if (nextView.getTop() <= mSuspensionHeight) {
//被顶掉的效果
mSuspensionBar.setY(-(mSuspensionHeight - nextView.getTop()));
} else {
mSuspensionBar.setY(0);
}
}
}
}
});
如果对于上面的代码有所疑惑,其实可以将 mSuspensionBar 的背景设置为不同的颜色,同时设置一下透明度,就可以看到其原本的运作状态了,如下图,其中 mSuspensionBar.setY(0)
时当效果尤为明显:
其中,较为特殊的就是上滑的情况了,在上面的代码注释中也有说明,上滑时总共有四种情况:
1、nextItem -> Post and firstVisibleItem -> Comment mCurrentPosition = ((Comment) firstVisibleItem).getParentPostPosition()
2、nextItem -> Post and firstVisibleItem -> Post mCurrentPosition = firstVisPos
3、nextItem -> Comment and firstVisibleItem -> Comment mSuspensionBar 不动
4、nextItem -> Comment and firstVisibleItem -> Post mSuspensionBar 不动
其中 ->
符号表示该 Item 对应的具体类型,只有头两种情况下,mSuspensionBar
才需要根据具体的情况设置动态效果,剩下的两种情况下只要固定不动就可以了。
还需要说明的是,当 nextItem -> Post and firstVisibleItem -> Comment
时,正常情况是无法知道第一个可见 Item (即 firstVisibleItem
)所附属于的 Post 的 position
,因此需要在初始化这些 Comment Item 的时候就设置好其附属于的 Post 的 position
,然后动态获取。
就像下面的代码,会在初始化 Comment 元素时为其设置所附属的 Post 的 position
:
//模拟数据
List list = new ArrayList<>();
int index = 0;
int parentPostPos;
Random random = new Random();
for (int i = 0; i < 10; i++) {
Post post = new Post("pos = " + index);
parentPostPos = index;
list.add(post);
index++;
int k = random.nextInt(5);
post.comments = new ArrayList<>();
for (int j = 0; j < k; j++) {
Comment comment = new Comment("pos = " + index, parentPostPos);
post.comments.add(comment);
index++;
}
}
最后,再推荐一篇实现本文效果的文章:
RecyclerView 使用ItemDecoration 巧妙实现吸附效果
在该篇文章中的实现,是利用了 recyclerView.addItemDecoration()
来自定义 ItemDecoration 实现的效果,这种方式实现了与业务的解耦,但在具体实现上相比上面的实现更加复杂一点,而且在遇到第三方的涉及 RV 的框架时,就可能需要根据具体的框架去进行相应的修改了,如在使用 MultiType
时,就不是那么好契合了。