上面是需求图
整个布局是可以垂直方向滑动的,同时内部需要用垂直方向的网格布局来显示图片。并且通过让宽度或间距平分剩余空间,来实现对齐效果。如果丢个FlowRow代替Grid,是不能实现对齐的,基本会头重脚轻
同向可滑动嵌套的隐患
众所周知,同向的可滑动嵌套可能会导致性能问题。比如A布局允许垂直方向滑动,B是A布局中的一个RecyclerView,高度为wrapContent,由于B的父布局A可以垂直滑动,A对B的高度不做限制,B会一口气把所有的元素都加载出来,导致RecyclerView的延迟加载和回收逻辑都失效,如果数据太多的话会导致性能问题。如果是分页列表的话,那么它还会一直处于加载到底部-》触发加载下一页-》加载到底部的循环中,导致从性能问题进化到业务问题。
Compose中为了防止这个情况,对同向的滑动进行了限制,在大多数的同向可滑动布局嵌套时会直接抛出异常(除非child在对应方向上设置了具体的尺寸,比如B在A里面,B设置了Modifier.height(30.dp),这样可以正常运行)。
有一些场景,我们确定在特定的业务场景下,集合元素不会很多,而且UI就要求用高度自适应的网格布局来显示内容。在使用View时我们有很多方法,有直接的网格布局,也可以头硬上RecyclerView(虽然可能会导致问题,但View体系中并没有禁止开发者这么做,后果自负就是了)。
但是在Compose中,由于只提供了LazyXXXGrid,并没有那种不需要滑动,高度自适应,可以直接在滑动布局内部使用的Grid,有的时候会导致困扰——我不想把整个滑动界面改成LazyXXXGrid,我也确定Grid中的元素不会那么多,甚至只有几个,绝对不影响性能。
这个时候我们还只能自己实现一个简单的Grid了,直接上代码。
代码
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
/**
* 由于Compose只提供了懒加载的LazyVerticalGrid,又不允许同向的嵌套滑动,会直接抛出异常,所以需要我们自己实现Grid布局(很多时候我们知道嵌套滑动会导致懒加载失效,内容全部加载等问题,但是我们也确定一些使用场景里Grid的内容不会过多,就是想要使用Grid)。
*
* 用类似LazyVerticalGrid的布局效果:会在遵守[minItemSize]的前提下尽可能的在每一行放更多的项,并让他们平分剩余空间。
*
* 由于是简单实现,所以布局中的每一项会设置为正方形,高度向宽度对齐
*
* 比如总宽度为100,minItemSize为30,那么最终会有3列,每列宽度为100/3(这个例子中没有考虑[horizontalSpace])
* @param minItemSize 子项的最小尺寸。在布局的最大宽度小于这个尺寸时,会让子项尺寸为最大宽度;否则子项的宽度至少为这个尺寸
* @param horizontalSpace 水平方向的间距,只有一列时间距不会生效
* @param verticalSpace 垂直方向的间距,只有一行时间距不会生效
* @param content 直接往里丢内容即可,参考[Row],[Column]等布局的content参数
*/
@Composable
fun EasyGrid(
minItemSize: Dp,
modifier: Modifier = Modifier