Compose の既存の UI との統合

ビューベースの UI を備えたアプリがある場合、その UI 全体を一度に書き換える必要はありません。このページでは、新しい Compose 要素を既存の UI に追加する方法を紹介します。

共有 UI の移行

Compose に徐々に移行する場合は、Compose と View システムの両方で共有 UI 要素を使用しなければならないことがあります。たとえば、アプリにカスタム CallToActionButton コンポーネントがある場合、Compose と View ベースの両方の画面で使用する必要があるかもしれません。

Compose の場合、共有 UI 要素はコンポーザブルとなり、XML を使用してスタイル設定されている要素やカスタムビューの要素であるかに関係なく、アプリ全体で再利用できます。たとえば、カスタム外部リンクのカスタム Button コンポーネントに CallToActionButton コンポーザブルを作成するとします。

View ベースの画面でコンポーザブルを使用するには、AbstractComposeView から拡張するカスタム ビューラッパーを作成する必要があります。そのオーバーライドした Content コンポーザブルに、以下の例のように Compose テーマでラップして作成したコンポーザブルを配置します。

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

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

    var text by mutableStateOf("")
    var onClick by mutableStateOf({})

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

コンポーザブル パラメータは、カスタムビュー内で可変変数になることに注意してください。これにより、カスタムの CallToActionViewButton は、ビュー バインディングなどを使用して、従来のビューのようにインフレータブルかつ使用可能になります。以下の例をご覧ください。

class ViewBindingActivity : ComponentActivity() {

    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.greeting)
            onClick = { /* Do something */ }
        }
    }
}

カスタム コンポーネントに可変状態が含まれている場合は、状態に関する信頼できる情報源をご覧ください。

アプリのテーマを移行する

マテリアル デザインは、Android アプリのテーマ設定に推奨されるデザイン システムです。

