Jetpack Compose 从入门到入门(十)

本篇介绍如何将Jetpack Compose 添加到已有应用中,毕竟大多数情况都是在现有项目中使用。

Jetpack Compose 旨在配合既有的基于 View 的界面构造方式一起使用。如果您要构建新应用,最好的选择可能是使用 Compose 实现整个界面。但是,如果您要修改现有应用,那么请不要一次性迁移整个应用,而是可以将 Compose 与现有的界面设计实现相结合。

要将 Compose 与基于 View 的界面结合使用,有两种主要方法:

  • 将 Compose 元素添加到现有界面中。具体方式是创建基于 Compose 的全新页面,或者将 Compose 元素添加到现有的 activity、fragment 或 View 布局中。
  • 将基于 View 的界面元素添加到可组合函数中。如此一来,您便可在基于 Compose 的设计中添加 Android View。

1.设置您的开发环境

首先就是引入项目中的配置,这部分可以参加本系列第一篇的项目配置。需要注意的是各版本的最低要求。

例如你使用Compose 1.2.0版本,compileSdk需要最低到32、kotlin版本最低1.7.0,gradle最低7.2.0。还有Compose 需要的依赖,例如使用 activity-compose 依赖时,androidx.activity 最低为 1.3.0。

具体需要根据项目的情况来选择相应版本,不要因为引入 Compose破坏了项目原本的稳定运行。

2.Interoperability API

1.View 中的 Compose

如果是创建基于 Compose 的全新页面,那就相对比较简单,就是我们第一篇的内容,activity 调用 setContent() 方法,传递相应的@Composable函数。

我们看一个例子,如何将 Compose 元素添加到现有fragment布局中。首先将 ComposeView 添加到 XML 布局中:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/hello_world"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Hello Android!" />

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/compose_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

代码获取 ComposeView,设置适合宿主 View 的组合策略,并调用 setContent()以使用 Compose

class ExampleFragment : Fragment() {
    
    

    private var _binding: FragmentExampleBinding? = null
    // This property is only valid between onCreateView and onDestroyView.
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
    
    
        _binding = FragmentExampleBinding.inflate(inflater, container, false)
        val view = binding.root
        binding.composeView.apply {
    
    
            setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
            setContent {
    
    
                // In Compose world
                MaterialTheme {
    
    
                    Text("Hello Compose!")
                }
            }
        }
        return view
    }

    override fun onDestroyView() {
    
    
        super.onDestroyView()
        _binding = null
    }
}

调用 setViewCompositionStrategy 方法设置其他策略或创建自己的策略。例如DisposeOnLifecycleDestroyed 策略会在 lifecycle 被销毁时处理 Compose。当 LifecycleOwner 未知时,可以使用 DisposeOnViewTreeLifecycleDestroyed

如果同一布局中存在多个 ComposeView 元素,每个元素必须具有唯一的 ID 才能使 savedInstanceState 发挥作用。ComposeView ID 在 res/values/ids.xml 文件中进行定义。

class ExampleFragment : Fragment() {
    
    

  override fun onCreateView(...): View = LinearLayout(...).apply {
    
    
      addView(ComposeView(...).apply {
    
    
          id = R.id.compose_view_x
          ...
      })
      addView(TextView(...))
      addView(ComposeView(...).apply {
    
    
          id = R.id.compose_view_y
          ...
      })
    }
  }
}

2.Compose 中的 View

Compose 界面中添加 Android View ,比如项目中已有的自定义View等。这样就不需要重新去用Compose实现一遍了。

实现起来很简单,使用AndroidView。系统会向 AndroidView 传递一个返回 View 的 lambda。AndroidView 还提供了在View填充时被调用的 update 回调。

