本文是对自定义骨架屏框架的优化说明。
针对目前对骨架屏的需求及为了实现骨架屏而付出的繁重劳动,而设计的一款0编码0业务侵入的骨架屏框架。感兴趣的可以先去看看这篇文章:一种简单的Android骨架屏实现方案----0侵入0成本
额,如果不看,其实可能也不理解这篇是在说什么(⊙o⊙)…
列表样式优化
将列表的 item UI样式二维数组化,通过配置对应的二维坐标,来实现列表样式到骨架屏灰条的准确映射。
问题
列表样式的妥协始终是一个无法抹去的问题,虽说一个项目的列表按理来说应该就几种主要样式,但实际列表都是各不相同的。原始列表样式到骨架屏灰块样式无法准确对应,再多的理由也无法说服产品同学妥协。
且从用户的角度来看,确实是一种非友好的体验,虽然可能并不影响用户使用。
从开发者的角度来看,这种妥协即使有补救的措施,但方式上并不可取。开发者在自定义样式时是需要直接修改源码的。很显然,违背了设计模式的原则-开闭原则:对修改关闭,对扩展开放。
用一句通俗的话来说,那就是不够优雅啊。
那如何优雅的实现定制化的列表灰块样式呢,并且依然尽可能少的敲代码?
灵感来源
框架之前内置了三种简单的列表样式(上文有提及和配图)。在完善代码注释的时候,想告诉开发者这三种样式是什么样子的。但是注释里又不能配图,如何表达呢?思来想去之际,脑海中忽然闪现过这个注释图:
可以用标点符号拼出一个图来示意。于是框架中就有了下面这段注释。
style 1 表示左边是图片,右边跟着两行字符串
style 2 表示上面是一个方图,下面跟着两行字符串。
不知大家能否看出来,这些组合是不就是一个二维数组,有的是1,表示灰色单位,多个1连起来就是一条灰块。有的是0,表示空白。
对于style 1来说,是不就是一个类似这样的二维数组:
表示,将屏幕宽度均分为 8 列,
第一行,坐标从0到1的位置值是1 ,表示是灰色,坐标从3到7的位置值是1 ,表示灰色,其余位置为0 ,表示空白。
第二行,坐标从0到1的位置值是1 ,表示是灰色,坐标从3到7的位置值是1 ,表示灰色,其余位置为0 ,表示空白。
方案设计
通过组装一个ItemParams 类型的对象,来实现定制列表样式。
listviewItemType 依然是用来配置列表样式的字段,其值改为 ItemParams 类类型
class ItemParams(var params: List<LineSegmentGroup>) {
var columnNum = 20 //表示屏幕宽度分成多少列,影响的是一个单位的灰块的大小
var rowNum = 10 //行数,暂时没用
//将灰块按行分组
class LineSegmentGroup(
var line: Int, //行数
var graySegments: List<GraySegment> //当前行内的 各个灰块片段
)
//一个灰块片段, segment表示片段的起始坐标,mergeGap表示当前灰块和上一行的灰块是否融合为一起,没有间隙。
class GraySegment(var segment: Pair<Int, Int>, var mergeGap: Boolean = false)
}
上面的二维数组转换成代码配置如下:
val line0 = ItemParams.LineSegmentGroup(
0,
listOf(
ItemParams.GraySegment(0 to 1),
ItemParams.GraySegment(3 to 7)
)
)
val line1 = ItemParams.LineSegmentGroup(
1,
listOf(
ItemParams.GraySegment(0 to 1, mergeGap = true),
ItemParams.GraySegment(3 to 7)
)
)
val itemType = ItemParams(listOf(line0, line1))
itemType.columnNum = 8
listviewItemType =itemType //配置完毕
灰块如何计算出来的呢?
首先在父view RecyclerView或者listView 的Rect 范围内,将 rect按找 列数切分为一个个单元,然后根据配置的片段的起止坐标,来计算出灰块片段的长度,即 灰块rect 的 left和 right。根据当前所在的行数,来计算出灰块rect 的高度,即 top和bottom值。
不太清楚在说什么的还是建议去看一下之前的文章:
一种简单的Android骨架屏实现方案----0侵入0成本
项目实例
我们要实现下面列表UI的骨架屏灰块展示
我们可以大概将一个item对应到一个 8 × 10 的二维数组
展示UI的地方,就是红框围起来的,也就是骨架屏时要展示出灰色的地方,也就是数组值为1 的地方。
再将上面这个item准确的映射到一个数组中,黄色即灰块的位置。
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
| |
0
| ||||||||||
1
| ||||||||||
2
| ||||||||||
3
| ||||||||||
4
| ||||||||||
5
| ||||||||||
6
| ||||||||||
7
|
一个item的骨架屏样式就变成了一个包含着 0和1值的二维数组,
LineSegmentGroup类: 表示一行,其中包含本行中的各个小灰块,
line: 行数,
graySegments: 本行内的灰块列表
GraySegment类:表示一块单独的灰块片段
segment:Pair<Int, Int>: 表示一个灰块的从左到右的起止点坐标,
mergeGap: 表示是否顶部和上一行合并,即不保留此灰块的行间隙。
对应到代码配置中:
config {
listviewItemType = ItemParams(
listOf(
ItemParams.LineSegmentGroup(
line = 0,
graySegments = listOf(
ItemParams.GraySegment(0 to 1),
ItemParams.GraySegment(3 to 5),
ItemParams.GraySegment(8 to 9),
)
),
ItemParams.LineSegmentGroup(
line = 1,
graySegments = listOf(
ItemParams.GraySegment(0 to 1, true),
ItemParams.GraySegment(3 to 5),
ItemParams.GraySegment(9 to 9)
)
),
ItemParams.LineSegmentGroup(
line = 2,
graySegments = listOf(
ItemParams.GraySegment(0 to 1, true),
ItemParams.GraySegment(3 to 7)
)
),
ItemParams.LineSegmentGroup(
line = 4,
graySegments = listOf(
ItemParams.GraySegment(3 to 9)
)
),
ItemParams.LineSegmentGroup(
line = 5,
graySegments = listOf(
ItemParams.GraySegment(3 to 9)
)
),
ItemParams.LineSegmentGroup(
line = 6,
graySegments = listOf(
ItemParams.GraySegment(3 to 4),
ItemParams.GraySegment(6 to 7)
)
),
ItemParams.LineSegmentGroup(
line = 7,
graySegments = listOf(
ItemParams.GraySegment(3 to 6),
ItemParams.GraySegment(8 to 8),
ItemParams.GraySegment(9 to 9)
)
)
)
)
loadingAnim = ILoadingAnimtor.TYPE_BAI_JV_GUO_XI
skeletonEnable = true
customLoadingAnim = null
}
展示出的骨架屏效果:
如果想要继续的精细化展示,需要调整分隔的最小块单元大小即可。
即调整对应的表格的行列长度。
ItemParams 类的两个参数:
columnNum: 将屏幕宽度分隔成多少行
rowNum: item 最多由多少行组成,这个参数目前没用,
也就是说 columnNum 值越大,越精细还原item样式。
下面的效果是将 columnNum 设置为20 的 灰块效果,