Using ComposeView in PopupWindow
CodeEditor supports a number of components namely EditorAutoCompletion, EditorTextActionWindow etc, when you want to customize the layout of them, you will have two approaches:
- Using legacy XML to define the layouts
- Using Compose to define the layout with
ComposeView
In this documentation, we will dive into the approach of using Compose to define the layout for EditorTextActionWindow.
Challenges of attempting ComposeView in PopupWindow
As the EditorTextActionWindow internally uses PopupWindow, and provides setContentView() to apply the view to the component.
ERROR
However, if you directly put the Compose content in the PopupWindow, an error will throw.
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.PopupWindow$PopupDecorView{9dfea2f V.E...... R.....I. 0,0-0,0}
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:242)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.access$createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:1)
...By default, the PopupWindow cannot be worked with Compose. To solve this, we need a FrameLayout to be the parent layout of the PopupWindow, we then use this FrameLayout to contain the Compose content, and apply the ViewTreeLifecycleOwner and ViewTreeSavedStateRegistryOwner to the FrameLayout.
TIP
We can directly retrieve the ViewTreeLifecycleOwner and ViewTreeSavedStateRegistryOwner via the CompositionLocal.
val viewTreeLifecycleOwner = LocalViewTreeLifecycleOwner.current
val viewTreeSavedStateRegistryOwner = LocalViewTreeSavedStateRegistry.currentDefine a FrameLayout
We will use android.R.id.content for the content child of the View as it is neccessary to let Compose find the content child.
val composeView = ComposeView(context).apply {
setContent {
// the Compose content...
}
}
val parentView = FrameLayout(context).apply {
id = android.R.id.content
setViewTreeLifecycleOwner(viewTreeLifecycleOwner)
setViewTreeSavedStateRegistryOwner(viewTreeSavedStateRegistryOwner)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
addView(composeView)
}Complete the EditorTextActionWindow layout
Here is the example of customizing the layout for EditorTextActionWindow in Compose.
data class EditorTextActionItem(
val label: String,
val icon: ImageVector
)val actionItems = listOf(
EditorTextActionItem(
label = "Select all",
icon = /* ... */
),
EditorTextActionItem(
label = "Copy",
icon = /* ... */
)
EditorTextActionItem(
label = "Paste",
icon = /* ... */
)
// ...
)@Composable
fun EditorTextActionWindow(
modifier: Modifier = Modifier,
items: List<EditorTextActionItem>,
onItemClick: (EditorTextActionItem) -> Unit
): FrameLayout {
val context = LocalContext.current
val viewTreeLifecycleOwner = LocalViewTreeLifecycleOwner.current
val viewTreeSavedStateRegistryOwner = LocalViewTreeSavedStateRegistry.current
val composeView = ComposeView(context).apply {
setContent {
EditorTextActionContent(modifier, items, onItemClick)
}
}
val parentView = FrameLayout(context).apply {
id = android.R.id.content
setViewTreeLifecycleOwner(viewTreeLifecycleOwner)
setViewTreeSavedStateRegistryOwner(viewTreeSavedStateRegistryOwner)
layoutParams = FrameLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
addView(composeView)
}
return parentView
}
@Composable
private fun EditorTextActionContent(
modifier: Modifier = Modifier,
items: List<EditorTextActionItem>,
onItemClick: (EditorTextActionItem) -> Unit
) {
Row(modifier) {
items.forEach { item ->
IconButton(
onClick = { onItemClick(item) }
) {
Icon(
imageVector = item.icon,
contentDescription = item.label
)
}
}
}
}Finally, apply the layout into EditorTextActionWindow.
editor.getComponent<EditorTextActionWindow>().setContentView(parentView)