Jetpack Compose 主导航中的全屏 IconList

Full-Screen IconList in Jetpack Compose Main Navigation

提问人:Daniel Hernández 提问时间:11/18/2023 最后编辑:Daniel Hernández 更新时间:11/22/2023 访问量:171

问:

考虑到我尝试使用 LazyVerticalGrid.modifier.fillMaxHeight() 和 fillMaxSize() 等修饰符而没有达到预期的结果,如何确保在 Scaffold 组件中使用时 Jetpack Compose Main 导航视图中的 IconList 占据整个屏幕?

编辑:

根据@EthanWu建议对我的代码进行了一些更改,密度不考虑使用顶栏应用的填充

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Preview
@Composable
fun HomeScreenPrev() {
    TotalApplicationTheme {
        val functionalities = listOf(
            Functionality("Search", "Search functionality", Icons.Default.ShoppingCart),
            Functionality("Settings", "Settings functionality", Icons.Filled.Face),
            Functionality("Like", "Like functionality", Icons.Default.ThumbUp),
            Functionality("Warning", "Warning functionality", Icons.Default.Warning),
            Functionality("Search", "Search functionality", Icons.Default.Search),
            Functionality("Settings", "Settings functionality", Icons.Default.Settings),
        )
        HomeScreen(functionalities = functionalities)
    }
}

@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable
fun HomeScreen(functionalities: List<Functionality>) {
    Scaffold(
        topBar = { MainTopBar() },
    ) { ActionPanel(functionalities = functionalities, modifier = Modifier.padding(it)) }
}

@Composable
fun ActionPanel(functionalities: List<Functionality>, modifier: Modifier) {
    val density = LocalDensity.current
    with(density) {
        val eachHeight =
            (LocalConfiguration.current.screenHeightDp) * density.density / (functionalities.size / 2)
        LazyVerticalGrid(
            modifier = modifier, columns = GridCells.Fixed(2)
        ) {
            items(functionalities) {
                TaskCard(it, modifier = Modifier.height(eachHeight.toDp()))
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun TaskCard(functionality: Functionality, modifier: Modifier = Modifier) {
    Card(modifier = modifier,
        shape = RectangleShape,
        border = BorderStroke(1.dp, Color.LightGray),
        colors = CardDefaults.cardColors(
            containerColor = MaterialTheme.colorScheme.tertiary, //Card background color
            contentColor = Color.Black  //Card content color,e.g.text
        ),
        onClick = {
            /*TODO*/
        }) {

        Column(
            modifier = Modifier
                .padding(8.dp)
                .fillMaxWidth()
        ) {
            Spacer(modifier = Modifier.height(8.dp))
            Icon(
                imageVector = functionality.icon,
                contentDescription = null,
                modifier = Modifier
                    .size(48.dp)
                    .align(Alignment.CenterHorizontally)
            )
            Spacer(modifier = Modifier.height(8.dp))
            Text(text = functionality.name, style = MaterialTheme.typography.titleSmall)
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = functionality.description, style = MaterialTheme.typography.bodyMedium)
        }
    }
}

当前结果

Current Screen with cards

Android Kotlin android-jetpack-compose

评论


答:

1赞 EthanWu 11/20/2023 #1

您可以使用“布局检查器”来了解布局的外观。

Android 开发人员的本指南会有所帮助。

但是,您可能会发现应该设置网格中每行的高度以达到您想要执行的操作。

为此,您可以尝试获取当前布局高度并将其除以列数,然后将每个项目的高度设置为该值。

val density = LocalDensity.current
with(density){
    val h = LocalConfiguration.current.screenHeightDp*density.density/(listItem.size/numberOfColumns)
    LazyVerticalGrid(
    ...
    ){
        items(...){
            Box(
                modifier = Modifier
                          .fillMaxWidth()
                          .height(h.toDp())
            ){
                ...
             }
        }
    }
}

结果是这样的:

enter image description here

编辑:

下面是一个示例:

val density = LocalDensity.current
Test2Theme {
    // A surface container using the 'background' color from the theme
    Scaffold(modifier = Modifier.fillMaxSize()) {
        val listItem = listOf("1", "2", "3", "4", "5", "6")
        val numberOfColumns = 2
        with(density) {
            val eachHeight =
                LocalConfiguration.current.screenHeightDp*density.density/(listItem.size/numberOfColumns)
            LazyVerticalGrid(
                columns = GridCells.Fixed(numberOfColumns),
                modifier = Modifier
                    .padding(it)
                    .fillMaxSize()
            ) {
                items(listItem) {
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                            .height(eachHeight.toDp())
                            .border(
                                BorderStroke(1.dp, Color.Black)
                            )
                    ) {
                        Text(
                            text = it,
                            textAlign = TextAlign.Center,
                            modifier = Modifier.fillMaxSize()
                        )
                    }
                }
            }
        }
    }
}

不同分辨率的结果

enter image description here

enter image description here

编辑:

以上方式仅适用于全屏。 如果要获取组件大小,Modifier.onGloballyPosition 会在合成后传递大小。

因此,要实现您的目标,就像:

val listItem = listOf("1", "2", "3", "4", "5", "6")
val numberOfColumns = 2
var eachHeight by remember {
    mutableStateOf(0.dp)
}
LazyVerticalGrid(
    columns = GridCells.Fixed(numberOfColumns),
    modifier = Modifier
        .padding(it)
        .fillMaxSize()
        .onGloballyPositioned {
            eachHeight =
                with(density) { (it.size.height / (listItem.size / numberOfColumns)).toDp() }
        }
) {
    items(listItem) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(eachHeight)
                .border(
                    BorderStroke(1.dp, Color.Black)
                )
        ) {
            ...
        }
    }
}

