雕虫晓技(五)网格分页布局源码解析(上)-小专栏

上面是它的应用场景之一,再看一下实现这种场景所需的代码:

//布局管理器PagerGridLayoutManagerlayoutManager=newPagerGridLayoutManager(1,4,PagerGridLayoutManager.HORIZONTAL);mRecyclerView.setLayoutManager(layoutManager);//滚动辅助器PagerGridSnapHelpersnapHelper=newPagerGridSnapHelper();snapHelper.attachToRecyclerView(mRecyclerView);没错,想要实现这样分页滚动的效果,只需要四五行代码就可以了,至于RecyclerView和Adapter使用官方提供的即可。

之前项目中有类似的需求,在网上寻找了一些实现方案,结果均不太满意。

有些方案使用起来过于麻烦,例如ViewPager+GridView,不用我说,用过的都知道,这种方案数据绑定十分麻烦,并且会多一层View嵌套,相对来说会损耗一些性能。

有些则是存在重大缺陷,例如内存泄露,性能问题等,像上面那种场景仅展示几个固定条目的情况还不明显,但是当需要动态加载几百个条目的时候缺陷就显现出来了,会造成严重的滑动卡顿,当数据达到一定数量级的时候,可能直接导致ANR。

在试过诸多方案,踩过很多坑以后,依旧没有找到合适的方案,于是自己动手,丰衣足食,也就有了这个项目。

如果你只是需要这样一个组件,那么直接点击上面的链接,看它的说明文档就可以了,本文不是你需要的,但如果你想要知道它的具体实现方案,对它进行改进的话,那么下文的内容可能会对你有所帮助。

首先项目所需要的核心内容主要有以下几点:

所需效果大概就如上图所示,为了避免重复造轮子,在一开始我想要使用一些现有的组件来完成。

最先想到的自然是网格布局,但是呢,项目需要动态加载数百条的数据,网格布局本身不带有条目自动回收创建功能,如果同让上百个View存在于一个页面之中,不卡爆才怪。

之后想到的是ViewPager+GridLayout,但是这种方案数据拆分和绑定十分麻烦,遂放弃。

然后想RecyclerView+GridLayoutManager看起来靠谱一点,首先使用GridLayoutMnager,作出网格效果,然后监听滚动事件来控制滚动距离,一切看起来都是那么美好,但是,事实证明这种方案还是太难使用。

首先网格布局同时只能控制行数或者列数其中一个,如果想要如果想要像设定那样2行3列,一页整好现实6条数据,那么View的宽高是需要动态计算的,如果设置了固定大小,必然会导致适配问题,

其次,数据不一定是整页,如果是2行3列,一页6条数据,那么使用GridLayoutManager滑动后可能会出现这样的效果,另外,数据排列顺序也并非我所需要的:

这显然不是我想要的效果,在数据不足一页时,我需要的效果是这样的:

如果想要在不动GridLayoutManager的情况下实现需求,则需要执行如下操作:

假设,需要显示2行3列,共8条数据,那么需要执行如下操作:

将不足一页部分补足一页

1、2、3、4、5、6、7、81、2、3、4、5、6、7、8、空、空、空、空通过数据变换调整数据次序使其显示符合预期

1、2、3、4、5、6、7、8、空、空、空、空1、4、2、5、3、6、7、空、8、空、空、空通过监听滚动控制滚动距离来实现分页显示

另外,页面数据是分页加载显示的,如果使用上面这种方案,单是数据处理逻辑就能把我绕进去。

在经过深思熟虑之后,我决定自定义一个LayoutManager来实现这个“简单”的需求。幸好RecyclerView的扩展性非常强,自定义一个LayoutManager也不是什么难事,下面我们就一步步的的实现一个分页网格布局。

首先我们创建一个PagerGridLayoutManager并继承RecyclerView.LayoutManager,实现其抽象方法,一个LayoutManager就可以用了,如下:

publicclassPagerGridLayoutManagerextendsRecyclerView.LayoutManager{@OverridepublicRecyclerView.LayoutParamsgenerateDefaultLayoutParams(){returnnewRecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);}}这是一个符合规范的LayoutManager,但是目前它不会将任何View显示在界面上,因为它没有对条目进行处理,也就意味着没有条目会添加到界面上。但我们先不着急处理子条目,在真正布局之前,我们先来解决一些简单的基础问题。

由于行列数会直接关系到一个页面显示条目的个数,进而影响到总共可以滚动的j距离和页数总数,所以它们要在构造方法中设置。

而滚动方向一般都是确定的,横向或者竖向,并且基本不变,所以,它也可以在构造方法中进行设置,提醒使用者不要忘记滚动方向。

