Jetpack Compose Interoperability
For Compose, there are concerns about compatibility when using new technologies for existing projects. For Compose, at least, the integration with View should be seamless.
Flexibility in building uIs is guaranteed:
- For the new interface, you want to use Compose, okay.
- Compose does not support, use View.
- Existing interface does not want to move, can not move.
- For a part of an existing interface that you want to use Compose, you can.
- Some UI effects you want to reuse, ok, you can just take them and embed them.
This article is a few simple calls to each other small demo, the initial use of the time can be copied and pasted very handy.
The official document: developer.android.com/jetpack/com…
Compose all the UI in your Activity or Fragment
Use Compose in Activity
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContent { // In here, we can call composables!
MaterialTheme {
Greeting(name = "compose")}}}}@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")}Copy the code
Use Compose in Fragment
class PureComposeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup? , savedInstanceState:Bundle?).: View {
return ComposeView(requireContext()).apply {
setContent {
MaterialTheme {
Text("Hello Compose!")}}}}}Copy the code
Use Compose in the View
ComposeView is embedded in Xml:
Add ComposeView to a plain XML layout file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/hello_world"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello from XML layout" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Copy the code
To use this method, first look up by id, and then setContent:
class ComposeViewInXmlActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_compose_view_in_xml)
findViewById<ComposeView>(R.id.compose_view).setContent {
// In Compose world
MaterialTheme {
Text("Hello Compose!")}}}}Copy the code
Dynamically add ComposeView
Adding a View in code using addView() also works for ComposeView:
class ComposeViewInViewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(LinearLayout(this).apply {
orientation = VERTICAL
addView(ComposeView(this@ComposeViewInViewActivity).apply {
id = R.id.compose_view_x
setContent {
MaterialTheme {
Text("Hello Compose View 1")
}
}
})
addView(TextView(context).apply {
text = "I'm am old TextView"
})
addView(ComposeView(context).apply {
id = R.id.compose_view_y
setContent {
MaterialTheme {
Text("Hello Compose View 2")}}})})}}Copy the code
Here we add three children to the LinearLayout: two ComposeViews and a TextView in between.
The ComposeView, which acts as a bridge, is a ViewGroup that is itself a View, so it can be mixed into the View hierarchy tree, and its setContent() method opens up the Compose world. Here you can pass in the composable method to draw the UI.
Use View in Compose
Compose is now used to build the UI. When do you need to embed a View in your UI?
- There is no Compose version of the View to use, for example
AdView
.MapView
.WebView
. - There’s a piece of UI I wrote earlier that I don’t want to touch (for now or ever) and want to use directly.
- Because Compose cannot achieve the desired effect, use the View instead.
Add Android View to Compose
Example:
@Composable
fun CustomView(a) {
val state = remember { mutableStateOf(0)}//widget.Button
AndroidView(
factory = { ctx ->
//Here you can construct your View
android.widget.Button(ctx).apply {
text = "My Button"
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
setOnClickListener {
state.value++
}
}
},
modifier = Modifier.padding(8.dp)
)
//widget.TextView
AndroidView(factory = { ctx ->
//Here you can construct your View
TextView(ctx).apply {
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT)
}
}, update = {
it.text = "You have clicked the buttons: " + state.value.toString() + " times"})}Copy the code
The bridge here is AndroidView, which is a Composable method:
@Composable
fun <T : View> AndroidView(
factory: (Context) -> T,
modifier: Modifier = Modifier,
update: (T) -> Unit = NoOpUpdate
)
Copy the code
The factory takes a Context argument that builds a view. the update method is a callback, and the inflate is executed after that, as well as when the read state value changes.
Use XML layout in Compose
The aforementioned approach of using AndroidView in Compose is fine for a small amount of UI. What if you need to reuse an existing XML layout? Fear not, the View Binding is here.
It’s also easy to use:
- First you need to turn on the View Binding.
buildFeatures {
compose true
viewBinding true
}
Copy the code
- Second, you need an XML layout, such as a
complex_layout
. - Then add a Compose View Binding dependency:
androidx.compose.ui:ui-viewbinding
.
Then build, generate the Binding class, and there you go.
@Composable
private fun ComposableFromLayout(a) {
AndroidViewBinding(ComplexLayoutBinding::inflate) {
sampleButton.setBackgroundColor(Color.GRAY)
}
}
Copy the code
The ComplexLayoutBinding is a class generated based on the layout name.
The Composable method of AndroidView is still called inside the AndroidViewBinding.
In Compose, display fragments
This scenario might sound a bit weird, because Compose’s design philosophy seems to be meant to say goodbye to Fragment. In Compose’s UI, find a place to display a Fragment.
But there are so many scenarios, you might actually run into them.
Fragments are added through the FragmentManager and require a layout container. To change the ViewBinding example above, add a fragmentContainer to the layout and click to show the Fragment:
Column(Modifier.fillMaxSize()) {
Text("I'm a Compose Text!")
Button(
onClick = {
showFragment()
}
) {
Text(text = "Show Fragment")
}
ComposableFromLayout()
}
@Composable
private fun ComposableFromLayout(a) {
AndroidViewBinding(
FragmentContrainerBinding::inflate,
modifier = Modifier.fillMaxSize()
) {
}
}
private fun showFragment(a) {
supportFragmentManager
.beginTransaction()
.add(R.id.fragmentContainer, PureComposeFragment())
.commit()
}
Copy the code
There is no question of timing here, because clicking the button to display the Fragment drags the timing back. If you want to display the Fragment directly during initialization, you might throw an exception:
java.lang.IllegalArgumentException: No view found for id
Copy the code
Solutions:
@Composable
private fun ComposableFromLayout(a) {
AndroidViewBinding(
FragmentContrainerBinding::inflate,
modifier = Modifier.fillMaxSize()
) {
// here is safe
showFragment()
}
}
Copy the code
Show at least once in Your vehicle at Container View.
Theme & Style
For migrating the View app to Compose, you may need the Theme Adapter: github.com/material-co…
About using compose in view of the existing app: developer.android.com/jetpack/com…
conclusion
The combination of Compose and View relies primarily on two Bridges. It’s kind of interesting:
ComposeView
It’s actually an Android View.AndroidView
It’s actually a Composable method.
Compose and View compatibility ensures that the project can be migrated gradually and safely, much like kotlin’s Java project migration. As for the learning curve, I don’t have enough experience. Anyway, I have to learn sooner or later.