また、ListView にはリストのデータが空の時に表示させる emptyView を指定することができます。データがないときに画面が真っ黒になるとユーザーはアプリが壊れたと思ってしまうかもしれないので、空のときにはメッセージをだしましょうとよく言われます。
ただ、この emptyView を指定するとリストのデータが空のときに、ヘッダーやフッターも表示されなくなります。
emptyView を指定している状態でヘッダーやフッターを表示できるのか調べてみました。
まず、ListView で emptyView への切り替えをどこでしているかいうと AdapterView の updateEmptyStatus(boolean empty) です。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/widget/AdapterView.java#717
- 717 private void updateEmptyStatus(boolean empty) {
- 718 if (isInFilterMode()) {
- 719 empty = false;
- 720 }
- 721
- 722 if (empty) {
- 723 if (mEmptyView != null) {
- 724 mEmptyView.setVisibility(View.VISIBLE);
- 725 setVisibility(View.GONE);
- 726 } else {
- 728 setVisibility(View.VISIBLE);
- 729 }
- 730
- 734 if (mDataChanged) {
- 735 this.onLayout(false, mLeft, mTop, mRight, mBottom);
- 736 }
- 737 } else {
- 738 if (mEmptyView != null) mEmptyView.setVisibility(View.GONE);
- 739 setVisibility(View.VISIBLE);
- 740 }
- 741 }
その次の if else 文が本体と emptyView の表示・非表示の切り替えをしているところです。
これをみると empty が true でも emptyView があれば本体が表示されることがわかります。
では、この updateEmptyStatus() がどこから呼ばれているかというと
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/widget/AdapterView.java#643
setEmptyView()
- 643 public void setEmptyView(View emptyView) {
- 644 mEmptyView = emptyView;
- 645
- 646 final T adapter = getAdapter();
- 647 final boolean empty = ((adapter == null) || adapter.isEmpty());
- 648 updateEmptyStatus(empty);
- 649 }
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/widget/AdapterView.java#717
checkFocus()
- 698 void checkFocus() {
- 699 final T adapter = getAdapter();
- 700 final boolean empty = adapter == null || adapter.getCount() == 0;
- 701 final boolean focusable = !empty || isInFilterMode();
- 705 super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
- 706 super.setFocusable(focusable && mDesiredFocusableState);
- 707 if (mEmptyView != null) {
- 708 updateEmptyStatus((adapter == null) || adapter.isEmpty());
- 709 }
- 710 }
いずれも adapter が null もしくは adapter.isEmpty() が true なら updateEmptyStatus() の引数として true が渡されています。
つまりまとめると
1. adapter != null && adapter.isEmpty == false → 本体が表示される
2. adapter == null or adapter.isEmpty == true
→ emptyView != null → 本体 が表示される
→ emptyView == null → emptyView が表示される
なので、結論としては、 isEmpty() で常に false を返すように Override するか、emptyView を null にすればよい。
ListView を単体で使うときは明示的に setEmptyView() するか、android/id:empty の View を XML で定義するので意識できるますが、Android で用意されている ListView 用の ListActivity と ListFragment を使うときにはちょっと注意が必要です。
Android 4.0 では、ListActivity でのデフォルトのレイアウトとして com.android.internal.R.layout.list_content_simple をセットしています。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/ListActivity.java#308
- 308 private void ensureList() {
- 309 if (mList != null) {
- 310 return;
- 311 }
- 312 setContentView(com.android.internal.R.layout.list_content_simple);
- 313
- 314 }
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/layout/list_content_simple.xml
- 20 <listview xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list" 21="" android:layout_width="match_parent" 22="" android:layout_height="match_parent" 23="" android:drawselectorontop="false" 24="">
- tview>
ちなみに Android 2.3.4 では、com.android.internal.R.layout.list_content をセットしていますが、なかのレイアウトは 4.0 の list_content_simple と同じです。
http://tools.oesf.biz/android-2.3.4_r1.0/xref/frameworks/base/core/java/android/app/ListActivity.java#308
- 308 private void ensureList() {
- 309 if (mList != null) {
- 310 return;
- 311 }
- 312 setContentView(com.android.internal.R.layout.list_content);
- 313
- 314 }
http://tools.oesf.biz/android-2.3.4_r1.0/xref/frameworks/base/core/res/res/layout/list_content.xml
- 20 <ListView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@android:id/list"
- 21 android:layout_width="match_parent"
- 22 android:layout_height="match_parent"
- 23 android:drawSelectorOnTop="false"
- 24 />
一方、ListFragment のデフォルトのレイアウトとしては com.android.internal.R.layout.list_content がセットされています。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/ListFragment.java#191
- 190 @Override
- 191 public View onCreateView(LayoutInflater inflater, ViewGroup container,
- 192 Bundle savedInstanceState) {
- 193 return inflater.inflate(com.android.internal.R.layout.list_content,
- 194 container, false);
- 195 }
こっちはちょっと複雑なレイアウトになっています。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/res/res/layout/list_content.xml
- 18 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- 19 android:layout_width="match_parent"
- 20 android:layout_height="match_parent">
- 21
- 22 <LinearLayout android:id="@+id/progressContainer"
- 23 android:orientation="vertical"
- 24 android:layout_width="match_parent"
- 25 android:layout_height="match_parent"
- 26 android:visibility="gone"
- 27 android:gravity="center">
- 28
- 29 <ProgressBar style="?android:attr/progressBarStyleLarge"
- 30 android:layout_width="wrap_content"
- 31 android:layout_height="wrap_content" />
- 32 <TextView android:layout_width="wrap_content"
- 33 android:layout_height="wrap_content"
- 34 android:textAppearance="?android:attr/textAppearanceSmall"
- 35 android:text="@string/loading"
- 36 android:paddingTop="4dip"
- 37 android:singleLine="true" />
- 38
- 39 </LinearLayout>
- 40
- 41 <FrameLayout android:id="@+id/listContainer"
- 42 android:layout_width="match_parent"
- 43 android:layout_height="match_parent">
- 44
- 45 <ListView android:id="@android:id/list"
- 46 android:layout_width="match_parent"
- 47 android:layout_height="match_parent"
- 48 android:drawSelectorOnTop="false" />
- 49 <TextView android:id="@+android:id/internalEmpty"
- 50 android:layout_width="match_parent"
- 51 android:layout_height="match_parent"
- 52 android:gravity="center"
- 53 android:textAppearance="?android:attr/textAppearanceLarge" />
- 54 </FrameLayout>
- 55
- 56 </FrameLayout>
注目してほしいのが @+android:id/internalEmpty という ID の TextView です。
ListFragment には setEmptyText() という、データが空のときに表示する文字をセットするメソッドが用意されています。このメソッドが呼ばれると、次のように ListView の setEmptyView() が呼ばれます。
http://tools.oesf.biz/android-4.0.1_r1.0/xref/frameworks/base/core/java/android/app/ListFragment.java#289
- 289 public void setEmptyText(CharSequence text) {
- 290 ensureList();
- 291 if (mStandardEmptyView == null) {
- 292 throw new IllegalStateException("Can't be used with a custom content view");
- 293 }
- 294 mStandardEmptyView.setText(text);
- 295 if (mEmptyText == null) {
- 296 mList.setEmptyView(mStandardEmptyView);
- 297 }
- 298 mEmptyText = text;
- 299 }
ここの mStandardEmptyView というのが上記の @+android:id/internalEmpty という ID の TextView に対応しています。
ということで、ListFragment でなんとなくやってた setEmptyText() をコメントアウトしたらヘッダーでるようになったー!
ただし、残念ながらこの場合も
-----
ヘッダー
empty message
フッター
-----
のようにはできないです。ヘッダー・フッターと emptyView は一緒に出すことはコードを見た限りではできないですねー
updateEmptyStatus が protected だったらいろいろできたのに。。。
やるとしたらこんな感じかな。
ヘッダーと、emptyView を同じレイアウトXMLから生成するくらいしか方法がないかな。
- public class MainActivity extends ListActivity implements View.OnClickListener{
- ArrayAdapter<String> mAdapter;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
- LayoutInflater inflater = getLayoutInflater();
- View header = inflater.inflate(R.layout.header, null, false);
- getListView().addHeaderView(header);
- setListAdapter(mAdapter);
- View emptyHeader = getListView().getEmptyView();
- emptyHeader.setOnClickListener(this);
- header.setOnClickListener(this);
- }
- @Override
- public void onClick(View v) {
- if(mAdapter.isEmpty()) {
- mAdapter.add("Test");
- }
- else {
- mAdapter.remove("Test");
- }
- }
- }
- <?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" />
- <LinearLayout
- android:id="@android:id/empty"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical" >
- <include layout="@layout/header"/>
- <TextView
- android:layout_width="match_parent"
- android:layout_height="0dip"
- android:layout_weight="1"
- android:gravity="center"
- android:text="No data"
- android:textSize="30sp" />
- </LinearLayout>
- </FrameLayout>
- <?xml version="1.0" encoding="utf-8"?>
- <Button xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/header"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="Header" />
- public class MainFragment extends ListFragment implements View.OnClickListener {
- ArrayAdapter<String> mAdapter;
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- return inflater.inflate(R.layout.main, container, false);
- }
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- mAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1);
- LayoutInflater inflater = getActivity().getLayoutInflater();
- View header = inflater.inflate(R.layout.header, null, false);
- getListView().addHeaderView(header);
- setListAdapter(mAdapter);
- View emptyHeader = getListView().getEmptyView();
- emptyHeader.setOnClickListener(this);
- header.setOnClickListener(this);
- }
- @Override
- public void onClick(View v) {
- if(mAdapter.isEmpty()) {
- mAdapter.add("Test");
- }
- else {
- mAdapter.remove("Test");
- }
- }
- }
もちろん Adapter を extends して isEmpty() で true を返すようにすれば emptyView がセットされていても本体が表示されるようになります。 具体的な用途は思いつかないですが、データのあるなしにかかわらず独自の基準で emptyView の表示・非表示を切り替えたい場合には便利だと思います。
ちなみに、ListFragment で emptyView を使う場合ははまりポイントがいっぱいなので、 このエントリを見ておくことをオススメします!
Y.A.M の 雑記帳: Android ListFragment でカスタムレイアウトを使うと setEmptyText() が使えない -
0 件のコメント:
コメントを投稿