所以为PagerGridLayoutManager添加如下的属性和构造方法:

publicstaticfinalintVERTICAL=0;//垂直滚动publicstaticfinalintHORIZONTAL=1;//水平滚动@IntDef({VERTICAL,HORIZONTAL})public@interfaceOrientationType{}//滚动类型@OrientationTypeprivateintmOrientation=HORIZONTAL;//默认水平滚动privateintmRows=0;//行数privateintmColumns=0;//列数privateintmOnePageSize=0;//一页的条目数量/***构造函数**@paramrows行数*@paramcolumns列数*@paramorientation方向*/publicPagerGridLayoutManager(@IntRange(from=1,to=100)introws,@IntRange(from=1,to=100)intcolumns,@OrientationTypeintorientation){mOrientation=orientation;mRows=rows;mColumns=columns;mOnePageSize=mRows*mColumns;}注意:

在确定了滚动方向后,顺便就可以实现LayoutManager以下两个方法了,这两个方法会真正的决定RecyclerView可以滚动的方向。

/**是否可以水平滚动*@returntrue是,false不是。*/@OverridepublicbooleancanScrollHorizontally(){returnmOrientation==HORIZONTAL;}/**是否可以垂直滚动*@returntrue是,false不是。*/@OverridepublicbooleancanScrollVertically(){returnmOrientation==VERTICAL;}2.4计算子条目的宽高由于我们是分页网格显示,目前已经知道了行列数,如果再知道RecyclerView的宽高,就能算出单个子条目的所能占用的宽高了。

因此我们再添加两个方法用于获取RecyclerView的可用宽高:

/**获取可用的宽度*@return宽度-padding*/privateintgetUsableWidth(){returngetWidth()-getPaddingLeft()-getPaddingRight();}/**获取可用的高度*@return高度-padding*/privateintgetUsableHeight(){returngetHeight()-getPaddingTop()-getPaddingBottom();}注意:可用宽高要减去Padding数值。

有了总的可用宽高,在分别处以行列数就可以得到每一个子条目占用的宽高了。

privateintmItemWidth=0;//条目宽度privateintmItemHeight=0;//条目高度mItemWidth=getUsableWidth()/mColumns;mItemHeight=getUsableHeight()/mRows;2.5计算条目显示区域既然知道了条目的宽高,那么只要知道这个条目所在位置就能确切的知道它的显示区域了。

这里使用的计算方案是:条目所在页面的偏移量+条目在页面内的偏移量。同时由于页面可能会反复的滑动,因此不可能每次滚动时都重新计算一下条目的位置,因此计算过的条目用mItemFrames存储起来,之后想要获取该条目的显示区域,直接从mItemFrames中取出即可,防止重复计算造成的性能浪费。至于存储所耗费的内存空间,其实并不算大,存储10万个Rect耗费内存也才4M左右,正常情况下一般不会超过一万条数据,所耗费的空间一般不会超过0.5M,大可以放心使用。

