Jetpack Compose

【書籍特別編】Jetpack Compose 開発シリーズ | 第3回 ボタンをタップできるようにしよう

Sara

💡 今回の内容
ボタンをクリックできるようにして、アプリを仕上げていきましょう。

📚 このシリーズについて
本シリーズでは、書籍『いきなりプログラミング Android アプリ開発』の第1章のアプリを Jetpack Compose で開発していく手順を紹介します。

📎 メニュー
第0回:イントロダクション
第1回:プロジェクトの用意と Jetpack Compose の概要
第2回:アプリ画面を作ってみよう
➡️ 第3回:ボタンをタップできるようにしよう

ボタンのクリックイベント

📘 参考:p.50〜書籍はこちら

書籍と同じ方法で書くと?

まずは書籍と同じように「変数 count を用意して、ボタンをタップした回数を表示する」というコードを書いてみましょう。

AppScreen() にコードを書きます。

3行目のコードを追加、8行目をコメントアウトして9行目を追加、18行目 onClick 部分にコードを追加します。

@Composable
fun AppScreen() {
    var count = 0
    Column(
        // 省略
    ) {
        Text(
            //text = stringResource(R.string.message),
            text = "$count",
            fontSize = 18.sp
        )
        Spacer(Modifier.height(40.dp))
        Image(
            // 省略
        )
        Spacer(Modifier.height(40.dp))
        Button(
            onClick = { count++ }
        ) {
            Text(stringResource(R.string.btn_water))

「ボタンをタップしたら count に1を加算して Text() に表示」というコードにしました。

ボタンをタップしたら count が増えていくはずなので、動作を確認してみましょう。

インテラクティブモードの使い方

Jetpack Compose のプレビューには Interactive Mode(インテラクティブモード) という機能が用意されていて、エミュレータを起動せずにアプリ画面を操作できるようになっています。

これを使ってボタンをタップをしてみましょう。

プレビュー画面の右上にあるをクリックして [Start Interactive Mode] を選択します。

[水をあげる] ボタンをタップしても、カウントが 0 から変わりませんね(理由はこのあと説明します)。

確認が終わったら [Stop Interactive Mode] で終了します。

簡単な動作確認はインテラクティブモードで出来ますが、全ての動作を確認できるものではありません。上手く動作しないときはエミュレータを使いましょう。

なぜカウントが変わらない?

なぜボタンをタップしてもカウントが変わらなかったかというと、Jetpack Compose には「再コンポーズ」という仕組みがあるからです。

再コンポーズは、値が変更されたらコンポーザブル関数を再実行するという仕組みです「変数 count の値が変わる → 再度 AppScreen コンポーザブル関数を実行 → 画面が更新される」という順番でコードが実行されます。

ここで問題になるのが count の値が変わって AppScreen コンポーザブル関数が再実行されると、そのたびに count の値が 0 に戻ってしまうということです。これが原因で、ボタンをタップしても0が表示され続けていました。

値が0にリセットされないようにするには、変数 count の値を保持しておく必要があります。

コードを修正しましょう

再コンポーズしても count の値が保持されるようにコードを修正しましょう。

41行目あたりの

var count = 0

var count by remember { mutableIntStateOf(0) }

に変更します。

必要な import

by 部分に赤い波線が出るかもしれません。その場合は by 部分にカーソルを合わせると表示される [import operators ‘IntState.getValue’, State.getValue’] をクリックします。

もう一度カーソルを合わせて、同じように青いメッセージを選択します。

必要な import 文が追加されました。正しく追加できないときは import 部分に直接コードを書いても OK です。

Remeber と State

remember は「覚える」、state は「状態」という意味で、状態(変数の値)を覚えておくためのコードです。

変数 count の値は int 型(数値)なので、mutableIntStateOf(0) で初期値が0の値を用意しています。

初期値なしの文字列の場合は

var text by remember { mutableStateOf("") }

と書きます。

Jetpack Compose では、何度も使うことになるコードです。ここで仕組みを説明すると長くなってしまうので「とりあえずこのように書く!」と覚えておきましょう。

プレビューを確認

もう一度インテラクティブモードを起動してボタンをタップしてみましょう。

今度はボタンをタップするたびにカウントが更新されましたね。

メッセージと画像を切り替える

📘 参考:p.52〜書籍はこちら

次はボタンがタップされた回数に合わせて、メッセージと画像を切り替えましょう。

変数の用意

count と同じようにメッセージと画像も remember と state で保持しておきます。

4・5行目のコードを追加します。

@Composable
fun AppScreen() {
    var count by remember { mutableIntStateOf(0) }
    var messageId by remember { mutableIntStateOf(R.string.message) }
    var imageId by remember { mutableIntStateOf(R.drawable.f0) }

    Column(
        modifier = Modifier.fillMaxSize(),

メッセージと画像は id を使って取り出します。id は int 型(数値)なので、ここでも mutableIntStateOf() を使っています。

この変数を使って Text() と Image() に値をセットしましょう。

11・16行目のようにコードを変更します。

@Composable
fun AppScreen() {
    var count by remember { mutableIntStateOf(0) }
    var messageId by remember { mutableIntStateOf(R.string.message) }
    var imageId by remember { mutableIntStateOf(R.drawable.f0) }

    Column(
        // 省略
    ) {
        Text(
            text = stringResource(messageId),
            fontSize = 18.sp
        )
        Spacer(Modifier.height(40.dp))
        Image(
            painter = painterResource(imageId),
            contentDescription = null,
            modifier = Modifier.size(200.dp)
        )

メッセージと画像を決める

次に、変数 count の値によって表示するメッセージと画像を決めるコードを書きます。

書籍と同じように when を使って判定します。細かい条件分けについては書籍 p.52 をご確認ください。

7〜36行目のコードを追加します。

@Composable
fun AppScreen() {
    var count by remember { mutableIntStateOf(0) }
    var messageId by remember { mutableIntStateOf(R.string.message) }
    var imageId by remember { mutableIntStateOf(R.drawable.f0) }

    when (count) {
        0 -> {
            messageId = R.string.message
            imageId = R.drawable.f0
        }
        in 1..19 -> {
            messageId = R.string.message0
            imageId = R.drawable.f0
        }
        in 20..39 -> {
            messageId = R.string.message1
            imageId = R.drawable.f1
        }
        in 40..59 -> {
            messageId = R.string.message2
            imageId = R.drawable.f2
        }
        in 60..79 -> {
            messageId = R.string.message3
            imageId = R.drawable.f3
        }
        in 80..99 -> {
            messageId = R.string.message4
            imageId = R.drawable.f4
        }
        else -> {
            messageId = R.string.message5
            imageId = R.drawable.f5
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,

動作確認

インテラクティブモードを起動してボタンをタップしてみましょう。

ボタンをタップした回数に合わせて、メッセージと画像が切り替われば成功です!
* 変化がわかりやすいように、以下の画像では count++ ではなく count += 10 として実行しています。

ボタンの仕上げ

ボタンの表示・非表示を切り替える

📘 参考:p.55書籍はこちら

ボタンを間違えてタップしないように、「リセット」ボタンは花が咲くまで非表示にしておきましょう。

2つのボタンそれぞれに、3・10行目のコードを追加します。

Button(
    onClick = { count++ },
    modifier = Modifier.alpha(if (count < 100) 1f else 0f)
) {
    Text(stringResource(R.string.btn_water))
}
Spacer(Modifier.height(20.dp))
OutlinedButton(
    onClick = {},
    modifier = Modifier.alpha(if (count < 100) 0f else 1f)
) {
    Text(stringResource(R.string.btn_reset))
}

alpha は透過度を指定するもので

  • 0f → 透過(見えない)
  • 1f → 非透過(見える)

という状態にできます。

[水をやる] ボタンは、count が 100 未満のときは表示、それ以外は非表示としています。

反対に [リセット] ボタンは 、count が 100 未満のときは非表示、それ以外は表示としています。

リセットボタンの実装

📘 参考:p.57書籍はこちら

最後に [リセット] ボタンをタップした時の処理です。

count を 0 に戻すだけで、最初の状態に戻すことができます(2行目)。

OutlinedButton(
    onClick = { count = 0 },
    modifier = Modifier.alpha(if (count < 100) 0f else 1f)
) {
    Text(stringResource(R.string.btn_reset))
}

エミュレータで確認

最後にエミュレータでもアプリの動作を確認しておきます。

エミュレータにアプリ画面を表示するためには onCreate メソッドから AppScreen() を忘れずに呼び出しましょう(7行目)。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            FlowerComposeTheme {
                AppScreen()
            }
        }
    }
}

アプリを実行できれば完成です!
* 変化がわかりやすいように、以下の画像では count++ ではなく count += 10 として実行しています。

完成

最後までお読みいただきありがとうございました!

XML よりも簡潔なコードを書くことができましたね。簡単なアプリでしたが Jetpack Compose の雰囲気を少し知っていただけたのではないかと思います。

Jetpack Compose の特徴の1つは「状態(値)を保持すること」です。毎回 remember と state を使って値を保持するのは面倒に見えますが、値を変更すると自動的に UI も更新されるので、予期せぬエラーやミスを防ぐことができるというメリットもあります。

今後は Jetpack Compose での開発が主流になっていくことが考えられるので、ぜひ簡単なコードから挑戦してみてください。

当サイトでも Jetpack Compose に関する記事や講座を準備中です。ご興味のある方はぜひお知らせメールにご登録ください✉️

完成コード

Q
MainActivity.kt
package com.example.flowercompose

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Button
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.flowercompose.ui.theme.FlowerComposeTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            FlowerComposeTheme {
                AppScreen()
            }
        }
    }
}

@Composable
fun AppScreen() {
    var count by remember { mutableIntStateOf(0) }
    var messageId by remember { mutableIntStateOf(R.string.message) }
    var imageId by remember { mutableIntStateOf(R.drawable.f0) }

    when (count) {
        0 -> {
            messageId = R.string.message
            imageId = R.drawable.f0
        }
        in 1..19 -> {
            messageId = R.string.message0
            imageId = R.drawable.f0
        }
        in 20..39 -> {
            messageId = R.string.message1
            imageId = R.drawable.f1
        }
        in 40..59 -> {
            messageId = R.string.message2
            imageId = R.drawable.f2
        }
        in 60..79 -> {
            messageId = R.string.message3
            imageId = R.drawable.f3
        }
        in 80..99 -> {
            messageId = R.string.message4
            imageId = R.drawable.f4
        }
        else -> {
            messageId = R.string.message5
            imageId = R.drawable.f5
        }
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = stringResource(messageId),
            fontSize = 18.sp
        )
        Spacer(Modifier.height(40.dp))
        Image(
            painter = painterResource(imageId),
            contentDescription = null,
            modifier = Modifier.size(200.dp)
        )
        Spacer(Modifier.height(40.dp))
        Button(
            onClick = { count++ },
            modifier = Modifier.alpha(if (count < 100) 1f else 0f)
        ) {
            Text(stringResource(R.string.btn_water))
        }
        Spacer(Modifier.height(20.dp))
        OutlinedButton(
            onClick = { count = 0 },
            modifier = Modifier.alpha(if (count < 100) 0f else 1f)
        ) {
            Text(stringResource(R.string.btn_reset))
        }
    }
}

@Preview(showBackground = true)
@Composable
private fun AppScreenPreview() {
    AppScreen()
}

書籍の紹介

書籍では、フラワーシュミレータを含めた全6種類のアプリの作り方を学ぶことができます。(Jetpack Compose ではなく XML による開発です)

「コードを書く→アプリを動かす→コードを書く…」という手順を繰り返すことで、途中で挫折しにくい構成になっています。

とにかく動作するアプリを作ってみたい方、難しい参考書で挫折しまった方にオススメです!

guest
0 Comments
古い順
新しい順 人気順
Inline Feedbacks
View all comments
ABOUT ME
Sara
Sara
運営者
書籍やオンライン講座でプログラミングを勉強してフリーランスのプログラマーになりました。
このサイトでは「わかりやすく・シンプル」をモットーに、プログラミングの基礎からアプリ開発まで紹介します。
独学でプログラミングを勉強をしている方、基礎は勉強したけれど次に何をすれば良いか分からない...という方のお役に立てるサイトを目指しています。
主な使用言語:Kotlin / Java / PHP
>> 詳しいプロフィール
>> お問い合わせ
>> 書籍を出版しました!
本格的に学びたい方へ

Code for Fun プログラミング講座

POINT 01

動くコード

プログラミングの文法を学んでも、そこからどのようにアプリ開発ができるのかイメージが湧きにくいものです。

Code for Fun のプログラミング講座では、ゲームやカレンダーなどアプリとして機能するものを作りながらプログラミングを学ぶことができます。

POINT 02

自分のペースで

オンライン講座なので、ご自身のペースで学習を進めて頂けます。

受講期限もないので、いつでも前のレッスンに戻ることができるので安心です。

お申し込みしたその日からすぐに始めることができます。

POINT 03

個別サポート

プログラミング学習では、エラーが起きることはよくあります。そんな時はお気軽にお問い合わせください!

コメント欄またはメールによるサポートを回数無制限でご利用頂けます。(*講座に関連するご質問のみ対応)

今すぐ無料でお試し

0
この記事にコメントするx
記事URLをコピーしました