CheckBox の背景を変える時は気をつけろ！

Android の標準コンポーネントに、`CheckBox`があります。
名前の通り、チェックマークをつけたり消したりするためのもので、設定画面等でよく目にするコンポーネントです。

# CheckBox のスタイルを調整したい

## `CheckBox`のスタイル調整に失敗するパターン

`CheckBox`は、`TextView`を継承した`Button`を継承し、かつ`Checkable`インタフェースを実装した`CompoundButton`を継承したものです。
クリックイベントをハンドリングできてかつ checked か unchecked かの 2 つの状態を持ちます。
この他、CompoundButton の子には、RadioButton や ToggleButton などもあります。

`Button`では、`selector`を持つ`Drawable`を`background`に設定することで、押したかどうかや、フォーカスの状態などを表現します。`CompoundButton`では、さらにチェック状態も表現することになりますが、`CheckBox`や`RadioButton`は、背景とは別に、チェックマークを表すための`button`という属性も持っています。
通常では、背景は状態によって特に何かしらの変化があるわけではなく、チェックマークのみが変化を起こします。

この時、チェックマークと文字ラベルとの間隔を調整しようと、以下のようにすると失敗します。

```xml

<CheckBox
	...
	android:drawablePadding="10dp"
    .../>
```

出来ないですね。期待した通りの動きをしてくれないです。

どうしてこうなった／(^o^)＼

## じゃあどうやるのか

この辺の話題は、[yanzm さんがまとめている記事](http://y-anz-m.blogspot.jp/2011/02/android-radiobutton.html)だったり、[StackOverFlow の回答](http://stackoverflow.com/questions/4037795/android-spacing-between-checkbox-and-text) だったりが参考になります。 

## 検証

`CompoundButton`の実装に迫って検証されています。

### バグる原因

わりとハマるパターンが書かれていて、例えば、`CheckBox`の属性を以下のようにするとラベルの配置がバグるとか。

```xml
<CheckBox
	...
	android:background="#CCCCCC"/>
```

なんでやの。という話も yanzm さんのブログにかかれていますが、要約すると、チェックマークとラベルの間隔は、背景に設定された 9-patch によって実現されており、上記のように背景を上書きしてしまうと、調整の役割を果たしていたものが居なくなるため、うまく動かなくなる、ということです。
なので、`drawablePadding`も言うことを聞かないわけです。

### 回避策

で、じゃあどうするのかというと、主には、`Drawable` で頑張る方法と、自分で`View`をつくってしまう方法の 2 通りが紹介されています。

### Drawable をつくろう

9 patch を作り直せるのであれば、まあ、それでもいいかもしれませんね。ただ dp 調整が面倒くさい。
そこで、xml で`Drawable`をでっち上げてしまうという方法を取ります。これなら dp 調整も簡単。

```xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <padding
        android:left="5dp"
        android:top="0dp"
        android:right="0dp"
        android:bottom="0dp"/>
</shape>
```

最初はこれで行けると思っていた時期が私にもありました。

### もう少し作りこむ

実は、これもハマりパターンがあって、ちゃんとやらないと端末によって意図しない動作をしてくださいます。

まず、上の xml には色の指定がありません。そうすると、端末によって色の出方が変化します。

Nexus シリーズは指定がないので、`CheckBox` の親要素が持っている `background` の色になります。
一方、Xperia や INFOBAR では、何も指定していないことをいいことに、背景が真っ黒扱いになり、何も指定した記憶が無いのに、`CheckBox` の背景が真っ黒になります。

ので、以下のように、透過したければ透過することをちゃんと宣言しておく必要があります。

```xml

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid color="@android:color/transparent"/>
    <padding
        android:left="5dp"
        android:top="0dp"
        android:right="0dp"
        android:bottom="0dp"/>
</shape>
```

これで終わるかというと、そうは問屋が卸さないんです。

動かしてみると、<padding> 要素で指定したはずの隙間が設定されておらず、見事にチェックマークとラベルがかぶっています。やめてさしあげろください。

### Layer-List を使って何とかする

といったところで、[StackOverFlow でこんなの](http://stackoverflow.com/questions/1283085/padding-doesnt-affect-shape-in-an-xml-layout)見つけました。この回答でも `<padding>` 効かないのをどうしようってなってて、最終的に `<padding>` なんて使わないで `<layer-list>` の `<item>` に対して左右の隙間の取り方を属性として設定してやればええやんってなってました。

なんてこったい＼(^o^)／ 

```xml
<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:left="5dp" android:right="0dp" android:top="0dp" android:bottom="0dp">
        <bitmap android:src="@drawable/background"/>
    </item>
</layer-list>
```

## 応用編

チェックマークの画像、うまく設定できていますか？

標準のものではなく、自分でカスタマイズしたものを使おうとすると、これはこれでハマります。
なぜかといえば、9-patch で作られたあの背景が邪魔をしてくるのです。

9-patch のなかでは、予めチェックマーク用の領域が確保されています。つまり、その領域の大きさとおなじ大きさで画像を作らないと、ハマるわけです。

で、領域より小さい画像を作ってしまった時にどうするか。そんなときにも、`<layer-list>`が役に立ちます。

```xml

<layer-list
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape>
            <solid android:color="@android:color/transparent"/>
            <size android:width="20dp"/> <!-- チェックマークの大きさ分だけ領域を取る -->
        </shape>
    </item>
</layer-list>
```

Nexus だと、何もしなくても`button`の`Drawable`の大きさで表示してくれるんですが、それ以外の端末ではうまく行きません。`android:background="@null"`とかすると、今度は Nexus 以外の端末ではチェックマークが消え去ります。

そこで、上記の`<layer-list>`の登場、というわけです。

はなからちゃんと画像をスペックどおり作れば、とも思いますが、こういう場合もある、ということで。