AlertDialogのスタイルをカスタマイズ

この記事は、[Android その３ Advent Calendar 2016 \- Qiita](http://qiita.com/advent-calendar/2016/android_third)の12日目の記事です！

---
**注意!!**
この記事は、appcompat-v7のバージョン25.0.1で試した内容です。最新バージョン25.1.0では、android.support.v7.app.AlertControllerの実装が変更されており、使用できない箇所があります。

---
#AlertDialogのスタイルをカスタマイズ


[Support Library v7 appcompat ライブラリ](https://developer.android.com/topic/libraries/support-library/features.html?hl=ja#v7)を使用するると、Android 2.3（API レベル 9）以上のデバイスで、アプリでマテリアルデザインを使用することができ、ダイアログもマテリアルデザインになります。

```java
import android.support.v7.app.AlertDialog;

// 省略

	AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
	builder.setTitle("確認");
	builder.setMessage("ＸＸＸしてもよろしいですか？");
	builder.setPositiveButton("はい", null);
	builder.setNegativeButton("いいえ", null);
	builder.setNeutralButton("キャンセル", null);
	builder.create().show();
```

![device-2016-12-09-143313.png](https://qiita-image-store.s3.amazonaws.com/0/12177/953ce775-018d-1595-7b43-99f7101be405.png)

NeutralButtonが左端にあり、PositiveButtonとNegativeButtonが右に寄ったデザインですね。


通常のユーザーが使用するアプリであればこのデザインでよいとは思いますが、幼児/高年齢向けアプリを考えたとき、もっと大きな文字で、押しやすいボタンのデザインにしたいと思ったので、ダイアログをカスタマイズしてみます。

### styles.xml

まず、スタイルを定義します。

```xml:styles.xml
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog">

</style>
```

AlertDialog.Builderの、第二引数 int themeResIdにテーマを指定することでカスタムのスタイルの適用することができます。[AlertDialog\.Builder \| Android Developers](https://developer.android.com/reference/android/support/v7/app/AlertDialog.Builder.html#AlertDialog.Builder(android.content.Context, int))

```java
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.MyAlertDialogStyle);
```

アプリすべてのダイアログにスタイルを適用する場合は、AppThemeのandroid:alertDialogThemeに指定します。

```xml:themes.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/primary</item>
    <item name="colorPrimaryDark">@color/primary_dark</item>
    <item name="colorAccent">@color/accent</item>
    <item name="android:alertDialogTheme">@style/MyAlertDialogStyle</item>
</style>
```

### ボタンのテキストカラー

colorAccentを指定します。

```xml:styles.xml
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">@color/primary</item>
</style>
```

![device-2016-12-11-220726.png](https://qiita-image-store.s3.amazonaws.com/0/12177/37d69bb5-3335-7361-48d0-48edf6e3024a.png)

### タイトルとボタンのテキストの大きさ

android:textSizeを指定します。

```xml:styles.xml
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">@color/primary</item>
    <item name="android:textSize">@dimen/dialog_text_size</item>
</style>
```

![device-2016-12-11-221633.png](https://qiita-image-store.s3.amazonaws.com/0/12177/dac18f97-4e0a-d911-70cb-1ef253178509.png)

ボタンが縦に並んでしまいました...
メッセージのサイズは変更されませんね...

### ダイアログの大きさを変更

android:windowMinWidthMajor, android:windowMinWidthMinorを指定します。

```xml:styles.xml
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">@color/primary</item>
    <item name="android:textSize">@dimen/dialog_text_size</item>
    <item name="android:windowMinWidthMajor">@dimen/dialog_min_width_major</item>
    <item name="android:windowMinWidthMinor">@dimen/dialog_min_width_minor</item>
</style>
```

- android:windowMinWidthMajor ... 端末が横向きの場合のダイアログの幅の最小サイズ
- android:windowMinWidthMinor ... 端末が縦向きの場合のダイアログの幅の最小サイズ

##### dimenは画面全体の比率(%)で指定することができます。

```xml:dimens.xml
<resources>
    <item type="dimen" name="dialog_min_width_major">95%</item>
    <item type="dimen" name="dialog_min_width_minor">95%</item>
</resources>
```

![device-2016-12-11-223226.png](https://qiita-image-store.s3.amazonaws.com/0/12177/727f9db7-e7bb-96c0-c5e9-0af69d43168e.png)

### テキストスタイル

android:textColorPrimaryを指定するとテキストカラーを変更できます。

```xml:styles.xml
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">@color/primary</item>
    <item name="android:textColorPrimary">@color/colorPrimary</item>
</style>
```

android:windowTitleStyleを定義するとタイトルのスタイルを変更できます。

```xml:styles.xml
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">@color/primary</item>
    <item name="android:windowTitleStyle">@style/windowTitleStyle</item>
</style>

<style name="windowTitleStyle" parent="TextAppearance.AppCompat.Large">
    <item name="android:textSize">@dimen/dialog_text_size</item>
</style>
```

### メッセージのテキストサイズ

メッセージのテキストサイズを指定きる属性が見当たりませんでしたのでコードでハックします。

```java
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.MyAlertDialogStyle);
builder.setTitle("確認");
builder.setMessage("ＸＸＸしてもよろしいですか？");
builder.setPositiveButton("はい", null);
builder.setNegativeButton("いいえ", null);
builder.setNeutralButton("キャンセル", null);

AlertDialog dialog = builder.show();

// メッセージテキストを変更する。
TextView textView = (TextView) dialog.findViewById(android.R.id.message);
textView.setTextSize(48.0f);
```

![device-2016-12-11-224301.png](https://qiita-image-store.s3.amazonaws.com/0/12177/8a177b77-98a5-47a0-a7a9-4be52e1225e1.png)


### ボタンのスタイル

buttonBarNeutralButtonStyle, buttonBarNegativeButtonStyle, buttonBarPositiveButtonStyle で指定することが出来ます。

```xml:styles.xml
<style name="MyAlertDialogStyle" parent="Theme.AppCompat.Light.Dialog">
    <item name="colorAccent">@color/primary</item>
    <item name="android:textSize">@dimen/dialog_text_size</item>
    <item name="android:windowMinWidthMajor">@dimen/dialog_min_width_major</item>
    <item name="android:windowMinWidthMinor">@dimen/dialog_min_width_minor</item>

    <item name="buttonBarNeutralButtonStyle">@style/buttonBarButtonStyle.Neutral</item>
    <item name="buttonBarNegativeButtonStyle">@style/buttonBarButtonStyle.Negative</item>
    <item name="buttonBarPositiveButtonStyle">@style/buttonBarButtonStyle.Positive</item>

</style>
```

##### テキストカラー

```xml:styles.xml
<style name="buttonBarButtonStyle" parent="Widget.AppCompat.Button.Borderless.Colored">
</style>

<style name="buttonBarButtonStyle.Neutral">
</style>

<style name="buttonBarButtonStyle.Negative">
    <item name="android:textColor">@color/red</item>
</style>

<style name="buttonBarButtonStyle.Positive">
    <item name="android:textColor">@color/blue</item>
</style>
```

親(parent)に Widget.AppCompat.Button.Borderless.Colored を指定すると、テキストカラーのデフォルトがcolorAccentになります。

![device-2016-12-11-230939.png](https://qiita-image-store.s3.amazonaws.com/0/12177/1a957701-a0a1-d759-581f-d544c018ace2.png)


##### カラーボタン

親(parent)に Widget.AppCompat.Button.Coloredを指定すると、colorAccentを背景にしたボタンを配置されます。

```xml:styles.xml
<style name="buttonBarButtonStyle" parent="Widget.AppCompat.Button.Colored">
</style>

<style name="buttonBarButtonStyle.Neutral">
</style>

<style name="buttonBarButtonStyle.Negative">
    <item name="android:textColor">@color/red</item>
</style>

<style name="buttonBarButtonStyle.Positive">
    <item name="android:textColor">@color/blue</item>
</style>

```

![device-2016-12-11-235950.png](https://qiita-image-store.s3.amazonaws.com/0/12177/f34ceb01-4c5e-46a7-808c-ee0c81b0fd49.png)


###### 背景

~~背景はのサイズを大きくしたり、カラーを変更したり場合には、android:backgroundを変更します。~~
~~リップル効果が着いた背景にしたい場合は、appcompatのソースを参考にするとよいでしょう。~~

背景カラーを変更したり場合には、app:backgroundTint を指定します。

```xml
<style name="buttonBarButtonStyle.Positive">
    <item name="backgroundTint">@color/colorAccent</item>
</style>
```

### ボタンの配置

マテリアルデザイン以前のダイアログように、ボタンが中央に3つ並べたスタイルにしたい場合は、コードでハックします。

```java
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this, R.style.MyAlertDialogStyle);
builder.setTitle("確認");
builder.setMessage("ＸＸＸしてもよろしいですか？");
builder.setPositiveButton("はい", null);
// builder.setNegativeButton("いいえ", null);
builder.setNeutralButton("キャンセル", null);

AlertDialog dialog = builder.show();

// メッセージテキストを変更する。
TextView textView = (TextView) dialog.findViewById(android.R.id.message);
textView.setTextSize(48.0f);


// ボタンパネルのコンテナを取得する。
LinearLayout containerButtons = (LinearLayout) dialog.findViewById(R.id.buttonPanel);

// Space Viewを削除する。
containerButtons.removeView(containerButtons.getChildAt(1));

// ボタンパネルの子ViewのボタンのGravityをCENTERに設定する
containerButtons.setGravity(Gravity.CENTER);

// LinearLayoutの比率の合計を3に設定する。
containerButtons.setWeightSum(3.0f);

// 各ボタンを取得する。
Button button3 = (Button) containerButtons.findViewById(android.R.id.button3);
Button button2 = (Button) containerButtons.findViewById(android.R.id.button2);
Button button1 = (Button) containerButtons.findViewById(android.R.id.button1);

// 各ボタンの比率を指定する。
((LinearLayout.LayoutParams)button3.getLayoutParams()).weight = 1.0f;
((LinearLayout.LayoutParams)button2.getLayoutParams()).weight = 1.0f;
((LinearLayout.LayoutParams)button1.getLayoutParams()).weight = 1.0f;

// 各ボタンの幅を0にする。
((LinearLayout.LayoutParams)button3.getLayoutParams()).width = 0;
((LinearLayout.LayoutParams)button2.getLayoutParams()).width = 0;
((LinearLayout.LayoutParams)button1.getLayoutParams()).width = 0;
```

![device-2016-12-12-104514.png](https://qiita-image-store.s3.amazonaws.com/0/12177/c9cd3a93-dad8-c15e-1c0c-cf3c0376f2fb.png)

### Layoutでハック

appcompat ライブラリで使用されているリソースを上書きすることで、ダイアログのデザインを変更することも可能です。


[sdk/extras/android/support/v7/appcompat/res/layout/abc\_alert\_dialog\_material\.xml \- android\_tools \- Git at Google](https://chromium.googlesource.com/android_tools/+/HEAD/sdk/extras/android/support/v7/appcompat/res/layout/abc_alert_dialog_material.xml)

```xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/parentPanel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
    <LinearLayout
            android:id="@+id/topPanel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
        <LinearLayout
                android:id="@+id/title_template"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:gravity="center_vertical"
                android:paddingLeft="?attr/dialogPreferredPadding"
                android:paddingRight="?attr/dialogPreferredPadding"
                android:paddingTop="@dimen/abc_dialog_padding_top_material">
            <ImageView
                    android:id="@android:id/icon"
                    android:layout_width="32dip"
                    android:layout_height="32dip"
                    android:scaleType="fitCenter"
                    android:src="@null"
                    style="@style/RtlOverlay.Widget.AppCompat.DialogTitle.Icon"/>
            <android.support.v7.widget.DialogTitle
                    android:id="@+id/alertTitle"
                    style="?attr/android:windowTitleStyle"
                    android:singleLine="true"
                    android:ellipsize="end"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textAlignment="viewStart" />
        </LinearLayout>
        <!-- If the client uses a customTitle, it will be added here. -->
    </LinearLayout>
    <FrameLayout
            android:id="@+id/contentPanel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:minHeight="48dp">
        <View android:id="@+id/scrollIndicatorUp"
              android:visibility="gone"
              android:layout_width="match_parent"
              android:layout_height="1dp"
              android:layout_gravity="top"
              android:background="?attr/colorControlHighlight"/>
        <android.support.v4.widget.NestedScrollView
                android:id="@+id/scrollView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:clipToPadding="false">
            <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
                <TextView
                        android:id="@android:id/message"
                        style="@style/TextAppearance.AppCompat.Subhead"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:paddingLeft="?attr/dialogPreferredPadding"
                        android:paddingTop="@dimen/abc_dialog_padding_top_material"
                        android:paddingRight="?attr/dialogPreferredPadding"/>
                <View
                        android:id="@+id/textSpacerNoButtons"
                        android:visibility="gone"
                        android:layout_width="0dp"
                        android:layout_height="@dimen/abc_dialog_padding_top_material"/>
            </LinearLayout>
        </android.support.v4.widget.NestedScrollView>
        <View android:id="@+id/scrollIndicatorDown"
              android:visibility="gone"
              android:layout_width="match_parent"
              android:layout_height="1dp"
              android:layout_gravity="bottom"
              android:background="?attr/colorControlHighlight"/>
    </FrameLayout>
    <FrameLayout
            android:id="@+id/customPanel"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:minHeight="48dp">
        <FrameLayout
                android:id="@+id/custom"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"/>
    </FrameLayout>
    <include layout="@layout/abc_alert_dialog_button_bar_material" />
</LinearLayout>
```

[sdk/extras/android/support/v7/appcompat/res/layout/abc\_alert\_dialog\_button\_bar\_material\.xml \- android\_tools \- Git at Google](https://chromium.googlesource.com/android_tools/+/HEAD/sdk/extras/android/support/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml)

```xml:layout/abc_alert_dialog_button_bar_material.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.ButtonBarLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/buttonPanel"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layoutDirection="locale"
    android:orientation="horizontal"
    android:paddingLeft="12dp"
    android:paddingRight="12dp"
    android:paddingTop="4dp"
    android:paddingBottom="4dp"
    android:gravity="bottom"
    app:allowStacking="@bool/abc_allow_stacked_button_bar"
    style="?attr/buttonBarStyle">
    <Button
        android:id="@android:id/button3"
        style="?attr/buttonBarNeutralButtonStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <android.support.v4.widget.Space
        android:id="@+id/spacer"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:visibility="invisible" />
    <Button
        android:id="@android:id/button2"
        style="?attr/buttonBarNegativeButtonStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <Button
        android:id="@android:id/button1"
        style="?attr/buttonBarPositiveButtonStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</android.support.v7.widget.ButtonBarLayout>
```

**※ `<android.support.v4.widget.Space　android:id="@+id/spacer"/>` は必須です。削除するとエラーが発生します。**

appcompat ライブラリで使用されているリソースを追うことで、何処でどのstyleや属性が使用しているか確認できますので、
ターゲットユーザーにとって使いやすいデザインにハックしてみたいと思います。

### 参考
- [Tips and Tricks for Android Material Support Library 2: Electric Boogaloo – Code @ Hootsuite](http://code.hootsuite.com/tips-and-tricks-for-android-material-support-library-2-electric-boogaloo/)
- [Y\.A\.M の 雑記帳: AppCompat でカラーの Raised Button を設定する正しい方法](http://y-anz-m.blogspot.jp/2015/09/appcompat-raised-button.html)
- [AppCompatButtonのスタイルをMaterialにする \- Qiita](http://qiita.com/MeilCli/items/614a1afba9105a791f5c)