クラシル開発ブログ

クラシル開発ブログ

手軽になめらかUI/UXを実現したい〜Material ComponentsのProgressIndicatorを使ってみた〜

ogp

こんにちは。dely開発部にてクラシルのAndroidエンジニアを担当しているnozaです。 月日の流れは早いもので、前回の記事から間があいてしまいましたね。

先月、Material Components for Androidのバージョン1.3.0*1が公開されましたね。 主な内容として下記のComponentが追加されてます。

  • MaterialTimePicker
  • ProgressIndicator

個人的にはProgressIndicatorを待ち望んでいました。 というのも、Android SDKで提供されているProgressBarだと、思った通りに手軽にプログレス表示するのが難しかったからです。 今回はProgressBarのモヤっとポイントと、ProgressIndicatorだとどんなプログレス表示が実現できるのかを紹介していきます。

ProgressBar*2

Android SDKで提供されるもので、標準の設定で円形にしたり水平バーにしたりできます。 画面内のコンテンツを取得するための通信中などに用いることが多いと思います。

Android版クラシルでは、画面内のコンテンツ表示の邪魔をしないようにToolbarの下にバーの形状で設置しようとしました。

f:id:nozakichi:20210303130527g:plain
こんな感じにしたいの図

しかしながら、お手軽に実現できないモヤっとポイントがいくつかあったのです。

ProgressBarのモヤっとポイント

バーの上下の余白

indeterminate=trueの場合、標準の設定で下記のようなアニメーションが実現できます。

f:id:nozakichi:20210302185111g:plain

しかし、このアニメーションで設定されるVectorDrawable(API Level21未満だとpng画像)自体に上下の余白が入っています。 Viewの範囲をわかりやすくするために背景色#ddddddを入れてみると・・・

f:id:nozakichi:20210302223533p:plain

このようにViewの描画領域とバーの間に余白があることがわかります。

意図した場所に配置するためにはこの余白を考慮して工夫する必要がありました。 工夫の例をいくつか挙げてみます。

例1:自前でDrawableを用意して、余白をコントロールする。

標準で用意されているDrawableに余白が含まれているなら、自前で作成してしまえば良いです。 が、VectorDrawableやアニメーションなど用意するものが多く、ある程度知識も必要になるため面倒くさいです。

progressDrawableindeterminateDrawableというattributeで設定可能

例2:上下のマージンに程よいマイナス値を設定してごまかす

<ProgressBar
    style="@style/Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="-6dp"
    android:layout_marginBottom="-6dp"
    android:indeterminate="true" />

marginにマイナス値を設定することで余白分を無くしています。 が、小手先で対応している感じがあってモヤっとします。

例3:layout_heightとscaleYでごまかす

<ProgressBar
    style="@style/Widget.AppCompat.ProgressBar.Horizontal"
    android:layout_width="match_parent"
    android:layout_height="4dp"
    android:indeterminate="true"
    android:scaleY="4" />

高さ4dpにすることで、バーの表示が縮んでしまうのでscaleY="4"で引き伸ばしています。

f:id:nozakichi:20210302224253p:plain

上下に余計な余白は入っていませんが、縮めて引き伸ばしているためボケてしまっています。 かっこ悪いですね。

といった感じで、お手軽でしっくりくる解決ができずモヤっとしていました。

表示/非表示時の切り替え

例えば通信中にはProgressBarを表示し、通信完了したら非表示にしたいと思います。 よくやる方法としてはvisibilityを操作する方法ですが、それだとパッと切り替わってしまうためチカチカした印象になります。

f:id:nozakichi:20210303132739g:plain
通信状態に応じてProgressBarが突然出たり消える例

突然何かが表示されたり消えたりすると潜在的な違和感を与えてしまうので、もっと滑らかに出たり消えたりさせたいものです。 表示状態を切り替える時にアニメーションをうまいこと実行させるのも手ですが、お手軽に実現したいというモヤっと感がありました。

そこで、Material ComponentのProgressIndicatorの出番です。


Material ComponentのProgressIndicator

前述したモヤっと感を、Material ComponentのProgressIndicatorを利用して解決してみましょう。 水平バーで表現できるLinearProgressIndicatorを使います。

 <com.google.android.material.progressindicator.LinearProgressIndicator
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:indeterminate="true"
        app:hideAnimationBehavior="outward"
        app:showAnimationBehavior="inward"
        app:indicatorColor="#ffaa4e"
        app:trackColor="#eeebe7" />

上記のxmlで定義したLinearProgressIndicatorをlinearProgressIndicatorという変数名で取得しておきつつ・・・

// 表示したい時に呼び出す
linearProgressIndicator.show()

// 非表示にしたい時に呼び出す
linearProgressIndicator.hide()

※Material Componentの導入*3は省略しています。

f:id:nozakichi:20210303130527g:plain
LinearProgressIndicatorで実装した例

はい、これだけでできました! 滑らかで美しい!

ProgressIndicatorで設定可能なattributes

Material DesignのProgress indicators*4にどんな風に扱えるのか記載がありますが、どんな見た目になるのかやってみましょう。