@Composable
fun CustomView() {
    
    
    val selectedItem = remember {
    
     mutableStateOf(0) }

    // Adds view to Compose
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = {
    
     context ->
            // Creates custom view
            CustomView(context).apply {
    
    
                myView.setOnClickListener {
    
    
                    selectedItem.value = 1
                }
            }
        },
        update = {
    
     view ->
            // 视图已被填充或读取的状态更新
            // 当selectedItem在这里读取时,AndroidView将在状态改变时重新组合
            view.coordinator.selectedItem = selectedItem.value
        }
    )
}

@Composable
fun ContentExample() {
    
    
    Column(Modifier.fillMaxSize()) {
    
    
        Text("Look at this CustomView!")
        CustomView()
    }
}

如需嵌入XML布局,可以使用 androidx.compose.ui:ui-viewbinding 库提供的 AndroidViewBinding API。

@Composable
fun AndroidViewBindingExample() {
    
    
    AndroidViewBinding(ExampleLayoutBinding::inflate) {
    
    
        exampleView.setBackgroundColor(Color.GRAY)
    }
}

3.Compose 中的 fragment

添加Fragment可以使用FragmentContainerView ,将XML 属性 android:name 设置为 Fragment 的类名称。

例如定义 my_fragment_layout.xml

<androidx.fragment.app.FragmentContainerView
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/fragment_container_view"
  android:layout_height="match_parent"
  android:layout_width="match_parent"
  android:name="com.example.MyFragment" />

然后就可以获取相应的Fragment:

@Composable
fun FragmentInComposeExample() {
    
    
    AndroidViewBinding(MyFragmentLayoutBinding::inflate) {
    
    
        val myFragment = fragmentContainerView.getFragment<MyFragment>()
        // ...
    }
}

3.共享使用

逐步迁移到 Compose的过程中,可能需要在ComposeView 系统中使用共同的界面元素。比如我用Compose写了一个名为CallToActionButton的Button,如何在 ComposeView中都使用?

@Composable
fun CallToActionButton(
    text: String,
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
) {
    
    
    Button(
        colors = ButtonDefaults.buttonColors(
            backgroundColor = MaterialTheme.colors.secondary
        ),
        onClick = onClick,
        modifier = modifier,
    ) {
    
    
        Text(text)
    }
}

办法是需要创建一个继承 AbstractComposeView 的自定义VIew封装容器。

class CallToActionViewButton @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {
    
    

    var text by mutableStateOf<String>("")
    var onClick by mutableStateOf<() -> Unit>({
    
    })

    @Composable
    override fun Content() {
    
    
        YourAppTheme {
    
    
            CallToActionButton(text, onClick)
        }
    }
}

具体调用:

class ExampleActivity : Activity() {
    
    

    private lateinit var binding: ActivityExampleBinding

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        binding = ActivityExampleBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.callToAction.apply {
    
    
            text = getString(R.string.something)
            onClick = {
    
     /* Do something */ }
        }
    }
}

4.Compose 调用 Android 框架

如果需要利用 Android 框架类,比如ContextService,可以使用 CompositionLocalcurrent属性访问前者当前值。例如,以下代码通过向 Toast.makeToast 方法提供 LocalContext.current 来显示消息框消息。

@Composable
fun ToastGreetingButton(greeting: String) {
    
    
    val context = LocalContext.current
    Button(onClick = {
    
    
        Toast.makeText(context, greeting, Toast.LENGTH_SHORT).show()
    }) {
    
    
        Text("Greet")
    }
}

除了LocalContext外,还有LocalLifecycleOwnerLocalView 等供我们直接使用。


对于Compose来说,单向数据流模式非常适合它。所以MVI架构是一种不错的选择,后面有时间的话可以说说这块。

那么本篇到此结束,本系列也基本完成。官方文档总的来说还是比较详细的,本系列也算是官方文档的一个个人实践总结,希望可以帮到你。

看完后是不是从入门到入门,哈哈,具体还是需要多实践。

5.参考

猜你喜欢

转载自blog.csdn.net/qq_17766199/article/details/127183772