Jetpack Compose:DropdownMenu在 Compose对话框中忽略锚点,直接出现在屏幕底部

移动开发 2026-07-12

我正在尝试实现一个自定义的 Jetpack Compose 对话框,其中包含两个带有各自锚定DropdownMenu的元素:

  1. 一个带向下箭头图标的标题行,单击时应触发一个菜单。
  2. 一个OutlinedTextField,输入超过3 个字符后应自动触发一个建议菜单。

问题: 尽管将每个元素都包裹在Box中以作为锚点,DropdownMenu仍然不会出现在相应元素的下方。相反,它似乎忽略了布局层级,在对话框的左上角(或屏幕)渲染。

我尝试过的做法:

  • 将IconButton与 TextField分别包装在各自的Box容器中。
  • 将DropdownMenu放在触发元素的同一父级中使用。
  • 使用PopupProperties(focusable = false) 进行测试。

预期结果: 每个菜单都应在对话框的布局中锚定在各自的触发元素(图标或文本字段)上。

以下是我的源代码

@Composable
fun CustomComposeDialog(onDismiss: () -> Unit) {
    var textInput1 by remember { mutableStateOf("") }
    var textInput2 by remember { mutableStateOf("") }

    // Independent states for two different menus
    var headerMenuExpanded by remember { mutableStateOf(false) }
    val textFieldMenuExpanded = textInput1.length > 3

    Dialog(onDismissRequest = onDismiss) {
        // The container for the dialog content
        Surface(
            shape = RoundedCornerShape(16.dp),
            color = MaterialTheme.colorScheme.surface,
            modifier = Modifier.fillMaxWidth().padding(16.dp)
        ) {
            Column(
                modifier = Modifier.padding(20.dp),
                verticalArrangement = Arrangement.spacedBy(16.dp)
            ) {
                Text(
                    text = "Custom Entry",
                    style = MaterialTheme.typography.headlineSmall
                )

                // --- 1. Header with Icon & its Menu ---
                Box {
                    Row(
                        modifier = Modifier.fillMaxWidth(),
                        verticalAlignment = Alignment.CenterVertically,
                        horizontalArrangement = Arrangement.SpaceBetween
                    ) {
                        Text("Category Options")
                        IconButton(onClick = { headerMenuExpanded = true }) {
                            Icon(Icons.Default.ArrowDropDown, contentDescription = null)
                        }
                    }

                    DropdownMenu(
                        expanded = headerMenuExpanded,
                        onDismissRequest = { headerMenuExpanded = false }
                    ) {
                        DropdownMenuItem(text = { Text("Option A") }, onClick = { headerMenuExpanded = false })
                        DropdownMenuItem(text = { Text("Option B") }, onClick = { headerMenuExpanded = false })
                    }
                }

                // --- 2. First Text Field & its Anchored Menu ---
                Box {
                    OutlinedTextField(
                        value = textInput1,
                        onValueChange = { textInput1 = it },
                        label = { Text("Search or Type...") },
                        modifier = Modifier.fillMaxWidth()
                    )

                    // This menu anchors specifically to the TextField Box
                    DropdownMenu(
                        expanded = textFieldMenuExpanded,
                        onDismissRequest = { /* Controlled by text length */ },
                        // Focusable false allows the user to keep typing while menu is open
                        properties = androidx.compose.ui.window.PopupProperties(focusable = false)
                    ) {
                        val suggestions = listOf("Result 1", "Result 2", "Result 3")
                        suggestions.forEach { suggestion ->
                            DropdownMenuItem(
                                text = { Text(suggestion) },
                                onClick = { 
                                    textInput1 = suggestion 
                                    // Note: In a real app, you'd reset a flag here to hide it
                                }
                            )
                        }
                    }
                }

                // --- 3. Second Text Field ---
                OutlinedTextField(
                    value = textInput2,
                    onValueChange = { textInput2 = it },
                    label = { Text("Additional Notes") },
                    modifier = Modifier.fillMaxWidth()
                )

                // Close Button
                TextButton(
                    onClick = onDismiss,
                    modifier = Modifier.align(Alignment.End)
                ) {
                    Text("Close")
                }
            }
        }
    }
}

以下是我的当前结果!

在此输入图片描述在此输入图片描述

解决方案

选项 (1) — 针对TextField下拉

用以下内容替换你的TextField + DropdownMenu:

@OptIn(ExperimentalMaterial3Api::class)
ExposedDropdownMenuBox(
    expanded = textFieldMenuExpanded,
    onExpandedChange = { }
) {
    OutlinedTextField(
        value = textInput1,
        onValueChange = { textInput1 = it },
        label = { Text("Search or Type...") },
        modifier = Modifier
            .fillMaxWidth()
            .menuAnchor() // 🔥 IMPORTANT
    )

    ExposedDropdownMenu(
        expanded = textFieldMenuExpanded,
        onDismissRequest = { }
    ) {
        val suggestions = listOf("Result 1", "Result 2", "Result 3")
        suggestions.forEach {
            DropdownMenuItem(
                text = { Text(it) },
                onClick = { textInput1 = it }
            )
        }
    }
}

选项 (2) — 针对Header下拉(手动锚点)

对于标题行,DropdownMenu 在对话框中并不能很好地自动锚定。

var headerExpanded by remember { mutableStateOf(false) }

Box {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .clickable { headerExpanded = true },
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
    ) {
        Text("Category Options")
        Icon(Icons.Default.ArrowDropDown, null)
    }

    DropdownMenu(
        expanded = headerExpanded,
        onDismissRequest = { headerExpanded = false },
        modifier = Modifier.align(Alignment.TopEnd) // 🔥 force anchor
    ) {
        DropdownMenuItem(
            text = { Text("Option A") },
            onClick = { headerExpanded = false }
        )
        DropdownMenuItem(
            text = { Text("Option B") },
            onClick = { headerExpanded = false }
        )
    }
}

如果你仍然看到错位:

Add this:
Dialog(
    onDismissRequest = onDismiss,
    properties = DialogProperties(usePlatformDefaultWidth = false)
)
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章