2012年4月9日月曜日

AndroidのボタンをXMLのみでスタイリング可能なShapeDrawableで描画するには?

AndroidのボタンをFlexと同様にプログラムを書かずにXMLのみでスタイリングをする方法を模索してきた。

琴線探査: AndroidのボタンをSVGで描画するには?
琴線探査: 続・AndroidのボタンをSVGで描画するには?(ローテクだけど高速版)
琴線探査: AndroidのボタンをShapeDrawableで描画するには?

単純な図形しか描画できないものの、今回である程度満足のいくやり方が固まった。


ボタンのテンプレート(shapeとselector)を作る

まずはres/drawableフォルダを作る。ここに次の3つのファイルを作る。


styleablebutton_up.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#555555" />

    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />

    <corners android:radius="2dp" />

    <stroke
        android:width="1dp"
        android:color="#FFFFFF" />

</shape>


sytleablebutton_down.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle" >

    <solid android:color="#AAAAAA" />

    <padding
        android:bottom="5dp"
        android:left="5dp"
        android:right="5dp"
        android:top="5dp" />

    <corners android:radius="2dp" />

    <stroke
        android:width="1dp"
        android:color="#FFFFFF" />

</shape>


styleablebutton.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@drawable/styleablebutton_down" android:state_pressed="true"/>
    <item android:drawable="@drawable/styleablebutton_up"/>

</selector>


attrs.xmlを作る

次に、このようにvalues/attrs.xmlをつくる。

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="StyleableButton">
        <attr name="fillColorUp" format="string" />
        <attr name="fillColorDown" format="string" />
    </declare-styleable>

</resources>


styles.xmlを作る

次に、このようにvalues/styles.xmlを作る。

このファイルはmain.xmlで楽をするために作ることにした。

しかし、main.xmlでそれぞれのボタン定義で同じ内容を記述する場合は必須ではない。ただ面倒なので作っておいたほうがいい。CSSのクラス定義のようなものだと考えるといい。

<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <style name="styleablebutton" parent="@android:style/Widget.Button">
        <item name="android:layout_width">wrap_content</item>
        <item name="android:layout_height">wrap_content</item>
        <item name="android:layout_margin">5dp</item>
        <item name="android:padding">5dp</item>
        <item name="android:background">@drawable/styleablebutton</item>
        <item name="android:textColor">#FFFFFFFF</item>
        <item name="android:textSize">10dp</item>
        <item name="android:text">StyleableButton</item>
    </style>

</resources>


スタイリング用カスタムクラスを作る

次に、ex.sdrbパッケージにこのようなStyleableButton.javaクラスを作る。

package ex.sdrb;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.StateListDrawable;
import android.util.AttributeSet;
import android.widget.Button;


public class StyleableButton extends Button {


  protected Context context;
  protected AttributeSet attrs;


  public StyleableButton(Context context, AttributeSet attrs) {

    super(context, attrs);
    this.attrs = attrs;
    this.context = context;

    StateListDrawable sld = (StateListDrawable) getBackground();
    TypedArray styles = context.obtainStyledAttributes(attrs, R.styleable.StyleableButton);

    String colorStr;
    GradientDrawable gd;

    // idx=0はDOWN
    colorStr = styles.getString(R.styleable.StyleableButton_fillColorDown);
    if (colorStr != null) {
      sld.selectDrawable(0);
      gd = (GradientDrawable) sld.getCurrent();
      gd.setColor(Color.parseColor(colorStr));
    }

    // idx=1はUP
    colorStr = styles.getString(R.styleable.StyleableButton_fillColorUp);
    if (colorStr != null) {
      sld.selectDrawable(1);
      gd = (GradientDrawable) sld.getCurrent();
      gd.setColor(Color.parseColor(colorStr));
    }

  }

}

背景に設定されたStateListDrawable(styleablebutton.xmlのセレクタ)を取得し、そこからUPステートとDOWNステートのDrawableを取得してattrs.xmlで定義したスタイルパラメーター名の値を読み込んで設定していく。

ここではStateListDrawableから取得できるDrawableがGradientDrawableであることがポイント。ShapeDrawableじゃないのね。


main.xmlの編集

res/layout/main.xmlをこのように編集する。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sdrb="http://schemas.android.com/apk/res/ex.sdrb"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical"
    android:padding="10dp" >

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        android:text="StyleableButton" />

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        sdrb:fillColorDown="#FF4444"
        sdrb:fillColorUp="#553333" />

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        sdrb:fillColorDown="#44FF44"
        sdrb:fillColorUp="#335533" />

    <ex.sdrb.StyleableButton
        style="@style/styleablebutton"
        sdrb:fillColorDown="#4444FF"
        sdrb:fillColorUp="#333355" />

</LinearLayout>

ポイントは

・「xmlns:sdrb」のネームスペースの設定
・「ex.sdrb.StyleableButton」タグでボタン定義
・「style="@style/styleablebutton"」
・「sdrb:fillColorDown」
・「sdrb:fillColorUp」


実行結果


Eclipseプロジェクトファイル:StyleableDrawableResourceButton.zip(160KB)


まとめ

今回はスタイルパラメーターの定義をfillColorのUP、DOWNしかしなかったが、ストロークの幅や色など、attr.xmlとそれに対応するStyleableButton.javaの作り方次第で様々なパラメータ操作をXMLだけで行うことができることがわかった。

簡単なシェイプしか描画できないけど、単に枠を描画したいだけなら9patchを使うよりはるかに柔軟で楽だ。

また、attr.xmlのパラメーター定義は何もスタイリングだけに限らないだろう。パラメーターをメソッドの引数として考えれば、様々な動作をXMLだけで定義することができると思う。