欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-14 21:08:46
...

参考文章:Android 轻松实现 RecyclerView 悬浮条

本例源码:https://github.com/374901588/RVSuspensionBarTest


在参考文章中,实现的是如下效果:
RecyclerView Item 的悬浮效果(吸附效果)的实现

实现的基本原理就是在一个 FrameLayout 中,设置一个 RV,然后在设置一个和 ItemView 一样布局结构以及样式的悬浮条,然后悬浮条根据条件动态设置位置。

而该文章中博主也说明了这种效果的实现方案,但那是在 RV 的 Item 只有一个层级的情况下,即所有的 ItemView 都是同一类型的,而我是在使用了 drakeet 大佬的 MultiType,实现了 数据扁平化处理
RecyclerView Item 的悬浮效果(吸附效果)的实现
引自:Android 复杂的列表视图新写法 MultiType

我需要让下图的 RV 也实现那种效果:
RecyclerView Item 的悬浮效果(吸附效果)的实现
虽然存在 Post 和 Comment 两种类型的 ItemView,但是由于扁平化的处理,两种 ItemView 都处于同一层次结构,而不是嵌套的关系。即两种 ItemView 都 RV 的直接 ItemView,并且其中某些 Post 类型的 ItemView 可能会不存在附属的 Comment ItemView。这种情况下,如果完全按照参考文章的那种实现方法的话,则会得不到预期的效果,下图为预期效果图:
RecyclerView Item 的悬浮效果(吸附效果)的实现

当然,主要的思想根据原参考文章的类似,但是在一些细节方面就需要做一下修改,这本例的实现中,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) 时当效果尤为明显:
RecyclerView Item 的悬浮效果(吸附效果)的实现

其中,较为特殊的就是上滑的情况了,在上面的代码注释中也有说明,上滑时总共有四种情况:

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 时,就不是那么好契合了。

相关标签: RecyclerView