チュートリアル

Jetpack Compose チュートリアル

Jetpack Compose は、Android のネイティブ UI を構築するための最新のツールキットです。Jetpack Compose は、簡潔なコード、パワフルなツール、直感的な Kotlin API により、Android での UI 開発を簡素化し、加速します。

このチュートリアルでは、宣言型の関数を持つシンプルな UI コンポーネントを作成します。XML レイアウトを編集したり、Layout Editor を使用したりすることはありません。Jetpack Compose 関数を呼び出して、必要な要素を記述するだけです。残りの処理は Compose コンパイラが行います。

フルプレビュー
フルプレビュー

レッスン 1: コンポーズ可能な関数

Jetpack Compose は、コンポーズ可能な関数に基づいて構築されています。コンポーズ可能な関数を使用すると、UI の外観とデータの依存関係を指定することにより、アプリの UI をプログラムで定義できます。UI の構築プロセス(要素の初期化や親へのアタッチなど)に注意を払う必要はありません。コンポーズ可能な関数は、関数名に @Composable アノテーションを追加するだけで作成できます。

テキスト要素を追加する

まず、Android Studio Arctic Fox の最新バージョンをダウンロードし、Empty Compose Activity テンプレートを使用してアプリを作成します。 デフォルト テンプレートにはあらかじめいくつかの Compose 要素が含まれていますが、ここでは最初から順を追って作成しましょう。

まず、onCreate メソッド内にテキスト要素を追加して、「Hello world!」というテキストを表示します。これを行うには、content ブロックを定義して、Text() 関数を呼び出します。setContent ブロックでは、コンポーズ可能な関数を呼び出すアクティビティのレイアウトを定義します。コンポーズ可能な関数は、他のコンポーズ可能な関数からのみ呼び出すことができます。

Jetpack Compose は、Kotlin コンパイラ プラグインを使用して、これらのコンポーズ可能な関数をアプリの UI 要素に変換します。たとえば、Compose UI ライブラリで定義されている Text() 関数は、画面にテキストラベルを表示します。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
プレビューを表示
プレビューを非表示

コンポーズ可能な関数を定義する

関数をコンポーズ可能にするには、@Composable アノテーションを追加します。これを試すには、名前を受け取り、その名前を使用してテキスト要素を構成する MessageCard() 関数を定義します。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
プレビューを表示
プレビューを非表示

Android Studio で関数をプレビューする

Android Studio では、アプリを Android デバイスまたはエミュレータにインストールしなくても、コンポーズ可能な関数を IDE 内でプレビューできます。コンポーズ可能な関数は、すべてのパラメータのデフォルト値を提供する必要があります。したがって、MessageCard() 関数を直接プレビューすることはできません。代わりに、PreviewMessageCard() という名前で 2 つ目の関数を作成しましょう。この関数は、適切なパラメータで MessageCard() を呼び出します。@Composable の前に @Preview アノテーションを追加します。

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
プレビューを表示
プレビューを非表示

プロジェクトを再ビルドします。PreviewMessageCard() 関数はどこでも呼び出されないのでアプリ自体に変更はありませんが、Android Studio にプレビュー ウィンドウが追加されます。このウィンドウには、@Preview アノテーションが付けられたコンポーズ可能な関数によって作成された UI 要素のプレビューが表示されます。プレビュー ウィンドウの上部にある更新ボタンをクリックすると、プレビューを随時更新できます。

図 1. Android Studio を使用したコンポーズ可能な関数のプレビュー
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Text("Hello world!")
        }
    }
}
  
プレビューを表示
プレビューを非表示
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard("Android")
        }
    }
}

@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

  
プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(name: String) {
    Text(text = "Hello $name!")
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard("Android")
}
  
プレビューを表示
プレビューを非表示
図 1. Android Studio を使用したコンポーズ可能な関数のプレビュー

レッスン 2: レイアウト

UI 要素は階層状で、要素が他の要素に含まれます。Compose 内で、他のコンポーズ可能な関数からコンポーズ可能な関数を呼び出すことで、UI 階層を作成します。

複数のテキストを追加する

ここまでの作業で、最初のコンポーズ可能な関数とプレビューを作成しました。次は、Jetpack Compose のその他の機能を確認するため、アニメーションで展開できるメッセージのリストを表示するシンプルなメッセージ画面を作成します。
手始めに、メッセージのコンポーザブルをより充実させるため、作成者の名前とメッセージの内容を表示しましょう。まず、String ではなく Message オブジェクトを受け入れるようにコンポーザブルのパラメータを変更し、MessageCard コンポーザブル内に別の Text コンポーザブルを追加する必要があります。プレビューも必ず更新してください。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
プレビューを表示
プレビューを非表示

