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

jgovgodb  于 2023-10-14  发布在  Android
关注(0)|答案(5)|浏览(115)

我如何在渲染树的全屏下制作一个可组合的,类似于Dialog可组合的工作方式?
例如,当用户单击图像时,它会显示图像的全屏预览,而不会更改当前路线。
我可以用position: absoluteposition: fixed在CSS中做到这一点,但我如何在Jetpack Compose中做到这一点呢?这可能吗
一个解决方案是在树的顶部有一个组合对象,可以从树的其他地方传递另一个组合对象作为参数,但这听起来有点混乱。当然有更好的办法。

tcbh2hod

tcbh2hod1#

据我所知,您希望能够从嵌套层次结构中进行绘制,而不受父约束的限制。
我们面临着类似的问题,并研究了PopupDropDownDialog等Composable的实现方式。
他们所做的是在Window上添加一个全新的ComposeView
因此,他们基本上是从一个空白的画布开始。
通过使其透明,它看起来像对话框/弹出窗口/下拉列表显示在顶部。
不幸的是,我们无法找到一个Composable,它为我们提供了将新的ComposeView添加到Window的功能,因此我们复制了相关部分并进行了以下操作。

@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)
    }
}

下面是一个例子,你可以使用它

@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")
                }
            }
        }
    }
}

黄色框设置了一些约束,这些约束将防止内部的Composables绘制到其边界之外。

z0qdvdin

z0qdvdin2#

使用对话框组合,我已经能够得到一个适当的全屏组合在任何嵌套之一。它比其他一些答案更快,更容易。

Dialog(
    onDismissRequest = { /* Do something when back button pressed */ },
    properties = DialogProperties(dismissOnBackPress = true, dismissOnClickOutside = false, usePlatformDefaultWidth = false)
){
    /* Your full screen content */
}
2sbarzqh

2sbarzqh3#

如果我理解正确的话,你只是不想去任何地方。像这样的东西。

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
                )
            }
        }
    }

我只是跟踪视图的类型。在我的例子中,我没有显示一个对话框,我删除了我的整个画廊,并显示一个图像。
或者,你可以在你的调用下面有一个if(viewImage)条件,并在它上面放置一个“dialog”。

h9vpoimq

h9vpoimq4#

在注意到,至少现在,我们没有任何Composable来做“简单的”全屏,我决定实现我的一个,主要是基于@foxtrotuniform6969和@ntoskrnl的想法。此外,我试图尽可能不使用平台相关的功能,然后我认为这是非常适合桌面/Android。
您可以在this GitHub repository中检查基本实现。
顺便说一下,实现的想法只是:

  • 创建一个组合对象来 Package 可以调用FullScreen组合对象的目标组合对象树;
  • 使用.onGloballyPositioned()修改器从与根屏幕尺寸匹配的辅助Box中删除全屏尺寸/大小;
  • 将全屏大小和树中创建的所有FullScreen组合存储到适当的compositionLocalOf示例中(请参见documentation)。

我试着在桌面项目中使用这个,似乎是工作,但我还没有在Android中测试。该仓库还包含一个示例。
如果可以的话,请随意在存储库中导航并发送一个pull request。:)

wz3gfoph

wz3gfoph5#

您可以使用Popup来实现以下功能:
https://developer.android.com/reference/kotlin/androidx/compose/ui/window/package-summary#Popup(androidx. compose. ui. window. PopupPositionProvider,kotlin.Function0,androidx. compose. ui. window. PopupPropertys,kotlin.Function0)
打开包含给定内容的弹出窗口。使用自定义popupPositionProvider定位弹出窗口。

@Composable fun Popup(
  popupPositionProvider: PopupPositionProvider,
  onDismissRequest: (() -> Unit)? = null,
  properties: PopupProperties = PopupProperties(),
  content: @Composable () -> Unit 
): Unit

我做了一个这样的组合。它可以从层次结构中的任何地方打开,并显示一些填充屏幕。这一个显示了一个全屏半透明的背景,然后一些内容:

@Composable
private fun MyPopup(
    modifier: Modifier = Modifier,
    onDismiss: () -> Unit = {},
    content: @Composable () -> Unit,
) {
    Popup(
        onDismissRequest = onDismiss,
        properties = PopupProperties(
            focusable = true,
            dismissOnBackPress = true,
        ),
    ) {
        Box(
            modifier.fillMaxSize(),
        ) {
            Canvas(Modifier.fillMaxSize()) {
                drawRect(
                    color = Color(0xCC7D7D7D),
                    size = size,
                )
            }
            Row(
                modifier = Modifier
                    .width(320.dp)
                    .height(300.dp)
                    .background(color = MaterialTheme.colorScheme.onPrimary)
                    .align(Alignment.Center)
            ) {
                content()
            }
        }
    }
}

像这样使用它:

MyPopup {
    Text("Hello World")
}

相关问题