hizi memo

びぼうろく📝

ViewOutlineProvider で Extended FAB をつくる

Androidアプリにおいて, ViewOutlineProvider を用いて Material Design 2 にて追加された Extended FAB(Floating Action Button) を作成する方法を紹介します.

tl; dr

  • Extended FAB は, Material Design 2 で追加された新しいコンポーネント
  • ViewOutlineProvider は, View の Outline を制御するためのもので, これによってelevation でつく影の形を変えられる.
  • 一番簡単にやるなら, rounded corner な drawable を作成し, Button の background にそれを設定しつつ, android:outlineProvider="background" を設定するだけ.
  • ただし, corner の radius を十分に大きい適当な値を入れるなど, 不安定な方法なので, 自分で ViewOutlineProvider を継承したものを実装し, それを適用するほうが綺麗.

Extended FAB

Material Design の公式ページでも紹介されている通り, Extended FAB とは より幅が広く, テキストを含んだFAB と紹介されています.

テキストは必須ですが, アイコンは Optional となっています. テキストがない場合は, Extended FAB ではなく FAB を使用すればよい話だからです. アイコンを置く場合は, テキストの左側でなければなりませんが, RTLな言語の場合はその限りではありません.

以下の画像のものが, 今回実装したものになります.

ViewOutlineProvider

今回の実装では表題の通り, ViewOutlineProvider を使用します. これは API Level 21 から追加されたクラスで, View の Outline を設定するためのクラスです. Extended FAB の両端が丸くなっており, その形通りにelevationなどのshadowを表示するために活用します.

実装パターン1

もっとも単純な実装方法は, 十分な Corner Radius をもった Drawable を Background に設定し, xmlにおいて対象のViewに対して android:outlineProvider="background" をセットしてあげる方法です. これは, background の形と同じように outline を切り取るという設定になります. その他の設定として, padding を考慮した paddedBounds, paddingを考慮しない bounds, outline を指定しない none が設定できます.

以下が Background に設定する Shape Drawable です. Button の minHeight は48dpと仮定して, Radius は 24dp に設定します.

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="24dp"/>
    <gradient
        android:angle="135"
        android:endColor="#00a8ff"
        android:startColor="#0097e6"/>
</shape>

そして, 作成したDrawable を android:background に設定し, android:outlineProvider="background" を設定します.

  <Button
      android:id="@+id/addToCardButton"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:background="@drawable/background_extended_fab" <!-- 作成したDrawable -->
      android:outlineProvider="background"/>

しかし, この方法だと端末の文字サイズを通常より大きくした状態だと minHeight を超えたサイズになる可能性があり, 結果として Radius が足りずデザインが崩れてしまうことになります. そのため, この方法はあまりオススメできません.

実装パターン2

より安全な方法として, ViewOutlineProvider を継承した独自クラスを実装する方法です. どのような形で View を切り取るかを, いくつかの制約のもとで独自に設定できます.

実装は非常に単純で, 本質的なコードはたった1行のみになります.

class ExtendedFabOutlineProvider : ViewOutlineProvider() {

    override fun getOutline(view: View, outline: Outline) {
        outline.setRoundRect(0, 0, view.width, view.height, view.height / 2f)
    }
}

次に, 対象となる View に対して, 作成した ViewOutlineProvider を設定し, clipToOutlinetrue をセットします.

view.outlineProvider = ExtendedFabOutlineProvider()
view.clipToOutline = true

また, この方法ではコードから角丸を実現しているため, 対象となる View の Background は, 角丸のDrawableを作成する必要はなく, 単純に color を設定してあげるだけで問題ありません.

この方法が, パターン1より優れているのは, 実際のViewの高さをもとにradiusを決定している点です. これにより, 前述のデザイン崩れが起きる心配はありません. 基本的には, こちらの実装方法を用いることをオススメします.

その他

  • ViewOutlineProvider は, 角丸くらいしか作れない
  • setRoundRect 以外にも, setOval も使えるらしい
  • setConventPath というものも使えるが, 動かなかった 🤔
    • この辺り解決している方がいたら教えて欲しい...