Kotlin Compose 自定义布局 StaggeredGrid

Posted 安果移不动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin Compose 自定义布局 StaggeredGrid相关的知识,希望对你有一定的参考价值。

要实现效果

 是可以往左边滑动的。

第一步我们写card 模块

就是里面的子布局

package com.anguomob.jecpack.activity.compose.layout

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.anguomob.jetpack.ui.theme.ComposeTheme

@Composable
fun StaggeredGrid(modifier: Modifier = Modifier) 
    ComposeTheme 
        Chip(modifier =modifier , text = "Arts % Crafts")
    


@Composable
fun Chip(modifier: Modifier, text: String) 
    //一个卡片 圆角,里面包含一个Row,第一列是Box 第二例是文本
    Card(
        modifier = modifier,
        //Hairline 发际线 默认一个像素
        border = BorderStroke(color = Color.Black, width = Dp.Hairline),
        shape = RoundedCornerShape(8.dp)
    ) 
        Row(
            modifier = Modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp, bottom = 8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) 
            Box(
                modifier = Modifier
                    .size(16.dp)
                    .background(color = MaterialTheme.colors.secondary)

            )
            Spacer(modifier = Modifier.width(4.dp))
            Text(text)
        
    

效果

再进行分析这个大的布局

 

经过一系列的计算 父布局就是这样

@Composable
fun StaggeredGrid(modifier: Modifier = Modifier, rows: Int = 3, content: @Composable () -> Unit) 
    Layout(modifier = modifier, content = content)  measurables, constraints ->
        //用于保存每行的宽度数值
        val rowWidths = IntArray(rows)  0 
        //用于保存每行的高度数值
        val rowHeightss = IntArray(rows)  0 

        val placeables = measurables.mapIndexed  index, measurable ->
            //测量每一个元素
            val placeable = measurable.measure(constraints)
            //计算每一行的宽度与高度
            //元素下标。假设总共11个元素
            //index 0 1 2 3 4 5 6 7 8 9 10
            //行数 假设3行
            //rows=3
            //保存行高数组的下标值:
            //row:0,1,2
            val row = index % rows
            //一行的宽度等于这一行所有元素宽度之和
            rowWidths[row] += placeable.width
            //一行的高度应该是最高的那个元素的高度
            rowHeightss[row] = kotlin.math.max(rowHeightss[row], placeable.height)
            placeable
        
        //计算表格的高度
        //表格的宽度应该是所有行当中最宽的哪一行的宽度
        val width = rowWidths.maxOrNull() ?: constraints.minWidth
        //表格的高度应该是所有高度之和
        val height = rowHeightss.sumOf  it 
        //设置每一行的y坐标
        val rowY = IntArray(rows)  0 
        //索引从1开始,因为第一行y坐标为0,rows[0]=0
        for (i in 1 until rows) 
//            rowY[1] = rowY[0] + rowHeightss[0]
            rowY[i] = rowY[i - 1] + rowHeightss[i - 1]
        

        layout(width, height) 
            val rowX = IntArray(rows)  0 
            placeables.forEachIndexed  index, placeable ->
                //index 0 1 2 3 4 5 6 7 8 9 10
                //行数 假设3行
                //rows=3
                //保存行高数组的下标值:
                //row:0,1,2
                val row = index % rows
                placeable.placeRelative(x = rowX[row], y = rowY[row])
                //第一列 x全部设置为0 下一列的x坐标要累加上前面元素的宽度
                //设置下一列的x坐标
                rowX[row] += placeable.width
            
        
    

 我们来生成一些假数据去测试他

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(80, 100, 60, 70)
    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

这里pictrue还有color没用到

实际使用

@Composable
fun StaggeredGridBodyContent(modifier: Modifier = Modifier) 
    val dataList = getStaggeredList()
    Row(
        modifier = modifier
            .background(color = Color.LightGray)
            .padding(6.dp)
            .horizontalScroll(rememberScrollState())
            .verticalScroll(rememberScrollState()),
    ) 

        StaggeredGrid(modifier = Modifier, rows = 20) 
            for (data in dataList) 
                Chip(
                    modifier = Modifier
                        .padding(8.dp)
                        .height(data.height.dp), text = data.name
                )
            

        

    

效果

能上下滑动,能左右滑动,但是不可以斜着滑动。宽度也可以千奇百怪一点

如果你有充分新的想象力就可以变得更有趣

完整代码

package com.anguomob.jecpack.activity.compose.layout

import androidx.compose.foundation.*
import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.anguomob.jetpack.ui.theme.ComposeTheme