進捗を表す部分はindicatorColorで、その下地はtrackColorで色を設定できます。

f:id:nozakichi:20210302230200p:plain

また、indicatorColorには色の配列を設定可能です。

<!-- colors.xml に下記を定義 -->
<array name="progress_colors">
    <item>#f00</item>
    <item>#ff0</item>
    <item>#0f0</item>
    <item>#00f</item>
</array>


<com.google.android.material.progressindicator.LinearProgressIndicator
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:indeterminate="true"
    app:indicatorColor="@array/progress_colors" />


indeterminateな水平バーを使用する場合、indeterminateAnimationTypeというattributeで、色配列がどのように適応されるのかを指定できます。

indeterminate
AnimationType
見た目
disjoint f:id:nozakichi:20210303133005g:plain
contiguous f:id:nozakichi:20210303133104g:plain

ただし、"contiguous"という設定を使用するには条件がいくつかあるので注意です。

  • trackCornerRadius(後に説明)は設定不可
  • indicatorColorで設定した色が3色以上
    • 配列が3つ以上でも、色が3色以上である必要がある

※設定を無視してくれればいいのですが、レイアウトxmlの読み込みでエラーになりクラッシュしました。



trackThicknessで高さ(太さ?)を、 trackCornerRadiusで、indicator部分に丸みを設定できます。

"丸み"とはなんなのかは、実際の表示をみてもらうとわかりやすいです。 (よりわかりやすくするためにtrackThicknessを1増してます)

見た目
trackCornerRadius設定なし f:id:nozakichi:20210302231927g:plain
trackCornerRadius設定あり f:id:nozakichi:20210302232021g:plain



表示アニメーション

表示時はapp:hideAnimationBehavior、非表示時はapp:showAnimationBehaviorのattributeで設定できます、 ProgressIndicatorは標準ではnoneが設定されていて、アニメーションがありません。

設定値 非表示時(hide) 表示時(show)
none 標準で設定されているもの。アニメーションしない。 標準で設定されているもの。アニメーションしない。
outward 下端から上端に向かって折りたたまれる 下端から上端まで拡大
inward 上端から下端に向かって折りたたまれる 上端から下端まで拡大

それぞれを設定してみた時の見た目はこんな感じです。

hide show 見た目
outward outward f:id:nozakichi:20210303133150g:plain
outward inward f:id:nozakichi:20210303133303g:plain
inward outward f:id:nozakichi:20210303133327g:plain
inward inward f:id:nozakichi:20210303133215g:plain

ちなみに表示アニメーションが実行された後はvisibilityが変更されます。 show()の後にはView.VISIBLEが、hide()の後にはView.INVISIBLEView.GONEとなりますが、これはsetVisibilityAfterHide(int visibility)というメソッドで設定できます。



アニメーション方向

標準では左から右へ満たされたり流れたりするアニメーションですがapp:indicatorDirectionLinearというattributeで変更可能です。

determinateな場合

indicatorDirectionLinear 見た目
leftToRight f:id:nozakichi:20210303133422g:plain
rightToLeft f:id:nozakichi:20210303133453g:plain

indeterminateな場合

indicatorDirectionLinear 見た目
leftToRight f:id:nozakichi:20210303133523g:plain
rightToLeft f:id:nozakichi:20210303133543g:plain

startToEndendToStartも設定可能だが省略 ※LinearProgressIndicatorだけに用意されたattribute

他にもCircularProgressIndicator用に用意されたattributeもあるのですが、もりもりになってきたのでひとまずここまで。



ProgressBar、ProgressIndicatorの使い所

ProgressIndicatorの紹介をしてきましたが、どんなプログレス表示をしたいかによってProgressBarを使うのか、ProgressIndicatorを使うのかは変わってくると思います。

ProgressIndicatorはMaterialDesignな見た目の表示はお手軽に実装できますが、独自のアニメーション(例えばロゴがグルグル回ったり跳ねたり・・・)を設定するattributeは用意されていません。 凝ったプログレス表示をしたい場合は、Drawableを自前で作成してProgressBarに適応した方が良さそうです。 筆者は今回ProgressBarの中身も覗いてみたのですが、アニメーションの設定とか楽しそうだなと思いました。



おわりに

MaterialComponentのProgressIndicatorについて紹介してきましたが、Android版クラシルでも実際に導入しています。 Android版クラシルでは、読み込み中の表示から通信中、コンテンツ取得後の表示までがなめらかでユーザーの目に優しいものになっていると思います。 それはサービスに大きく関わるような効果ではありませんが、アプリ全体を通じて「安心感」や「温かみ」のようなものをユーザへ与えられているのではないかなと思います。

極端な例を用意してみましたが、どう感じますか?

f:id:nozakichi:20210303133607g:plain f:id:nozakichi:20210303133645g:plain

新しい機能を通じてユーザーに最適で最大の価値を提供する事はもちろん大切なことですが、このような細かなところへの配慮を重ねることでも快適さを実現していきたいですね。