このコードは、コンテンツ ビュー内に 2 つのテキスト要素を作成します。しかし、テキスト要素をどう配置するかに関する情報を指定していないので、それらは重なって描画され、テキストを判読できません。

列を使用する

Column 関数を使用すると、要素を垂直方向に揃えることができます。MessageCard() 関数に Column を追加します。
アイテムを水平方向に揃えるには Row を使用し、要素を積み重ねるには Box を使用します。

@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

プレビューを表示
プレビューを非表示

画像要素を追加する

メッセージ カードをもっと充実させるため、送信者のプロフィール写真を追加しましょう。リソース マネージャー を使ってフォト ライブラリから画像をインポートするか、こちらの画像を使用します。適切に構造化設計され、内部に Image コンポーザブルを含んでいる Row コンポーザブルを追加します。

@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
プレビューを表示
プレビューを非表示

レイアウトを構成する

メッセージ レイアウトの構造は適切ですが、要素が適切に配置されておらず、画像が大きすぎます。コンポーザブルを装飾または構成するため、Compose は修飾子を使用します。修飾子により、コンポーザブルのサイズ、レイアウト、外観を変更したり、高度なインタラクション(要素をクリック可能にするなど)を追加したりできます。修飾子を連結すると、より充実したコンポーザブルを作成できます。いくつかの修飾子を使用してレイアウトを改善してみましょう。

@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
プレビューを表示
プレビューを非表示
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MessageCard(Message("Android", "Jetpack Compose"))
        }
    }
}

data class Message(val author: String, val body: String)

@Composable
fun MessageCard(msg: Message) {
    Text(text = msg.author)
    Text(text = msg.body)
}

@Preview
@Composable
fun PreviewMessageCard() {
    MessageCard(
        msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
    )
}

  
プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(msg: Message) {
    Column {
        Text(text = msg.author)
        Text(text = msg.body)
    }
}

プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(msg: Message) {
    Row {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
        )
    
       Column {
            Text(text = msg.author)
            Text(text = msg.body)
        }
  
    }
  
}
  
プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(msg: Message) {
    // Add padding around our message
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = "Contact profile picture",
            modifier = Modifier
                // Set image size to 40 dp
                .size(40.dp)
                // Clip image to be shaped as a circle
                .clip(CircleShape)
        )

        // Add a horizontal space between the image and the column
        Spacer(modifier = Modifier.width(8.dp))

        Column {
            Text(text = msg.author)
            // Add a vertical space between the author and message texts
            Spacer(modifier = Modifier.height(4.dp))
            Text(text = msg.body)
        }
    }
}
  
プレビューを表示
プレビューを非表示

レッスン 3: マテリアル デザイン

Compose はマテリアル デザインの原則をサポートするために作られています。UI 要素の多くは、最初からマテリアル デザインを実装しています。このレッスンでは、マテリアル ウィジェットを使用してアプリのスタイルを設定します。

マテリアル デザインを使用する

メッセージのデザインにレイアウトが設定されましたが、まだ見栄えがよくありません。

Jetpack Compose には、すぐに使えるマテリアル デザインとその UI 要素の実装が用意されています。マテリアル デザインのスタイルを使用して、MessageCard コンポーザブルの外観を改善しましょう。

まず、プロジェクト内で作成されたマテリアル テーマ(この例では ComposeTutorialTheme)を使用して、MessageCard 関数をラップします。これは、@PreviewsetContent 関数の両方で行います。

マテリアル デザインは、色、タイポグラフィ、図形の 3 つの柱を中心に構築されています。それらを 1 つずつ追加しましょう。

注: Empty Compose Activity テンプレートは、プロジェクト用のデフォルト テーマを生成します。このテーマにより、MaterialTheme をカスタマイズできます。プロジェクトに ComposeTutorial 以外の名前を付けると、ui.theme パッケージでカスタムテーマを見つけることができます。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                MessageCard(Message("Android", "Jetpack Compose"))
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        MessageCard(
            msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
        )
    }
}

  
プレビューを表示
プレビューを非表示

ラップされたテーマの色を使って簡単にスタイルを設定できます。色が必要な場合は、どこでもテーマの値を使用できます。

タイトルにスタイルを設定し、画像に枠線を追加しましょう。

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
プレビューを表示
プレビューを非表示

タイポグラフィ

マテリアル タイポグラフィ スタイルは、テキスト コンポーザブルに追加するだけで、MaterialTheme で利用できます。

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.body2
           )
       }
   }
}

  
プレビューを表示
プレビューを非表示

図形

図形を使って最終的な仕上げを行えます。レイアウトを改善するため、メッセージにパディングも追加します。

@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.body2
               )
           }
       }
   }
}

  
プレビューを表示
プレビューを非表示

ダークテーマを有効にする

