2012年4月19日木曜日

Android カスタム ViewGroup 用XMLのルートタグを <merge> にする

レイアウトXMLファイルから View を生成するときに LayoutInfalter の inflate() メソッドを使いますが、inflate() メソッドには引数が2つのものと3つのものがあります。 引数が2つのメソッドは、内部で inflate(resource, root, root != null) のように引数が3つのメソッドを呼んでいます。
この attachToRoot に true を指定した場合と false にした場合で返ってくるルートビューが異なります。

例えば <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> </LinearLayout> LayoutInflater inflater = getLayoutInflater(); FrameLayout root = new FrameLayout(this); に対して View v = inflater.inflate(R.layout.main, root, true); とした場合は v は FrameLayout (つまり root)になります。

一方、 View v = inflater.inflate(R.layout.main, root, false); または View v = inflater.inflate(R.layout.main, null, true); or View v = inflater.inflate(R.layout.main, null, false); とした場合は v は LinearLayout (つまり R.layout.main のルートビュー)になります。

inflater.inflate(R.layout.main, root, false)



inflater.inflate(R.layout.main, null, false)

の違いは、root が null でない場合はそれに合うような LayoutParams が返ってくるルートビューにセットされるという点です。

http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/view/LayoutInflater.java#471 471 if (root != null) { 472 if (DEBUG) { 473 System.out.println("Creating params from root: " + 474 root); 475 } 476 // Create layout params that match root, if supplied 477 params = root.generateLayoutParams(attrs); 478 if (!attachToRoot) { 479 // Set the layout params for temp if we are not 480 // attaching. (If we are, we use addView, below) 481 temp.setLayoutParams(params); 482 } 483 } さて、この inflate() メソッドをみると、<merge> タグのチェックを行っている箇所があります。 424 public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { ... 453 if (TAG_MERGE.equals(name)) { 454 if (root == null || !attachToRoot) { 455 throw new InflateException("<merge /> can be used only with a valid " 456 + "ViewGroup root and attachToRoot=true"); 457 } 458 459 rInflate(parser, root, attrs, false); 460 } else { XML の最初のスタートタグが <merge> だった場合、root が null だったり attachToRoot が false だと InflateException が投げられます。

考えてみれば当たり前ですね。<merge> タグは addView されたときに親の View と合体するということなので、合体対象がいない場合戻り値の View として返すものがなくなってしまいます。



どういう場面で <merge> タグのレイアウトを inflate() することがあるかというとオリジナルの ViewGroup を作る場合です。

例えば、ボタンが縦に3つ並んだ LinearLayout をオリジナルの ViewGroup にしたいとします。

つまり、 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> </LinearLayout> </LinearLayout> <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <yanzm.example.viewgroupmerge.MyViewGroup android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout> にしたいということです。

そのためには、この MyViewGroup 自身に縦に並ぶボタン3つを持たせる必要があります。


初心者がやりがちなのがこういうコードです。 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> </LinearLayout> public class MyViewGroup extends FrameLayout { public MyViewGroup(Context context) { super(context); init(context); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MyViewGroup(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflater.inflate(R.layout.custom_layout, null, false); addView(v); } } これでも動きますが、View 階層が一つ多くなってしまうのでよくありません。 そこで、<merge> タグを使って次のようにすると、元と同じ View 階層にとどめておけます。 <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" /> </merge> public class MyViewGroup extends LinearLayout { public MyViewGroup(Context context) { super(context); init(context); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public MyViewGroup(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(context); } private void init(Context context) { setOrientation(LinearLayout.VERTICAL); LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflater.inflate(R.layout.custom_layout, this, true); } } こうすれば <merge> 部分が MyViewGroup と合体してくれます。


さらに、View には inflate(Context context, int resource, ViewGroup root) という static メソッドがあり、 このメソッドを使ってさらに簡単に書けます。 private void init(Context context) { setOrientation(LinearLayout.VERTICAL); View.inflate(context, R.layout.custom_layout, this); } LayoutInflater のインスタンスを取得するとしては以下の方法をよく使います。


0 件のコメント:

コメントを投稿