Android
android開発

Androidのボタンスタイルを理解する

AppCompat.Buttonスタイル

とにもかくにも、スタイル定義を理解するためにも、AppCompat.Buttonを理解しましょう。

AppCompat.Buttonには主に以下の4種類のスタイルがあります。

  • Widget.AppCompat.Button
  • Widget.AppCompat.Button.Colored
  • Widget.AppCompat.Button.Borderless
  • Widget.AppCompat.Button.Borderless.Colored
Default backgroundTint="#4DB6AC"
image.png image.png

当然、Borderlessスタイルは背景がないのでtintされません。

AppCompat.Button

Androidの標準のボタンです。
旧マテリアルデザインではRaised Button、現在のマテリアルデザインではContained Buttonなどと呼ばれています

TextAppearance
?android:textAppearanceButton = TextAppearance.AppCompat.Widget.Button

AppCompat.Buttonの色

enabled disabled1
background ?colorButtonNormal ?colorButtonNormal
textColor2 ?android:textColorPrimary ?android:textColorPrimary
Ripple ?colorControlHighlight -

サイズ

AppCompat.ButtonminHeight48dpです。
しかし、実際表示してみると明らかにそれより小さいことがわかると思います。

これは、AppCompat.Buttonに指定されているbackgroundリソースにインセットが設けられているためです。
それぞれ、上下に6dp、左右に4dpのインセットが設定されています。

結果的に、

48dp (minHeight) - 6dp (insetTop) - 6dp (insetBottom) = 36dp

が見た目上の高さとなっています。

なお、この設定は旧マテリアルデザインのスペックに沿っています。
現在のマテリアルデザインガイドラインではコンポーネントのサイズと見た目のサイズを違えるという記述は消えているため、そのうちなくなるかもしれません。
(そもそもAppCompatよりもMaterial Componentsが推奨されていく可能性のほうが高いと思いますが)

elevation

AppCompat.Buttonの特徴としてelevationが設定されています。
ただし、このelevationandroid:elevation="0dp"としても無効にすることはできません。

AppCompat.Buttonelevationは、実際はandroid:stateListAnimatorによって制御されているため、無効にしたい場合はandroid:stateListAnimator="@null"とする必要があります。

AppCompat.Button.Colored

AppCompat.Buttonのサブスタイルで、ボタンに適用されるテーマのcolorAccentの色になるボタンです。

TextAppearance
TextAppearance.AppCompat.Widget.Button.Colored

AppCompat.Button.Coloredの色

enabled disabled
background ?colorAccent ?colorButtonNormal
textColor ?android:textColorPrimaryInverse ?android:textColorPrimary1
Ripple ?colorControlHighlight -

ただ単にtintする場合、AppCompat.ButtonAppCompat.Button.Coloredどちらのスタイルを使うかについては、ボタンの明度がLightかDarkかで決めていいと思います。(ざっくりテキストがInverseかどうかの差です)

AppCompat.Button.Borderless

旧マテリアルデザインではFlat Button、現在はText Buttonと呼ばれているボタンです。

AppCompat.Buttonのbackgroundからshapeを除いているだけです。

TextAppearance
?android:textAppearanceButton = TextAppearance.AppCompat.Widget.Button

AppCompat.Button.Borderlessの色

enabled disabled1
background - -
textColor ?android:textColorPrimary ?android:textColorPrimary
Ripple ?colorControlHighlight -

AppCompat.Button.Borderless.Colored

AppCompat.Button.Borderlessのテキストに色付けをおこなったスタイルです。

AppCompat.Button.Borderlessの色

enabled disabled
background - -
textColor ?colorAccent ?android:textColorSecondary1
Ripple ?colorControlHighlight -

AppCompat.Button系スタイルの色はどう指定するのがよいか?

AppCompat.Button系スタイルの色の変え方には2種類あります。

1つは、最初に扱ったbackgroundTintを利用する方法です。
2つめは、ThemeOverlayを利用する方法です。

それぞれにメリット・デメリットが存します。

backgoundTint

backgroundTintとは、backgroundにあてた画像リソースにカラーフィルタをかけて色を変化させる属性です。

