レイアウトファイルを利用してCustomViewを作る
いまさらですが、サポートライブラリの中の人から分かりやすいスライドも出ているし、CustomViewの作り方をあらためて勉強し直しました。
参考
目次
- Viewを継承したクラスを作る
- values/attrs.xmlにカスタムViewと同名のdeclare-styleableを定義
- デフォルト属性値をvalues/styles.xmlで定義する
- カスタムView用のレイアウトファイルを書く
- カスタム属性値をコンストラクタの中で取得する
- レイアウトXMLを利用する
- カスタムViewの要素を取得
- カスタムViewに値をセット
- Activityのレイアウトに配置
- 後から属性値や表示される値を変えたり、値を取得したりしたい
- 最終的に出来上がったコード
Viewを継承したクラスを作る
まず、ViewかViewのサブクラスを継承したクラスを用意します。
コンストラクタを3つ作成する必要がありますが、初期化に必要な処理を書くのは3つめのコンストラクタにします。
また、自動生成のままだとsuper
になっており、うっかりこれをそのままにしておいて「せっかく書いたコンストラクタが呼び出されない、なんで」ということになって焦るので、ちゃんとthis
に書き換えるのを忘れないでください。
package com.example.woshidan.customviewtest; import android.content.Context; import android.util.AttributeSet; import android.view.View; /** * Created by yoshidanozomi on 2015/12/19. */ public class CustomView extends View { public CustomView(Context context) { this(context, null); // 自動生成のsuperのままにしないように! } public CustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); // 自動生成のsuperのままにしないように! } public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // ここで色々やる } }
values/attrs.xmlにカスタムViewと同名のdeclare-styleableを定義
次に、values/attrs.xmlにカスタムViewと同名のdeclare-styleable要素を定義します。
<?xml version="1.0" encoding="utf-8"?> <resources> <!-- http://yaraki.github.io/slides/gdm01/#16 --> <!-- formatの各属性については http://qiita.com/Hoshi_7/items/57c3a79c43efe05b5368 --> <declare-styleable name="CustomView"> <attr name="name" format="string" /> <attr name="email" format="string" /> <!-- enum値 --> <attr name="grade"> <enum name="beginner" value="0"/> <enum name="master" value="1"/> </attr> </declare-styleable> </resources>
デフォルト属性値をvalues/styles.xmlで定義する
<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> </style> <!-- CustomView用 --> <style name="Widget.CustomViewTest.CustomView" parent="android:Widget" > <item name="name">testUser</item> <item name="email">test@example.com</item> <item name="grade">beginner</item> </style> </resources>
カスタムView用のレイアウトファイルを書く
今回は下記のようなレイアウトにしようと思います。
<!-- view_custom.xml --> <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:background="#66f" android:padding="16dp" android:layout_width="match_parent" android:layout_height="200dp"> <TextView android:id="@+id/name" android:layout_alignParentBottom="true" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#fff" android:textSize="20dp" android:text="UserName"/> <TextView android:id="@+id/email" android:layout_alignParentBottom="true" android:layout_marginLeft="16dp" android:layout_toRightOf="@id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#fff" android:textSize="15dp" android:text="test@example.com"/> <TextView android:id="@+id/grade" android:layout_marginBottom="12dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/name" android:padding="4dp" android:background="@color/grade_beginner" android:textStyle="bold" android:textColor="#fff" android:text="@string/grade_begginer"/> </RelativeLayout>
Javaから属性値を与えやすいように、begginer
のラベルのところの値はxmlに定義しています。
<!-- values/string.xml --> <string name="grade_begginer">begginer</string> <string name="grade_master">master</string> <!-- values/colors.xml --> <color name="grade_master">#082</color> <color name="grade_beginner">#ec0</color>
カスタム属性値をコンストラクタの中で取得する
public class CustomView extends View { private String mName; private String mEmail; private int mGrade; ... public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // 参考: http://yaraki.github.io/slides/gdm01/#19 // 属性リストを取得 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView, // 属性の定義 defStyleAttr, R.style.Widget_CustomViewTest_CustomView); // デフォルトの属性値 // 属性値を取得 mEmail = a.getString(R.styleable.CustomView_email); mName = a.getString(R.styleable.CustomView_name); mGrade = a.getInt(R.styleable.CustomView_grade, 0); }
レイアウトXMLを利用する
// 3つめの要素はViewGroupである必要があるので、継承するクラスをViewGroupのクラスに変更しました public class CustomView extends RelativeLayout { // さっきのコードの続き // このViewGroupの親要素はマージの形で元の要素の箇所に渡されます。 inflate(context, R.layout.view_custom, this);
カスタムViewの要素を取得
public class CustomView extends RelativeLayout { ... private TextView mNameView; private TextView mEmailView; private TextView mGradeView; // さっきのコードの続き // カスタムViewの中の各Viewの要素を取得 mNameView = (TextView) findViewById(R.id.name); mEmailView = (TextView) findViewById(R.id.email); mGradeView = (TextView) findViewById(R.id.grade);
カスタムViewに値をセット
mNameView.setText(mName); mEmailView.setText(mEmail); if (mGrade == BEGINNER) { mGradeView.setText(getResources().getText(R.string.grade_begginer)); mGradeView.setBackgroundColor(getResources().getColor(R.color.grade_beginner)); // API23以上でないとdeprectedでない方のgetColor()は動作しません(本当は処理をAPIバージョンで分けるのが推奨) } else { mGradeView.setText(getResources().getText(R.string.grade_master)); mGradeView.setBackgroundColor(getResources().getColor(R.color.grade_master)); }
Activityのレイアウトに配置
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:custom="http://schemas.android.com/apk/res-auto" android:orientation="vertical" 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"> <!-- 属性値がデフォルトのスタイルから設定されているのを見る例 --> <com.example.woshidan.customviewtest.CustomView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <!-- 属性値をxmlからセットする例 --> <com.example.woshidan.customviewtest.CustomView android:layout_width="wrap_content" android:layout_height="wrap_content" custom:name="MasterUser" custom:email="master@example.com" custom:grade="master" /> </LinearLayout>
結果はこうなります。
後から属性値や表示される値を変えたり、値を取得したりしたい
CustomView
のクラスにセッターやゲッターを書きます。
// CustomView public void setName(String name) { mName = name; mNameView.setText(mName); } public void setEmail(String email) { mEmail = email; mEmailView.setText(mEmail); } public void setGrade(int grade) { mGrade = grade; if (mGrade == BEGINNER) { mGradeView.setText(getResources().getText(R.string.grade_begginer)); mGradeView.setBackgroundColor(getResources().getColor(R.color.grade_beginner)); // API23以上でないとdeprectedでない方のgetColor()は動作しません(本当は処理をAPIバージョンで分けるのが推奨) } else { mGradeView.setText(getResources().getText(R.string.grade_master)); mGradeView.setBackgroundColor(getResources().getColor(R.color.grade_master)); } }
最終的に出来上がったコード
最終的に出来上がったコードは下記のgistに置いてます。