Compose でビューを使用する

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

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

@Composable
fun CustomView() {
    var selectedItem by remember { mutableIntStateOf(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 オーバーロードの使用を検討してください。このオーバーロードにより、Compose は、Lazy リストの場合のように、包含するコンポジションがそのまま再利用されるときに、基盤となる View インスタンスを再利用できます。

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

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

@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 内のフラグメント

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

フラグメントを含めるには、AndroidFragment コンポーザブルを使用します。Fragment クラスを AndroidFragment に渡すと、AndroidFragment はそのクラスのインスタンスをコンポジションに直接追加します。AndroidFragment は、指定された状態の AndroidFragment を作成する fragmentState オブジェクト、新しいフラグメントに渡す arguments、コンポジションからフラグメントを提供する onUpdate コールバックも提供します。他の多くの組み込みコンポーザブルと同様に、AndroidFragmentModifier パラメータを受け取ります。これは親コンポーザブルでの位置を設定したりするために使用できます。

Compose で AndroidFragment を次のように呼び出します。

@Composable
fun FragmentInComposeExample() {
    AndroidFragment<MyFragment>()
}

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 について説明したので、その他の考慮事項についてさらに詳しく見ていきましょう。