XMLベースのコードをJetpack Composeに移行する方法 3-ComposeView~旧来のXMLの一部だけ利用
概要
前々回と前回の記事では、旧来の方式のXMLレイアウト・Kotlin/Javaで作成した旧来のカスタムビューをJetpack Composeのコードから、再利用する方法を記述しました。
この記事では、旧来のXMLレイアウトの一部をJetpack Composeで作る/書き直す方法を紹介します。
また、FragmentのView全てをJetpack Composeに置き換えることで、旧来のXMLレイアウトを無くすことも可能です。
旧来のコード
旧来のコードからJetpack Composeを呼び出すので、わかりやすさのために、まずは、旧来のコードを書きます。
ちなみにActivityは一部Jetpack Composeを使ったとしても変更がありません。
class ComposeViewUsageActivity : AppCompatActivity() {
companion object {
fun intent(context: Context) = Intent(context, ComposeViewUsageActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.compose_view_activity)
if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.container, ComposeViewUsageFragment.newInstance())
.commitNow()
}
}
}
Fragmentは変わります。今回は、DataBindingを使った場合の記述をしています。
class ComposeViewUsageFragment : Fragment(R.layout.compose_view_fragment) {
companion object {
fun newInstance() = ComposeViewUsageFragment()
}
private lateinit var viewModel: ComposeViewModel
private var _binding: ComposeViewFragmentBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this)[ComposeViewModel::class.java]
_binding = DataBindingUtil.bind(view)
binding.viewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
以下のレイアウトXMLがあったとして、@+id/label2
の部分をJetpack Composeに置き換えたいと思います。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.ko2ic.spike.migrate.compose.ui.ComposeViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/label1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Hello"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/label2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Hello label2!!"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/label1" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
XMLレイアウトの一部をJetpack Composeで使う方法
XMLレイアウト
@+id/label2
を @+id/compose_view
に変えています。
marginは今回はJetpack Compose側で定義するので消しました。
TextView
を androidx.compose.ui.platform.ComposeView
に変えています。
それにと伴いandroid:text
も削除しています。
androidx.compose.ui.platform.ComposeView
を使いFragmentで実装することで、Jetpack Composeの実装をXMLレイアウトに当て込むことができるようになります。
- <TextView
- android:id="@+id/label2"
+ <androidx.compose.ui.platform.ComposeView
+ android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginTop="24dp"
- android:text="Hello label2!!"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/label1" />
Fragment
以下のコードに変更するとTextViewの@+id/label1
の表示の「Hello」の下にJetpack Composeで記述した「Hello Compose!!」が表示されます。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this)[ComposeViewModel::class.java]
_binding = DataBindingUtil.bind(view)
binding.viewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner
+ binding.composeView.apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ AppCompatTheme {
+ HelloCompose()
+ }
+ }
+ }
}
+ @Composable
+ private fun HelloCompose() {
+ Text(
+ "Hello Compose!!",
+ style = MaterialTheme.typography.body2,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(24.dp)
+ .wrapContentWidth(Alignment.CenterHorizontally)
+ )
}
バインディングでXMLのidのcomopose_view
に対して、Jetpack Composeお馴染みのsetContent
を呼び出しています。
注目すべきなところは、テーマにAppCompatTheme
を設定しているところとsetViewCompositionStrategy
です。
これら二つについて次に説明します。
Themeについて
XMLレイアウトで実装してきたプロジェクトでは、すでにXMLベースでのTheme設定をしているはずです。それの再利用をした方が便利です。それを実現するためにライブラリが用意されています。
Material Design Components(MDC)を使用している場合は、以下を追加して、MdcTheme
or Mdc3Theme
を指定してください。
implementation "com.google.android.material:compose-theme-adapter:1.1.7"
or
implementation "com.google.android.material:compose-theme-adapter-3:1.0.7"
AppCompat XML テーマを使用している場合は、以下を追加して、AppCompatTheme
を指定してください。
implementation "com.google.accompanist:accompanist-appcompat-theme:0.16.0"
setViewCompositionStrategyについて
Jetpack Composeでは、ビューがウィンドウからデタッチされるたびに、Compositionを破棄するのがデフォルトになっています。setViewCompositionStrategy
は、その破棄のタイミングを変更する関数です。
ComposeView
の場合は、フラグメントのライフサイクルに合わせて破棄したいので、DisposeOnViewTreeLifecycleDestroyed
を指定します。
FragmentのViewをJetpack Composeにする方法
以下のようにComposeView
を直接インスタンスすれば良いです。XMLレイアウトを介さない際のデメリットは、BindingAdapterが機能しないことでしょうか。
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(DisposeOnViewTreeLifecycleDestroyed)
setContent {
MaterialTheme {
// In Compose world
Text("Hello Compose!")
}
}
}
}
FragmentのViewでComposeViewが二つある場合
以下のような既存のコード(LinearLayout
)の内部で、ComposeViewが複数ある場合は、savedInstanceState
が動作するようにidを指定する必要があります。
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
...
})
}
}
idは、res/values/ids.xml
などで定義しておく必要があります。
<resources>
<item name="compose_view_x" type="id" />
<item name="compose_view_y" type="id" />
</resources>
Preview
Fragment内のJetpack ComposeでもActivityの時と同じようにPreviewは普通に使えます。
@Preview(
uiMode = Configuration.UI_MODE_NIGHT_YES,
showBackground = true
)
@Preview
@Composable
fun DarkHelloComposePreview() {
AppCompatTheme {
HelloCompose()
}
}
まとめ
ComposeViewによって、既存のソースを少しづつJetpack Composeに変更していくことができます。
それもXMLレイアウトの一部だけ、フラグメントの全部、BindingAdapterをフルで使い続けたいなど、色々なパターンで少しづつ変更可能なように提供されているのが特徴でしょう。
ソーシャル経済メディア NewsPicks メンバーの発信を集約しています。公式テックブログはこちら→ tech.uzabase.com/archive/category/NewsPicks
Discussion