Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る
チュートリアル

Jetpack Compose の基本

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

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

フルプレビュー
注: Jetpack Compose は現在、アルファ版です。API サーフェスは未確定であり、変更が加えられる予定です。
フルプレビュー

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

Jetpack Compose は、コンポーズ可能な関数に基づいて構築されています。これらの関数を使用すると、UI の形状とデータの依存関係を記述することで、アプリの UI をプログラムで定義できます。UI の構築プロセスを重視する必要はありません。関数名に @Composable アノテーションを追加するだけでコンポーズ可能な関数を作成できます。

テキスト要素を追加する

まず、Jetpack Compose の設定手順に沿って、空の Compose アクティビティ テンプレートを使用してアプリを作成します。デフォルト テンプレートには Compose 要素の一部がすでに含まれていますが、ここでは最初から順に作成しましょう。まず、「Greeting」関数と「Default Preview」関数を削除し、MainActivity から setContent ブロックを削除します。アクティビティは空白のままにします。空のアプリをコンパイルして実行します。

次に、空のアクティビティにテキスト要素を追加します。これを行うには、content ブロックを定義し、Text() 関数を呼び出します。

setContent ブロックでアクティビティのレイアウトを定義します。レイアウト コンテンツを XML ファイルで定義するのではなく、コンポーズ可能な関数を呼び出します。Jetpack Compose は、カスタム Kotlin コンパイラ プラグインを使用して、これらのコンポーズ可能な関数をアプリの UI 要素に変換します。たとえば、Text() 関数は Compose UI ライブラリで定義されています。この関数を呼び出して、アプリ内でテキスト要素を宣言します。

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

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

コンポーズ可能な関数は、他のコンポーズ可能な関数のスコープ内からのみ呼び出すことができます。関数をコンポーズ可能にするには、@Composable アノテーションを追加します。これを試すには、名前を受け取る Greeting() 関数を定義し、その名前を使用してテキスト要素を設定します。

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

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

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

Android Studio の現在の Canary ビルドでは、IDE 内でコンポーズ可能な関数をプレビューできます。アプリを Android デバイスまたはエミュレータにダウンロードする必要はありません。主な制限として、コンポーズ可能な関数にはパラメータを使用できません。このため、Greeting() 関数を直接プレビューすることはできません。代わりに、PreviewGreeting() という 2 番目の関数を作成します。この関数は、適切なパラメータで Greeting() を呼び出します。@Composable の前に @Preview アノテーションを追加します。

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

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

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

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

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

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

レッスン 2: レイアウト

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

テキストから始める

アクティビティに戻り、Greeting() 関数を新しい NewsStory() 関数に置き換えます。残りのチュートリアルでは、その NewsStory() 関数を修正して使用し、Activity コードはもう変更しません。

アプリからは呼び出さない別個のプレビュー関数を作成することをおすすめします。専用のプレビュー関数によって、パフォーマンスが改善され、複数のプレビューを後で簡単に設定できるようになります。デフォルトのプレビュー関数を作成し、NewsStory() 関数の呼び出しのみを行います。このチュートリアルで、NewsStory() を変更すると、プレビューに変更が反映されます。

このコードは、コンテンツ ビュー内に 3 つのテキスト要素を作成します。ただし、3 つのテキスト要素の配置方法に関する情報を提供していないため、それらは重なって描画され、テキストを読み取ることができません。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
プレビューを表示
プレビューを非表示

列を使用する

Column 関数を使用すると、要素を垂直方向にスタックできます。NewsStory() 関数に Column を追加します。

デフォルト設定では、すべての子が間隔を空けずに 1 つずつ順にスタックされます。コンテンツ ビューでこの列自体は、左上隅に配置されます。

@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示

スタイル設定を列に追加する

Column の呼び出しにパラメータを渡して、列のサイズと位置、列の子の配置方法を設定できます。

設定には次の意味があります。

  • modifier: レイアウトを設定できます。この場合、Modifier.padding 修飾子を適用して、周囲のビューから列をインセットします。
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示

画像を追加する

テキストの上に画像を追加します。リソース マネージャーを使用して、この写真をアプリのドローアブル リソースに追加します。名前は header とします。

