RecyclerView自定义LayoutManager实现横向瀑布流

2024-02-26 11:08

本文主要是介绍RecyclerView自定义LayoutManager实现横向瀑布流,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近由于公司项目需要,做了一个横向瀑布流的组件,如下图;这个组件是通过自定义LayoutManager实现,LayoutManager为我们提供了强大的自定义功能,但是实现过程却不简单,捣鼓了两天,也就算基本可以用了;Demo源码在最下面,这里主要记录一些自定义LayoutManager过程中需要注意的细节和关键点;

(这个Demo以及下面的记录参考文章:http://blog.csdn.net/zxt0601/article/details/52948009)

一 关键重写方法:

1、generateDefaultLayoutParams();

如果没有特殊需求,大部分情况下,我们只需要如下重写该方法即可。

@Overridepublic RecyclerView.LayoutParams generateDefaultLayoutParams() {return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);}
  • 1
  • 2
  • 3
  • 4
    2、onLayoutChildren();

    该方法是LayoutManager的入口。它会在如下情况下被调用: 
    1 在RecyclerView初始化时,会被调用两次。 
    2 在调用adapter.notifyDataSetChanged()时,会被调用。 
    3 在调用setAdapter替换Adapter时,会被调用。 
    4 在RecyclerView执行动画时,它也会被调用。 
    即RecyclerView 初始化 、 数据源改变时 都会被调用。 

    它相当于ViewGroup的onLayout()方法,所以我们需要在里面layout当前屏幕可见的所有子View,千万不要layout出所有的子View如果在这里绘制所有的子View,那么在我们每次调用NotifyDataSetChanged方法时,就会重新绘制所有的子View,如果有一万条数据,那么将会等待5S左右的时间,ANR!

    3、竖直滚动需要 重写canScrollVertically()和scrollVerticallyBy()

        @Overridepublic boolean canScrollVertically() {return true;}

    在canScrollVertically()方法中,我们要实现滚动、重绘、子View回收和重用,控制滚动速度;

    滚动和重绘:

    滚动时需要注意边界判断;

    子View 的回收和重用:

    一个View只是暂时被清除掉,稍后立刻就要用到,使用detach。它会被缓存进scrapCache的区域。 
    一个View 不再显示在屏幕上,需要被清除掉,并且下次再显示它的时机目前未知 ,使用remove。它会被以viewType分组,缓存进RecyclerViewPool里。 
    注意:一个View只被detach,没有被recycle的话,不会放进RecyclerViewPool里,会一直存在recycler的scrap 中。

    这里引出一个平时没有关注细节,即RecyclerView.Adapter的getItemViewType()方法;如果重写这个方法如下的话:

                @Overridepublic int getItemViewType(int position) {return position;}
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    这样每一个ItemViewType都不一样,RecyclerView不会有任何的复用,因为每一个ItemView在RecyclerViewPool里都找不到可以复用的holder,ItemView有n个,onCreateViewHolder方法会执行n次。

    控制滚动速度:

    该方法return的值如果和传进来的dy值不同,RecyclerView就会认为到达边界,就会停止fling并显示边界光晕;

    经测试该return值只用来判断边界,没有其他作用;

    所以我们就可以在此处修改滑动速度,在未到达边界时返回dy,这样就不会让RecyclerView误认为已到达边界了;


    二 常用API:

    布局API:

    //找recycler要一个childItemView,我们不管它是从scrap里取,还是从RecyclerViewPool里取,亦或是onCreateViewHolder里拿。
    View view = recycler.getViewForPosition(xxx);  //获取postion为xxx的View
    
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    addView(view);//将View添加至RecyclerView中,
    addView(child, 0);//将View添加至RecyclerView中,childIndex为0,但是View的位置还是由layout的位置决定,该方法在逆序layout子View时有大用
    • 1
    • 2
    • 1
    • 2
    measureChildWithMargins(scrap, 0, 0);//测量View,这个方法会考虑到View的ItemDecoration以及Margin
    • 1
    • 1
    //将ViewLayout出来,显示在屏幕上,内部会自动追加上该View的ItemDecoration和Margin。此时我们的View已经可见了
    layoutDecoratedWithMargins(view, leftOffset, topOffset,leftOffset + getDecoratedMeasuredWidth(view),topOffset + getDecoratedMeasuredHeight(view));
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    回收API:

    detachAndScrapAttachedViews(recycler);//detach轻量回收所有View
    detachAndScrapView(view, recycler);//detach轻量回收指定View// recycle真的回收一个View ,该View再次回来需要执行onBindViewHolder方法
    removeAndRecycleView(View child, Recycler recycler)
    removeAndRecycleAllViews(Recycler recycler);
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    detachView(view);//超级轻量回收一个View,马上就要添加回来
    attachView(view);//将上个方法detach的View attach回来
    recycler.recycleView(viewCache.valueAt(i));//detachView 后 没有attachView的话 就要真的回收掉他们
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    移动子ViewAPI:

    offsetChildrenVertical(-dy); // 竖直平移容器内的item 
    offsetChildrenHorizontal(-dx);//水平平移容器内的item
    • 1
    • 2
    • 1
    • 2

    工具API:

    public int getPosition(View view)//获取某个view 的 layoutPosition,很有用的方法,却鲜(没)有文章提及,是我翻看源码找到的。
    • 1
    • 1

    //以下方法会我们考虑ItemDecoration的存在,但部分函数没有考虑margin的存在
    getDecoratedLeft(view)=view.getLeft()
    getDecoratedTop(view)=view.getTop()
    getDecoratedRight(view)=view.getRight()
    getDecoratedBottom(view)=view.getBottom()
    getDecoratedMeasuredHeight(view)=view.getMeasuredWidth()
    getDecoratedMeasuredHeight(view)=view.getMeasuredHeight()
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    //由于上述方法没有考虑margin的存在,所以我参考LinearLayoutManager的源码:/*** 获取某个childView在水平方向所占的空间** @param view* @return*/public int getDecoratedMeasurementHorizontal(View view) {final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();return getDecoratedMeasuredWidth(view) + params.leftMargin+ params.rightMargin;}/*** 获取某个childView在竖直方向所占的空间** @param view* @return*/public int getDecoratedMeasurementVertical(View view) {final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();return getDecoratedMeasuredHeight(view) + params.topMargin+ params.bottomMargin;}

    三 Demo的说明:

    实现功能:实现横向流式布局,实现了子View 的回收和重用,实现了SmoothScrollToPosition功能,实现了NotifyDataSetChanged方法更新数据的功能;

    存在缺陷:如果更换数据(特指某Position上的子View的大小可能改变),需要给RecyclerView重新new一个LayoutManager,否则显示会有问题;

    未实现ScrollToPosition功能;未实现定向更新功能;


    注:有关于该组件在首次非常快速滑动时,可能出现子View位置计算错误的问题(推测应该是滑动过快,而计算并未能实时完成,最终造成位置计算错误),已通过控制滑动速度和fling速度解决;

    Demo下载地址;




     
    

    这篇关于RecyclerView自定义LayoutManager实现横向瀑布流的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



    http://www.chinasem.cn/article/748659

    相关文章

    MySQL中查找重复值的实现

    《MySQL中查找重复值的实现》查找重复值是一项常见需求,比如在数据清理、数据分析、数据质量检查等场景下,我们常常需要找出表中某列或多列的重复值,具有一定的参考价值,感兴趣的可以了解一下... 目录技术背景实现步骤方法一:使用GROUP BY和HAVING子句方法二:仅返回重复值方法三:返回完整记录方法四:

    IDEA中新建/切换Git分支的实现步骤

    《IDEA中新建/切换Git分支的实现步骤》本文主要介绍了IDEA中新建/切换Git分支的实现步骤,通过菜单创建新分支并选择是否切换,创建后在Git详情或右键Checkout中切换分支,感兴趣的可以了... 前提:项目已被Git托管1、点击上方栏Git->NewBrancjsh...2、输入新的分支的

    Python实现对阿里云OSS对象存储的操作详解

    《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

    关于集合与数组转换实现方法

    《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

    使用Python实现可恢复式多线程下载器

    《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

    java实现docker镜像上传到harbor仓库的方式

    《java实现docker镜像上传到harbor仓库的方式》:本文主要介绍java实现docker镜像上传到harbor仓库的方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录1. 前 言2. 编写工具类2.1 引入依赖包2.2 使用当前服务器的docker环境推送镜像2.2

    C++20管道运算符的实现示例

    《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

    Java easyExcel实现导入多sheet的Excel

    《JavaeasyExcel实现导入多sheet的Excel》这篇文章主要为大家详细介绍了如何使用JavaeasyExcel实现导入多sheet的Excel,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录1.官网2.Excel样式3.代码1.官网easyExcel官网2.Excel样式3.代码

    python实现对数据公钥加密与私钥解密

    《python实现对数据公钥加密与私钥解密》这篇文章主要为大家详细介绍了如何使用python实现对数据公钥加密与私钥解密,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录公钥私钥的生成使用公钥加密使用私钥解密公钥私钥的生成这一部分,使用python生成公钥与私钥,然后保存在两个文

    浏览器插件cursor实现自动注册、续杯的详细过程

    《浏览器插件cursor实现自动注册、续杯的详细过程》Cursor简易注册助手脚本通过自动化邮箱填写和验证码获取流程,大大简化了Cursor的注册过程,它不仅提高了注册效率,还通过友好的用户界面和详细... 目录前言功能概述使用方法安装脚本使用流程邮箱输入页面验证码页面实战演示技术实现核心功能实现1. 随机