Jetpack Compose:制作全屏(绝对定位)组件

nba*_*man 22 android kotlin android-jetpack android-jetpack-compose

我怎样才能在全屏渲染树的深处制作一个可组合项,类似于Dialog可组合项的工作原理?

例如,当用户单击图像时,它会显示该图像的全屏预览,而无需更改当前路线。

我可以在 CSS 中使用position: absoluteor来完成此操作position: fixed,但如何在 Jetpack Compose 中执行此操作?有可能吗?

一种解决方案是在树的顶部有一个可组合项,可以将另一个可组合项作为参数从树中的其他位置传递,但这听起来有点混乱。当然有更好的方法。

tim*_*tim 23

据我所知,您希望能够从嵌套层次结构中进行绘制,而不受父级约束的限制。

我们遇到了类似的问题,并研究了可组合项(例如PopupDropDown和 )的实现方式Dialog

他们所做的就是ComposeViewWindow.
正因为如此,他们基本上是从一张空白的画布开始的。
通过使其透明,看起来 Dialog/Popup/DropDown 出现在顶部。

不幸的是,我们找不到一个可组合项为我们提供添加新功能的功能ComposeViewWindow因此我们复制了相关部分并进行了以下操作。

@Composable
fun FullScreen(content: @Composable () -> Unit) {
    val view = LocalView.current
    val parentComposition = rememberCompositionContext()
    val currentContent by rememberUpdatedState(content)
    val id = rememberSaveable { UUID.randomUUID() }

    val fullScreenLayout = remember {
        FullScreenLayout(
            view,
            id
        ).apply {
            setContent(parentComposition) {
                currentContent()
            }
        }
    }

    DisposableEffect(fullScreenLayout) {
        fullScreenLayout.show()
        onDispose { fullScreenLayout.dismiss() }
    }
}

@SuppressLint("ViewConstructor")
private class FullScreenLayout(
    private val composeView: View,
    uniqueId: UUID
) : AbstractComposeView(composeView.context) {

    private val windowManager =
        composeView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager

    private val params = createLayoutParams()

    override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
        private set

    init {
        id = android.R.id.content
        ViewTreeLifecycleOwner.set(this, ViewTreeLifecycleOwner.get(composeView))
        ViewTreeViewModelStoreOwner.set(this, ViewTreeViewModelStoreOwner.get(composeView))
        ViewTreeSavedStateRegistryOwner.set(this, ViewTreeSavedStateRegistryOwner.get(composeView))

        setTag(R.id.compose_view_saveable_id_tag, "CustomLayout:$uniqueId")
    }

    private var content: @Composable () -> Unit by mutableStateOf({})

    @Composable
    override fun Content() {
        content()
    }

    fun setContent(parent: CompositionContext, content: @Composable () -> Unit) {
        setParentCompositionContext(parent)
        this.content = content
        shouldCreateCompositionOnAttachedToWindow = true
    }

    private fun createLayoutParams(): WindowManager.LayoutParams =
        WindowManager.LayoutParams().apply {
            type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
            token = composeView.applicationWindowToken
            width = WindowManager.LayoutParams.MATCH_PARENT
            height = WindowManager.LayoutParams.MATCH_PARENT
            format = PixelFormat.TRANSLUCENT
            flags = WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
                WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
        }

    fun show() {
        windowManager.addView(this, params)
    }

    fun dismiss() {
        disposeComposition()
        ViewTreeLifecycleOwner.set(this, null)
        windowManager.removeViewImmediate(this)
    }
}
Run Code Online (Sandbox Code Playgroud)

这是一个如何使用它的示例

@Composable
internal fun Screen() {
    Column(
        Modifier
            .fillMaxSize()
            .background(Color.Red)
    ) {
        Text("Hello World")

        Box(Modifier.size(100.dp).background(Color.Yellow)) {
            DeeplyNestedComposable()
        }
    }
}

@Composable
fun DeeplyNestedComposable() {
    var showFullScreenSomething by remember { mutableStateOf(false) }
    TextButton(onClick = { showFullScreenSomething = true }) {
        Text("Show full screen content")
    }

    if (showFullScreenSomething) {
        FullScreen {
            Box(
                Modifier
                    .fillMaxSize()
                    .background(Color.Green)
            ) {
                Text("Full screen text", Modifier.align(Alignment.Center))
                TextButton(onClick = { showFullScreenSomething = false }) {
                    Text("Close")
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

黄色框设置了一些约束,这将防止可组合项从内部绘制到其边界之外。

在此输入图像描述


Col*_*gon 8

使用 Dialog 可组合项,我能够在任何嵌套的可组合项中获得正确的全屏可组合项。它比其他一些答案更快更容易。

Dialog(
    onDismissRequest = { /* Do something when back button pressed */ },
    properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = false, usePlatformDefaultWidth = false)
){
    /* Your full screen content */
}
Run Code Online (Sandbox Code Playgroud)

  • 这不是全屏,因为状态栏仍然可见。 (2认同)

bad*_*vok 0

如果我理解正确的话,你只是不想导航到任何地方。我有这样的事情。

 when (val viewType = viewModel.viewTypeGallery.get()) {
        is GalleryViewModel.GalleryViewType.Gallery -> {
            Gallery(viewModel, scope, installId, filePathModifier, fragment, setImageUploadType)
        }
        is GalleryViewModel.GalleryViewType.ImageViewer -> {
            Row(Modifier.fillMaxWidth()) {
                Image(
                    modifier = Modifier
                        .fillMaxSize(),
                    painter = rememberCoilPainter(viewType.imgUrl),
                    contentScale = ContentScale.Crop,
                    contentDescription = null
                )
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

我只是跟踪视图的类型。就我而言,我没有显示对话框,而是删除整个图库并显示图像。

或者,您可以在调用下方添加一个 if(viewImage) 条件,并将“对话框”放在其顶部。