この前、Xcode6の新しい機能(IBInspectable)の機能を紹介して、ソースコードを書かずに値を設定する方法を紹介しました。

これをAndroidStudioでも同じことが出来るので紹介したいと思います。

クラスなどの紹介はまた別記事にするとして、こんな感じで実装できるよっていうのを紹介したいと思います。

サンプルの説明

サンプルはこちらのレポジトリに公開しています。

NinjaAdMax/SampleCustomViewAttr

簡単に紹介すると、円を描画するクラスを自作しました。

円を描画するのに

  • x座標
  • y座標
  • 半径
  • 円の色

というのを簡単に設定出来るようにしました。

ではまずソースコードを紹介します。

ソースコード

res/values/attr.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomView">
<attr name="custom_circle_x" format="dimension|reference" />
<attr name="custom_circle_y" format="dimension|reference" />
<attr name="custom_circle_position">
<enum name="top" value="0" />
<enum name="bottom" value="1"/>
<enum name="left" value="2" />
<enum name="right" value="3" />
<enum name="center" value="4"/>
<enum name="top_left" value="5" />
<enum name="top_right" value="6" />
<enum name="bottom_left" value="7" />
<enum name="bottom_right" value="8" />
</attr>
<attr name="custom_circle_radius" format="dimension|reference" />
<attr name="custom_circle_color" format="color|reference" />
</declare-styleable>
</resources>
CustomViewCirclePosition.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum CustomViewCirclePosition {
Top(0), Bottom(1), Left(2), Right(3), Center(4),
TopLeft(5), TopRight(6), BottomLeft(7), BottomRight(8),
Null(9999);
private int value;
private CustomViewCirclePosition(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
public static CustomViewCirclePosition valueOf(int value) {
CustomViewCirclePosition position = Null;
for (CustomViewCirclePosition p : CustomViewCirclePosition.values()) {
if (p.getValue() == value) {
position = p;
break;
}
}
return position;
}
}
CustomView.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import jp.shinobi.admax.android.sampleattributeset.R;
import jp.shinobi.admax.android.sampleattributeset.enums.CustomViewCirclePosition;
public class CustomView extends View {
private int color;
private float circleX;
private float circleY;
private float circleRadius;
private CustomViewCirclePosition circlePosition;
private static final float DEFAULT_CIRCLE_X = 100.0f;
private static final float DEFAULT_CIRCLE_Y = 100.0f;
private static final float DEFAULT_CIRCLE_RADIUS = 100.0f;
private static final int DEFAULT_CIRCLE_POSITION = 999;
private static final int DEFAULT_CIRCLE_COLOR = Color.argb(255, 0, 0, 0);
public CustomView(Context context) {
super(context);
this.initializeView(context);
}
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
this.initializeView(context, attrs);
}
public CustomView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.initializeView(context, attrs);
}
private void initializeView(Context context) {
this.color = DEFAULT_CIRCLE_COLOR;
this.circleX = DEFAULT_CIRCLE_X;
this.circleY = DEFAULT_CIRCLE_Y;
this.circleRadius = DEFAULT_CIRCLE_RADIUS;
this.circlePosition = CustomViewCirclePosition.Null;
}
private void initializeView(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
this.color = typedArray.getColor(R.styleable.CustomView_custom_circle_color, DEFAULT_CIRCLE_COLOR);
this.circleX = typedArray.getDimension(R.styleable.CustomView_custom_circle_x, DEFAULT_CIRCLE_X);
this.circleY = typedArray.getDimension(R.styleable.CustomView_custom_circle_y, DEFAULT_CIRCLE_Y);
this.circleRadius = typedArray.getDimension(R.styleable.CustomView_custom_circle_radius, DEFAULT_CIRCLE_RADIUS);
int circlePosition = typedArray.getInt(R.styleable.CustomView_custom_circle_position, DEFAULT_CIRCLE_POSITION);
this.circlePosition = CustomViewCirclePosition.valueOf(circlePosition);
typedArray.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(this.color);
paint.setAntiAlias(true);
float circleX = this.getCircleX(circlePosition);
float circleY = this.getCircleY(circlePosition);
canvas.drawCircle(circleX, circleY, circleRadius, paint);
}
private float getCircleX(CustomViewCirclePosition position) {
float circleX = this.circleX;
switch (position) {
case Left:
case TopLeft:
case BottomLeft:
circleX = 0 + this.circleRadius;
break;
case Top:
case Center:
case Bottom:
circleX = this.getWidth() / 2;
break;
case Right:
case TopRight:
case BottomRight:
circleX = this.getWidth() - this.circleRadius;
break;
case Null:
default:
break;
}
return circleX;
}
private float getCircleY(CustomViewCirclePosition position) {
float circleY = this.circleY;
switch (position) {
case Top:
case TopRight:
case TopLeft:
circleY = 0 + this.circleRadius;
break;
case Center:
case Left:
case Right:
circleY = this.getHeight() / 2;
break;
case Bottom:
case BottomLeft:
case BottomRight:
circleY = this.getHeight() - this.circleRadius;
break;
case Null:
default:
break;
}
return circleY;
}
}
activity_main.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:customView="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity">
<jp.shinobi.admax.android.sampleattributeset.views.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent"
customView:custom_circle_position="bottom_right"
customView:custom_circle_radius="100dp"
customView:custom_circle_color="#0000FF"
/>
</RelativeLayout>

