[Android] ListFragment の View をカスタマイズする方法 - adakoda - http://goo.gl/UBgyu
で、onCreateView() を override してカスタムレイアウトを指定すると setEmptyText() で落ちるという話があって、正しい ID を指定してないからでは〜?的な流れになったのですが、どうもそうではないようなので調べてみました。
まず、setEmptyText(CharSequence text) は API Level 11 (Android 3.0) で追加された API です。
結果をまとめると
・ListActivity
・setEmptyView() : 使える
・getEmptyView() : 使える
・@android:id/list, @android:id/empty を使った
カスタムレイアウト : 使える
・ListFragment
・@android:id/list, @android:id/empty を使った
カスタムレイアウト : 使える
・カスタムレイアウトを使わない場合
・getListView().setEmptyView() : なにもおこらない
・getListView().getEmptyView() : 使える
・setEmptyText() : 使える
・カスタムレイアウトを使った場合
・getListView().setEmptyView() : なにもおこらない
(@android:id/empty がそのまま表示される)
・getListView().getEmptyView() : 使える
・setEmptyText() : 落ちる
ListFragment で、カスタムレイアウトで setEmptyText() を使いたい場合は、次のように override しておくこと
@Override
public void setEmptyText(CharSequence text) {
TextView tv = (TextView)getListView().getEmptyView();
tv.setText(text);
}
ちなみに、このときのカスタムレイアウトはこんな感じ
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawSelectorOnTop="false" />
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="No data"
android:textSize="30sp"/>
</FrameLayout>
さて、結論をいったので、それまでの過程もちょっと紹介。
まずは、ListFragment の onCreateView() で生成される View のなかから empty 用っぽい View を見つけて、それの ID から resourceName とればわかるんじゃね?と思ってやってみました。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = super.onCreateView(inflater, container, savedInstanceState);
Log.d("NoteListFragment", v.getClass().getName() + ", " + v.getId());
if (v instanceof ViewGroup) {
ViewGroup vg = (ViewGroup) v;
for (int i = 0; i < vg.getChildCount(); i++) {
View child = vg.getChildAt(i);
String resourcename = getResourceNameByID(android.R.id.class, child.getId());
Log.d("NoteListFragment", child.getClass().getName() + ", "
+ child.getId() + ", " + resourcename);
if(child instanceof ViewGroup) {
ViewGroup vg2 = (ViewGroup) child;
for (int j = 0; j < vg2.getChildCount(); j++) {
View child2 = vg2.getChildAt(j);
String resourcename2 = getResourceNameByID(android.R.id.class, child2.getId());
Log.d("NoteListFragment", child2.getClass().getName() + ", "
+ child2.getId() + ", " + resourcename2);
}
}
}
}
return v;
}
public String getResourceNameByID(Class<?> clazz, int resourceId)
throws IllegalArgumentException {
Field[] fields = clazz.getFields();
for (Field f : fields) {
try {
if (resourceId == f.getInt(null)) {
return f.getName();
}
} catch (Exception e) {
e.printStackTrace();
throw new IllegalArgumentException();
}
}
return null;
}
リフレクション。リフレクション。
結果はこんな感じ
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.FrameLayout, -1
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.LinearLayout, 16908904, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.ProgressBar, -1, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.TextView, -1, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.FrameLayout, 16908905, null
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.ListView, 16908298, list
07-05 13:46:40.090: DEBUG/NoteListFragment(30597): android.widget.TextView, 16908906, null
ListView には list (= android.R.id.list) が割当てられているのに、その下の TextView (EmptyView だと思われる) には null が入っている。つまり、android.R.id.** に対象の ID がなかったということです。コードで動的に生成してるのかな?
念のためこの TextView が EmptyView なのかチェック
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
...
setEmptyText(getActivity().getString(R.string.no_notes));
Log.d("NoteListFragment", getListView().getEmptyView().getId() + "");
}
結果は
07-05 13:56:22.510: DEBUG/NoteListFragment(30953): 16908906
ビンゴ。
ちなみに setEmptyText() する前に getListView().getEmptyView().getId() するとヌルポになります。
EmptyView が生成されてないんですね。このことからも EmptyView が動的に生成されてるのがわかります。
なので、レイアウトをカスタムしないで setEmptyText を override するときは super.setEmpty() を忘れずに最初にしてください。そうしないとその後の getListView().getEmptyView() には null が返ってきてしまいます。
@Override
public void setEmptyText(CharSequence text) {
super.setEmptyText(text); // don't forget
TextView tv = (TextView)getListView().getEmptyView();
tv.setText(text);
}
カスタムを使っていてもいなくても、カスタムレイアウトに android:empty が入っていてもいなくても動くようにするには、こんな感じかなぁ。。。
@Override
public void setEmptyText(CharSequence text) {
try {
super.setEmptyText(text);
}
catch (IllegalStateException e1){
e1.printStackTrace();
Log.d("NoteListFragment", e1.getClass().getName() + " : " + e1.getMessage());
try {
TextView tv = (TextView)getListView().getEmptyView();
tv.setText(text);
}
catch(NullPointerException e2) {
e2.printStackTrace();
Log.d("NoteListFragment", e2.getClass().getName() + " : " + e2.getMessage());
TextView tv = new TextView(getActivity());
tv.setText(text);
tv.setId(android.R.id.empty);
ViewParent v = getListView().getParent();
if(v instanceof ViewGroup) {
((ViewGroup)v).addView(tv);
}
getListView().setEmptyView(tv);
}
}
}
setListShownも
返信削除mProgressContainer = root.findViewById(com.android.internal.R.id.progressContainer);
で適当なのが取得できなくて死ぬ罠…(´・ω・`) ListFragmentはデフォView以外使うべきではないってことなのかなぁ