Unityでインスペクターから値や参照を設定したいけど,publicなフィールドは嫌。[SerializeFieldAttribute]
結論。SerializeFieldAttribute属性をフィールドに設定することで,フィールドをpublicにしなくても,privateなフィールドでもインスペクターで値を設定できるようです。
現状の問題点
Unityでは,Monobehaviorのサブクラスのpublicなフィールドの値・参照を,GUI(Inspector View)から設定できます。 (HierarchyView中やProjectViewのプレファブのGameObjectのコンポーネントになっている必要はあります。)
これは,下記のようなメリットがあります。
一方で,下記のようなデメリットもあります。
- 値・参照が変更された時に任意の処理を行うことがプロパティではできるが,フィールドではできない。
- Inspectorでの初期化はしたいが,他のクラス・オブジェクトからフィールドにアクセスしてほしくない場合など,そのような制限ができない。
- アクセッサー(getter)とミューテーター(setter)に違うアクセス修飾子を設定できない。
またC#では,publicなフィールドではなく,プロパティを使うべきとも言われています。
命名規約での解決法
これを解決するために,以下のような方法,というより命名規約を考えました。 (もっといい方法を後から述べます。)
- 値・参照をInspector Viewから設定したいオブジェクトのpublicフィールドを定義する
- フィールド名は,"_"をプレフィックスに持つこととする
- 自動実装プロパティではなくて,_をプレフィックスに持つフィールドを用いたプロパティを作る
- 他のクラスからは,_をプレフィックスに持つフィールドにはアクセスしない。
このルールにより,Inspector Viewから値・参照を設定でき、アクセスはプロパティ経由からのみに制限ができます。(ただしルールを守れば。)また,適切なアクセス修飾子も,アクセスと同時に任意の処理を行うことも可能です。これを実現したサンプルは下記のようになります。
using UnityEngine; using System.Collections; namespace MRstar.Sample.UsingSerializeField { public class FieldInt : MonoBehaviour { // This field is used for only initializing with inspector. public int _intField; public int Field { get { return _intField; } private set { _intField = value; } } } }
とはいえ,「_」をプレフィックスに持つフィールドはpublicなフィールドなので,ルールで縛ったとしても,そのルールを下記のように破れば他のクラスから対象フィールドにアクセスすることは可能です。
// 想定したプロパティでのアクセス Debug.Log (string.Format ("{0}", _fieldInt.Field)); // 想定していないpublicフィールドへのアクセス Debug.Log (string.Format ("{0}", _fieldInt._intField)); _fieldInt._intField = 1;
困りました。
SerializeFieldAttribute
一応ルールとして縛りはしたけれど,実質アクセスできているので意味のないルールのようにも思えてきました。
困っていたら,というより悩んでいたところ,SerializeFieldAttributeという属性で解決できるということを教えてもらいました。
このSerializeFieldAttributeを使えば冒頭でも述べた通り,SerializeFieldAttributeを付与したprivateなフィールドをpublicなフィールドのようにInspector Viewから値・参照を設定できるようになります。
using UnityEngine; using System.Collections; namespace MRstar.Sample.UsingSerializeField { public class SerializeFieldInt : MonoBehaviour { [SerializeField] private int _intField; public int Field { get { return _intField; } private set { _intField = value; } } } }
使う側では,
// フィールドはprivateなので,プロパティでしかアクセスできない Debug.Log (string.Format ("{0}", _serialzeFieldInt.Field));
です。 privateなフィールドですので,他のクラスからはアクセスはできません。 しかし,SeralizeFieldAttributeがついているのでInspector Viewから値・参照を設定できます。
便利な属性を教えてもらいました! 自分としては,publicなフィールドをつくることが若干,気持ち悪かったので,すっきりしました。 属性をつけるのと,プロパティを用意するというのは少し面倒ですが。
サンプルプロジェクト: https://github.com/RyotaMurohoshi/unity_sample_serialize_field
Javaのコンストラクターなどの初期化について (1)
Javaのコンストラクターなどの初期化について整理してみようと思います。
- title(String型)
- message(String型)
- description(String型)
という3つのString型のフィールドを持ち,コンストラクタでそれらを初期化するクラスを作ります。 下記は良くない実装です。
package com.mrstar.sample_initialize; public class PrimaryConstructorSample { private final String title; private final String message; private final String description; public PrimaryConstructorSample(String title, String message, String description){ this.title = title; this.message = message; this.description = description; } public PrimaryConstructorSample(String title, String message){ this.title = title; this.message = message; this.description = "Default Description."; } public PrimaryConstructorSample(String title){ this.title = title; this.message = "Default Message"; this.description = "Default Description."; } // ~~ 以下省略 ~~ // }
この実装は重複があります。 例えば,文字列型の引数を2つとるコンストラクタと3つとるコンストラクタ,それぞれ二カ所でdescriptionフィールドに"Default Description."という,文字列リテラルを代入しています。"Default Description."を違う文字列に変えることにしたとします。片方を変更したは良いがもう片方を忘れてしまようなことが起きてしまう可能性があります。
package com.mrstar.sample_initialize; public class PrimaryConstructorSample { private final String title; private final String message; private final String description; public PrimaryConstructorSample(String title, String message, String description){ this.title = title; this.message = message; this.description = description; } public PrimaryConstructorSample(String title, String message){ this(title, message, "Default Description."); } public PrimaryConstructorSample(String title){ this(title, "Default Message"); } // ~~ 以下省略 ~~ // }
引数が1つのコンストラクタと2つのコンストラクタは,引数を3つのコンストラクタに処理を委譲しています。 一番上の3つのString型をとるコンストラクタのように,他のコンストラクタからthisで呼ばれるコンストラクタをプライマリコンストラクタと呼ぶようです。プライマリコンストラクタではないコンストラクタでは,プライマリコンストラクタと違う部分のみを記述すれば良くなります。これによりコードの重複が消えました。
少しコンストラクタの引数に処理を加えて,フィールドを変えてみます。 下記はコンパイルエラーになります。
// コンパイルエラー public PrimaryConstructorSample(String title, String message){ String decoratedMessage = String.format("「%s」", message); this(title, decoratedMessage); }
コンストラクタ内でthis(またsuperの呼び出しも),コンストラクタの先頭でしか呼べないからです。
さて,先頭でしかthisを呼び出せないのでメソッド化してみます。 しかし,これもコンパイルエラーになってしまいます。
// コンパイルエラー private String getDecoratedMessage(String message){ return String.format("「%s」", message); } public PrimaryConstructorSample(String title, String message){ this(title, getDecoratedMessage(message), "Sample description."); }
thisの呼び出し(superの呼び出し)の際には,インスタンスメソッドは呼び出せません。 インスタンスメソッドの呼び出しは,this.getDecoratedMessage(message)と等価ですが,コンストラクタ内のthisの呼び出しが終わるまで,thisのメソッドの呼び出し・フィールドの参照はできません。
private static String getDecoratedMessage(String message){ return String.format("「%s」", message); }
これでコンパイルエラーはとれます。
ジェネリクス(総称型)について その3
前回,型パラメータが一つのジェネリクスを使ったクラスを作ってみました。 今回は型パラメータが二つのクラスを試してみます。
package com.mrstar.practice_generics; public class SampleGeneric<T1, T2> { private T1 mValue1; private T2 mValue2; public SampleGeneric(T1 value1, T2 value2) { mValue1 = value1; mValue2 = value2; } public T1 getValue1() { return mValue1; } public T2 getValue2() { return mValue2; } @Override public String toString() { return String.format("%s %s", mValue1.toString(), mValue2.toString()); } }
使う側はこんな感じで。
SampleGeneric<Integer, Integer> sample0 = new SampleGeneric<Integer, Integer>(2, 1); System.out.println(sample0); // 2 1 SampleGeneric<String, Integer> sample1 = new SampleGeneric<String, Integer>("Hello", 0); System.out.println(sample1); // Hello 0 SampleGeneric<Integer, String> sample2 = new SampleGeneric<Integer, String>(1, "World"); System.out.println(sample2); // 1 World
もう一つ。
インターフェースでも型パラメータを使ってみます。
package com.mrstar.practice_generics; public interface MyFunction<S, R> { R func(S source); }
このインタフェースは,funcというメソッドのみを持ちます。
このインタフェースを実装したクラスを定義してみます。
package com.mrstar.practice_generics; public class StringLengthFunction implements MyFunction<String, Integer> { @Override public Integer func(String source) { return source.length(); } }
使ってみます。
System.out.println(new StringLengthFunction().func("hello")); // 5 System.out.println(new StringLengthFunction().func("Hello world!")); // 12
もう一つ。
package com.mrstar.practice_generics; public class DegreeToRadianFunction implements MyFunction<Integer, Double> { @Override public Double func(Integer degree) { return Math.PI * degree / 180.0; } }
これも使ってみます。
System.out.println(new DegreeToRadianFunction().func(0)); // 0.0 System.out.println(new DegreeToRadianFunction().func(90)); // 1.5707963267948966 System.out.println(new DegreeToRadianFunction().func(180)); // 3.141592653589793 System.out.println(new DegreeToRadianFunction().func(360)); // 6.283185307179586
ジェネリクス(総称型)について その2
Javaプログラムにおいて,型パラメータを持つクラスを定義してみました
package com.mrstar.practice_generics; public class GenericSample<T> { private T mValue; public GenericSample(T value) { mValue = value; } public T getValue() { return mValue; } public void setValue(T value) { mValue = value; } }
使ってみました。
GenericSample<Sample> sample0 = new GenericSample<Sample>(new Sample()); Sample value = sample0.getValue(); sample0.setValue(new Sample()); // sample0.setValue("hello"); コンパイルエラー GenericSample<String> sample1 = new GenericSample<String>("Hello Generics."); String value1 = sample1.getValue(); sample1.setValue("Hello World."); // sample1.setValue(1); コンパイルエラー
使い方はこんな感じです。 次に,static メソッドの型パラメータを持つメソッドを定義して使ってみました。
package com.mrstar.practice_generics; public class GenericStaticSample { public static <T> T staticFunction(T value) { return value; } }
呼び出してみます。
Sample sample0 = GenericStaticSample.staticFunction(new Sample()); Sample sample1 = GenericStaticSample.<Sample>staticFunction(new Sample());
下の呼び方は知りませんでした。 型パラメータを指定して呼び出すこともできるんですね。
ジェネリクス(総称型)について その1
型とか継承とかジェネリクスの話。 特にジェネリクスは理解していると思っていたのですが,理解が甘かったです。
package com.mrstar.practice_generics; public class Super { }
このSuperというクラスと,
package com.mrstar.practice_generics; public class Sub extends Super { }
Superを継承したSubというクラスがあるとします。
Super object0 = new Super(); Sub object1 = new Sub();
任意の型の変数に,その型のインスタンスを代入することは当然できます。
Super object2 = new Sub(); // Sub object3 = new Super(); コンパイルエラー
スーパークラスの変数に,サブクラスのインスタンスを代入することは可能だけれども,サブクラスの変数にスーパークラスのインスタンスは代入することができません。
ArrayListを使ってみます。
ArrayList<Super> list0 = new ArrayList<Super>(); list0.add(new Super()); list0.add(new Sub());
ArrayList <Super>の変数にArrayList<Super>型のインスタンスを代入します。 それに対して,Super型のインスタンスや,Sub型のインスタンスをaddします。
ArrayList<Sub> list1 = new ArrayList<Sub>(); //list1.add(new Super()); コンパイルエラー list1.add(new Sub());
ArrayList<Sub>の変数にArrayList<Sub>型のインスタンスを代入します。 それに対してSub型のインスタンスはaddできますが,当然Super型のインスタンスはaddできません。
さて,ここからです。
ArrayList<Super> list = new ArrayList<Sub>(): // これはコンパイルが通る?コンパイルエラー?
がっつり迷ってしまいました。
ていうか分かりませんでした。
コンパイルエラーでした。
仮にこれがコンパイルエラーになら無い場合を考えてみます。
その場合はArrayLlist<Sub>型のインスタンスがArrayList<Super>型の変数listに入っています。
ArrayList<Super>型の変数listは,Super型のインスタンスやSub型以外のSuper型を継承したインスタンスもaddできなくてはいけません。 ですが,ArrayList<Sub>のインスタンスは,Super型のインスタンスもSub型以外のSuper型を継承したインスタンスもaddはできません。 矛盾が生じますね。
そもそもスーパークラスの変数にサブクラスのインスタンスは代入出来ますが,型パラメータですからね,今回の問題は。
分かったつもりになっていました。
ジェネリクスについてはもっと勉強しないといけません。
ListAdapterについて調べてみた(4) SimpleAdapter編その3
前回の続きということで,SImpleAdapterの使い方です。今回はViewBinderを見て行きます。
前回,SimpleAdapterの内部でどのようにViewとデータがバインドされるかを確認しました。SimpleAdapterは,BaseAdapterを継承していろいろ実装するよりも少ないコード量でバインドはできるのですが,柔軟な処理はできません。例えば,URLのデータが文字列形式であり,AsyncTaskを用いてそのURLから画像を取得し,その画像をImageViewにバインドする,というようなよく行われる処理ができません。
このような処理は,ViewBinderを使えば実行することが可能です。実例を挙げて,使い方を見て行きます。
ViewBinderを使ってやりたいこと
先ほど上げた例を実際に例示するとViewBinder以外のコード量が多くなってしまうので,今回は違う例を示します。
色の情報を表現する以下のようなシンプルなモデルがあります。
public class ColorData { private final String name; private final String colorString; public ColorData(String name, String colorString) { this.name = name; this.colorString = colorString; } public String getName() { return name; } public String getColorString() { return colorString; } }
nameには,青や赤など色の名前が文字列で入れます。colorStringには,#0000ffや#ff0000などその色の16進表現の文字列が入れます。次のような感じです。
Color red = new ColorData("赤", "#ff0000"); Color blue = new ColorData("青", "#0000ff");
次にViewです。ListView中の1つのセルは,色の名前を表示するTextViewと色を実際に表示する単色のImageViewを構成要素とします。
色の名前をTextViewにバインドすることは前回と前々回でみてきた内容から,SimpleAdapterの機能を使えば可能です。
しかし,色の16進表現の文字列とImageViewをどのようにSimpleAdapterでバインドすればいいでしょうか。文字列のデータとImageViewのバインドは,デフォルトでは次のどちらかの処理が行われます。文字列の中身がイメージリソースのidの数値の文字列表現になっていて,Integer型に変換され,setImageResourceが呼ばれる。もしくは,その文字列をUri.parseした結果がImageView#setImageURIの引数になる。
今回はそのどちらでもありません。色の16進表現の文字列をImageViewとバインドする時,通常のバインドでなくて,独自のバインド処理を定義して実行をする必要があります。
それではこのような処理を,ViewBinderを使って実現します。
ViewBinderの定義と使い方
private static class ColorViewBinder implements ViewBinder { @Override public boolean setViewValue(View view, Object data, String textRepresentation) { if (view.getId() == R.id.color_image_view) { int color = Color.parseColor(data.toString()); ImageView imageView = (ImageView) view; imageView.setImageDrawable(new ColorDrawable(color)); return true; } else { return false; } } }
このViewBinderを,ListActivityのサブクラスのonCreateメソッド内で,
SimpleAdapter adapter = new SimpleAdapter(this, data, layoutResourceId, FROM, TO); adapter.setViewBinder(new ColorViewBinder()); setListAdapter(adapter);
の用に記述すれば,やりたいことは実現可能です。(コード全体は最後に載せます。)
それではViewBinderの詳細をみていきましょう。
ViewBinderについて
Android Developersのクラスリファレンスはこちら
完全修飾クラス名は,android.widget.SimpleAdapter.ViewBinder。クラスでなくて,public static なSImpleAdapterのクラス内のinterfaceですね。メソッドは次の一つのみです。
abstract boolean setViewValue(View view, Object data, String textRepresentation)
このメソッドの引数は
- View型のview データをバインドするView
- Object型のdata Viewにバインドするデータ
- String型のtextRepresentation dataのtoStringメソッドの結果。もしこれがnullだったら空文字
となっています。特別なバインドを行いたい場合このsetViewValueメソッドをその目的に応じて実装すればいいです。
boolean型の返り値は,もしViewBinderによってデータとビューがバインドされたらtrueを返し,バインドしないのであればfalseを返します。SimpleAdapterインスタンスはViewBinderのインスタンスがセットされると,全てのビューとデータのバインド時に,このsetViewValueメソッドを呼び出します。色の文字列表現をImageViewにバインドする場合は,ViewBinder#setViewValueで定義した処理を行いたいです。しかし,今回の例の色の名前をTextViewにバインドするような処理は,デフォルトのSimpleAdapterの処理に任せ,自分で実装はしたくありません。Viewのidを調べ,独自の処理を行いたいものでなかったら,返り値でfalseを返せば,前回のSimpleAdapterのデフォルトのバインド処理が行われます。
今回のViewBinderをみてみる
今回の作ったViewBinderを実装したクラスのsetViewValueをみてみます。
public boolean setViewValue(View view, Object data, String textRepresentation) { if (view.getId() == R.id.color_image_view) { int color = Color.parseColor(data.toString()); ImageView imageView = (ImageView) view; imageView.setImageDrawable(new ColorDrawable(color)); return true; } else { return false; } }
まず,Viewのidを調べます。独自でバインド処理を行いたいImageView以外ではfalseを返すようにします。独自処理をしたいImageView(idはcolor_image_view)では,文字列をcolorのint表現に変換,ColorDrawableインスタンスの作成,ImageViewへのセットを行います。最後にViewBinder内でバインドし,デフォルトのバインド処理をする必要が無いので,返り値はtrueとします。
まとめと個人的な意見
3回に渡ってSimpleAdapterを見てきました。Adapterは「BaesAdapterを継承したクラスを定義し,その中でgetViewメソッドをオーバーライドして,ViewHolderを定義して,セルのViewのタグにViewHolderをセットする」という風に書かれた記事やブログなどは数も多くよく見るのですが,それに比べるとSimpleAdapterの記事やブログは少なく感じます。
パフォーマンスの問題はあれどSimpleAdapter(場合によってはArrayAdapter)をうまく使うことでで少ないコード量で目的のアダプターを作れるます。ViewHolderを使った場合とパフォーマンスの差がどうなのかにもよりますが,個人的にはSImpleAdapterの方が好きです。findViewByIdってどのくらい重いのかな?
2010年のGoogle IOのなかで,ListViewに関するセッションがあります。(こちら)
上記の講演・スライドでもあるように,たしかにViewHolderを使う方が早くなるというのは分かるのですが,2013年の今の端末だとその差がどれくらいになるのかと,その差がどのくらい気になる物なのか,すこし興味があります。
それでは,また会えることを祈りつつ。
サンプルコード
package com.mrstar.hello_adapter; import android.app.ListActivity; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.View; import android.widget.ImageView; import android.widget.SimpleAdapter; import android.widget.SimpleAdapter.ViewBinder; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class SimpleAdapterWithViewBinderActivity extends ListActivity { private static final String KEY_NAME = "name"; private static final String KEY_COLOR = "color"; private static final String[] FROM = new String[] { KEY_NAME, KEY_COLOR }; private static final int[] TO = new int[] { R.id.name_text_view, R.id.color_image_view }; private static final List<ColorData> COLOR_DATA; static { COLOR_DATA = new ArrayList<ColorData>(); COLOR_DATA.add(new ColorData("青", "#0000ff")); COLOR_DATA.add(new ColorData("シアン", "#00ffff")); COLOR_DATA.add(new ColorData("緑", "#00ff00")); COLOR_DATA.add(new ColorData("マゼンタ", "#ff00ff")); COLOR_DATA.add(new ColorData("赤", "#ff0000")); COLOR_DATA.add(new ColorData("黄色", "#ffff00")); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); List<ColorData> colorDataList = new ArrayList<ColorData>(COLOR_DATA); List<Map<String, ?>> data = colorDataListToMapList(colorDataList); int layoutResourceId = R.layout.list_item_for_simple_adapter; SimpleAdapter adapter = new SimpleAdapter(this, data, layoutResourceId, FROM, TO); adapter.setViewBinder(new ColorViewBinder()); setListAdapter(adapter); } private static class ColorViewBinder implements ViewBinder { @Override public boolean setViewValue(View view, Object data, String textRepresentation) { if (view.getId() == R.id.color_image_view) { int color = Color.parseColor(data.toString()); ImageView imageView = (ImageView) view; imageView.setImageDrawable(new ColorDrawable(color)); return true; } else { return false; } } } private List<Map<String, ?>> colorDataListToMapList(List<ColorData> colorDataList) { List<Map<String, ?>> data = new ArrayList<Map<String, ?>>(); for (ColorData colorItem : colorDataList) { data.add(colorDataToMap(colorItem)); } return data; } private Map<String, Object> colorDataToMap(ColorData colorData) { Map<String, Object> map = new HashMap<String, Object>(); map.put(KEY_NAME, colorData.getName()); map.put(KEY_COLOR, colorData.getColorString()); return map; } }
ListAdapterについて調べてみた(3) SimpleAdapter編その2
前々回はArrayAdapterを,前回はSimpleAdapterを使ってみました。今回は前回の続きとして,SimpleAdapterのソースコードとクラスリファレンスを詳しく見て行きたいと思います。
バインドできるビューとデータ型
BaseAdapterを継承してAdapterを作る場合,getViewメソッドを実装し,getItemメソッドを使ってデータを取得して,そのデータをセルの子Viewにバインドする必要があります。また,Viewを使い回したりViewHolderを定義し,セルのViewのタグにセットしたりなど毎回毎回同じような処理を記述する必要があります。
一方で,SimpleAdapterは(List< Map< String,?>>型の)データとセルのレイアウト中の子Viewを,(どういう対応でバインドするかを引数で指定すれば)勝手にバインドしてくれます。勝手にバインドしてくれますが,全てのViewに対してバインドできるわけではありません。つまりSimpleAdapterのコンストラクタの第5引数,int型の配列の要素に入るViewのidは,全てのViewのサブクラスを指定出来るわけではありません。
- CheckBoxなどCheckableインターフェースを実装したView
- TextViewとそのサブクラス
- ImageViewとそのサブクラス
また,それぞれのViewに対応出来るデータの型にも決まりがあります。
SimpleAdapterのbindViewの一部をみてみる
今回,クラスリファレンスに加えて,Android4.3のSimpleAdapterのコードのbindViewメソッドを見てみました。
下記はbindViewメソッドの中の一つのViewと(コード中v),それに対応する一つのデータ(コード中data)を実際にバインドする箇所を抜き出した物です。また,ここでtextというのは,dataのtoString()メソッドの結果を格納している変数です。(nullな場合は,空文字に置き換わっています。)
if (v instanceof Checkable) { if (data instanceof Boolean) { ((Checkable) v).setChecked((Boolean) data); } else if (v instanceof TextView) { // Note: keep the instanceof TextView check at the bottom of these // ifs since a lot of views are TextViews (e.g. CheckBoxes). setViewText((TextView) v, text); } else { throw new IllegalStateException(v.getClass().getName() + " should be bound to a Boolean, not a " + (data == null ? "<unknown type>" : data.getClass())); } } else if (v instanceof TextView) { // Note: keep the instanceof TextView check at the bottom of these // ifs since a lot of views are TextViews (e.g. CheckBoxes). setViewText((TextView) v, text); } else if (v instanceof ImageView) { if (data instanceof Integer) { setViewImage((ImageView) v, (Integer) data); } else { setViewImage((ImageView) v, text); } } else { throw new IllegalStateException(v.getClass().getName() + " is not a " + " view that can be bounds by this SimpleAdapter"); }
CheckBoxなどCheckableインターフェースを実装したViewのバインドをみてみる
まずCheckableインターフェースを実装したViewかどうかをチェックします。
if (v instanceof Checkable) {
そして条件が真であれば,下記のようにバインドします。
if (data instanceof Boolean) { ((Checkable) v).setChecked((Boolean) data); } else if (v instanceof TextView) { // Note: keep the instanceof TextView check at the bottom of these // ifs since a lot of views are TextViews (e.g. CheckBoxes). setViewText((TextView) v, text); } else { throw new IllegalStateException(v.getClass().getName() + " should be bound to a Boolean, not a " + (data == null ? "<unknown type>" : data.getClass())); }
Boolean型オブジェクトのデータがCheckableインターフェースを実装するViewにバインドされる場合Boolean型のオブジェクトを引数にCheckable#setChecked(boolean)メソッドが実行されます。
また,バインドされるデータがBoolean型ではなくかつ,ViewがTextViewのインスタンスであれば,データのtoStringメソッドの結果を,TextView#setTextメソッドの引数としてバインドします。
それ以外の場合は,IllegalStateExceptionが投げられます。
TextViewとそのサブクラスのバインドをみてみる
} else if (v instanceof TextView) { // Note: keep the instanceof TextView check at the bottom of these // ifs since a lot of views are TextViews (e.g. CheckBoxes). setViewText((TextView) v, text);
Checkableを実装しているかどうかをチェックして結果が偽であれば,次はTextViewとそのサブクラスかどうかを確認します。もし条件が真ならsetViewTextというメソッドを用いて,バインドします。内部ではTextView#setText(CharSequence)メソッドを実行しています。
ImageViewとそのサブクラスのバインドをみてみる
} else if (v instanceof ImageView) { if (data instanceof Integer) { setViewImage((ImageView) v, (Integer) data); } else { setViewImage((ImageView) v, text); }
Checkableを実装したViewとTextViewの次は,ViewがImageViewのサブクラスであるか調べます。この場合は,データがInteger型かどうかで処理が変わります。上記のコード中で,Integer型かどうか調べて分岐先両方でsetViewImageを実行していますが,このメソッドは,第2引数がInterger型のものとString型のものがあります。
データがInteger型の場合,setViewImageメソッド内でImageView.setImageResource(int)が行われます。引数はデータです。
データがIntegerクラスでない場合,以下のような処理が行われます。
public void setViewImage(ImageView v, String value) { try { v.setImageResource(Integer.parseInt(value)); } catch (NumberFormatException nfe) { v.setImageURI(Uri.parse(value)); } }
まとめ
よく使うTextViewとImageViewがバインドは簡単にできることが分かりました。
これに加えてViewBinderを使えば複雑な処理をすることも可能です。
次回はViewBinderを使ってみたいと思います。