X

Jetpack Compose: ProgressIndicatorコンポーザブルを使う

今回のJetpack ComposeサンプルはProgressIndicatorコンポーザブルです。androidx.compose.materialパッケージにはCircularProgressIndicatorとLinearProgressIndicatorの2種類があります。

プログレスインジゲータ(進捗インジゲータ)の役割は、処理が進行中であること・終了まで時間がかかること・画面が固まってしまっているわけではないとユーザーに伝えることです。CircularProgressIndicatorは円状の、LinearProgressIndicatorは直線状の進捗表示用のコンポーザブルです。それぞれ画像等のローディング、APIからの応答待ち、動画や音声など重い処理の実行中、画面の読み込みなどさまざまなシチュエーションで待ち状態であることを示します。

CircularProgressIndicatorを表示する

CircularProgressIndicatorの表示例

CircularProgressIndicatorコンポーザブルでは弧を描いたインジゲータ表示を繰り返します。ローディング時間が不特定なとき(サイズのわからない読み込みやネットワークの状況に依存して変化するときなど)に適しており、CircularProgressIndicatorの引数を加えることで進捗インジゲータの色やサイズも変更できます。

@Composable
fun SimpleProgress() {
    CircularProgressIndicator()
}
@Composable
fun CircularProgressIndicator(
    modifier: Modifier = Modifier,
    color: Color = MaterialTheme.colors.primary, // 色の指定
    strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth // 円弧のサイズを指定
) {

LinearProgressIndicatorを表示する

LinearProgressIndicatorの表示例

LinearProgressIndicatorコンポーザブルでは直線のトラックの上を移動するインジゲータ表示を繰り返します。ローディング時間が不特定なとき(サイズのわからない読み込みやネットワークの状況に依存して変化するときなど)に適しており、LinearProgressIndicatorの引数を加えることで進捗インジゲータの色やサイズも変更できます。

@Composable
fun SimpleLinearProgress() {
    LinearProgressIndicator()
}
@Composable
fun LinearProgressIndicator(
    modifier: Modifier = Modifier,
    color: Color = MaterialTheme.colors.primary,  // 色の指定
    backgroundColor: Color = color.copy(alpha = IndicatorBackgroundOpacity) // トラックの背景色を指定
) {

Determinateな進捗を表示する

Determinateなインジゲータ(左側)とIndeterminateなインジゲータ(右側) マテリアルデザインガイドラインより引用

Determinateな進捗インジゲータは長さが決まっているものを対象にしています。つまり進捗の完了率を検出できる場合に使用します。たとえばファイルを展開する際に総ファイルサイズがわかっていて現在位置が明確である場合などです。そのようなケースでは終了までの長さが確定した進捗ゲージを使えます(100%に到達したら消えるようなインジゲータです)。

次のコードはDeterminateなCircularProgressIndicatorコンポーザブルとLinearProgressIndicatorコンポーザブルの定義です。どちらも引数にprogressというFloat値をとり、0.0から1.0の範囲で進捗を設定します(0.3であれば30%を示します)。

@Composable
fun CircularProgressIndicator(
    /*@FloatRange(from = 0.0, to = 1.0)*/
    progress: Float,
    modifier: Modifier = Modifier,
    color: Color = MaterialTheme.colors.primary,
    strokeWidth: Dp = ProgressIndicatorDefaults.StrokeWidth
) {
@Composable
fun LinearProgressIndicator(
    /*@FloatRange(from = 0.0, to = 1.0)*/
    progress: Float,
    modifier: Modifier = Modifier,
    color: Color = MaterialTheme.colors.primary,
    backgroundColor: Color = color.copy(alpha = IndicatorBackgroundOpacity)
) {

別の例としてはダウンロードするファイル数が決まっていてファイル数をDeterminateな進捗として扱いたいケースもあるかもしれません。しかし、これは実ダウンロード時間と比例しないため=総ダウンロードサイズとネットワークに依存するので多くの場合、適切とは言い難いでしょう。「ファイル数」が大きな意味を持つ場合にのみ選択すべきですね。

進捗インジゲータを中央に表示する

LinearProgressIndicatorとCircularProgressIndicatorのセンタリング例

アプリを実装していると中央揃え・センタリングした進捗インジゲータが欲しくなるときがありますので、レイアウト例を紹介します(とくにCircularProgressIndicatorコンポーザブルはCardや画像などの表示コンテンツの読み込み表現で使うことが多いです)。

        setContent {
            MyApplicationTheme {
                Surface(color = MaterialTheme.colors.background) {
                    Box(modifier = Modifier.fillMaxSize(),
                        contentAlignment = Alignment.Center
                    ){
                        CircularProgressIndicator()  // 丸い進捗インジゲータを中央に表示する
                        // LinearProgressIndicator() 線形のものを使いたい場合はこちら
                    }
                }
            }
        }

BoxコンポーザブルのレイアウトをModifier.fillMaxSizeしてAlignment.Centerで中央揃えしておきます。LinearProgressIndicatorはタブや画面全体などコンテンツの読み込みに使う印象が強いですが(もちろん画像などのローディング中の表現にも使えますので、使えないという意味ではありません)同じ利用シーンでLinearProgressIndicatorとCircularProgressIndicatorを混ぜて表示することは避けてください。

データ量・ロード処理を加味してProgressIndicatorを適切に使っていきましょう。画面のロードやコンテンツのサイズが大きく、待たされるとユーザーは「入力待ちなのかな?」と新たな操作を行ってしまい、アプリがさらに重くなったり、処理後に意図しない状態遷移を引き起こしたりします。日本では月末にかけて通信制限がかかり、ネットワークが遅い・タイムアウトまで長時間待ってしまうというケースも考えられます。開発中は理想的な環境であることが多いため、進捗インジゲータを使うタイミングは間違えないようにしたいですね。

今回の記事はこれでおしまいです。お疲れさまでした。

mhidaka: Software Engineerだよ。DroidKaigi Organizer / Androidと組込とRe:VIEW。techbooster主宰。mhidaka's writings http://booklog.jp/users/mhidaka 技術書典! http://techbookfest.org