ダークテーマ(または夜間モード)を有効にすると、特に夜間に画面の明るさが抑制され、デバイスのバッテリーを節約できます。マテリアル デザインのサポートにより、Jetpack Compose はデフォルトでダークテーマを処理できます。マテリアル カラーを使用した場合、テキストと背景は暗い背景に合わせて自動的に調整されます。

複数のプレビューをファイル内でそれぞれ別個の関数として作成したり、複数のアノテーションを同じ関数に追加したりできます。

新しいプレビュー アノテーションを追加して、夜間モードを有効にしましょう。

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
       )
   }
}
  
プレビューを表示
プレビューを非表示

ライトテーマとダークテーマの色の選択は、IDE によって生成される Theme.kt ファイルで定義されます。

ここまでの作業で、1 つの画像と、スタイルが異なる 2 つのテキストを表示するメッセージ UI 要素を作成しました。この UI 要素はライトテーマとダークテーマの両方で適切に表示されます。

@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
       )
   }
}
  
プレビューを表示
プレビューを非表示
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposeTutorialTheme {
                MessageCard(Message("Android", "Jetpack Compose"))
            }
        }
    }
}

@Preview
@Composable
fun PreviewMessageCard() {
    ComposeTutorialTheme {
        MessageCard(
            msg = Message("Colleague", "Take a look at Jetpack Compose, it's great!")
        )
    }
}

  
プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
       )

       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant
           )

           Spacer(modifier = Modifier.height(4.dp))
           Text(text = msg.body)
       }
   }
}

  
プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Text(
               text = msg.body,
               style = MaterialTheme.typography.body2
           )
       }
   }
}

  
プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(msg: Message) {
   Row(modifier = Modifier.padding(all = 8.dp)) {
       Image(
           painter = painterResource(R.drawable.profile_picture),
           contentDescription = null,
           modifier = Modifier
               .size(40.dp)
               .clip(CircleShape)
               .border(1.5.dp, MaterialTheme.colors.secondary, CircleShape)
       )
       Spacer(modifier = Modifier.width(8.dp))

       Column {
           Text(
               text = msg.author,
               color = MaterialTheme.colors.secondaryVariant,
               style = MaterialTheme.typography.subtitle2
           )

           Spacer(modifier = Modifier.height(4.dp))

           Surface(shape = MaterialTheme.shapes.medium, elevation = 1.dp) {
               Text(
                   text = msg.body,
                   modifier = Modifier.padding(all = 4.dp),
                   style = MaterialTheme.typography.body2
               )
           }
       }
   }
}

  
プレビューを表示
プレビューを非表示
@Preview(name = "Light Mode")
@Preview(
    uiMode = Configuration.UI_MODE_NIGHT_YES,
    showBackground = true,
    name = "Dark Mode"
)
@Composable
fun PreviewMessageCard() {
   ComposeTutorialTheme {
       MessageCard(
           msg = Message("Colleague", "Hey, take a look at Jetpack Compose, it's great!")
       )
   }
}
  
プレビューを表示
プレビューを非表示

レッスン 4: リストとアニメーション

リストとアニメーションはアプリのあらゆる場所で使用されます。このレッスンでは、Compose を使用してリストを簡単に作成し、楽しいアニメーションを追加する方法を学びます。

メッセージのリストを作成する

メッセージが 1 つしかないチャットは少々寂しいので、チャットを変更して複数のメッセージを表示しましょう。複数のメッセージを表示する Conversation 関数を作成する必要があります。このユースケースでは、Compose の LazyColumnLazyRow. を使用できます。これらのコンポーザブルは画面上に表示される要素のみをレンダリングするように設計されているので、長いリストで効果を発揮します。同時に、XML レイアウトで RecyclerView の複雑さを回避するのに役立ちます。

以下のコード スニペットでは、LazyColumn に子として items があります。それはパラメータとして List を受け取り、そのラムダは message という名前を付けたパラメータ(任意の名前を付けることができます)を受け取ります。このパラメータは Message のインスタンスです。 簡単に言うと、このラムダは指定された List のアイテムごとに呼び出されます。このサンプル データセットをプロジェクトにインポートして、チャットをすばやくブートストラップできるようにします。

import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
プレビューを表示
プレビューを非表示

メッセージの展開中にメッセージをアニメーション化する

サンプルアプリのチャットは次第に興味深いものになりつつあります。次は、アニメーションの作成を楽しみましょう。コンテンツ サイズと背景色の両方をアニメーション化して、表示するメッセージをもっと長いテキストに展開する機能を追加します。このローカルの UI 状態を保存するには、メッセージが展開されたているかどうかをトラッキングする必要があります。この状態の変化をトラッキングするには、remember 関数と mutableStateOf 関数を使用します。

コンポーズ可能な関数は、remember を使用してローカルの状態をメモリに格納し、mutableStateOf に渡される値への変更をトラッキングすることが可能です。この状態を使用するコンポーザブル(およびその子)は、値が更新されると自動的に再描画されます。これを再コンポジションと呼びます。