结果是这样的:

enter image description here

或者您可以创建自定义布局,例如

@Composable
fun MyGridLayout(
modifier: Modifier = Modifier,
row: Int,
col: Int,
content: @Composable ()-> Unit,
){
Layout(
    content = content,
    modifier = modifier
) { measurables, constraints ->
    //calculate each children's width & height
    var placeable = measurables.map {
        val width = constraints.maxWidth/col
        val height = constraints.maxHeight/row
        var c = Constraints(minWidth = width, maxWidth = width, maxHeight = height, minHeight = height)
        it.measure(c)
    }
    //then place them
    layout(constraints.maxWidth, constraints.maxHeight) {
        var yPosition = 0
        var xPosition = 0
        var mCol = 1
        placeable.forEach {
            it.placeRelative(xPosition, yPosition)
            if (mCol % col != 0) {
                xPosition += it.width
            }
            else {
                xPosition = 0
                yPosition += it.height
            }
            mCol+=1
        }
    }
}

并像这样使用它

Scaffold(
    modifier = Modifier.fillMaxSize(),
    topBar = {
        TopAppBar(
            ...
        )
    }
) {
    val listItem = listOf("1", "2", "3", "4", "5", "6")
    val numberOfColumns = 2
    MyGridLayout(
        modifier = Modifier.padding(it).fillMaxSize(),
        row = listItem.size/numberOfColumns,
        col = numberOfColumns
    ){
        listItem.forEach { str->
            Box(
                modifier = Modifier
                    .border(
                        BorderStroke(1.dp, Color.Black)
                    )
            ) {
              ...
            }
        }
    }
}

两个结果是一样的。

评论

0赞 Daniel Hernández 11/21/2023
我不完全理解使用列数来计算每个单元格高度背后的逻辑,我试图实现您在此处显示的内容,但仍然有空白
0赞 EthanWu 11/21/2023
对不起这个错误,我已经更新了答案,“numberOfColumn”应该是每行的列数,在这种情况下,它应该是 2。
0赞 Daniel Hernández 11/22/2023
它在某种程度上有效,现在的问题是密度没有考虑到 TopBar 的空间
0赞 EthanWu 11/22/2023
LocalConfiguration.current.screenHeightDp检索设备屏幕的高度。如果要获取组件大小,则该尺寸应无法正常工作。
0赞 Daniel Hernández 11/23/2023
自定义 GridLayout 完美运行