THE END
1.GridBagLayout网格组布局OSCHINA由GridBagLayout 类实现的布局管理器称为网格组布局管理器,它实现了一个动态的矩形网格,这个矩形网格由无数个矩形单元组成,每个组件可以占用一个或多个这样的单元格。 在向由 GridBagLayout 类管理的容器中添加组件时,需要为每个组件创建一个与之关联的 GridBagConstraints 类的对象,通过该类中的属性可以设置组件的布局...https://my.oschina.net/kaipan/blog/160669
2.GridLayout(Java2PlatformSE5.0)从类java.lang.Object 继承的方法 clone, equals, finalize, getClass, hashCode, notify, notifyAll, wait, wait, wait构造方法详细信息GridLayoutpublic GridLayout()创建具有默认值的网格布局,即每个组件占据一行一列。 从以下版本开始: JDK1.1GridLayout...http://jszx-jxpt.cuit.edu.cn/javaapi/java/awt/GridLayout.html
3.《Java项目开发实训教程》(宗哲玲)305“对话面板”(采用边界布局)放置“外部分割面板”(采用左右分割)左侧:放置“内部分割”面板(采用上下分割)右侧:放置“表格面板”上方:“数据面板”(采用绝对布局,放置各组件)下方:“按钮面板”(采用网格包布局,放置各按钮)three3.方法设计构造方法UserUpkeep()用于完成“用户维护”界面的初始化工作,界面布局设计如下...https://max.book118.com/html/2022/0807/5243130004004321.shtm
1.JAVA网格布局如何设置网格大小mob64ca12e6b22d的技术博客JAVA网格布局如何设置网格大小 在JAVA Swing中,网格布局是一种常见的布局管理器,可以将组件以网格的形式排列。每个组件都可以占据一个或多个网格单元,并且可以根据需要进行动态调整和调整大小。 在本文中,我们将介绍如何使用JAVA网格布局设置网格大小,并提供一个具体的示例来解决一个问题。 https://blog.51cto.com/u_16213391/7500056
2.JavaSwingGridLayout网格布局的实现代码java网格布局资源网格布局 Swing GridLayout 67 浏览量2020-08-25上传评论收藏56KBPDF举报 立即下载开通VIP(低至0.43/天) 买1年送1年 JavaSwing GridLayout 网格布局的实现代码 JavaSwing GridLayout 网格布局是JavaSwing库中的一种常用布局管理器,用于在容器中排列组件。下面将详细介绍JavaSwing GridLayout 网格布局的实现代码。 我...https://download.csdn.net/download/weixin_38587130/12744640
3.Java开发GUI之GridLayout网格布局Java开发GUI之GridLayout网格布局 GridLayout是简单的网格布局,使用其可以方便的实现多行多列的布局样式。 static void GridLayoutTest(){ Frame frame = new Frame("Grid"); GridLayout layout = new GridLayout(2, 3, 10, 10); Panel pannel = new Panel(layout); ...https://www.ctyun.cn/zhishi/p-336839
4.java常用的布局管理器有哪些问答Java常用的布局管理器有以下几种: BorderLayout(边界布局管理器):将容器分为东、西、南、北和中间5个区域,组件可以放置在这5个区域中。 FlowLayout(流布局管理器):按照添加的顺序依次排列组件,当一行放不下时会自动换行。 GridLayout(网格布局管理器):将容器分为行和列的网格,组件会按照网格的顺序从左到右、...https://www.yisu.com/ask/59854774.html
5.Bootstrap5布局表格网格极客教程例子2:在这个例子中,我们将使用.row和.col-*类创建不同尺寸的表格网格布局。<!DOCTYPE html> Bootstrap 5 Layout Form grid https://geek-docs.com/bootstrap/bootstrap-5/bootstrap-5-layout-form-grid.html
6.Android布局——线性布局相对布局帧布局表格布局网格布局...简介:Android布局——线性布局、相对布局、帧布局、表格布局、网格布局、约束布局 1.概述 1.1 布局的作用:布局是对界面结构的全面规划和安排,通过api中提供的各种布局能够快速的完成对界面的设计和规划 1.2 布局的种类:线性布局(LinearLayout)、相对布局(RelativeLayout)、帧布局(FrameLayout)、表格布局(TableLayout)、网格...http://www.jaozi.cn/?article/1477149
7.java中Swing五种常见的布局方式java本文通过代码示例给大家详细讲解了java中Swing五种常见的布局方式,以及相关注意知识点,有兴趣的朋友参考学习下。1、 边界布局(BorderLayout) 2、流式布局(FlowLayout) 3、网格布局(GridLayout) 4、盒子布局(BoxLaYout) 5、空布局(null) 还有其他两种布局,分别是GridBagLayout(网格包布局)、CardLayout(卡片布局) 注意...https://www.jb51.net/article/135684.htm
8.JAVA——45.flowlayout和gridlayout布局构造方法:GridLayout(int rows, int cols) :用指定的行数和列数创建网格布局。 package org.zhaiyujia.test1;import java.awt.GridLayout;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JPanel;public class Guitest1 extends JFrame { JButton b1,b2,b3,b4,b5; JPanel p; publi...https://www.tulingxueyuan.cn/tlzx/jsp/2833.html
9.Java一分钟之JavaFX布局管理:GridPane,VBox,HBoxJavaFX是Java的一个强大的图形用户界面(GUI)工具包,提供了多种布局管理器来帮助开发者组织和控制窗口中的控件。在本篇博客中,我们将深入探讨三种常用的布局管理器:GridPane、VBox和HBox,并讨论一些常见问题、易错点及如何避免它们。 1. GridPane GridPane允许你创建一个二维网格来放置控件。每个控件都有固定的行和列位...https://cloud.tencent.com/developer/article/2422499
10.Android15.0UI开发(六)——列表控件RecyclerView的网格布局...1.0 列表控件RecyclerView的网格布局排列实现,关键词GridLayoutManager。 LinearLayoutManager 实现顺序布局 GridLayoutManager 实现网格布局 StaggeredGridLayoutManager 实现瀑布流布局 2.0 新建项目RecyclerviewTest,目录如下: image 3.0 这里需要在app/build.gradle中配置,导入依赖包: ...https://www.jianshu.com/p/3446af05b1e8