@Composable
fun StaggeredGrid(modifier: Modifier = Modifier, rows: Int = 3, content: @Composable () -> Unit) 
    Layout(modifier = modifier, content = content)  measurables, constraints ->
        //用于保存每行的宽度数值
        val rowWidths = IntArray(rows)  0 
        //用于保存每行的高度数值
        val rowHeightss = IntArray(rows)  0 

        val placeables = measurables.mapIndexed  index, measurable ->
            //测量每一个元素
            val placeable = measurable.measure(constraints)
            //计算每一行的宽度与高度
            //元素下标。假设总共11个元素
            //index 0 1 2 3 4 5 6 7 8 9 10
            //行数 假设3行
            //rows=3
            //保存行高数组的下标值:
            //row:0,1,2
            val row = index % rows
            //一行的宽度等于这一行所有元素宽度之和
            rowWidths[row] += placeable.width
            //一行的高度应该是最高的那个元素的高度
            rowHeightss[row] = kotlin.math.max(rowHeightss[row], placeable.height)
            placeable
        
        //计算表格的高度
        //表格的宽度应该是所有行当中最宽的哪一行的宽度
        val width = rowWidths.maxOrNull() ?: constraints.minWidth
        //表格的高度应该是所有高度之和
        val height = rowHeightss.sumOf  it 
        //设置每一行的y坐标
        val rowY = IntArray(rows)  0 
        //索引从1开始,因为第一行y坐标为0,rows[0]=0
        for (i in 1 until rows) 
//            rowY[1] = rowY[0] + rowHeightss[0]
            rowY[i] = rowY[i - 1] + rowHeightss[i - 1]
        

        layout(width, height) 
            val rowX = IntArray(rows)  0 
            placeables.forEachIndexed  index, placeable ->
                //index 0 1 2 3 4 5 6 7 8 9 10
                //行数 假设3行
                //rows=3
                //保存行高数组的下标值:
                //row:0,1,2
                val row = index % rows
                placeable.placeRelative(x = rowX[row], y = rowY[row])
                //第一列 x全部设置为0 下一列的x坐标要累加上前面元素的宽度
                //设置下一列的x坐标
                rowX[row] += placeable.width
            
        
    


@Composable
fun Chip(modifier: Modifier, text: String, color: Color = MaterialTheme.colors.secondary) 
    //一个卡片 圆角,里面包含一个Row,第一列是Box 第二例是文本
    Card(
        modifier = modifier,
        //Hairline 发际线 默认一个像素
        border = BorderStroke(color = Color.Black, width = Dp.Hairline),
        shape = RoundedCornerShape(8.dp)
    ) 
        Row(
            modifier = Modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp, bottom = 8.dp),
            verticalAlignment = Alignment.CenterVertically
        ) 
            Box(
                modifier = Modifier
                    .size(16.dp)
                    .background(color = color)

            )
            Spacer(modifier = Modifier.width(4.dp))
            Text(text)
        
    


private val rangeForRandom = (0..100000)


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


fun getStaggeredList(): MutableList<StaggeredItem> 
    val list = mutableListOf<StaggeredItem>()
    val heightList = listOf(80, 100, 60, 70)
    val widthList = listOf(40, 50, 60, 70)
    repeat(500) 
        val height = heightList.random()
        val width = widthList.random()
        val picture = "https://picsum.photos/seed/$rangeForRandom.random()/500/$height"
        list.add(
            StaggeredItem(
                name = "name $it",
                height = height,
                width = width,
                color = Color(
                    red = (0..255).random(),
                    blue = (0..255).random(),
                    green = (0..255).random()
                ),
                picture = picture
            )
        )
    
    return list



@Composable
fun StaggeredGridBodyContent(modifier: Modifier = Modifier) 
    val dataList = getStaggeredList()
    Row(
        modifier = modifier
            .background(color = Color.LightGray)
            .padding(6.dp)
            .horizontalScroll(rememberScrollState())
            .verticalScroll(rememberScrollState())
    ) 

        StaggeredGrid(modifier = Modifier, rows = 20) 
            for (data in dataList) 
                Chip(
                    modifier = Modifier
                        .padding(8.dp)
                        .height(data.height.dp)
                        .width(data.width.dp),
                    text = data.name,
                    color = data.color
                )
            

        

    

 最后效果

 

以上是关于Kotlin Compose 自定义布局 StaggeredGrid的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin 写自定义 ViewGroup

Kotlin Compose 自定义 CompositionLocalProvider CompositionLocal

Kotlin 元编程之 KSP 实战:通过自定义注解配置Compose导航路由

Kotlin 元编程之 KSP 实战:通过自定义注解配置Compose导航路由

利用 JetPack Compose 手写一个自定义布局

Jetpack Compose 自定义布局以及固有特性测量