大家都知道用RecyclerView GridLayoutManager 可以实现上图效果,也可以直接用 MultiType来实现。
注意:下面都是基于compose 1.1.1版本,1.2.0版本的LazyVerticalGrid已经不是试验性质的api了,api也有了变化, 大家可以见官网
在Jetpack Compose 中我们可以使用LazyVerticalGrid来实现:
LazyVerticalGrid(cells = GridCells.Fixed(2),
contentPadding = PaddingValues(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
item(span = { GridItemSpan(3) }) { TopInfo() }
itemsIndexed(imageList) { index, item ->
key(index) {
GridItemContent(item)
}
}
}
到这里感觉还行哈,接下来再加多一个类型,这次是一行3个:
LazyVerticalGrid(cells = GridCells.Fixed(6),
contentPadding = PaddingValues(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
//top
item(span = { GridItemSpan(6) }) { TopInfo() }
//一行2个
itemsIndexed(items = list1,
spans = { _, _ ->
GridItemSpan(3)
},
itemContent = { index, item ->
GridItemContent(item.second)
})
//一行3个
itemsIndexed(items = list2,
spans = { _, _ ->
GridItemSpan(2)
},
itemContent = { index, item ->
GridItemContent(item.second)
})
}
神马情况,图中红圈这两个item怎么跑到上面一行去了!! 那就换一种方式:
LazyVerticalGrid(cells = GridCells.Fixed(6),
contentPadding = PaddingValues(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp)) {
itemsIndexed(items = list,
spans = { index, item ->
if (index == 0) {
GridItemSpan(6)
} else {
if (item.first == 1) {
GridItemSpan(3)
} else {
GridItemSpan(2)
}
}
},
itemContent = { index, item ->
if (index == 0) {
TopInfo()
} else {
GridItemContent(item.second)
}
})
结果发现还是一样的,并没有用, 这里的Jetpack Compose 是1.1.1版本的,这应该是LazyVerticalGrid的bug吧!目前LazyVerticalGrid还是实验性的。 (貌似compose 1.2.0-rc3依然有这个问题,在compose 1.2.0 LazyVerticalGrid已经不是试验性的API了,另外还多了一个LazyHorizontalGrid)
注意:实验性 API 将来可能会发生变化,也可能会被完全移除。
那如果想要做到顶图的效果要怎么办呢? 可以在这个类型的最后一行填补空的可组合项,让这一类型的最后一行把整行都填充就行了。 最后上代码:
const val COLUMN_COUNT = 6
enum class ViewType {
banner, image, imageOther
}
fun getSpanCount(viewType: ViewType) = when (viewType) {
ViewType.banner -> COLUMN_COUNT//一行只有一个item
ViewType.image -> COLUMN_COUNT / 2//一行两个item
else -> COLUMN_COUNT / 3//一行3个item
}
data class GridItem(val viewType: ViewType, val thumb: String) {
//判断是否是占位item
val isEmpty: Boolean
get() = thumb.isBlank()
}
这里有3种类型,顶部的banner,一行占2个item的图片类型,一行占3个item的图片类型,在GridItem中,thumb为空就认为它是占位item,这里就要对list的数据进行处理了:
/**
* 给该类型的最后行添加占位item
*/
fun getGridListByViewType(viewType: ViewType, list: List<String>): List<GridItem> {
var columnCount = COLUMN_COUNT / getSpanCount(viewType)//一行有多少个item
var lastRowColumnCount = list.size % columnCount//这个类型的最后一行有多少个item
return if (lastRowColumnCount > 0)//给最后一行添加占位item
list.map { GridItem(viewType, it) } +
List(columnCount - lastRowColumnCount) { GridItem(viewType, "") }
else
list.map { GridItem(viewType, it) }
}
测试数据:
//添加banner
gridList.add(GridItem(ViewType.banner,"xxx"))
val imageUrlList = listOf("xxx","xxx","...")
//添加ViewType.image的items
gridList.addAll(getGridListByViewType(ViewType.image, imageUrlList))
val otherImageList: List<String> =listOf("xxx","xxx","...")
//添加ViewType.imageOther的items
gridList.addAll(getGridListByViewType(ViewType.imageOther, otherImageList))
接着是可组合项:
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun GridContent() {
val viewModel: MultiTypeViewModel = viewModel()
val gridList = viewModel.gridList
LazyVerticalGrid(cells = GridCells.Fixed(COLUMN_COUNT),
contentPadding = PaddingValues(15.dp),
verticalArrangement = Arrangement.spacedBy(15.dp),
horizontalArrangement = Arrangement.spacedBy(15.dp)
){
itemsIndexed(items = gridList,
spans = { _, item ->
GridItemSpan(getSpanCount(item.viewType))
},
itemContent = { index, item ->
key(index) {
when (item.viewType) {
ViewType.banner -> {
Banner(item.thumb)
}
else -> {
GridItemContent(item)
}
}
}
})
}
}
@Composable
fun GridItemContent(item: GridItem) {
if (item.isEmpty) { //该类型最后一行占位用的
Box() {
//Text(text = "empty")
}
} else {
Card() {
ConstraintLayout(constraintSet = imageConstraint()) {
//coil图片库的compose用法
AsyncImage(
model = item.thumb,
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier.layoutId("image")
)
}
}
}
}
/**
* image是正方形的约束
*/
fun imageConstraint(): ConstraintSet {
return ConstraintSet {
val image = createRefFor("image")
constrain(image) {
width = Dimension.percent(1f)
height = Dimension.ratio("1:1")
}
}
}
OK 搞定!!
Compose 1.2.0 版本已经修复了下面的问题了,如果大家是用的Compose 1.2.0及以上就不用往下看了,下面的是Compose 1.1.1的bug
这里记录一下:
- contentPadding: 可以达到RecyclerView的
clipToPadding="false"效果 - verticalArrangement = Arrangement.spacedBy(SPACING.dp): 可以设置item纵向的间隔
- horizontalArrangement = Arrangement.spacedBy(SPACING.dp):可以设置item横向的间隔
额 还没搞定,结果发现它又有个bug,滑动到最下面发现有一行空白。
后来发现把verticalArrangement = Arrangement.spacedBy(10.dp) 这个参数去掉就好了,但是去掉这个item上下直接的间距就没有了,没办法最后只能去掉它,在item里面的可组合项里设置padding吧!
const val SPACING = 15
LazyVerticalGrid(cells = GridCells.Fixed(COLUMN_COUNT),
contentPadding = PaddingValues(top = SPACING.dp, start = SPACING.dp, end = SPACING.dp),
horizontalArrangement = Arrangement.spacedBy(SPACING.dp)
){
xxx
}
@Composable
fun GridItemContent(item: GridItem) {
if (item.isEmpty) { //该类型最后一行占位用的
Box(modifier = Modifier.padding(bottom = SPACING.dp)) {
}
} else {
Card(modifier = Modifier.padding(bottom = SPACING.dp)) {
xxx
}
}
}