diff options
25 files changed, 1582 insertions, 8 deletions
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 @@ <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="11" /> - <!-- This app has not been optimized for large screens. --> + <!-- The smallest screen this app works on is a phone. The app will + scale its UI to larger screens but doesn't make good use of them + so allow the compatibility mode button to be shown (mostly because + this is just convenient for testing). --> <supports-screens android:requiresSmallestWidthDp="320" android:compatibleWidthLimitDp="480" /> @@ -361,6 +364,15 @@ </intent-filter> </activity> + <activity android:name=".app.FragmentTabs" + android:label="@string/fragment_tabs" + android:enabled="@bool/atLeastHoneycomb"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.SAMPLE_CODE" /> + </intent-filter> + </activity> + <!-- Loader Samples --> <activity android:name=".app.LoaderCursor" diff --git a/samples/ApiDemos/res/values/strings.xml b/samples/ApiDemos/res/values/strings.xml index f95d8f2c0..da20a8f5f 100644 --- a/samples/ApiDemos/res/values/strings.xml +++ b/samples/ApiDemos/res/values/strings.xml @@ -150,6 +150,8 @@ <string name="fragment_stack">App/Fragment/Stack</string> <string name="new_fragment">New fragment</string> + <string name="fragment_tabs">App/Fragment/Tabs</string> + <string name="loader_cursor">App/Loader/Cursor</string> <string name="loader_custom">App/Loader/Custom</string> 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<FragmentStack.CountingFragment>( + this, "simple", FragmentStack.CountingFragment.class))); + bar.addTab(bar.newTab() + .setText("Contacts") + .setTabListener(new TabListener<LoaderCursor.CursorLoaderListFragment>( + this, "contacts", LoaderCursor.CursorLoaderListFragment.class))); + bar.addTab(bar.newTab() + .setText("Apps") + .setTabListener(new TabListener<LoaderCustom.AppListFragment>( + this, "apps", LoaderCustom.AppListFragment.class))); + bar.addTab(bar.newTab() + .setText("Throttle") + .setTabListener(new TabListener<LoaderThrottle.ThrottledLoaderListFragment>( + 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<T extends Fragment> implements ActionBar.TabListener { + private final Activity mActivity; + private final String mTag; + private final Class<T> mClass; + private final Bundle mArgs; + private Fragment mFragment; + + public TabListener(Activity activity, String tag, Class<T> clz) { + this(activity, tag, clz, null); + } + + public TabListener(Activity activity, String tag, Class<T> 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<Cursor> 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<List<AppEntry>> 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<Cursor> loader, Cursor data) { mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } public void onLoaderReset(Loader<Cursor> 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 @@ <dd>Demonstrates creating a stack of Fragment instances similar to the traditional stack of activities.</dd> + <dt><a href="FragmentTabs.html">Fragment Tabs</a></dt> + <dd>Demonstrates implementing ActionBar tabs by switching between + Fragments.</dd> + </dl> @@ -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.</dd> <dt><a href="ActionBarTabs.html">Action Bar Tabs</a></dt> <dd>Demonstrates the use of Action Bar tabs and how they interact with other action bar -features.</dd> +features. Also see the <a href="FragmentTabs.html">Fragment Tabs</a> for a more +complete example of how to switch between fragments.</dd> <dt><a href="ActionBarUsage.html">Action Bar Usage</a></dt> - <dd>Demonstrates imple usage of the Action Bar, including a SearchView as an action item. The + <dd>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.</dd> @@ -162,6 +167,10 @@ Mechanics.</dd> <dd>Demonstrates use of LoaderManager to perform a query for a Cursor that populates a ListFragment.</dd> + <dt><a href="LoaderCustom.html">Loader Custom</a></dt> + <dd>Demonstrates implementation and use of a custom Loader class. The + custom class here "loads" the currently installed applications.</dd> + <dt><a href="LoaderThrottle.html">Loader Throttle</a></dt> <dd>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 @@ <uses-sdk android:minSdkVersion="13" /> - <!-- This app has not been optimized for large screens. --> + <!-- The smallest screen this app works on is a phone. The app will + scale its UI to larger screens but doesn't make good use of them + so allow the compatibility mode button to be shown (mostly because + this is just convenient for testing). --> <supports-screens android:requiresSmallestWidthDp="320" android:compatibleWidthLimitDp="480" /> @@ -60,5 +63,13 @@ </intent-filter> </activity> + <activity android:name=".app.ActionBarTabsPager" + android:label="@string/action_bar_tabs_pager"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.example.android.supportv13.SUPPORT13_SAMPLE_CODE" /> + </intent-filter> + </activity> + </application> </manifest> 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 @@ <string name="fragment_state_pager_support">Fragment/State Pager</string> + <string name="action_bar_tabs_pager">Fragment/Action Bar Tabs Pager</string> </resources> 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<TabInfo> mTabs = new ArrayList<TabInfo>(); + + 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<mTabs.size(); i++) { + if (mTabs.get(i) == tag) { + mViewPager.setCurrentItem(i); + } + } + } + + @Override + public void onTabUnselected(Tab tab, FragmentTransaction ft) { + } + + @Override + public void onTabReselected(Tab tab, FragmentTransaction ft) { + } + } +} diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java b/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java new file mode 100644 index 000000000..8672ed22a --- /dev/null +++ b/samples/Support13Demos/src/com/example/android/supportv13/app/CountingFragment.java @@ -0,0 +1,67 @@ +/* + * 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 com.example.android.supportv13.R; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +public class CountingFragment extends Fragment { + int mNum; + + /** + * Create a new instance of CountingFragment, providing "num" + * as an argument. + */ + static CountingFragment newInstance(int num) { + CountingFragment f = new CountingFragment(); + + // Supply num input as an argument. + Bundle args = new Bundle(); + args.putInt("num", num); + f.setArguments(args); + + return f; + } + + /** + * When creating, retrieve this instance's number from its arguments. + */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mNum = getArguments() != null ? getArguments().getInt("num") : 1; + } + + /** + * The Fragment's UI is just a simple text view showing its + * instance number. + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.hello_world, container, false); + View tv = v.findViewById(R.id.text); + ((TextView)tv).setText("Fragment #" + mNum); + tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb)); + return v; + } +} diff --git a/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java b/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java new file mode 100644 index 000000000..38be2479e --- /dev/null +++ b/samples/Support13Demos/src/com/example/android/supportv13/app/CursorFragment.java @@ -0,0 +1,154 @@ +/* + * 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 android.app.ListFragment; +import android.app.LoaderManager; +import android.content.CursorLoader; +import android.content.Loader; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.ContactsContract.Contacts; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.ListView; +import android.widget.SearchView; +import android.widget.SimpleCursorAdapter; +import android.widget.SearchView.OnQueryTextListener; + + +public class CursorFragment extends ListFragment + implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { + + // 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<Cursor> 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<Cursor> 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<Cursor> 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. <h3 id="Fragment">Fragment</h3> <dl> + <dt><a href="ActionBarTabsPager.html">Action Bar Tabs Pager</a></dt> + <dd>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.</dd> + <dt><a href="FragmentPagerSupport.html">Fragment Pager Support</a></dt> <dd>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.</dd> + <dt><a href="FragmentStatePagerSupport.html">Fragment State Pager Support</a></dt> <dd>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 @@ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="13" /> - <!-- This app has not been optimized for large screens. --> + <!-- The smallest screen this app works on is a phone. The app will + scale its UI to larger screens but doesn't make good use of them + so allow the compatibility mode button to be shown (mostly because + this is just convenient for testing). --> <supports-screens android:requiresSmallestWidthDp="320" android:compatibleWidthLimitDp="480" /> @@ -147,6 +150,22 @@ </intent-filter> </activity> + <activity android:name=".app.FragmentTabs" + android:label="@string/fragment_tabs"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" /> + </intent-filter> + </activity> + + <activity android:name=".app.FragmentTabsPager" + android:label="@string/fragment_tabs_pager"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" /> + </intent-filter> + </activity> + <activity android:name=".app.FragmentPagerSupport" android:label="@string/fragment_pager_support"> <intent-filter> @@ -171,6 +190,14 @@ </intent-filter> </activity> + <activity android:name=".app.LoaderCustomSupport" + android:label="@string/loader_custom_support"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" /> + </intent-filter> + </activity> + <activity android:name=".app.LoaderThrottleSupport" android:label="@string/loader_throttle_support"> <intent-filter> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/assets/res/layout/tab_content.xml +** +** Copyright 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. +*/ +--> + +<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> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/assets/res/layout/tab_content.xml +** +** Copyright 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. +*/ +--> + +<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"/> + + <android.support.v4.view.ViewPager + android:id="@+id/pager" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + + </LinearLayout> +</TabHost> 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 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2007 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView android:id="@+id/icon" + android:layout_width="48dip" + android:layout_height="48dip" /> + + <TextView android:id="@+id/text" + android:layout_gravity="center_vertical" + android:layout_width="0dip" + android:layout_weight="1.0" + android:layout_height="wrap_content" /> + +</LinearLayout> 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 @@ <string name="fragment_stack_support">Fragment/Stack</string> <string name="new_fragment">New fragment</string> + <string name="fragment_tabs">Fragment/Tabs</string> + + <string name="fragment_tabs_pager">Fragment/Tabs and Pager</string> + <string name="fragment_pager_support">Fragment/Pager</string> <string name="first">First</string> <string name="last">Last</string> @@ -85,6 +89,8 @@ <string name="loader_cursor_support">Loader/Cursor</string> + <string name="loader_custom_support">Loader/Custom</string> + <string name="loader_throttle_support">Loader/Throttle</string> </resources> 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<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(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<TabInfo> mTabs = new ArrayList<TabInfo>(); + + 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<Cursor> 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<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { + 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<List<AppEntry>> { + final InterestingConfigChanges mLastConfig = new InterestingConfigChanges(); + final PackageManager mPm; + + List<AppEntry> 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<AppEntry> loadInBackground() { + // Retrieve all known applications. + List<ApplicationInfo> apps = mPm.getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES | + PackageManager.GET_DISABLED_COMPONENTS); + if (apps == null) { + apps = new ArrayList<ApplicationInfo>(); + } + + final Context context = getContext(); + + // Create corresponding array of entries and load their labels. + List<AppEntry> entries = new ArrayList<AppEntry>(apps.size()); + for (int i=0; i<apps.size(); i++) { + AppEntry entry = new AppEntry(this, apps.get(i)); + entry.loadLabel(context); + entries.add(entry); + } + + // Sort the list. + Collections.sort(entries, ALPHA_COMPARATOR); + + // Done! + return entries; + } + + /** + * Called when there is new data to deliver to the client. The + * super class will take care of delivering it; the implementation + * here just adds a little more logic. + */ + @Override public void deliverResult(List<AppEntry> 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<AppEntry> 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<AppEntry> 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<AppEntry> 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<AppEntry> { + 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<AppEntry> 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<List<AppEntry>> { + + // 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<List<AppEntry>> 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<List<AppEntry>> loader, List<AppEntry> 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<List<AppEntry>> 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<Cursor> loader, Cursor data) { mAdapter.swapCursor(data); + + // The list should now be shown. + if (isResumed()) { + setListShown(true); + } else { + setListShownNoAnimation(true); + } } public void onLoaderReset(Loader<Cursor> 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.</p> <dd>Demonstrates creating a stack of Fragment instances similar to the traditional stack of activities.</dd> + <dt><a href="FragmentTabs.html">Fragment Tabs</a></dt> + <dd>Demonstrates the use of fragments to implement switching between + tabs in a TabHost.</dd> + + <dt><a href="FragmentTabsPager.html">Fragment Tabs Pager</a></dt> + <dd>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.</dd> + </dl> <h3 id="LoaderManager">LoaderManager</h3> @@ -74,6 +83,10 @@ and loaders.</p> <dt><a href="LoaderCursorSupport.html">Loader Cursor</a></dt> <dd>Demonstrates use of LoaderManager to perform a query for a Cursor that populates a ListFragment.</dd> + + <dt><a href="LoaderCustomSupport.html">Loader Custom</a></dt> + <dd>Demonstrates implementation and use of a custom Loader class. The + custom class here "loads" the currently installed applications.</dd> <dt><a href="LoaderThrottleSupport.html">Loader Throttle</a></dt> <dd>Complete end-to-end demonstration of a simple content provider that |