Compose の状態 API(remembermutableStateOf)を使用すると、状態の変更により UI が自動的に更新されます。

注: 「by」を正しく使用するには、以下のインポートを追加する必要があります。Alt+Enter キーで追加できます。

class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.body2
                )
            }
        }
    }
}

  
プレビューを表示
プレビューを非表示

以上により、メッセージをクリックしたときに、isExpanded に基づいてメッセージ コンテンツの背景を変更できるようになりました。コンポーザブルでクリック イベントを処理するには、clickable 修飾子を使用します。単に Surface の背景色を切り替えるのではなく、背景色をアニメーション化するには、その値を段階的に MaterialTheme.colors.surface から MaterialTheme.colors.primary(またはその逆)に変更します。そのためには、animateColorAsState 関数を使用します。最後に、animateContentSize 修飾子を使用して、メッセージ コンテナのサイズを滑らかにアニメーション化します。

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.body2
                )
            }
        }
    }
}

  
プレビューを表示
プレビューを非表示
import androidx.compose.foundation.lazy.items

@Composable
fun Conversation(messages: List<Message>) {
    LazyColumn {
        items(messages) { message ->
            MessageCard(message)
        }
    }
}

@Preview
@Composable
fun PreviewConversation() {
    ComposeTutorialTheme {
        Conversation(SampleData.conversationSample)
    }
}

  
プレビューを表示
プレビューを非表示
class MainActivity : ComponentActivity() {
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContent {
           ComposeTutorialTheme {
               Conversation(SampleData.conversationSample)
           }
       }
   }
}

@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 1.dp,
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.body2
                )
            }
        }
    }
}

  
プレビューを表示
プレビューを非表示
@Composable
fun MessageCard(msg: Message) {
    Row(modifier = Modifier.padding(all = 8.dp)) {
        Image(
            painter = painterResource(R.drawable.profile_picture),
            contentDescription = null,
            modifier = Modifier
                .size(40.dp)
                .clip(CircleShape)
                .border(1.5.dp, MaterialTheme.colors.secondaryVariant, CircleShape)
        )
        Spacer(modifier = Modifier.width(8.dp))

        // We keep track if the message is expanded or not in this
        // variable
        var isExpanded by remember { mutableStateOf(false) }
        // surfaceColor will be updated gradually from one color to the other
        val surfaceColor: Color by animateColorAsState(
            if (isExpanded) MaterialTheme.colors.primary else MaterialTheme.colors.surface,
        )

        // We toggle the isExpanded variable when we click on this Column
        Column(modifier = Modifier.clickable { isExpanded = !isExpanded }) {
            Text(
                text = msg.author,
                color = MaterialTheme.colors.secondaryVariant,
                style = MaterialTheme.typography.subtitle2
            )

            Spacer(modifier = Modifier.height(4.dp))

            Surface(
                shape = MaterialTheme.shapes.medium,
                elevation = 1.dp,
                // surfaceColor color will be changing gradually from primary to surface
                color = surfaceColor,
                // animateContentSize will change the Surface size gradually
                modifier = Modifier.animateContentSize().padding(1.dp)
            ) {
                Text(
                    text = msg.body,
                    modifier = Modifier.padding(all = 4.dp),
                    // If the message is expanded, we display all its content
                    // otherwise we only display the first line
                    maxLines = if (isExpanded) Int.MAX_VALUE else 1,
                    style = MaterialTheme.typography.body2
                )
            }
        }
    }
}

  
プレビューを表示
プレビューを非表示

次の手順

お疲れ様でした。Compose チュートリアルはこれで完了です。ここでは、画像とテキストを含む展開可能なアニメーション化されたメッセージのリストを効果的に表示するシンプルなチャット画面を構築しました。画面はマテリアル デザインの原則に従ってデザインされ、ダークテーマとプレビューが含まれておいます。そして、すべてが 100 行に満たないコードで作成されています。

これまでの学習内容は次のとおりです。

  • コンポーズ可能な関数を定義する
  • コンポーザブルにさまざまな要素を追加する
  • レイアウト コンポーザブルを使用して UI コンポーネントを構造化する
  • 修飾子を使用してコンポーザブルを拡張する
  • 効果的なリストを作成する
  • 状態をトラッキングし、変更する
  • コンポーザブルにユーザー操作を追加する
  • メッセージの展開中にメッセージをアニメーション化する

これらの手順について詳細を確認するには、下記のリソースをご覧ください。

さらに学習する

セットアップ
Compose のチュートリアルを完了したら、Compose を使用してビルドを開始することができます。
パスウェイ
Jetpack Compose の学習と習得に役立つ、Codelab と動画の厳選されたパスウェイをご確認ください。