目次
最小構成のViewでListFragmentを表示しようとしたら両方表示されちゃった。
たとえばこういう構成を考える。
これをActionBar.Tabで制御する場合、
Activity#onCreate()で以下のようにして対応すれば
いずれかのListFragmentが表示されるかなと思った。
しかしこれでは、id/fragment1 と id/fragment2 が同時に表示されてしまう。
public class TestActivity extends Activity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_custom_listview);
createTab(savedInstanceState);
}
private void createTab(Bundle inState) {
ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.Tab tab;
FragmentManager fm = getFragmentManager();
Fragment f;
f = fm.findFragmentById(R.id.fragment1);
tab = bar.newTab();
tab.setTag(f);
tab.setText("Tab1");
tab.setTabListener(new TabControl());
bar.addTab(tab);
f = fm.findFragmentById(R.id.fragment2);
tab = bar.newTab();
tab.setTag(f);
tab.setText("Tab2");
tab.setTabListener(new TabControl());
bar.addTab(tab);
}
private static final class TabControl implements ActionBar.TabListener {
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Fragment fragment = (Fragment) tab.getTag();
ft.show(fragment);
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
Fragment fragment = (Fragment) tab.getTag();
ft.hide(fragment);
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
res/layout/main_custom_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="@+id/fragment1"
android:name="test.listfrag.CustomListViewTestFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<fragment
android:id="@+id/fragment2"
android:name="test.listfrag.CustomListViewTestFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</merge>
この構成では id/fragment2 が表示状態で起動しているので表示される。
当然 id/fragment1 も起動している。
その上で ActionBar.Tab の addTab() では、初回登録時しか自動で selectTab() が呼ばれないため、
結果、id/fragment2 の FragmentTransaction#hide() は呼ばれない。
(正確には ActionBar.TabListener#onTabUnselected() が呼ばれない)
まずはセコい方法でやってみる。(id/fragment1 の挿入位置を鑑みて・・・)
改めて構成を見直してみる。
ふと気づいたのだが、
res/layout/main_custom_listview.xmlで作っておいた<fragment>が、
<LinearLayout>になっている。
これは何を意味するのだろう?
正解を言うと、
public class CustomListViewTestFragment extends ListFragment
@Override
public View onCreateView
(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState
) { // カスタムビューを指定。
return inflater.inflate(R.layout.custom_listview, container, false);
}
で指定した res/layout/custom_listview.xml が
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
~~~
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
~~~
</LinearLayout>
という構成なのだ。
これをそのまま <fragment> が <merge> して id/fragment1 を付与している。
では、こうしておけばいいんではないか?
res/layout/custom_listview.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:visibility="gone" >
~~~
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
~~~
</LinearLayout>
最初から id/fragment1 になる <LinearLayout> を visibility="gone" にする!
なんという荒業。。。
で、やってみた。
ですよねー。
セコい方法をやり遂げてみる。
しかし腑に落ちない。
ちゃんと ActionBar.Tab は初回で selectTab() されているのだから、
FragmentTransaction#show() さえ走れば、fragment1/fragment2 のどちらかくらいは
表示されて然るべきだというのに。
納得がいかないのでこの辺を調べてみる。
frameworks/base/core/java/android/app/BackStackRecord.java
@hide なソースなのだけれど、これを見る限り動いてもよさそうなんだよね。
public FragmentTransaction hide(Fragment fragment) {
Op op = new Op();
op.cmd = OP_HIDE;
op.fragment = fragment;
addOp(op);
return this;
}
public FragmentTransaction show(Fragment fragment) {
Op op = new Op();
op.cmd = OP_SHOW;
op.fragment = fragment;
addOp(op);
return this;
}
あ、addOp()ってことは hide() => show() と叩けば、
必ず hide() が走ってから show() が走る?
ならこれでどうだ!
public class TestActivity extends Activity
private static final class TabControl implements ActionBar.TabListener {
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Fragment fragment = (Fragment) tab.getTag();
ft.hide(fragment);
ft.show(fragment);
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
Fragment fragment = (Fragment) tab.getTag();
ft.show(fragment);
ft.hide(fragment);
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
……。
………。
…………。
あ、表示された!
でもなんか納得いかないんだよなぁ。。。
正攻法でやってみる
もっとスマートにできないもんだろうか。
そう思って色々とやった結果、これが一番正攻法のように思う。
public class TestActivity extends Activity
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// XXX : Fragmentは表示したいときに追加するのが吉。
// なので自動で表示状態にしてしまうのは、今回は、よろしくない。
// setContentView(R.layout.main_custom_listview);
createTab(savedInstanceState);
}
private void createTab(Bundle inState) {
ActionBar bar = getActionBar();
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
ActionBar.Tab tab;
CustomListViewTestFragment f;
// XXX : 必要なインスタンスの生成だけを行い、登録は別途行う。
f = new CustomListViewTestFragment();
tab = bar.newTab();
tab.setTag(f);
tab.setText("Tab1");
tab.setTabListener(new TabControl());
bar.addTab(tab);
f = new CustomListViewTestFragment();
tab = bar.newTab();
tab.setTag(f);
tab.setText("Tab2");
tab.setTabListener(new TabControl());
bar.addTab(tab);
}
private static final class TabControl implements ActionBar.TabListener {
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Fragment fragment = (Fragment) tab.getTag();
// XXX : 今回の肝。Fragmentを必要なときに画面に追加する。
// 追加済みかどうかはFragmentが知っている!
if (!fragment.isAdded()) {
ft.add(android.R.id.content, fragment);
}
ft.show(fragment);
}
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
Fragment fragment = (Fragment) tab.getTag();
ft.hide(fragment);
}
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
}
こうすることで目的の画面は表示された。
構成もいい感じ。
まとめ
今回のことでいくつか分かったことがある。
- <Fragment>は自動的に onCreateView() で inflate() した ViewGroup に置き換わる。
- 重なるように Fragment を表示する場合は、setContentView()よりadd()する方が都合がいい。
- そうすれば自動的にparentのViewGroupにmergeがかかる。
- このとき FragmentTransaction#add() 済みかどうかは Fragment#isAdded() で確認する。
- show()/hide()で操作するときは、Fragment#onHiddenChanged() が便利。
- ただし、Fragmentを new したばかりの場合は ListFragment#getListView()をすると落ちる。
- onCreateView()後ではないので当然か・・・。
最終更新:2012年02月01日 03:55