Compose でビューを使用する

Compose UI に Android View 階層を含めることができます。このアプローチは特に、Compose でまだ利用できない UI 要素(AdView など)を使用する場合に便利です。このアプローチでは、設計したカスタムビューを再利用することもできます。

ビュー要素または階層を含めるには、AndroidView コンポーザブルを使用します。AndroidView には、View を返すラムダが渡されます。AndroidView には、ビューがインフレートされるときに呼び出される update コールバックも用意されています。AndroidView は、コールバック内で読み込まれた State が変更されるたびに、再コンポーズを行います。AndroidView は、他の多くの組み込みコンポーザブルと同様に、Modifier パラメータを受け取ります。これは親コンポーザブルでの位置を設定したりするために使用できます。

@Composable
fun CustomView() {
    var selectedItem by remember { mutableStateOf(0) }

    // Adds view to Compose
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = { context ->
            // Creates view
            MyView(context).apply {
                // Sets up listeners for View -> Compose communication
                setOnClickListener {
                    selectedItem = 1
                }
            }
        },
        update = { view ->
            // View's been inflated or state read in this block has been updated
            // Add logic here if necessary

            // As selectedItem is read here, AndroidView will recompose
            // whenever the state changes
            // Example of Compose -> View communication
            view.selectedItem = selectedItem
        }
    )
}

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

AndroidView(ビュー バインディングを使用)

XML レイアウトを埋め込むには、androidx.compose.ui:ui-viewbinding ライブラリで提供される AndroidViewBinding API を使用します。そのためには、プロジェクトでビュー バインディングを有効にする必要があります。

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

Lazy リストの AndroidView

Lazy リスト(LazyColumnLazyRowPager など)で AndroidView を使用している場合は、バージョン 1.4.0-rc01 で導入された AndroidView オーバーロードの使用を検討してください。このオーバーロードにより、含まれるコンポジションが Lazy リストのように再利用されるとき、Compose は基になる View インスタンスを再利用できるようになります。

この AndroidView のオーバーロードにより、次の 2 つのパラメータが追加されます。

  • onReset - View が再利用されることを知らせるために呼び出されるコールバック。ビューの再利用を有効にするには、これを null 以外にする必要があります。
  • onRelease(省略可)- View がコンポジションを終了し、再び再利用されないことを通知するために呼び出されるコールバック。

@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun AndroidViewInLazyList() {
    LazyColumn {
        items(100) { index ->
            AndroidView(
                modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
                factory = { context ->
                    MyView(context)
                },
                update = { view ->
                    view.selectedItem = index
                },
                onReset = { view ->
                    view.clear()
                }
            )
        }
    }
}

Compose のフラグメント

AndroidViewBinding コンポーザブルを使用して、Fragment を Compose に追加します。AndroidViewBinding には、コンポーザブルがコンポジションから離れたときにフラグメントを削除するなど、フラグメント固有の処理があります。

そのためには、FragmentContainerView を含む XML を Fragment のホルダーとしてインフレートします。

たとえば、my_fragment_layout.xml が定義されている場合、android:name XML 属性を Fragment のクラス名に置き換えて次のようなコードを使用できます。

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

Compose で、このフラグメントを次のようにインフレートします。

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

同じレイアウトで複数のフラグメントを使用する必要がある場合は、FragmentContainerView ごとに一意の ID を定義していることを確認してください。

Compose から Android フレームワークを呼び出す

Compose は、Android フレームワーク クラス内で動作します。たとえば、ActivityFragment などの Android ビューのクラスでホストされているため、Context、システム リソース、ServiceBroadcastReceiver などの Android フレームワーク クラスを使用することがあります。

システム リソースについて詳しくは、Compose のリソースをご覧ください。

コンポジション ローカル

CompositionLocal クラスを使用すると、コンポーズ可能な関数を通じて暗黙的にデータを渡すことができます。通常、UI ツリーの特定のノードに値が設定されます。その値は、コンポーズ可能な関数のパラメータとして CompositionLocal を宣言しなくても、コンポーズ可能な子孫で使用できます。

CompositionLocal は、Compose の Android フレームワーク タイプ(ContextConfiguration または Compose コードがホストされている View など)の値を対応する LocalContextLocalConfigurationLocalView に伝播するために使用されます。 IDE のオートコンプリートで検出しやすいように CompositionLocal クラスの先頭に Local が付いています。

CompositionLocal の現在の値にアクセスするには、current プロパティを使用します。たとえば、下記のコードは 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")
    }
}

より詳しい例については、このドキュメントの最後にある事例紹介: BroadcastReceivers をご覧ください。

その他の操作

必要な操作のためのユーティリティが定義されていない場合は、一般的な Compose ガイドラインに従い、データは下に流れ、イベントは上に流れるようにすることをおすすめします(詳しくは Compose における考え方をご覧ください)。たとえば、このコンポーザブルは別のアクティビティを起動します。

class OtherInteractionsActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // get data from savedInstanceState
        setContent {
            MaterialTheme {
                ExampleComposable(data, onButtonClick = {
                    startActivity(Intent(this, MyActivity::class.java))
                })
            }
        }
    }
}

@Composable
fun ExampleComposable(data: DataExample, onButtonClick: () -> Unit) {
    Button(onClick = onButtonClick) {
        Text(data.title)
    }
}

事例紹介: ブロードキャスト レシーバ

Compose で移行または実装する機能の現実的な例として、CompositionLocal副作用を紹介します。たとえば、BroadcastReceiver はコンポーズ可能な関数から登録する必要があります。

このソリューションでは、LocalContext(現在のコンテキストを使用するため)、rememberUpdatedState、および DisposableEffect の副作用を利用します。

@Composable
fun SystemBroadcastReceiver(
    systemAction: String,
    onSystemEvent: (intent: Intent?) -> Unit
) {
    // Grab the current context in this part of the UI tree
    val context = LocalContext.current

    // Safely use the latest onSystemEvent lambda passed to the function
    val currentOnSystemEvent by rememberUpdatedState(onSystemEvent)

    // If either context or systemAction changes, unregister and register again
    DisposableEffect(context, systemAction) {
        val intentFilter = IntentFilter(systemAction)
        val broadcast = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                currentOnSystemEvent(intent)
            }
        }

        context.registerReceiver(broadcast, intentFilter)

        // When the effect leaves the Composition, remove the callback
        onDispose {
            context.unregisterReceiver(broadcast)
        }
    }
}

@Composable
fun HomeScreen() {

    SystemBroadcastReceiver(Intent.ACTION_BATTERY_CHANGED) { batteryStatus ->
        val isCharging = /* Get from batteryStatus ... */ true
        /* Do something if the device is charging */
    }

    /* Rest of the HomeScreen */
}

次のステップ

ビューで Compose を使用する際と Compose でビューを使用する際の相互運用 API について説明したので、その他の考慮事項についてさらに詳しく見ていきましょう。