ページ

2013/10/23

[Android]Custom View の作成と AttributeSet と declare-styleable

今までカスタム View を作ったことがなかったので作ってみた。
ImageView を丸く切り抜き表示する CircleImageView を作ってみて
謎だった AttributeSet と declare-styleable がちょっとわかったので
忘れないように記事を書いておきます。

参考
Creating Custom Views | Android Developers

まずはコンストラクタ。View のコンストラクタは3種類あることが多いけどちゃんと使い分けられているらしい。
public class CircleImageView extends ImageView {

    /**
     * プログラムから動的に生成する場合に使われる
     * 
     * CircleClipView circleClipView = new CircleClipView(this);
     */
    public CircleImageView(Context context) {
        super(context);
    }

    /**
     * レイアウトファイルから生成する場合に使われる
     * 
     * <at.wada811.view.CircleClipView
     * android:layout_width="MATCH_PARENT"
     * android:layout_height="MATCH_PARENT"
     * />
     * 指定した属性値が attrs に入る
     */
    public CircleImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * いまいち謎
     */
    public CircleImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray attrsArray = getContext().obtainStyledAttributes(attrs, R.styleable.circle_view, defStyle, 0);
        mX = attrsArray.getFloat(R.styleable.circle_view_center_x, mX);
        mY = attrsArray.getFloat(R.styleable.circle_view_center_y, mY);
        mR = attrsArray.getInt(R.styleable.circle_view_radius, mR);
        // StyledAttributes should be recycled! 
        attrsArray.recycle();
    }
}
レイアウトパラメータを指定された場合には、上から2番目のAttributeSetを持つコンストラクタが呼び出されます(layout側main.xmlファイルで定義した場合など)。またスタイルを指定した場合はdefStyleを持つ3つめのコンストラクタが利用されます。
Viewをカスタマイズ(独自実装)する « Tech Booster
レイアウト XML から style などを指定しても 2つ目のコンストラクタが呼び出されて
3つ目のコンストラクタで defStyle に値が入ってこなくてイマイチ謎です。
でも style を指定したらちゃんと TypedArray に style で指定したデフォルト値が入っています。
Context#obtainStyledAttributes(AttributeSet, int[], int, int) | Android Developers
Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) | Android Developers

3つ目のコンストラクタの他の使い方としてプログラム側から呼び出す方法があります。
  1. 適用したい style (@style/circle_image_view_style_demo)を作る
  2. style を指定するための属性(@attr/circle_image_view_style)を作る
  3. Activity のテーマに作成した属性に対して適用したい style を指定する
  4. コンストラクタに作成した属性(@attr/circle_image_view_style)を渡す
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="circle_image_view_style_demo">
        <item name="center_x">50</item>
        <item name="center_y">50</item>
        <item name="radius">100</item>
    </style>
    <style name="circle_image_activity_style" parent="@style/AppTheme">
        <item name="circle_image_view_style">@style/circle_image_view_style_demo</item>
    </style>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="circle_image_view_style" format="integer|reference" />
</resources>
mCircleImageView = new CircleImageView(this, null, R.attr.circle_image_view_style);
参考
勉強中: ソースコード内で生成したViewインスタンスにStyleを適用

AttributeSet と declare-styleable について書くつもりが話がそれました。
AttributeSet は2つ目のコンストラクタのコメントにあるようにレイアウト XML の属性値が入っています。
declare-styleable はカスタム属性を定義することができます。
CircleImageView では以下のカスタム属性を定義しています。
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="circle_view">
        <attr name="center_x" format="float" />
        <attr name="center_y" format="float" />
        <attr name="radius" format="integer" />
    </declare-styleable>
</resources>
カスタム属性は以下のように使用することができます。
<at.wada811.view.CircleImageView
    xmlns:circle="http://schemas.android.com/apk/res-auto"
    android:id="@+id/circle"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:background="@color/blue_light"
    android:src="@drawable/lena"
    circle:center_x="50"
    circle:center_y="50"
    circle:radius="100" />
ここで指定した値は3つ目のコンストラクタで取得しているように取得することができます。
カスタム View の作成については以上です。

ソースコードはwada811/AndroidLibrary-wada811に含まれています。