Update API demos to match new tab interaction.

Follow changes to FragmentTabManager to continue to work
correctly with it.  Fix the code in the base API demo (which
can't use FragmentTabManager) to work correctly.

Bug #7232088: ListView saved state being lost in some cases

Change-Id: Ib7ba1ab20ff00f2cc95c9f6024ab94783237c9be
This commit is contained in:
Dianne Hackborn
2012-09-25 15:07:06 -07:00
parent e61482e7e5
commit 7263dad223
7 changed files with 127 additions and 291 deletions

View File

@@ -20,6 +20,7 @@ import android.app.ActionBar;
import android.app.ActionBar.Tab; import android.app.ActionBar.Tab;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Toast; import android.widget.Toast;
@@ -31,6 +32,7 @@ import android.widget.Toast;
public class FragmentNestingTabs extends Activity { public class FragmentNestingTabs extends Activity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
FragmentManager.enableDebugLogging(true);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final ActionBar bar = getActionBar(); final ActionBar bar = getActionBar();

View File

@@ -15,7 +15,7 @@
*/ */
package com.example.android.apis.app; package com.example.android.apis.app;
import java.util.HashMap; import java.util.ArrayList;
import com.example.android.apis.R; import com.example.android.apis.R;
@@ -29,28 +29,32 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TabHost; import android.widget.TabHost;
/**
* Sample fragment that contains tabs of other fragments.
*/
public class FragmentTabsFragment extends Fragment { public class FragmentTabsFragment extends Fragment {
TabHost mTabHost;
TabManager mTabManager; TabManager mTabManager;
String mCurrentTabTag;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mTabManager = new TabManager(getActivity(), getChildFragmentManager(),
R.id.realtabcontent);
}
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_tabs_fragment, container, false); View v = inflater.inflate(R.layout.fragment_tabs_fragment, container, false);
mTabHost = (TabHost)v.findViewById(android.R.id.tabhost); TabHost host = mTabManager.handleCreateView(v);
mTabHost.setup();
mTabManager = new TabManager(getActivity(), getChildFragmentManager(), mTabManager.addTab(host.newTabSpec("result").setIndicator("Result"),
mTabHost, R.id.realtabcontent);
mTabManager.addTab(mTabHost.newTabSpec("result").setIndicator("Result"),
FragmentReceiveResult.ReceiveResultFragment.class, null); FragmentReceiveResult.ReceiveResultFragment.class, null);
mTabManager.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"), mTabManager.addTab(host.newTabSpec("contacts").setIndicator("Contacts"),
LoaderCursor.CursorLoaderListFragment.class, null); LoaderCursor.CursorLoaderListFragment.class, null);
mTabManager.addTab(mTabHost.newTabSpec("apps").setIndicator("Apps"), mTabManager.addTab(host.newTabSpec("apps").setIndicator("Apps"),
LoaderCustom.AppListFragment.class, null); LoaderCustom.AppListFragment.class, null);
mTabManager.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), mTabManager.addTab(host.newTabSpec("throttle").setIndicator("Throttle"),
LoaderThrottle.ThrottledLoaderListFragment.class, null); LoaderThrottle.ThrottledLoaderListFragment.class, null);
return v; return v;
@@ -59,47 +63,37 @@ public class FragmentTabsFragment extends Fragment {
@Override @Override
public void onViewStateRestored(Bundle savedInstanceState) { public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState); super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) { mTabManager.handleViewStateRestored(savedInstanceState);
mCurrentTabTag = savedInstanceState.getString("tab");
}
mTabHost.setCurrentTabByTag(mCurrentTabTag);
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
// Need to remember the selected tab so that we can restore it if mTabManager.handleDestroyView();
// we later re-create the views.
mCurrentTabTag = mTabHost.getCurrentTabTag();
mTabHost = null;
mTabManager = null;
} }
@Override @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
outState.putString("tab", mTabHost != null mTabManager.handleSaveInstanceState(outState);
? mTabHost.getCurrentTabTag() : mCurrentTabTag);
} }
/** /**
* This is a helper class that implements a generic mechanism for * This is a helper class that implements a generic mechanism for
* associating fragments with the tabs in a tab host. It relies on a * associating fragments with the tabs in a tab host. DO NOT USE THIS.
* trick. Normally a tab host has a simple API for supplying a View or * If you want tabs in a fragment, use the support v13 library's
* Intent that each tab will show. This is not sufficient for switching * FragmentTabHost class, which takes care of all of this for you (in
* between fragments. So instead we make the content part of the tab host * a simpler way even).
* 0dp high (it is not shown) and the TabManager supplies its own dummy
* view to show as the tab content. It listens to changes in tabs, and takes
* care of switch to the correct fragment shown in a separate content area
* whenever the selected tab changes.
*/ */
public static class TabManager implements TabHost.OnTabChangeListener { public static class TabManager implements TabHost.OnTabChangeListener {
private final Context mContext; private final Context mContext;
private final FragmentManager mManager; private final FragmentManager mManager;
private final TabHost mTabHost;
private final int mContainerId; private final int mContainerId;
private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>(); private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
TabInfo mLastTab; private TabHost mTabHost;
private TabInfo mLastTab;
private boolean mInitialized;
private String mCurrentTabTag;
static final class TabInfo { static final class TabInfo {
private final String tag; private final String tag;
@@ -130,40 +124,109 @@ public class FragmentTabsFragment extends Fragment {
} }
} }
public TabManager(Context context, FragmentManager manager, TabHost tabHost, public TabManager(Context context, FragmentManager manager, int containerId) {
int containerId) {
mContext = context; mContext = context;
mManager = manager; mManager = manager;
mTabHost = tabHost;
mContainerId = containerId; mContainerId = containerId;
}
public TabHost handleCreateView(View root) {
if (mTabHost != null) {
throw new IllegalStateException("TabHost already set");
}
mTabHost = (TabHost)root.findViewById(android.R.id.tabhost);
mTabHost.setup();
mTabHost.setOnTabChangedListener(this); mTabHost.setOnTabChangedListener(this);
return mTabHost;
} }
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) { public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext)); tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag(); String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args); TabInfo info = new TabInfo(tag, clss, args);
mTabs.add(info);
mTabHost.addTab(tabSpec);
}
// Check to see if we already have a fragment for this tab, probably public void handleViewStateRestored(Bundle savedInstanceState) {
// from a previously saved state. If so, deactivate it, because our if (savedInstanceState != null) {
// initial state is that a tab isn't shown. mCurrentTabTag = savedInstanceState.getString("tab");
info.fragment = mManager.findFragmentByTag(tag); }
if (info.fragment != null && !info.fragment.isDetached()) { mTabHost.setCurrentTabByTag(mCurrentTabTag);
FragmentTransaction ft = mManager.beginTransaction();
ft.detach(info.fragment); String currentTab = mTabHost.getCurrentTabTag();
ft.commit();
// Go through all tabs and make sure their fragments match
// the correct state.
FragmentTransaction ft = null;
for (int i=0; i<mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
tab.fragment = mManager.findFragmentByTag(tab.tag);
if (tab.fragment != null && !tab.fragment.isDetached()) {
if (tab.tag.equals(currentTab)) {
// The fragment for this tab is already there and
// active, and it is what we really want to have
// as the current tab. Nothing to do.
mLastTab = tab;
} else {
// This fragment was restored in the active state,
// but is not the current tab. Deactivate it.
if (ft == null) {
ft = mManager.beginTransaction();
}
ft.detach(tab.fragment);
}
}
} }
mTabs.put(tag, info); // We are now ready to go. Make sure we are switched to the
mTabHost.addTab(tabSpec); // correct tab.
mInitialized = true;
ft = doTabChanged(currentTab, ft);
if (ft != null) {
ft.commit();
mManager.executePendingTransactions();
}
}
public void handleDestroyView() {
mCurrentTabTag = mTabHost.getCurrentTabTag();
mTabHost = null;
mTabs.clear();
mInitialized = false;
}
public void handleSaveInstanceState(Bundle outState) {
outState.putString("tab", mTabHost != null
? mTabHost.getCurrentTabTag() : mCurrentTabTag);
} }
@Override @Override
public void onTabChanged(String tabId) { public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId); if (!mInitialized) {
return;
}
FragmentTransaction ft = doTabChanged(tabId, null);
if (ft != null) {
ft.commit();
}
}
private FragmentTransaction doTabChanged(String tabId, FragmentTransaction ft) {
TabInfo newTab = null;
for (int i=0; i<mTabs.size(); i++) {
TabInfo tab = mTabs.get(i);
if (tab.tag.equals(tabId)) {
newTab = tab;
}
}
if (newTab == null) {
throw new IllegalStateException("No tab known for tag " + tabId);
}
if (mLastTab != newTab) { if (mLastTab != newTab) {
FragmentTransaction ft = mManager.beginTransaction(); if (ft == null) {
ft = mManager.beginTransaction();
}
if (mLastTab != null) { if (mLastTab != null) {
if (mLastTab.fragment != null) { if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment); ft.detach(mLastTab.fragment);
@@ -180,9 +243,8 @@ public class FragmentTabsFragment extends Fragment {
} }
mLastTab = newTab; mLastTab = newTab;
ft.commit();
mManager.executePendingTransactions();
} }
return ft;
} }
} }
} }

