AlertDialog#findViewByIdはshow以降じゃないと使えない
Androidはビューをインフレートすることでカスタムなダイアログを生成することができる。
- Activity中にて
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater factory = LayoutInflater.from(this); View layoutForDialog = factory.inflate(R.layout.layoutfordialog, null); Dialog dialog = new AlertDialog.Builder(this) .setIcon(android.R.drawable.ic_input_get) .setTitle("ダイアログのタイトル") .setCancelable(true) .setMessage("ダイアログのメッセージ") .setView(layoutForDialog) .setPositiveButton("OK", null) .setNegativeButton("CANCEL", null) .create(); dialog.show();
- 実行結果
このとき、ダイアログが表示される前にダイアログ中のビューに一工夫しようと以下のようにコードを追加するも上手くいかない。
TextView tv = (TextView)dialog.findViewById(R.id.TextView01); tv.setText("テキストを変える"); dialog.show();
トレースするとfindViewByIdの戻りがnullのようだ。(Caused by: java.lang.NullPointerException) しかし、R.id.TextView01で識別されるビューは間違い無く存在している。
ならばと、Dialog#showの後にコードを移すと
dialog.show(); TextView tv = (TextView)dialog.findViewById(R.id.TextView01); tv.setText("テキストを変える");
今度は成功する。(以前に書いたようにAndroidのダイアログはスレッドをブロックしない(モーダルではない)ので、showメソッドは瞬時にリターンする)
こりゃおかしいなとshowメソッドの前後でビューの階層を調べてみたのだが、
- Dialog.Show()前のビュー階層
dialog.getWindow.getDecorView(). mChildren View[12] [0] LinearLayout : mChildren View[12] [0] TextView [1] FrameLayout : mChildren View[12] [0] FrameLayout : mChildren View[12] [0] null :
- Dialog.Show()後のビュー階層
dialog.getWindow.getDecorView(). mChildren View[12] [0] FrameLayout : mChildren View[12] [0] WeightedLinearLayout : mChildren View[12] [0] LinearLayout [1] LinearLayout [2] FrameLayout : mChildren View[12] [0] FrameLayout : mChildren View[12] [0] LinearLayout : mChildren View[12] [0] TextView <= TextView1 [1] TextView [2] TextView :
と全く違う。
どうやら流し込まれたビューの階層構造はshowメソッドを実行しないと完成しないようだ。
前述した通り、AndroidのダイアログはshowメソッドがUIスレッドをブロックすることはないので、showメソッドリターン後にビューを取得、変更する処理を書いても問題は無いが、以前に書いたように他のスレッドでダイアログを生成した場合や、ダイアログを別スレッドから表示するなど、showメソッドの実行前に処理を書きたい場合はどうすればよいのだろう。
答えは簡単で、インフレートしたビューをダイアログに渡す前に目的のビューを取得しておけば良いだけだ。
-
- 修正後
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); LayoutInflater factory = LayoutInflater.from(this); View layoutForDialog = factory.inflate(R.layout.layoutfordialog, null); TextView tv = (TextView)layoutForDialog.findViewById(R.id.TextView01); tv.setText("テキストを変える"); Dialog dialog = new AlertDialog.Builder(this) .setIcon(android.R.drawable.ic_input_get) .setTitle("ダイアログのタイトル") .setCancelable(true) .setMessage("ダイアログのメッセージ") .setView(layoutForDialog) .setPositiveButton("OK", null) .setNegativeButton("CANCEL", null) .create(); dialog.show();
-
- 実行結果
追記:例として使用した、ダイアログに流し込むレイアウト用XMLを載せるのを忘れていた。
- /layout/layoutfordialog.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" > <TextView android:id="@+id/TextView01" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ビュー1" ></TextView> <TextView android:id="@+id/TextView02" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ビュー2" ></TextView> <TextView android:id="@+id/TextView03" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ビュー3" ></TextView> </LinearLayout>