美文网首页Android Techandroid开发Android UI
Android轻松实现RecyclerView悬浮条

Android轻松实现RecyclerView悬浮条

作者: _飞翔的荷兰豆 | 来源:发表于2016-11-07 21:26 被阅读21073次

在我们在刷Instagram的动态时,你是否注意到这样一个小小的动效,就是当一条动态(以卡片形式呈现)向上滑动时,动态卡片的头部会始终悬浮在列表最上方,直到下一张动态卡片的头部将它顶掉并替换它悬浮着。言语可能说不清楚,就直接来看一下它的效果好了。

Instagram的悬浮条

综合我上面的文字描述加上这张Gif图,我想大家应该知道这是个什么样的效果了吧。那么不废话了,接下来我就来说说一种很简单的实现方法吧。

思路

虽然实现起来炒鸡简单,但还是花了我一个多小时的时间思考实现。先说说思考过程吧,那天中午,Instagram给我推了一条消息(哈,就是我最喜欢的偶像金泰妍更新了Ins),于是我就点进去看了,喜欢了之后就开始研究这个效果,我反复地上下滑这个列表,因为Ins的列表有滚动条,我就发现每次滚动条在那个悬浮条附近的时候就会特别短。看到这个现象,敏锐的你是不是察觉到了什么?没错,我感觉这个就像是FrameLayout的效果,一个FrameLayout里按顺序有列表,悬浮条两个View,悬浮条覆盖在列表的上方,它在合适的时机更新自己的位置,在合适的时机更新自己的信息,然后看上去就像是一个悬浮的效果。

接下来我们思考的核心就转移到了如何确定并找到这个合适的时机。

再仔细观察上面的Gif图,我们可以确定当第二个列表项的头部距离列表顶端一个悬浮条的距离时,悬浮条随着列表的滑动改变自身的位置,从而看起来像是被顶掉的效果。画一张简单位置示意图

那么,数据更新的时机也很容易确定,就是在悬浮条恰好完全被顶掉的时候,更新自己的数据,并移动到列表顶部。

至于如何找到这个时机会在接下来的实现部分讲解。

实现

建立布局

如上面所言,就是一个简单的FrameLayout。

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/appbar_scrolling_view_behavior">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/feed_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@android:color/white"
        android:scrollbars="vertical" />

    <your-head-layout>
    ……
    </your-head-layout>
</FrameLayout>

注意这里FrameLayout的第二个child应该为你列表项要悬浮显示的布局。

找到时机

根据我们的思路,我们首先要找到第二个列表项的头部距离列表顶端一个悬浮条的距离时的那个时机,如果我们能找到这个时机,那么第二个时机也相当于找出来了。

这里我们使用的是RecyclerView来实现列表,我们都知道RecyclerView的列表布局是由LayoutManager来确定的,由于一般要实现悬浮条显示效果的列表一般都为线性列表,即我们一般会使用LinearLayoutManager。通过LinearLayoutManager,我们可以很方便的获取到RecyclerView中相应位置的View,这里我们需要获取当前悬浮条数据来源的View和其下一个数据来源的View。这两个View有什么用呢?悬浮条显示的信息是来自第一个可见View的,而其下方的View正是第二个列表项,我们可以获取到它的top值。好了接下来就真的很简单了,我们只要给RecyclerView加一个ScrollListener,并在相应的回调里做之前我们想好的事就ok了,来看一下代码

mFeedList.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        mSuspensionHeight = mSuspensionBar.getHeight();
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        View view = linearLayoutManager.findViewByPosition(mCurrentPosition + 1);
        if (view != null) {
            if (view.getTop() <= mSuspensionHeight) {
                mSuspensionBar.setY(-(mSuspensionHeight - view.getTop()));
            } else {
                mSuspensionBar.setY(0);
            }
        }

        if (mCurrentPosition != linearLayoutManager.findFirstVisibleItemPosition()) {
            mCurrentPosition = linearLayoutManager.findFirstVisibleItemPosition();
            mSuspensionBar.setY(0);

            updateSuspensionBar();
        }
    }
});

Tips:其中mCurrentPosition为悬浮条信息来自的那个列表项在RecyclerView的位置。还有这里的ScrollListener可以添加多个,在RecyclerView中会检查所有的ScrollListener并触发。

One more thing...

接下来,我们还需要……开玩笑,哪来的One more thing,我们已经完成了?什么?这么快?这么一点代码?恩,没错,就是只要这么一点代码就好了,我们来看一下最后我们实现的效果(当然最终效果的好坏还是取决与你列表项的布局,比如在Ins里这个效果就很好看呢~)

结语

哈哈,是不是很简单呢,最后再说一下封装的事,本来我是想封装一下的,由于每个人的列表布局都不一样,数据更新方式也不一样,就不封装了,是的,我水平不行,虽然我不想承认~不过代码真心特别少哦,源码地址:https://github.com/wuapnjie/SuspensionBar