View File

@@ -1,50 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/* Copyright 2012, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
-->
<TabHost
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TabWidget
android:id="@android:id/tabs"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"/>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="0"/>
<FrameLayout
android:id="@+android:id/realtabcontent"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
</TabHost>

View File

@@ -15,173 +15,38 @@
*/ */
package com.example.android.supportv13.app; package com.example.android.supportv13.app;
import java.util.HashMap;
import com.example.android.supportv13.R; import com.example.android.supportv13.R;
import android.app.Fragment; import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.support.v13.app.FragmentTabHost;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TabHost;
public class FragmentTabsFragment extends Fragment { public class FragmentTabsFragment extends Fragment {
TabHost mTabHost; private FragmentTabHost mTabHost;
TabManager mTabManager;
String mCurrentTabTag;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_tabs_fragment, container, false); mTabHost = new FragmentTabHost(getActivity());
mTabHost = (TabHost)v.findViewById(android.R.id.tabhost); mTabHost.setup(getActivity(), getChildFragmentManager(), R.id.pager);
mTabHost.setup();
mTabManager = new TabManager(getActivity(), getChildFragmentManager(), mTabHost.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
mTabHost, R.id.realtabcontent);
mTabManager.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"),
CountingFragment.class, null); CountingFragment.class, null);
mTabManager.addTab(mTabHost.newTabSpec("array").setIndicator("Array"), mTabHost.addTab(mTabHost.newTabSpec("array").setIndicator("Array"),
FragmentPagerSupport.ArrayListFragment.class, null); FragmentPagerSupport.ArrayListFragment.class, null);
mTabManager.addTab(mTabHost.newTabSpec("cursor").setIndicator("Cursor"), mTabHost.addTab(mTabHost.newTabSpec("cursor").setIndicator("Cursor"),
CursorFragment.class, null); CursorFragment.class, null);
return v; return mTabHost;
}
@Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mCurrentTabTag = savedInstanceState.getString("tab");
}
mTabHost.setCurrentTabByTag(mCurrentTabTag);
} }
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
// Need to remember the selected tab so that we can restore it if
// we later re-create the views.
mCurrentTabTag = mTabHost.getCurrentTabTag();
mTabHost = null; mTabHost = null;
mTabManager = null;
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("tab", mTabHost != null
? mTabHost.getCurrentTabTag() : mCurrentTabTag);
}
/**
* This is a helper class that implements a generic mechanism for
* associating fragments with the tabs in a tab host. It relies on a
* trick. Normally a tab host has a simple API for supplying a View or
* Intent that each tab will show. This is not sufficient for switching
* between fragments. So instead we make the content part of the tab host
* 0dp high (it is not shown) and the TabManager supplies its own dummy
* view to show as the tab content. It listens to changes in tabs, and takes
* care of switch to the correct fragment shown in a separate content area
* whenever the selected tab changes.
*/
public static class TabManager implements TabHost.OnTabChangeListener {
private final Context mContext;
private final FragmentManager mManager;
private final TabHost mTabHost;
private final int mContainerId;
private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>();
TabInfo mLastTab;
static final class TabInfo {
private final String tag;
private final Class<?> clss;
private final Bundle args;
private Fragment fragment;
TabInfo(String _tag, Class<?> _class, Bundle _args) {
tag = _tag;
clss = _class;
args = _args;
}
}
static class DummyTabFactory implements TabHost.TabContentFactory {
private final Context mContext;
public DummyTabFactory(Context context) {
mContext = context;
}
@Override
public View createTabContent(String tag) {
View v = new View(mContext);
v.setMinimumWidth(0);
v.setMinimumHeight(0);
return v;
}
}
public TabManager(Context context, FragmentManager manager, TabHost tabHost,
int containerId) {
mContext = context;
mManager = manager;
mTabHost = tabHost;
mContainerId = containerId;
mTabHost.setOnTabChangedListener(this);
}
public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) {
tabSpec.setContent(new DummyTabFactory(mContext));
String tag = tabSpec.getTag();
TabInfo info = new TabInfo(tag, clss, args);
// Check to see if we already have a fragment for this tab, probably
// from a previously saved state. If so, deactivate it, because our
// initial state is that a tab isn't shown.
info.fragment = mManager.findFragmentByTag(tag);
if (info.fragment != null && !info.fragment.isDetached()) {
FragmentTransaction ft = mManager.beginTransaction();
ft.detach(info.fragment);
ft.commit();
}
mTabs.put(tag, info);
mTabHost.addTab(tabSpec);
}
@Override
public void onTabChanged(String tabId) {
TabInfo newTab = mTabs.get(tabId);
if (mLastTab != newTab) {
FragmentTransaction ft = mManager.beginTransaction();
if (mLastTab != null) {
if (mLastTab.fragment != null) {
ft.detach(mLastTab.fragment);
}
}
if (newTab != null) {
if (newTab.fragment == null) {
newTab.fragment = Fragment.instantiate(mContext,
newTab.clss.getName(), newTab.args);
ft.add(mContainerId, newTab.fragment, newTab.tag);
} else {
ft.attach(newTab.fragment);
}
}
mLastTab = newTab;
ft.commit();
mManager.executePendingTransactions();
}
}
} }
} }
//END_INCLUDE(complete) //END_INCLUDE(complete)

