29. Compose实现瀑布流 StaggeredGrid

简介

  1. 在xml向Compose的迁移过程中遇到的问题,瀑布流没有官方的控件,但在官方的owl demo中看到了自定义的
  2. owl直接自定义操作的Layout,测量和分配位置,最外层要套一层滑动的Column
  3. 有些第三方作者写的是利用两个LazyColumn经过改造达到目的
  4. 现在体验的效果还是感觉官方的流畅一些,但也存在问题 列表大的时候可能会触发OOM ,下拉加载更多要自己想办法实现,不能结合paging来做

附上我学习Compose的代码(demo中有三个实现方案)
github

创建数据

图片的数据需要VPN,来自谷歌的demo图

private val rangeForRandom = (0..100000)


data class StaggeredItem(
    val name: String,
    val height: Int,
    val color: Color,
    val picture: String,
)


fun getStaggeredList(): MutableList<StaggeredItem> {
    val list = mutableListOf<StaggeredItem>()
    val heightList = listOf(300, 320, 350, 280)
    repeat(500) {
        val height = heightList.random()
        val picture = "https://picsum.photos/seed/${rangeForRandom.random()}/500/$height"
        list.add(
            StaggeredItem(
                name = "name $it",
                height = height,
                color = Color(
                    red = (0..255).random(),
                    blue = (0..255).random(),
                    green = (0..255).random()
                ),
                picture = picture
            )
        )
    }
    return list
}

使用

@Composable
fun ListOne() {
    val list = getStaggeredList()
    Column(
        modifier = Modifier.verticalScroll(
            rememberScrollState(),
            flingBehavior = StockFlingBehaviours.smoothScroll()
        )
    ) {
        StaggeredVerticalGrid(
            maxColumnWidth = 220.dp,
            modifier = Modifier.padding(4.dp)
        ) {
            list.forEachIndexed { _, staggeredItem ->
                StaggeredItem(staggeredItem)
            }
        }
    }
}

Item


@Composable
fun StaggeredItem(staggeredItem: StaggeredItem) {
    Box(
        modifier = Modifier
            .padding(horizontal = 4.dp, vertical = 4.dp)
            .background(color = staggeredItem.color, shape = RoundedCornerShape(9.dp))
            .fillMaxWidth()
            .height(staggeredItem.height.dp),
        contentAlignment = Alignment.Center
    ) {
        XLogger.d("---->${staggeredItem.picture}")
        Image(
            painter = rememberImagePainter(data = staggeredItem.picture),
            contentDescription = null,
            contentScale = ContentScale.FillBounds,
            modifier = Modifier
                .fillMaxSize()
                .clip(RoundedCornerShape(9.dp))
        )
        Text(
            text = staggeredItem.name,
            color = Color.White,
            fontSize = 25.sp,
            fontWeight = FontWeight.Medium
        )
    }
}

StaggeredVerticalGrid


import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.Dp
import kotlin.math.ceil


@Composable
fun StaggeredVerticalGrid(
    modifier: Modifier = Modifier,
    maxColumnWidth: Dp,
    content: @Composable () -> Unit
) {
    Layout(
        modifier = modifier,
        content = content,
    ) { measurables, constraints ->
        check(constraints.hasBoundedWidth) {
            "Unbounded width not supported"
        }
        val columns = ceil(constraints.maxWidth / maxColumnWidth.toPx()).toInt()
        val columnWidth = constraints.maxWidth / columns
        val itemConstraints = constraints.copy(maxWidth = columnWidth)
        val colHeights = IntArray(columns) { 0 } // track each column's height
        val placeables = measurables.map { measurable ->
            val column = shortestColumn(colHeights)
            val placeable = measurable.measure(itemConstraints)
            colHeights[column] += placeable.height
            placeable
        }

        val height = colHeights.maxOrNull()?.coerceIn(constraints.minHeight, constraints.maxHeight)
            ?: constraints.minHeight
        layout(
            width = constraints.maxWidth,
            height = height
        ) {
            val colY = IntArray(columns) { 0 }
            placeables.forEach { placeable ->
                val column = shortestColumn(colY)
                placeable.place(
                    x = columnWidth * column,
                    y = colY[column]
                )
                colY[column] += placeable.height
            }
        }
    }
}

private fun shortestColumn(colHeights: IntArray): Int {
    var minHeight = Int.MAX_VALUE
    var column = 0
    colHeights.forEachIndexed { index, height ->
        if (height < minHeight) {
            minHeight = height
            column = index
        }
    }
    return column
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值