View ベースのアプリの場合、マテリアルには次の 3 つのバージョンが用意されています。

  • AppCompat ライブラリを使用したマテリアル デザイン 1(Theme.AppCompat.*
  • MDC-Android ライブラリを使用したマテリアル デザイン 2(Theme.MaterialComponents.*
  • MDC-Android ライブラリを使用したマテリアル デザイン 3(Theme.Material3.*

Compose アプリの場合、マテリアルには次の 2 つのバージョンが用意されています。

  • Compose マテリアル ライブラリを使用したマテリアル デザイン 2(androidx.compose.material.MaterialTheme
  • Compose Material 3 ライブラリを使用したマテリアル デザイン 3(androidx.compose.material3.MaterialTheme

アプリのデザイン システムで最新バージョン(マテリアル 3)を使用する必要がある場合は、そうすることをおすすめします。View と Compose の両方の移行ガイドが用意されています。

Compose で新しい画面を作成するときは、使用しているマテリアル デザインのバージョンに関係なく、Compose Material ライブラリから UI を出力するコンポーザブルの前に MaterialTheme を適用してください。マテリアル コンポーネント(ButtonText など)は MaterialTheme が設定されているかどうかに左右され、設定されていない場合の動作は未定義になります。

Jetpack Compose サンプルはすべて、MaterialTheme 上に構築されたカスタムの Compose テーマを使用しています。

詳細については、Compose でのデザイン システムXML テーマを Compose に移行をご覧ください。

WindowInsets と IME アニメーション

Compose 1.2.0 以降では、修飾子を使用してレイアウト内で WindowInsets を処理できます。IME アニメーションもサポートされています。

class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        setContent {
            MaterialTheme {
              MyScreen()
            }
        }
    }
}

@Composable
fun MyScreen() {
    Box {
        LazyColumn(
            modifier = Modifier
                .fillMaxSize() // fill the entire window
                .imePadding() // padding for the bottom for the IME
                .imeNestedScroll(), // scroll IME at the bottom
            content = { }
        )
        FloatingActionButton(
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .padding(16.dp) // normal 16dp of padding for FABs
                .navigationBarsPadding() // padding for navigation bar
                .imePadding(), // padding for when IME appears
            onClick = { }
        ) {
            Icon( /* ... */)
        }
    }
}

キーボードを表示するように UI 要素が上下方向にスクロールするアニメーション

図 2. IME アニメーション

プレゼンテーションからの状態の分離を優先する

伝統的に View はステートフルです。View は、表示する方法に加えて、表示する内容を記述するフィールドを管理します。View を Compose に変換する際は、状態ホイスティングで詳しく説明しているように、レンダリングされるデータを分離して単方向データフローを実現するよう努めてください。

たとえば、View には、「表示される」、「表示されない」、「消失した」のいずれかの状態を記述する visibility プロパティがあります。これは View の固有のプロパティです。View の可視性は他のコードによって変更されることがありますが、現在の可視性の状態を実際に認識するのは View 自身のみです。View が確実に表示されるようにするロジックはエラーが発生しやすく、多くの場合 View 自身に関連付けられます。

一方、Compose では、Kotlin の条件付きロジックを使用して、まったく異なるコンポーザブルを簡単に表示できます。

if (showCautionIcon) {
    CautionIcon(/* ... */)
}

設計上、CautionIcon は自身が表示されている理由を知る必要も考慮する必要もなく、visibility の概念はありません。それは Composition 内にあるかないかのいずれかです。

状態管理とプレゼンテーション ロジックを明確に分離することで、コンテンツを状態の UI への変換として表示する方法をより自由に変更できます。また、必要なときに状態をホイスティングできるので、コンポーザブルがより再利用しやすくなります。これは、状態の所有権の柔軟性がより高いためです。

カプセル化された再利用可能なコンポーネントを活用する

多くの場合、View 要素にはそれが存在する場所(Activity 内、Dialog 内、Fragment 内、または別の View 階層内のどこか)の情報が含まれています。それらはしばしば静的レイアウト ファイルからインフレートされるので、View の全体的な構造は非常に固定的なものになりがちです。その結果、結合が密になり、View の変更または再利用が困難になります。

たとえば、カスタム View は、特定の ID を持つ特定の型の子ビューを持っていると想定し、なんらかのアクションに応じてそのプロパティを直接変更します。これにより、それらの View 要素は互いに密結合されます。カスタム View は、子を見つけられない場合、クラッシュしたり破損したりすることがあります。また、子はカスタム View の親がないと再利用できないことがあります。

再利用可能なコンポーザブルを使用する Compose では、こうしたことはそれほど問題になりません。親は状態とコールバックを簡単に指定できるので、再利用可能なコンポーザブルは、それが使用される場所が正確にわからなくても記述できます。

var isEnabled by rememberSaveable { mutableStateOf(false) }

Column {
    ImageWithEnabledOverlay(isEnabled)
    ControlPanelWithToggle(
        isEnabled = isEnabled,
        onEnabledChanged = { isEnabled = it }
    )
}

上記の例では、3 つの部分すべてがより高度にカプセル化され、結合が疎になっています。

  • ImageWithEnabledOverlay が知る必要があるのは、現在の isEnabled の状態だけです。ControlPanelWithToggle が存在するかどうか、またそれが制御可能かどうかを知る必要はありません。

  • ControlPanelWithToggleImageWithEnabledOverlay の存在を認識しません。isEnabled の表示方法は無指定にすることも、1 つまたは複数指定することもできます。ControlPanelWithToggle は変更の必要がありません。

  • 親にとって、ImageWithEnabledOverlay または ControlPanelWithToggle のネストの深さは問題になりません。このような子には、アニメーションの変更、コンテンツのスワップアウト、他の子へのコンテンツの引き渡しなどがあります。

このパターンは「制御の反転」と呼ばれます。詳しくは、CompositionLocal のドキュメントをご覧ください。

画面サイズの変更処理

レスポンシブな View レイアウトの主な作成方法の一つは、異なるウィンドウ サイズごとに異なるリソースを用意することです。従来のように、修飾されたリソースを使用して画面レベルのレイアウトを決定することもできますが、Compose では、通常の条件付きロジックを使用してもっと簡単にコード内でレイアウトを完全に変更できます。詳しくは、各種の画面サイズのサポートをご覧ください。

さらに、アダプティブ UI の作成用に Compose が提供する手法については、アダプティブ レイアウトを作成するをご覧ください。

View でのネストされたスクロール

(両方向でネストされている)スクロール可能な View 要素とスクロール可能なコンポーザブルの間でネストされたスクロールの相互運用を実現する方法について詳しくは、ネストされたスクロールの相互運用をご覧ください。

RecyclerView の Compose

RecyclerView バージョン 1.3.0-alpha02 以降、RecyclerView のコンポーザブルのパフォーマンスが向上しています。そのメリットを得るために、RecyclerView はバージョン 1.3.0-alpha02 以降を使用してください。