RecyclerView Item 的悬浮效果(吸附效果)的实现

参考文章: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<Post> 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 时,就不是那么好契合了。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值