見にくいかと思いますが、じっくり読みたい場合はGitHubにあげたサンプルをご覧ください。

こんな感じで完成します。

x座標、y座標を指定した場合

ScreenShot28

positionを指定した場合

ScreenShot29

それでは細々と説明していきます。

res/values/attr.xml

ここで独自Viewのプロパティを作成しています。

<declare-styleable />nameは値を設定したいクラス名を指定してください。

<declare-styleable />に関してはとっても有名なyanzmさんの記事がわかりやすかったのでコチラを参考にしてください。

Y.A.Mの雑記帳: declare-styleable メモ

<attr />の中で指定するformatに関しては以下の記事を参考にしました。

Android - styleableプロパティ全要素のリファレンスを書いてみた - Qiita

すべてのformatに対する説明と値の取得方法が書かれています。

CustomViewCirclePosition.java

あまりAndroidでenumを使う事は好まれていいないですが、わかりやすさ重視ということで使わせていただきました。

private static final intで書くのがいいかと思いますが、やはり冗長的になってしまうので…。

他には特に理由はありません。

CustomView.java

これがカスタムビュー本体のクラスになります。

onDrawでやっていることは簡単なことなので割愛するとして、注目してほしいのはコンストラクタの部分です。

layoutファイルでクラスを指定すると以下のコンストラクタが呼ばれます。

1
2
3
4
public CustomView(Context context, AttributeSet attrs) {
super(context, attrs);
this.initializeView(context, attrs);
}

このAttributeSetというクラスの中にプロパティで設定した値が含まれています。

ちなみにですが、今回はかっこ良く名前空間を使って値を指定していますが、名前空間なしでもできます。

例えば以下のようにです。

1
2
3
4
5
6
7
<jp.shinobi.admax.android.sampleattributeset.views.CustomView
android:layout_width="match_parent"
android:layout_height="match_parent"
custom_circle_position="bottom_right"
custom_circle_radius="100dp"
custom_circle_color="#0000FF"
/>

これの厄介な所は型指定が出来ないので、作ったクラス側であれこれしなければなりません。

なので出来る限りattr.xmlを用意してわかりやすくするのがいいかと思います。

ただしこのクラスを公開するとして、.jarファイルにした場合はresディレクトリ以下は含まれないため、名前空間なしで値を指定してもらう事になります。

.aarファイルの場合はresディレクトリ以下も含まれるので問題ありません。

ここらへんは別途記事にしたいと思います。

※ 2014-10-06(月)に記事書きました。

【Android】AndroidLibrary作成時に生成される.aarについて

まあ設定した値の取得方法に関してはソースコードを見てもらえばわかるかと思います。

という感じでよりスタイリッシュに自作Viewクラスを作成することが出来ます。

ちなみにLayoutファイルを扱っているときに表示されるPreviewはonDrawメソッドまで実行してくれます。

なのでXcode6の新機能、Live Renderingも実は実装されているということになります。

実装するまではちょっとめんどくさいですが、出来れば便利なものなのでぜひ実装してみてください。

終わりに

くどいですが、サンプルはこちらに公開してあります。

NinjaAdMax/SampleCustomViewAttr

自作Viewを作ったら、公開してみんな(世の中のプログラマー達)に使ってもらいたいですよね。

ということで、簡単に公開する方法を次回は紹介したいと思います。

以上になります。

参考

Y.A.Mの雑記帳: declare-styleable メモ

Android - styleableプロパティ全要素のリファレンスを書いてみた - Qiita