diff --git a/samples/ApiDemos/AndroidManifest.xml b/samples/ApiDemos/AndroidManifest.xml index e487d09a1..9199c1664 100644 --- a/samples/ApiDemos/AndroidManifest.xml +++ b/samples/ApiDemos/AndroidManifest.xml @@ -38,7 +38,10 @@ - + @@ -361,6 +364,15 @@ + + + + + + + App/Fragment/Stack New fragment + App/Fragment/Tabs + App/Loader/Cursor App/Loader/Custom diff --git a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java index 8a7bf5176..11c1bc279 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/ActionBarTabs.java @@ -78,6 +78,11 @@ public class ActionBarTabs extends Activity { * to it, it will be committed at the end of the full tab switch operation. * This lets tab switches be atomic without the app needing to track * the interactions between different tabs. + * + * NOTE: This is a very simple implementation that does not retain + * fragment state of the non-visible tabs across activity instances. + * Look at the FragmentTabs example for how to do a more complete + * implementation. */ private class TabListener implements ActionBar.TabListener { private TabContentFragment mFragment; diff --git a/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java new file mode 100644 index 000000000..baaca49c0 --- /dev/null +++ b/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2011 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. + */ +package com.example.android.apis.app; + +import com.example.android.apis.R; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.os.Bundle; +import android.widget.Toast; + +/** + * This demonstrates the use of action bar tabs and how they interact + * with other action bar features. + */ +public class FragmentTabs extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + final ActionBar bar = getActionBar(); + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); + + bar.addTab(bar.newTab() + .setText("Simple") + .setTabListener(new TabListener( + this, "simple", FragmentStack.CountingFragment.class))); + bar.addTab(bar.newTab() + .setText("Contacts") + .setTabListener(new TabListener( + this, "contacts", LoaderCursor.CursorLoaderListFragment.class))); + bar.addTab(bar.newTab() + .setText("Apps") + .setTabListener(new TabListener( + this, "apps", LoaderCustom.AppListFragment.class))); + bar.addTab(bar.newTab() + .setText("Throttle") + .setTabListener(new TabListener( + this, "throttle", LoaderThrottle.ThrottledLoaderListFragment.class))); + + if (savedInstanceState != null) { + bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); + } + + public static class TabListener implements ActionBar.TabListener { + private final Activity mActivity; + private final String mTag; + private final Class mClass; + private final Bundle mArgs; + private Fragment mFragment; + + public TabListener(Activity activity, String tag, Class clz) { + this(activity, tag, clz, null); + } + + public TabListener(Activity activity, String tag, Class clz, Bundle args) { + mActivity = activity; + mTag = tag; + mClass = clz; + mArgs = 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. + mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); + if (mFragment != null && !mFragment.isDetached()) { + FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); + ft.detach(mFragment); + ft.commit(); + } + } + + public void onTabSelected(Tab tab, FragmentTransaction ft) { + if (mFragment == null) { + mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); + ft.add(android.R.id.content, mFragment, mTag); + } else { + ft.attach(mFragment); + } + } + + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + if (mFragment != null) { + ft.detach(mFragment); + } + } + + public void onTabReselected(Tab tab, FragmentTransaction ft) { + Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java index a8ac0d4cc..fd2fa6850 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.java @@ -159,7 +159,11 @@ public class LoaderCursor extends Activity { mAdapter.swapCursor(data); // The list should now be shown. - setListShown(true); + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } public void onLoaderReset(Loader loader) { diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java index 883ab1439..e1e77a99b 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderCustom.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Android Open Source Project + * Copyright (C) 2011 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. @@ -466,7 +466,11 @@ public class LoaderCustom extends Activity { mAdapter.setData(data); // The list should now be shown. - setListShown(true); + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } @Override public void onLoaderReset(Loader> loader) { diff --git a/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java b/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java index 1c4c83965..af674c0b5 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java +++ b/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.java @@ -409,6 +409,9 @@ public class LoaderThrottle extends Activity { new int[] { android.R.id.text1 }, 0); setListAdapter(mAdapter); + // Start out with a progress indicator. + setListShown(false); + // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); @@ -492,6 +495,13 @@ public class LoaderThrottle extends Activity { public void onLoadFinished(Loader loader, Cursor data) { mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } public void onLoaderReset(Loader loader) { diff --git a/samples/ApiDemos/src/com/example/android/apis/app/_index.html b/samples/ApiDemos/src/com/example/android/apis/app/_index.html index 71ccb543e..e9d1cf57a 100644 --- a/samples/ApiDemos/src/com/example/android/apis/app/_index.html +++ b/samples/ApiDemos/src/com/example/android/apis/app/_index.html @@ -134,6 +134,10 @@
Demonstrates creating a stack of Fragment instances similar to the traditional stack of activities.
+
Fragment Tabs
+
Demonstrates implementing ActionBar tabs by switching between + Fragments.
+ @@ -145,9 +149,10 @@ menu. This demo is for informative purposes only; see Usage for an example of us Action Bar in a more idiomatic manner.
Action Bar Tabs
Demonstrates the use of Action Bar tabs and how they interact with other action bar -features.
+features. Also see the Fragment Tabs for a more +complete example of how to switch between fragments.
Action Bar Usage
-
Demonstrates imple usage of the Action Bar, including a SearchView as an action item. The +
Demonstrates simple usage of the Action Bar, including a SearchView as an action item. The default Honeycomb theme includes the Action Bar by default and a menu resource is used to populate the menu data itself. If you'd like to see how these things work under the hood, see Mechanics.
@@ -162,6 +167,10 @@ Mechanics.
Demonstrates use of LoaderManager to perform a query for a Cursor that populates a ListFragment.
+
Loader Custom
+
Demonstrates implementation and use of a custom Loader class. The + custom class here "loads" the currently installed applications.
+
Loader Throttle
Complete end-to-end demonstration of a simple content provider that populates data in a list through a cursor loader. The UI allows the list diff --git a/samples/Support13Demos/AndroidManifest.xml b/samples/Support13Demos/AndroidManifest.xml index e54bf7f23..bc32d099f 100644 --- a/samples/Support13Demos/AndroidManifest.xml +++ b/samples/Support13Demos/AndroidManifest.xml @@ -26,7 +26,10 @@ - + @@ -60,5 +63,13 @@ + + + + + + + diff --git a/samples/Support13Demos/res/values/strings.xml b/samples/Support13Demos/res/values/strings.xml index 818597c58..2fd12d496 100644 --- a/samples/Support13Demos/res/values/strings.xml +++ b/samples/Support13Demos/res/values/strings.xml @@ -29,4 +29,5 @@ Fragment/State Pager + Fragment/Action Bar Tabs Pager diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java b/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java new file mode 100644 index 000000000..6c0f803de --- /dev/null +++ b/samples/Support13Demos/src/com/example/android/supportv13/app/ActionBarTabsPager.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2011 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. + */ +package com.example.android.supportv13.app; + +import java.util.ArrayList; + +import com.example.android.supportv13.R; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.Activity; +import android.app.Fragment; +import android.app.FragmentTransaction; +import android.content.Context; +import android.os.Bundle; +import android.support.v13.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; + +/** + * This demonstrates the use of action bar tabs and how they interact + * with other action bar features. + */ +public class ActionBarTabsPager extends Activity { + ViewPager mViewPager; + TabsAdapter mTabsAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mViewPager = new ViewPager(this); + mViewPager.setId(R.id.pager); + setContentView(mViewPager); + + final ActionBar bar = getActionBar(); + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); + + mTabsAdapter = new TabsAdapter(this, mViewPager); + mTabsAdapter.addTab(bar.newTab().setText("Simple"), + CountingFragment.class, null); + mTabsAdapter.addTab(bar.newTab().setText("List"), + FragmentPagerSupport.ArrayListFragment.class, null); + mTabsAdapter.addTab(bar.newTab().setText("Cursor"), + CursorFragment.class, null); + + if (savedInstanceState != null) { + bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); + } + + /** + * This is a helper class that implements the management of tabs and all + * details of connecting a ViewPager with associated TabHost. 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 pages. So instead we make the content part of the tab host + * 0dp high (it is not shown) and the TabsAdapter 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 paged in the ViewPager whenever the selected + * tab changes. + */ + public static class TabsAdapter extends FragmentPagerAdapter + implements ActionBar.TabListener, ViewPager.OnPageChangeListener { + private final Context mContext; + private final ActionBar mActionBar; + private final ViewPager mViewPager; + private final ArrayList mTabs = new ArrayList(); + + static final class TabInfo { + private final Class clss; + private final Bundle args; + + TabInfo(Class _class, Bundle _args) { + clss = _class; + args = _args; + } + } + + public TabsAdapter(Activity activity, ViewPager pager) { + super(activity.getFragmentManager()); + mContext = activity; + mActionBar = activity.getActionBar(); + mViewPager = pager; + mViewPager.setAdapter(this); + mViewPager.setOnPageChangeListener(this); + } + + public void addTab(ActionBar.Tab tab, Class clss, Bundle args) { + TabInfo info = new TabInfo(clss, args); + tab.setTag(info); + tab.setTabListener(this); + mTabs.add(info); + mActionBar.addTab(tab); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mTabs.size(); + } + + @Override + public Fragment getItem(int position) { + TabInfo info = mTabs.get(position); + return Fragment.instantiate(mContext, info.clss.getName(), info.args); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + mActionBar.setSelectedNavigationItem(position); + } + + @Override + public void onTabSelected(Tab tab, FragmentTransaction ft) { + Object tag = tab.getTag(); + for (int i=0; i { + + // This is the Adapter being used to display the list's data. + SimpleCursorAdapter mAdapter; + + // If non-null, this is the current filter the user has provided. + String mCurFilter; + + @Override public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText("No phone numbers"); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + + // Create an empty adapter we will use to display the loaded data. + mAdapter = new SimpleCursorAdapter(getActivity(), + android.R.layout.simple_list_item_2, null, + new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, + new int[] { android.R.id.text1, android.R.id.text2 }, 0); + setListAdapter(mAdapter); + + // Start out with a progress indicator. + setListShown(false); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // Place an action bar item for searching. + MenuItem item = menu.add("Search"); + item.setIcon(android.R.drawable.ic_menu_search); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + SearchView sv = new SearchView(getActivity()); + sv.setOnQueryTextListener(this); + item.setActionView(sv); + } + + public boolean onQueryTextChange(String newText) { + // Called when the action bar search text has changed. Update + // the search filter, and restart the loader to do a new query + // with this filter. + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; + getLoaderManager().restartLoader(0, null, this); + return true; + } + + @Override public boolean onQueryTextSubmit(String query) { + // Don't care about this. + return true; + } + + @Override public void onListItemClick(ListView l, View v, int position, long id) { + // Insert desired behavior here. + Log.i("FragmentComplexList", "Item clicked: " + id); + } + + // These are the Contacts rows that we will retrieve. + static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { + Contacts._ID, + Contacts.DISPLAY_NAME, + Contacts.CONTACT_STATUS, + Contacts.CONTACT_PRESENCE, + Contacts.PHOTO_ID, + Contacts.LOOKUP_KEY, + }; + + public Loader onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + // First, pick the base URI to use depending on whether we are + // currently filtering. + Uri baseUri; + if (mCurFilter != null) { + baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, + Uri.encode(mCurFilter)); + } else { + baseUri = Contacts.CONTENT_URI; + } + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + + Contacts.DISPLAY_NAME + " != '' ))"; + return new CursorLoader(getActivity(), baseUri, + CONTACTS_SUMMARY_PROJECTION, select, null, + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); + } + + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } + } + + public void onLoaderReset(Loader loader) { + // This is called when the last Cursor provided to onLoadFinished() + // above is about to be closed. We need to make sure we are no + // longer using it. + mAdapter.swapCursor(null); + } +} diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html b/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html index 47673dad9..832d60ebc 100644 --- a/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html +++ b/samples/Support13Demos/src/com/example/android/supportv13/app/_index.html @@ -8,10 +8,16 @@ package features of the static support library fir API 13 or later.

Fragment

+
Action Bar Tabs Pager
+
Demonstrates the use of fragments to implement switching between + ActionBar tabs, using a ViewPager to manager the fragments so that + the user can also fling left and right to switch tabs.
+
Fragment Pager Support
Demonstrates the use of the v4 support class ViewPager with a FragmentPagerAdapter to build a user interface where the user can fling left or right to switch between fragments.
+
Fragment State Pager Support
Demonstrates the use of the v4 support class ViewPager with a FragmentStatePagerAdapter to build a user interface where the user can fling diff --git a/samples/Support4Demos/AndroidManifest.xml b/samples/Support4Demos/AndroidManifest.xml index db4dbd12c..5c7495468 100644 --- a/samples/Support4Demos/AndroidManifest.xml +++ b/samples/Support4Demos/AndroidManifest.xml @@ -26,7 +26,10 @@ - + @@ -147,6 +150,22 @@ + + + + + + + + + + + + + + @@ -171,6 +190,14 @@ + + + + + + + diff --git a/samples/Support4Demos/res/layout/fragment_tabs.xml b/samples/Support4Demos/res/layout/fragment_tabs.xml new file mode 100644 index 000000000..0d62ef645 --- /dev/null +++ b/samples/Support4Demos/res/layout/fragment_tabs.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + diff --git a/samples/Support4Demos/res/layout/fragment_tabs_pager.xml b/samples/Support4Demos/res/layout/fragment_tabs_pager.xml new file mode 100644 index 000000000..c36cf3c30 --- /dev/null +++ b/samples/Support4Demos/res/layout/fragment_tabs_pager.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + diff --git a/samples/Support4Demos/res/layout/list_item_icon_text.xml b/samples/Support4Demos/res/layout/list_item_icon_text.xml new file mode 100644 index 000000000..c3825b719 --- /dev/null +++ b/samples/Support4Demos/res/layout/list_item_icon_text.xml @@ -0,0 +1,32 @@ + + + + + + + + + + diff --git a/samples/Support4Demos/res/values/strings.xml b/samples/Support4Demos/res/values/strings.xml index 477cd9b20..fa437dbff 100644 --- a/samples/Support4Demos/res/values/strings.xml +++ b/samples/Support4Demos/res/values/strings.xml @@ -77,6 +77,10 @@ Fragment/Stack New fragment + Fragment/Tabs + + Fragment/Tabs and Pager + Fragment/Pager First Last @@ -85,6 +89,8 @@ Loader/Cursor + Loader/Custom + Loader/Throttle diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java new file mode 100644 index 000000000..44bce31a0 --- /dev/null +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabs.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2011 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. + */ +package com.example.android.supportv4.app; + +import java.util.HashMap; + +import com.example.android.supportv4.R; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentTransaction; +import android.view.View; +import android.widget.TabHost; + +/** + * This demonstrates how you can implement switching between the tabs of a + * TabHost through fragments. It uses a trick (see the code below) to allow + * the tabs to switch between fragments instead of simple views. + */ +public class FragmentTabs extends FragmentActivity { + TabHost mTabHost; + TabManager mTabManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.fragment_tabs); + mTabHost = (TabHost)findViewById(android.R.id.tabhost); + mTabHost.setup(); + + mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent); + + mTabManager.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"), + FragmentStackSupport.CountingFragment.class, null); + mTabManager.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"), + LoaderCursorSupport.CursorLoaderListFragment.class, null); + mTabManager.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"), + LoaderCustomSupport.AppListFragment.class, null); + mTabManager.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), + 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()); + } + + /** + * 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 FragmentActivity mActivity; + private final TabHost mTabHost; + private final int mContainerId; + private final HashMap mTabs = new HashMap(); + 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(FragmentActivity activity, TabHost tabHost, int containerId) { + mActivity = activity; + mTabHost = tabHost; + mContainerId = containerId; + mTabHost.setOnTabChangedListener(this); + } + + public void addTab(TabHost.TabSpec tabSpec, Class clss, Bundle args) { + tabSpec.setContent(new DummyTabFactory(mActivity)); + 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 = mActivity.getSupportFragmentManager().findFragmentByTag(tag); + if (info.fragment != null && !info.fragment.isDetached()) { + FragmentTransaction ft = mActivity.getSupportFragmentManager().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 = mActivity.getSupportFragmentManager().beginTransaction(); + if (mLastTab != null) { + if (mLastTab.fragment != null) { + ft.detach(mLastTab.fragment); + } + } + if (newTab != null) { + if (newTab.fragment == null) { + newTab.fragment = Fragment.instantiate(mActivity, + newTab.clss.getName(), newTab.args); + ft.add(mContainerId, newTab.fragment, newTab.tag); + } else { + ft.attach(newTab.fragment); + } + } + + mLastTab = newTab; + ft.commit(); + mActivity.getSupportFragmentManager().executePendingTransactions(); + } + } + } +} diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsPager.java b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsPager.java new file mode 100644 index 000000000..6db9d3c6e --- /dev/null +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/FragmentTabsPager.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2011 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. + */ +package com.example.android.supportv4.app; + +import java.util.ArrayList; + +import com.example.android.supportv4.R; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.widget.TabHost; + +/** + * Demonstrates combining a TabHost with a ViewPager to implement a tab UI + * that switches between tabs and also allows the user to perform horizontal + * flicks to move between the tabs. + */ +public class FragmentTabsPager extends FragmentActivity { + TabHost mTabHost; + ViewPager mViewPager; + TabsAdapter mTabsAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.fragment_tabs_pager); + mTabHost = (TabHost)findViewById(android.R.id.tabhost); + mTabHost.setup(); + + mViewPager = (ViewPager)findViewById(R.id.pager); + + mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager); + + mTabsAdapter.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"), + FragmentStackSupport.CountingFragment.class, null); + mTabsAdapter.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"), + LoaderCursorSupport.CursorLoaderListFragment.class, null); + mTabsAdapter.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"), + LoaderCustomSupport.AppListFragment.class, null); + mTabsAdapter.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), + 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()); + } + + /** + * This is a helper class that implements the management of tabs and all + * details of connecting a ViewPager with associated TabHost. 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 pages. So instead we make the content part of the tab host + * 0dp high (it is not shown) and the TabsAdapter 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 paged in the ViewPager whenever the selected + * tab changes. + */ + public static class TabsAdapter extends FragmentPagerAdapter + implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener { + private final Context mContext; + private final TabHost mTabHost; + private final ViewPager mViewPager; + private final ArrayList mTabs = new ArrayList(); + + static final class TabInfo { + private final String tag; + private final Class clss; + private final Bundle args; + + 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 TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) { + super(activity.getSupportFragmentManager()); + mContext = activity; + mTabHost = tabHost; + mViewPager = pager; + mTabHost.setOnTabChangedListener(this); + mViewPager.setAdapter(this); + mViewPager.setOnPageChangeListener(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); + mTabs.add(info); + mTabHost.addTab(tabSpec); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mTabs.size(); + } + + @Override + public Fragment getItem(int position) { + TabInfo info = mTabs.get(position); + return Fragment.instantiate(mContext, info.clss.getName(), info.args); + } + + @Override + public void onTabChanged(String tabId) { + int position = mTabHost.getCurrentTab(); + mViewPager.setCurrentItem(position); + } + + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + mTabHost.setCurrentTab(position); + } + } +} diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java index 07b9309a1..096316c28 100644 --- a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCursorSupport.java @@ -81,6 +81,9 @@ public class LoaderCursorSupport extends FragmentActivity { new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); + // Start out with a progress indicator. + setListShown(false); + // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); @@ -147,6 +150,13 @@ public class LoaderCursorSupport extends FragmentActivity { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } public void onLoaderReset(Loader loader) { diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java new file mode 100644 index 000000000..b222a2010 --- /dev/null +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderCustomSupport.java @@ -0,0 +1,482 @@ +/* + * Copyright (C) 2010 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. + */ + +package com.example.android.supportv4.app; + +import com.example.android.supportv4.R; + +import java.io.File; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.ListFragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.AsyncTaskLoader; +import android.support.v4.content.Loader; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.TextView; +import android.widget.SearchView.OnQueryTextListener; + +/** + * Demonstration of the implementation of a custom Loader. + */ +public class LoaderCustomSupport extends FragmentActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + FragmentManager fm = getSupportFragmentManager(); + + // Create the list fragment and add it as our sole content. + if (fm.findFragmentById(android.R.id.content) == null) { + AppListFragment list = new AppListFragment(); + fm.beginTransaction().add(android.R.id.content, list).commit(); + } + } + +//BEGIN_INCLUDE(loader) + /** + * This class holds the per-item data in our Loader. + */ + public static class AppEntry { + public AppEntry(AppListLoader loader, ApplicationInfo info) { + mLoader = loader; + mInfo = info; + mApkFile = new File(info.sourceDir); + } + + public ApplicationInfo getApplicationInfo() { + return mInfo; + } + + public String getLabel() { + return mLabel; + } + + public Drawable getIcon() { + if (mIcon == null) { + if (mApkFile.exists()) { + mIcon = mInfo.loadIcon(mLoader.mPm); + return mIcon; + } else { + mMounted = false; + } + } else if (!mMounted) { + // If the app wasn't mounted but is now mounted, reload + // its icon. + if (mApkFile.exists()) { + mMounted = true; + mIcon = mInfo.loadIcon(mLoader.mPm); + return mIcon; + } + } else { + return mIcon; + } + + return mLoader.getContext().getResources().getDrawable( + android.R.drawable.sym_def_app_icon); + } + + @Override public String toString() { + return mLabel; + } + + void loadLabel(Context context) { + if (mLabel == null || !mMounted) { + if (!mApkFile.exists()) { + mMounted = false; + mLabel = mInfo.packageName; + } else { + mMounted = true; + CharSequence label = mInfo.loadLabel(context.getPackageManager()); + mLabel = label != null ? label.toString() : mInfo.packageName; + } + } + } + + private final AppListLoader mLoader; + private final ApplicationInfo mInfo; + private final File mApkFile; + private String mLabel; + private Drawable mIcon; + private boolean mMounted; + } + + /** + * Perform alphabetical comparison of application entry objects. + */ + public static final Comparator ALPHA_COMPARATOR = new Comparator() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppEntry object1, AppEntry object2) { + return sCollator.compare(object1.getLabel(), object2.getLabel()); + } + }; + + /** + * Helper for determining if the configuration has changed in an interesting + * way so we need to rebuild the app list. + */ + public static class InterestingConfigChanges { + final Configuration mLastConfiguration = new Configuration(); + int mLastDensity; + + boolean applyNewConfig(Resources res) { + int configChanges = mLastConfiguration.updateFrom(res.getConfiguration()); + boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi; + if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE + |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) { + mLastDensity = res.getDisplayMetrics().densityDpi; + return true; + } + return false; + } + } + + /** + * Helper class to look for interesting changes to the installed apps + * so that the loader can be updated. + */ + public static class PackageIntentReceiver extends BroadcastReceiver { + final AppListLoader mLoader; + + public PackageIntentReceiver(AppListLoader loader) { + mLoader = loader; + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); + filter.addDataScheme("package"); + mLoader.getContext().registerReceiver(this, filter); + // Register for events related to sdcard installation. + IntentFilter sdFilter = new IntentFilter(); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); + mLoader.getContext().registerReceiver(this, sdFilter); + } + + @Override public void onReceive(Context context, Intent intent) { + // Tell the loader about the change. + mLoader.onContentChanged(); + } + } + + /** + * A custom Loader that loads all of the installed applications. + */ + public static class AppListLoader extends AsyncTaskLoader> { + final InterestingConfigChanges mLastConfig = new InterestingConfigChanges(); + final PackageManager mPm; + + List mApps; + PackageIntentReceiver mPackageObserver; + + public AppListLoader(Context context) { + super(context); + + // Retrieve the package manager for later use; note we don't + // use 'context' directly but instead the save global application + // context returned by getContext(). + mPm = getContext().getPackageManager(); + } + + /** + * This is where the bulk of our work is done. This function is + * called in a background thread and should generate a new set of + * data to be published by the loader. + */ + @Override public List loadInBackground() { + // Retrieve all known applications. + List apps = mPm.getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES | + PackageManager.GET_DISABLED_COMPONENTS); + if (apps == null) { + apps = new ArrayList(); + } + + final Context context = getContext(); + + // Create corresponding array of entries and load their labels. + List entries = new ArrayList(apps.size()); + for (int i=0; i apps) { + if (isReset()) { + // An async query came in while the loader is stopped. We + // don't need the result. + if (apps != null) { + onReleaseResources(apps); + } + } + List oldApps = apps; + mApps = apps; + + if (isStarted()) { + // If the Loader is currently started, we can immediately + // deliver its results. + super.deliverResult(apps); + } + + // At this point we can release the resources associated with + // 'oldApps' if needed; now that the new result is delivered we + // know that it is no longer in use. + if (oldApps != null) { + onReleaseResources(oldApps); + } + } + + /** + * Handles a request to start the Loader. + */ + @Override protected void onStartLoading() { + if (mApps != null) { + // If we currently have a result available, deliver it + // immediately. + deliverResult(mApps); + } + + // Start watching for changes in the app data. + if (mPackageObserver == null) { + mPackageObserver = new PackageIntentReceiver(this); + } + + // Has something interesting in the configuration changed since we + // last built the app list? + boolean configChange = mLastConfig.applyNewConfig(getContext().getResources()); + + if (takeContentChanged() || mApps == null || configChange) { + // If the data has changed since the last time it was loaded + // or is not currently available, start a load. + forceLoad(); + } + } + + /** + * Handles a request to stop the Loader. + */ + @Override protected void onStopLoading() { + // Attempt to cancel the current load task if possible. + cancelLoad(); + } + + /** + * Handles a request to cancel a load. + */ + @Override public void onCanceled(List apps) { + super.onCanceled(apps); + + // At this point we can release the resources associated with 'apps' + // if needed. + onReleaseResources(apps); + } + + /** + * Handles a request to completely reset the Loader. + */ + @Override protected void onReset() { + super.onReset(); + + // Ensure the loader is stopped + onStopLoading(); + + // At this point we can release the resources associated with 'apps' + // if needed. + if (mApps != null) { + onReleaseResources(mApps); + mApps = null; + } + + // Stop monitoring for changes. + if (mPackageObserver != null) { + getContext().unregisterReceiver(mPackageObserver); + mPackageObserver = null; + } + } + + /** + * Helper function to take care of releasing resources associated + * with an actively loaded data set. + */ + protected void onReleaseResources(List apps) { + // For a simple List<> there is nothing to do. For something + // like a Cursor, we would close it here. + } + } +//END_INCLUDE(loader) + +//BEGIN_INCLUDE(fragment) + public static class AppListAdapter extends ArrayAdapter { + private final LayoutInflater mInflater; + + public AppListAdapter(Context context) { + super(context, android.R.layout.simple_list_item_2); + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public void setData(List data) { + clear(); + if (data != null) { + addAll(data); + } + } + + /** + * Populate new items in the list. + */ + @Override public View getView(int position, View convertView, ViewGroup parent) { + View view; + + if (convertView == null) { + view = mInflater.inflate(R.layout.list_item_icon_text, parent, false); + } else { + view = convertView; + } + + AppEntry item = getItem(position); + ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon()); + ((TextView)view.findViewById(R.id.text)).setText(item.getLabel()); + + return view; + } + } + + public static class AppListFragment extends ListFragment + implements OnQueryTextListener, LoaderManager.LoaderCallbacks> { + + // This is the Adapter being used to display the list's data. + AppListAdapter mAdapter; + + // If non-null, this is the current filter the user has provided. + String mCurFilter; + + @Override public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText("No applications"); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + + // Create an empty adapter we will use to display the loaded data. + mAdapter = new AppListAdapter(getActivity()); + setListAdapter(mAdapter); + + // Start out with a progress indicator. + setListShown(false); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // Place an action bar item for searching. + MenuItem item = menu.add("Search"); + item.setIcon(android.R.drawable.ic_menu_search); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + SearchView sv = new SearchView(getActivity()); + sv.setOnQueryTextListener(this); + item.setActionView(sv); + } + + @Override public boolean onQueryTextChange(String newText) { + // Called when the action bar search text has changed. Since this + // is a simple array adapter, we can just have it do the filtering. + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; + mAdapter.getFilter().filter(mCurFilter); + return true; + } + + @Override public boolean onQueryTextSubmit(String query) { + // Don't care about this. + return true; + } + + @Override public void onListItemClick(ListView l, View v, int position, long id) { + // Insert desired behavior here. + Log.i("LoaderCustom", "Item clicked: " + id); + } + + @Override public Loader> onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader with no arguments, so it is simple. + return new AppListLoader(getActivity()); + } + + @Override public void onLoadFinished(Loader> loader, List data) { + // Set the new data in the adapter. + mAdapter.setData(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } + } + + @Override public void onLoaderReset(Loader> loader) { + // Clear the data in the adapter. + mAdapter.setData(null); + } + } +//END_INCLUDE(fragment) +} diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java index d16797bc5..de3f937ce 100644 --- a/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/LoaderThrottleSupport.java @@ -410,6 +410,9 @@ public class LoaderThrottleSupport extends FragmentActivity { new int[] { android.R.id.text1 }, 0); setListAdapter(mAdapter); + // Start out with a progress indicator. + setListShown(false); + // Prepare the loader. Either re-connect with an existing one, // or start a new one. getLoaderManager().initLoader(0, null, this); @@ -493,6 +496,13 @@ public class LoaderThrottleSupport extends FragmentActivity { public void onLoadFinished(Loader loader, Cursor data) { mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } public void onLoaderReset(Loader loader) { diff --git a/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html b/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html index 286d4a0ff..fa9af5ad1 100644 --- a/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html +++ b/samples/Support4Demos/src/com/example/android/supportv4/app/_index.html @@ -67,6 +67,15 @@ and loaders.

Demonstrates creating a stack of Fragment instances similar to the traditional stack of activities.
+
Fragment Tabs
+
Demonstrates the use of fragments to implement switching between + tabs in a TabHost.
+ +
Fragment Tabs Pager
+
Demonstrates the use of fragments to implement switching between + tabs in a TabHost, using a ViewPager to manager the fragments so that + the user can also fling left and right to switch tabs.
+

LoaderManager

@@ -74,6 +83,10 @@ and loaders.

Loader Cursor
Demonstrates use of LoaderManager to perform a query for a Cursor that populates a ListFragment.
+ +
Loader Custom
+
Demonstrates implementation and use of a custom Loader class. The + custom class here "loads" the currently installed applications.
Loader Throttle
Complete end-to-end demonstration of a simple content provider that