希望这篇文章可以对你有帮助,我也会继续努力的。

补充

上面这种情况我们RecyclerView的Item是单一的,但是我们的列表Item通常有很多种,只有在滑到我们想要类型的Item时才需要更新我们的悬浮条信息。比如很常见的通讯录,在我们滑到从A开头联系人滑到B开头联系人时,悬浮条的信息才从A变为B;再比如印象笔记的笔记列表,顶部的悬浮条是根据笔记的日期改变的。

那么,遇到这种情况我们应该怎么简单修改代码来实现我们需求呢?

其实很简单,思路已经由上面确定了,只是我们要让悬浮条移动的时机变化,变得更窄了,同时我们要更新的数据内容也发生了变化(这当然需要我们变换相应的布局)。

mFeedList.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        super.onScrollStateChanged(recyclerView, newState);
        mSuspensionHeight = mSuspensionBar.getHeight();
    }

    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        //我们只是简单的收窄了我们让悬浮条移动的条件,这里就是ItemType必须对应时才发生移动
        if (adapter.getItemViewType(mCurrentPosition + 1) == MultiFeedAdapter.TYPE_TIME) {
            View view = linearLayoutManager.findViewByPosition(mCurrentPosition + 1);
            if (view != null) {
                if (view.getTop() <= mSuspensionHeight) {
                    mSuspensionBar.setY(-(mSuspensionHeight - view.getTop()));
                } else {
                    mSuspensionBar.setY(0);
                }
            }
        }

        if (mCurrentPosition != linearLayoutManager.findFirstVisibleItemPosition()) {
            mCurrentPosition = linearLayoutManager.findFirstVisibleItemPosition();
            mSuspensionBar.setY(0);

            updateSuspensionBar();
        }
    }
});

上面的代码我们只要注意注释处,其他的和之前给出的相同。

总之,虽然大家的需求可能不同,但万变不离其宗。只要掌握了思路,什么需求都不怕。

Github 上已增加相应代码,最后看一下我们的效果,只在时间变化时才移动悬浮条

相关文章

