|
| 1 | +--- |
| 2 | +outline: deep |
| 3 | +--- |
| 4 | + |
| 5 | +# Using ComposeView in PopupWindow |
| 6 | + |
| 7 | +`CodeEditor` supports a number of components namely `EditorAutoCompletion`, `EditorTextActionWindow` etc, when you want to customize the layout of them, you will have two approaches: |
| 8 | + |
| 9 | +1. Using legacy XML to define the layouts |
| 10 | +2. Using Compose to define the layout with `ComposeView` |
| 11 | + |
| 12 | +In this documentation, we will dive into the approach of using Compose to define the layout for `EditorTextActionWindow`. |
| 13 | + |
| 14 | +## Challenges of attempting `ComposeView` in `PopupWindow` |
| 15 | + |
| 16 | +As the `EditorTextActionWindow` internally uses `PopupWindow`, and provides |
| 17 | +`setContentView()` to apply the view to the component. |
| 18 | + |
| 19 | +::: danger ERROR |
| 20 | +However, if you directly put the Compose content in the `PopupWindow`, an error will throw. |
| 21 | +```kotlin |
| 22 | +java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from android.widget.PopupWindow$PopupDecorView{9dfea2f V.E...... R.....I. 0,0-0,0} |
| 23 | + at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:242) |
| 24 | + at androidx.compose.ui.platform.WindowRecomposer_androidKt.access$createLifecycleAwareViewTreeRecomposer(WindowRecomposer.android.kt:1) |
| 25 | + ... |
| 26 | +``` |
| 27 | +::: |
| 28 | + |
| 29 | +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`. |
| 30 | + |
| 31 | +::: tip TIP |
| 32 | +We can directly retrieve the `ViewTreeLifecycleOwner` and `ViewTreeSavedStateRegistryOwner` via the `CompositionLocal`. |
| 33 | + |
| 34 | +```kotlin |
| 35 | +val viewTreeLifecycleOwner = LocalViewTreeLifecycleOwner.current |
| 36 | +val viewTreeSavedStateRegistryOwner = LocalViewTreeSavedStateRegistry.current |
| 37 | +``` |
| 38 | +::: |
| 39 | + |
| 40 | +## Define a `FrameLayout` |
| 41 | + |
| 42 | +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. |
| 43 | + |
| 44 | +```kotlin |
| 45 | +val composeView = ComposeView(context).apply { |
| 46 | + setContent { |
| 47 | + // the Compose content... |
| 48 | + } |
| 49 | +} |
| 50 | +val parentView = FrameLayout(context).apply { |
| 51 | + id = android.R.id.content |
| 52 | + setViewTreeLifecycleOwner(viewTreeLifecycleOwner) |
| 53 | + setViewTreeSavedStateRegistryOwner(viewTreeSavedStateRegistryOwner) |
| 54 | + layoutParams = FrameLayout.LayoutParams( |
| 55 | + ViewGroup.LayoutParams.WRAP_CONTENT, |
| 56 | + ViewGroup.LayoutParams.WRAP_CONTENT |
| 57 | + ) |
| 58 | + addView(composeView) |
| 59 | +} |
| 60 | +``` |
| 61 | + |
| 62 | +## Complete the `EditorTextActionWindow` layout |
| 63 | + |
| 64 | +Here is the example of customizing the layout for `EditorTextActionWindow` in Compose. |
| 65 | + |
| 66 | +```kotlin |
| 67 | +data class EditorTextActionItem( |
| 68 | + val label: String, |
| 69 | + val icon: ImageVector |
| 70 | +) |
| 71 | +``` |
| 72 | + |
| 73 | +```kotlin |
| 74 | +val actionItems = listOf( |
| 75 | + EditorTextActionItem( |
| 76 | + label = "Select all", |
| 77 | + icon = /* ... */ |
| 78 | + ), |
| 79 | + EditorTextActionItem( |
| 80 | + label = "Copy", |
| 81 | + icon = /* ... */ |
| 82 | + ) |
| 83 | + EditorTextActionItem( |
| 84 | + label = "Paste", |
| 85 | + icon = /* ... */ |
| 86 | + ) |
| 87 | + // ... |
| 88 | +) |
| 89 | +``` |
| 90 | + |
| 91 | +```kotlin |
| 92 | +@Composable |
| 93 | +fun EditorTextActionWindow( |
| 94 | + modifier: Modifier = Modifier, |
| 95 | + items: List<EditorTextActionItem>, |
| 96 | + onItemClick: (EditorTextActionItem) -> Unit |
| 97 | +): FrameLayout { |
| 98 | + val context = LocalContext.current |
| 99 | + val viewTreeLifecycleOwner = LocalViewTreeLifecycleOwner.current |
| 100 | + val viewTreeSavedStateRegistryOwner = LocalViewTreeSavedStateRegistry.current |
| 101 | + val composeView = ComposeView(context).apply { |
| 102 | + setContent { |
| 103 | + EditorTextActionContent(modifier, items, onItemClick) |
| 104 | + } |
| 105 | + } |
| 106 | + val parentView = FrameLayout(context).apply { |
| 107 | + id = android.R.id.content |
| 108 | + setViewTreeLifecycleOwner(viewTreeLifecycleOwner) |
| 109 | + setViewTreeSavedStateRegistryOwner(viewTreeSavedStateRegistryOwner) |
| 110 | + layoutParams = FrameLayout.LayoutParams( |
| 111 | + ViewGroup.LayoutParams.WRAP_CONTENT, |
| 112 | + ViewGroup.LayoutParams.WRAP_CONTENT |
| 113 | + ) |
| 114 | + addView(composeView) |
| 115 | + } |
| 116 | + return parentView |
| 117 | +} |
| 118 | + |
| 119 | +@Composable |
| 120 | +private fun EditorTextActionContent( |
| 121 | + modifier: Modifier = Modifier, |
| 122 | + items: List<EditorTextActionItem>, |
| 123 | + onItemClick: (EditorTextActionItem) -> Unit |
| 124 | +) { |
| 125 | + Row(modifier) { |
| 126 | + items.forEach { item -> |
| 127 | + IconButton( |
| 128 | + onClick = { onItemClick(item) } |
| 129 | + ) { |
| 130 | + Icon( |
| 131 | + imageVector = item.icon, |
| 132 | + contentDescription = item.label |
| 133 | + ) |
| 134 | + } |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +Finally, apply the layout into `EditorTextActionWindow`. |
| 141 | + |
| 142 | +```kotlin |
| 143 | +editor.getComponent<EditorTextActionWindow>().setContentView(parentView) |
| 144 | +``` |
0 commit comments