対応するminSdkVersionによって利用できるネームスペースが違うので気をつけてください。

Androidバージョン app:backgroundTint android:backgroundTint
21未満 o x
21+ o o (推奨)

backgroundTintに指定できるのは、ColorリソースまたはColorStateListリソースになります。

ColorStateListとは以下のような定義をしたリソースを指します。3

example_color_state_list
<selector>
    <item android:state_enabled="false" android:color="@color/disabled_color"/>
    <item android:color="@color/enabled_color"/>
</selector>

メリット

レイアウト時に即席でどんな色でも当てられるので、デメリットを気にしない場合はデザインの要求に対してコストが圧倒的に低いです。

<Button
    style="@style/Widget.AppCompat.Button.Colored"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:backgroundTint="@color/red"
    android:text="Red Button"/>

デメリット

ステート対応がめんどくさい。

特殊なステートの場合はどっちにしろめんどくさいのですが、enabled - disabledの状態にすらColorStateListを作る必要があります。

将来的に、ColorStateListリソースがres/drawableに大量に作られて管理しきれなくなります。

ThemeOverlay

ThemeOverlayとは、View及びViewGroup階層でテーマの任意の変数をオーバーライドする手法です。

theme属性で指定しますが、これについても対応するminSdkVersionによって利用できるネームスペースが違います。

Androidバージョン app:theme4 android:theme
21未満 o x
21+ o o (推奨)

ThemeOverlayの適用範囲は、そのView及びそれ以下のView階層です。

ThemeOverlayを使ったボタンの色の変更は、以下のようにします。

example_button
<Button
    style="@style/Widget.AppCompat.Button.Colored"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Red Button"
    android:theme="@style/ThemeOverlay.Button.Red"/>
example_button_theme
<style name="ThemeOverlay.Button.Red" parent="ThemeOverlay">
    <item name="colorAccent">@color/red</item>
</style>

AppCompat.Button.Coloredの色は、先述したように?colorAccentが利用されるため、ThemeOverlayでその値だけ変更することでボタンの色が変更できます。

メリット

既存のボタンのステート定義を尊重しつつ、部分的に色を変更できる。

ThemeOverlayで当てる場合、?colorAccentのみを変更しているためdisabled状態は変わらず標準の表現が使われます。
逆にdisabledの色も任意にやりたい場合は、?colorButtonNormalを変更すればいいだけです。

デメリット

即席で色を当てる用途で使うには手間がかかる。

テーマを定義しないといけないため、1箇所でしか使わないボタンスタイルのためにやる作業としては、コストが重いように感じます。

ボタンのスタイル管理

ここまで、AppCompat.Button系スタイルの話しかしませんでしたが、独自スタイルにおいても同じことが言えます。
そのためAppCompat.Buttonを理解することで、独自定義の方法や何を懸念するべきかということも見えてくるはずです。

例えば今僕の携わっているプロジェクトでは、アウトラインボタンという枠線のみのボタンスタイルが定義されていますが、AppCompat.Buttonを参考にdrawabletextAppearancestyleの定義を行っています。

結局tintとthemeどちらを利用すればいいか

ハッキリ言って一貫した答えはありません。
画面仕様、デザインの指定から読み取ったり、デザイナーさんに訊いてみるのが手っ取り早いです。

目安としては

  • disabledステートが必要ならThemeOverlay
  • とりあえず色付きのボタンが置ければいいならbackgroundTint

くらいの認識でいいと思います。

注釈


  1. disabled状態の場合、テーマの?android:disabledAlphaの値がアルファ値として適用されます。 

  2. android:textAppearanceのデフォルト値が?android:textAppearanceButtonになっており、そのテーマデフォルトとなるTextAppearanceの結果としてこの変数が利用されています。 

  3. selectorリソースにandroid:colorを指定したものです。Android Studio 3.1.4の時点では、itemタグのandroid:colorは補完されませんが実際は利用可能です、(他にもいくつか補完されない属性が存在しています) 

  4. app:themeの場合、結果がレイアウトプレビューには反映されません。その場合は、tools:themeにも同じ値を設定することで解決できます。