Android の標準コンポーネントに、CheckBox
があります。
名前の通り、チェックマークをつけたり消したりするためのもので、設定画面等でよく目にするコンポーネントです。
CheckBox のスタイルを調整したい
CheckBox
のスタイル調整に失敗するパターン
CheckBox
は、TextView
を継承したButton
を継承し、かつCheckable
インタフェースを実装したCompoundButton
を継承したものです。
クリックイベントをハンドリングできてかつ checked か unchecked かの 2 つの状態を持ちます。
この他、CompoundButton の子には、RadioButton や ToggleButton などもあります。
Button
では、selector
を持つDrawable
をbackground
に設定することで、押したかどうかや、フォーカスの状態などを表現します。CompoundButton
では、さらにチェック状態も表現することになりますが、CheckBox
やRadioButton
は、背景とは別に、チェックマークを表すためのbutton
という属性も持っています。
通常では、背景は状態によって特に何かしらの変化があるわけではなく、チェックマークのみが変化を起こします。
この時、チェックマークと文字ラベルとの間隔を調整しようと、以下のようにすると失敗します。
<CheckBox
...
android:drawablePadding="10dp"
.../>
出来ないですね。期待した通りの動きをしてくれないです。
どうしてこうなった/(^o^)\
じゃあどうやるのか
この辺の話題は、yanzm さんがまとめている記事だったり、StackOverFlow の回答 だったりが参考になります。
検証
CompoundButton
の実装に迫って検証されています。
バグる原因
わりとハマるパターンが書かれていて、例えば、CheckBox
の属性を以下のようにするとラベルの配置がバグるとか。
<CheckBox
...
android:background="#CCCCCC"/>
なんでやの。という話も yanzm さんのブログにかかれていますが、要約すると、チェックマークとラベルの間隔は、背景に設定された 9-patch によって実現されており、上記のように背景を上書きしてしまうと、調整の役割を果たしていたものが居なくなるため、うまく動かなくなる、ということです。
なので、drawablePadding
も言うことを聞かないわけです。
回避策
で、じゃあどうするのかというと、主には、Drawable
で頑張る方法と、自分でView
をつくってしまう方法の 2 通りが紹介されています。
Drawable をつくろう
9 patch を作り直せるのであれば、まあ、それでもいいかもしれませんね。ただ dp 調整が面倒くさい。
そこで、xml でDrawable
をでっち上げてしまうという方法を取ります。これなら dp 調整も簡単。
<?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 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>
これで終わるかというと、そうは問屋が卸さないんです。
動かしてみると、 要素で指定したはずの隙間が設定されておらず、見事にチェックマークとラベルがかぶっています。やめてさしあげろください。
Layer-List を使って何とかする
といったところで、StackOverFlow でこんなの見つけました。この回答でも <padding>
効かないのをどうしようってなってて、最終的に <padding>
なんて使わないで <layer-list>
の <item>
に対して左右の隙間の取り方を属性として設定してやればええやんってなってました。
なんてこったい\(^o^)/
<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>
が役に立ちます。
<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>
の登場、というわけです。
はなからちゃんと画像をスペックどおり作れば、とも思いますが、こういう場合もある、ということで。