View File

@@ -23,7 +23,7 @@ import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost; import android.support.v4.app.FragmentTabHost;
public class FragmentNestingTabsSupport extends FragmentActivity { public class FragmentNestingTabsSupport extends FragmentActivity {
FragmentTabHost mTabHost; private FragmentTabHost mTabHost;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -41,16 +41,6 @@ public class FragmentNestingTabsSupport extends FragmentActivity {
FragmentStackFragmentSupport.class, null); FragmentStackFragmentSupport.class, null);
mTabHost.addTab(mTabHost.newTabSpec("tabs").setIndicator("Tabs"), mTabHost.addTab(mTabHost.newTabSpec("tabs").setIndicator("Tabs"),
FragmentTabsFragmentSupport.class, null); FragmentTabsFragmentSupport.class, null);
if (savedInstanceState != null) {
mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("tab", mTabHost.getCurrentTabTag());
} }
} }
//END_INCLUDE(complete) //END_INCLUDE(complete)

View File

@@ -27,7 +27,7 @@ import android.support.v4.app.FragmentTabHost;
* TabHost through fragments, using FragmentTabHost. * TabHost through fragments, using FragmentTabHost.
*/ */
public class FragmentTabs extends FragmentActivity { public class FragmentTabs extends FragmentActivity {
FragmentTabHost mTabHost; private FragmentTabHost mTabHost;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -45,16 +45,6 @@ public class FragmentTabs extends FragmentActivity {
LoaderCustomSupport.AppListFragment.class, null); LoaderCustomSupport.AppListFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
LoaderThrottleSupport.ThrottledLoaderListFragment.class, null); LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
if (savedInstanceState != null) {
mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("tab", mTabHost.getCurrentTabTag());
} }
} }
//END_INCLUDE(complete) //END_INCLUDE(complete)