次に、NewsStory() 関数を変更します。Image() の呼び出しを追加して、画像を Column に格納します。これらのコンポーザブルは foundation パッケージに含まれています。追加が必要な場合もあります。Jetpack Compose の設定手順をご覧ください。画像の縦横比は正しく設定されませんが、次のステップで修正するため問題ありません。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示

画像がレイアウトに追加されますが、サイズはまだ適切ではありません。画像のスタイルを設定するには、サイズ ModifierImage() の呼び出しに渡します。

  • preferredHeight(180.dp): 画像の高さを指定します。
  • fillMaxWidth(): 画像の幅が、画像を配置するレイアウトの幅いっぱいになるように調整するよう指定します。

contentScale パラメータを Image() に渡す必要もあります。

  • contentScale = ContentScale.Crop: 画像の幅が、列の幅いっぱいになるように調整するよう指定します。必要に応じて、高さは切り捨てられます。
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示

画像と見出しを区切るには、Spacer を追加します。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsStory()
        }
    }
}

@Composable
fun NewsStory() {
    Text("A day in Shark Fin Cove")
    Text("Davenport, California")
    Text("December 2018")
}

@Preview
@Composable
fun DefaultPreview() {
    NewsStory()
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    Column {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)

    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        Image(image)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示

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

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

形状を適用する

マテリアル デザイン システムの柱の 1 つは Shape です。画像の角を丸めるには、clip() 関数を使用します。

Shape は非表示ですが、画像は Shape に合わせて切り抜かれているため、少し角が丸くなります。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示

テキストのスタイルを設定する

Compose を使用すると、マテリアル デザインの原則を簡単に活用できます。作成したコンポーネントに MaterialTheme を適用します。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
プレビューを表示
プレビューを非表示

変更はわずかですが、テキストは MaterialTheme のデフォルトのテキスト スタイルを使用するようになりました。次に、各テキスト要素に特定の段落スタイルを適用します。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
プレビューを表示
プレビューを非表示

この場合、記事のタイトルは非常に短いものでした。しかし、記事のタイトルが長すぎるような場合、それによってアプリの外観が損なわれることは望ましくありません。最初のテキスト要素を変更してみます。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
プレビューを表示
プレビューを非表示

テキスト要素を設定して最大長を 2 行に設定します。この設定は、テキストの長さが制限内に収まる場合は何も行いませんが、長すぎる場合は、表示されるテキストを自動的に切り捨てます。

@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        val imageModifier = Modifier
            .preferredHeight(180.dp)
            .fillMaxWidth()
            .clip(shape = RoundedCornerShape(4.dp))

        Image(image, modifier = imageModifier,
                  contentScale = ContentScale.Crop)
        Spacer(Modifier.preferredHeight(16.dp))

        Text("A day in Shark Fin Cove")
        Text("Davenport, California")
        Text("December 2018")
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove")
            Text("Davenport, California")
            Text("December 2018")
        }
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text("A day in Shark Fin Cove",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
プレビューを表示
プレビューを非表示
@Composable
fun NewsStory() {
    val image = imageResource(R.drawable.header)
    MaterialTheme {
        val typography = MaterialTheme.typography
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            val imageModifier = Modifier
                .preferredHeight(180.dp)
                .fillMaxWidth()
                .clip(shape = RoundedCornerShape(4.dp))

            Image(image, modifier = imageModifier,
                      contentScale = ContentScale.Crop)
            Spacer(Modifier.preferredHeight(16.dp))

            Text(
                "A day wandering through the sandhills " +
                     "in Shark Fin Cove, and a few of the " +
                     "sights I saw",
                style = typography.h6,
                maxLines = 2,
                overflow = TextOverflow.Ellipsis)
            Text("Davenport, California",
                style = typography.body2)
            Text("December 2018",
                style = typography.body2)
        }
    }
}
  
プレビューを表示
プレビューを非表示

すべて完了

これで終わりです。Compose の基本を学習しました。

学習した内容

  • コンポーズ可能な関数の定義
  • 列の使用とスタイル設定によるレイアウトの改善
  • マテリアル デザインの原則によるアプリのスタイル設定

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

さらに学習する

パスウェイ

Jetpack Compose の学習と習得に役立つ、Codelab と動画の厳選されたパスウェイをご確認ください。

ガイド

このチュートリアルで参照している Jetpack Compose API の詳細については、ドキュメントをご覧ください。

サンプル

Compose の優れた機能の使用方法を示すサンプルアプリをご覧ください。