网友评论

  • Todo2:写的非常不错,赞
    组件化和插件化的开发里程总结
    https://www.jianshu.com/p/df2a6717009d
  • jhyHenry:根本没必要这么麻烦,直接继承ItemDecoration,复写onDrawOver,在Canvas上绘制内容,在Item之后调用。(画的内容会覆盖在item的上层),简单粗暴
    西城鹿杖客:@jhyHenry 老哥来个demo吧, 好人一生平安~~~
    洛埋名:thk a lot
    _飞翔的荷兰豆:@jhyHenry 按你喜欢的方式写
  • Roc丶F:我想知道这个效果怎么加下拉刷新:joy:
  • c84a6998a6c6:太感谢了,搜recyclerview的相关的,意外发现这个悬浮条的帖子,正好要用到,感谢:+1:
  • js1225987336:楼主是怎么根据item中的数据设置相应的悬浮条的数据的呢
  • bb86dae3810e:谢谢楼主!!
  • 南宫逸诺:技术活 本公子赏了
  • FrankDaddy:很棒很棒,demo很缓解疲劳
  • Fi7z:如果用的是GridLayoutManager,好像就出问题了
  • Fi7z:很赞,比自定义分割线简单多了
  • Vander丶:楼主这个思路很棒,之前我实现这样的效果一直是用 分割线的那种实现的,感觉楼主这种可扩展性更强.
  • 0fbd3d13a248:lz ,思路差不多,我不是悬浮,toolbar渐变
  • TomLeisen:有一点小小的瑕疵,向上推的时候没有问题,推上去几条之后慢慢的往下啦,在两个title 交接的时候会友店闪硕,是更新造成的
  • f76dd08a2e4c:楼主你好 我问个问题 你更新后的那个DEMO的 你每个头部 实际上是占用了一个recycleview的item 当recycleview添加数据的时候 这样不会出错吗
  • 我脑中旳橡皮擦:添加SwipeRefreshLayout 官方下拉刷新控件的时候,顶部会挡住刷新的小圆圈吧:pensive:
    _飞翔的荷兰豆:额,看你怎么布局吧,或者你判断一下滑动到顶部时让悬浮条消失就好了
  • sankemao:可以, 按照这个思路实现了我想要的效果
    我脑中旳橡皮擦:添加SwipeRefreshLayout 官方下拉刷新控件的时候,顶部会挡住刷新的小圆圈吧:pensive:
    _飞翔的荷兰豆: @sankemao 嘿嘿🤗
  • b7d89fbd1907:您好,我从github上下载的 然后在item_feed和time的布局文件中都有 android:layout_toRightOf="@id/iv_avatar"
    编译出错了 没有找到这个 布局文件都对比过了 都是一样的
    b7d89fbd1907:@ninjass 大神 怎么把actionbar去掉,我在清单文件里 android:theme="@style/Theme.AppCompat.Light.NoActionBar">
    <activity android:name=".MainActivity">
    <intent-filter>
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
    </activity>
    <activity
    android:theme="@style/Theme.AppCompat.Light.NoActionBar"
    android:name=".MultiActivity"></activity>
    atcivity里不显示列表分组的头条了
    b7d89fbd1907:@飞翔的荷兰豆 thx
    _飞翔的荷兰豆: @ninjass 改成@+id试试
  • 小明从教科书中复活了:楼主,我发现上拉的时候悬浮条会闪烁一下显示上一条的title然后又变成当前title,请问怎么解决呢
    小明从教科书中复活了:@飞翔的荷兰豆 我就是直接在git上下载的demo呢 然后在虚拟机上跑的就是从Taeyeon 2滑到Taeyeon 1的时候交界处就会闪一下呢 不会录gif。。。
    _飞翔的荷兰豆: @小明从教科书中复活了 我写的demo里不会呢,可以发张gif效果图给我吗?悬浮条显示的是当前可视的第一个Item
    小明从教科书中复活了:是先上拉 然后再下拉 当title到顶部的时候出现的
  • 68768b474bfc:非常感谢您的分享。受您的启发,我把它封装成库:
    https://github.com/TellH/RecyclerStickyHeaderView
    _飞翔的荷兰豆:@TellH 能帮到你就好
  • 217cce305b3c:再补充下我刚才说的,我想实现的所看到的效果是:学生(算是组别名吧)-->学生1(列表项)、学生2、.......,学生n;老师-->老师1、老师2、......老师n,这样能实现吗?
    217cce305b3c: @飞翔的荷兰豆 嗯嗯,谢谢!大写的赞👍
    _飞翔的荷兰豆:@奇幻森林 文章已经更新,希望可以帮到你
    _飞翔的荷兰豆: @奇幻森林 可以的啦,等会我更新下这篇文章,只要选择移动到对应的itemtype时再更新就好了
  • 217cce305b3c:楼主,比如我有学生,老师两种数据,我想显示的效果是:悬浮条中根据数据类型分别显示:学生,老师;在学生这一类型下显示的是学生1,学生2,.......;在老师也类似,像你的这个解决方案可行吗?由于不能发截图,不知我表达的是否清楚 :grin:
  • 217cce305b3c:楼主,如果我一个列表项顶部下有多个列表项,就好比我一个组,上面的是组名,下面的是属于这一组下的数据,你能明白我表达的意思吧?像你这样的方法能实现吗?
    _飞翔的荷兰豆:@奇幻森林 你只要知道什么时候应该更新数据就好了,要更改一下判断条件,并不是每个item改变就更新,而是在特定时候更新。要对代码做相应的改动
  • 敲代码的小新:不错哦,支持下。
    _飞翔的荷兰豆: @敲代码的小新 谢谢支持🤗
  • 623e9ec16d5f:额,只会用listview做这东西
  • dccebae5c725:感觉是个妹子程序员
    _飞翔的荷兰豆:@light_Man 额,让你失望了,并不是妹子
  • 2a47ddbb94b7:我发现一个问题,Item可见部分若占了起码一屏,那往上滑的时候,view就一直为null
    _飞翔的荷兰豆:@Code丁 恩,是的呢,之前都没发现,现在已经修改,在View为null时也应该更新当前记录的Position,文章已经更新,Github上的代码也更新了,谢谢提醒
  • 125b0ce1a78e:不错,支持!!!另外抱走我软 :blush:
    _飞翔的荷兰豆:@泰过爱尼 谢谢支持~抽抽粉
  • 月下溪明:试试看
    _飞翔的荷兰豆:@风凡Stephen 谢谢支持~
  • 2a47ddbb94b7:求教菜单顶部悬浮:开始顶端为一个菜单,下面为一个banner,接下来又是个同顶端一样的菜单,这个菜单下面跟着列表。上拉的时候,下面的菜单到达顶部,原先顶部的菜单隐藏掉,原先下面的菜单在顶部悬浮,这个时候,只有列表在滑动了,在列表下拉的时候,滑动到列表顶端,悬浮的菜单就跟着列表一起下拉,重现上面的布局
    2a47ddbb94b7:@飞翔的荷兰豆 请问AppBarLayout我写一个顶部HorizontalScrollView 菜单,能在下部HorizontalScrollView 菜单上到顶部之前一直悬浮吗
    2a47ddbb94b7:@飞翔的荷兰豆 谢谢
    _飞翔的荷兰豆:@Code丁 这个我好像没做过诶,我感觉用CoordinatorLayout和AppBarLayout实现起来应该会容易一些
  • KhadaJhin_:可以!
    _飞翔的荷兰豆:@JuiceShui 对你有帮助就好~
  • d07e1834a502:好的文章不容易错过
    _飞翔的荷兰豆:@哪天幸福醒来 谢谢支持:blush:
  • Aldrich_N:重点是滚动距离得算好,快滑和慢滑可能出现bug
    _飞翔的荷兰豆:@Aldrich_N 具体项目,具体计算方法呗:blush:
  • 时光与梦s:厉害!一直想实现这种效果
    _飞翔的荷兰豆:@Wey_Ye 谢谢夸奖~
  • jianshuwangyd:iOS 的好像是自带这种效果
    _飞翔的荷兰豆:@爱表达不说话 是吗?那真是棒
  • 8908cb2c22a6:mSuspensionBar是怎么获取的,更新悬浮条的代码能看看吗
    _飞翔的荷兰豆:@retime 有源码地址啊
  • 菜鸟Android:楼主好厉害啊!!! :+1:
    _飞翔的荷兰豆:@iihghg 谢谢夸奖~
  • f43a88b70e5c:简单,实用,赞。
    _飞翔的荷兰豆:@_Burgess 谢谢夸奖~
  • Passon_Fang:思路很好
    _飞翔的荷兰豆:@DoubleFang 谢谢夸奖~
  • GG_Jin:wuli 软软
    _飞翔的荷兰豆:@GG_Jin 我觉得大家保存图像的方法都差不多吧,调用Bitmap.compress()
    GG_Jin:@飞翔的荷兰豆 你有没有好用都保存图片的 方法啊
    _飞翔的荷兰豆:@GG_Jin 喜欢
  • EDG不是本地g:这个初始化 怎么能直接是数据啊
    _飞翔的荷兰豆:@yxdd 因为这里设置的是悬浮条的y值,view的默认锚点在左上角,它被顶上去时,y值是小于0的
    EDG不是本地g:@飞翔的荷兰豆 还有一个问题啊。有点傻,但是我也没百度明白,请教下您吧,那个sety 为啥取相反数啊
    _飞翔的荷兰豆:@yxdd 你获得数据后,立马更新下就好了
  • 1412c5043b09:大神,recyclerView最上面的item被挡住了一点,请问怎么解决
    6d6e8cb478b4:我的也是被挡住了。请问你解决了么?求解决方法,
    _飞翔的荷兰豆:@1412c5043b09 什么意思呢?因为这个效果取决于你的布局,我不是很能想象你的情况。
  • 笑哥哥:棒棒的
    _飞翔的荷兰豆:@笑哥哥 谢谢夸奖~
  • ibrucekong:mark先
    _飞翔的荷兰豆:@ibrucekong 好的
  • 8f209a70c663::clap:🏻:clap:🏻:ghost::ghost:
    _飞翔的荷兰豆:@Ivorytower 谢谢支持:blush:
  • 叮宕:给你点个喜欢好了。
    _飞翔的荷兰豆:@叮宕 nexus4,原生的Android5.1
    叮宕:@飞翔的荷兰豆 你的系统是什么,感觉右上角的图标很好看。
    _飞翔的荷兰豆:@叮宕 谢谢~
  • 欧阳1314:评论因为taeyeon.
    _飞翔的荷兰豆:@唯Tae 哈哈,泰妍小天使
  • 7712c986a2a1:这条评论是因为wuli泰妍
    _飞翔的荷兰豆:@SuperGDFan 嗯嗯^ω^
  • 晓磊1990:很好
    _飞翔的荷兰豆:@1e8b9f95bf38 谢谢支持
  • a295f182e337:listview也可以实现
    _飞翔的荷兰豆:@River1024 先录屏成MP4,再用licecap录成Gif图
    a295f182e337: @飞翔的荷兰豆 我用listview做过类似的功能。你那个演示效果的动态图是怎么做的啊?
    _飞翔的荷兰豆:@River1024 恩,肯定可以,只是我没怎么用过listview
  • 昨日产品:支持!以后可以拿这个告诉程序,你就照着这样做!
    _飞翔的荷兰豆:@陈非非 谢谢支持:blush:
  • flyfeeling:大气
    _飞翔的荷兰豆:@flyfeeling 谢谢夸奖~
  • 7d0a14cf1595:支持
    _飞翔的荷兰豆:@没有梦想的boy 谢谢夸奖~
  • wan7451:简单😀
    _飞翔的荷兰豆:@Androider_ 谢谢支持:blush:

本文标题:Android轻松实现RecyclerView悬浮条

本文链接:https://www.haomeiwen.com/subject/tgoouttx.html