View File

@@ -26,8 +26,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
public class FragmentTabsFragmentSupport extends Fragment { public class FragmentTabsFragmentSupport extends Fragment {
FragmentTabHost mTabHost; private FragmentTabHost mTabHost;
String mCurrentTabTag;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -44,35 +43,13 @@ public class FragmentTabsFragmentSupport extends Fragment {
mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), mTabHost.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"),
LoaderThrottleSupport.ThrottledLoaderListFragment.class, null); LoaderThrottleSupport.ThrottledLoaderListFragment.class, null);
if (savedInstanceState != null) {
mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab"));
}
return mTabHost; return mTabHost;
} }
@Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mCurrentTabTag = savedInstanceState.getString("tab");
}
mTabHost.setCurrentTabByTag(mCurrentTabTag);
}
@Override @Override
public void onDestroyView() { public void onDestroyView() {
super.onDestroyView(); super.onDestroyView();
// Need to remember the selected tab so that we can restore it if
// we later re-create the views.
mCurrentTabTag = mTabHost.getCurrentTabTag();
mTabHost = null; mTabHost = null;
} }
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("tab", mTabHost != null
? mTabHost.getCurrentTabTag() : mCurrentTabTag);
}
} }
//END_INCLUDE(complete) //END_INCLUDE(complete)