development
Révision | be6b6b494f094eea0abcd83eb8770bc4b9f7e34e (tree) |
---|---|
l'heure | 2011-06-14 08:27:17 |
Auteur | Dianne Hackborn <hackbod@goog...> |
Commiter | Dianne Hackborn |
New API demos showing use of tabs with fragments.
Also various cleanup in other demos.
Change-Id: I4f5669117e28312bcd4b28795c5eca5f4ab6dcfb
@@ -38,7 +38,10 @@ | ||
38 | 38 | |
39 | 39 | <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="11" /> |
40 | 40 | |
41 | - <!-- This app has not been optimized for large screens. --> | |
41 | + <!-- The smallest screen this app works on is a phone. The app will | |
42 | + scale its UI to larger screens but doesn't make good use of them | |
43 | + so allow the compatibility mode button to be shown (mostly because | |
44 | + this is just convenient for testing). --> | |
42 | 45 | <supports-screens android:requiresSmallestWidthDp="320" |
43 | 46 | android:compatibleWidthLimitDp="480" /> |
44 | 47 |
@@ -361,6 +364,15 @@ | ||
361 | 364 | </intent-filter> |
362 | 365 | </activity> |
363 | 366 | |
367 | + <activity android:name=".app.FragmentTabs" | |
368 | + android:label="@string/fragment_tabs" | |
369 | + android:enabled="@bool/atLeastHoneycomb"> | |
370 | + <intent-filter> | |
371 | + <action android:name="android.intent.action.MAIN" /> | |
372 | + <category android:name="android.intent.category.SAMPLE_CODE" /> | |
373 | + </intent-filter> | |
374 | + </activity> | |
375 | + | |
364 | 376 | <!-- Loader Samples --> |
365 | 377 | |
366 | 378 | <activity android:name=".app.LoaderCursor" |
@@ -150,6 +150,8 @@ | ||
150 | 150 | <string name="fragment_stack">App/Fragment/Stack</string> |
151 | 151 | <string name="new_fragment">New fragment</string> |
152 | 152 | |
153 | + <string name="fragment_tabs">App/Fragment/Tabs</string> | |
154 | + | |
153 | 155 | <string name="loader_cursor">App/Loader/Cursor</string> |
154 | 156 | |
155 | 157 | <string name="loader_custom">App/Loader/Custom</string> |
@@ -78,6 +78,11 @@ public class ActionBarTabs extends Activity { | ||
78 | 78 | * to it, it will be committed at the end of the full tab switch operation. |
79 | 79 | * This lets tab switches be atomic without the app needing to track |
80 | 80 | * the interactions between different tabs. |
81 | + * | |
82 | + * NOTE: This is a very simple implementation that does not retain | |
83 | + * fragment state of the non-visible tabs across activity instances. | |
84 | + * Look at the FragmentTabs example for how to do a more complete | |
85 | + * implementation. | |
81 | 86 | */ |
82 | 87 | private class TabListener implements ActionBar.TabListener { |
83 | 88 | private TabContentFragment mFragment; |
@@ -0,0 +1,116 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2011 The Android Open Source Project | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package com.example.android.apis.app; | |
17 | + | |
18 | +import com.example.android.apis.R; | |
19 | + | |
20 | +import android.app.ActionBar; | |
21 | +import android.app.ActionBar.Tab; | |
22 | +import android.app.Activity; | |
23 | +import android.app.Fragment; | |
24 | +import android.app.FragmentTransaction; | |
25 | +import android.os.Bundle; | |
26 | +import android.widget.Toast; | |
27 | + | |
28 | +/** | |
29 | + * This demonstrates the use of action bar tabs and how they interact | |
30 | + * with other action bar features. | |
31 | + */ | |
32 | +public class FragmentTabs extends Activity { | |
33 | + @Override | |
34 | + protected void onCreate(Bundle savedInstanceState) { | |
35 | + super.onCreate(savedInstanceState); | |
36 | + | |
37 | + final ActionBar bar = getActionBar(); | |
38 | + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); | |
39 | + bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); | |
40 | + | |
41 | + bar.addTab(bar.newTab() | |
42 | + .setText("Simple") | |
43 | + .setTabListener(new TabListener<FragmentStack.CountingFragment>( | |
44 | + this, "simple", FragmentStack.CountingFragment.class))); | |
45 | + bar.addTab(bar.newTab() | |
46 | + .setText("Contacts") | |
47 | + .setTabListener(new TabListener<LoaderCursor.CursorLoaderListFragment>( | |
48 | + this, "contacts", LoaderCursor.CursorLoaderListFragment.class))); | |
49 | + bar.addTab(bar.newTab() | |
50 | + .setText("Apps") | |
51 | + .setTabListener(new TabListener<LoaderCustom.AppListFragment>( | |
52 | + this, "apps", LoaderCustom.AppListFragment.class))); | |
53 | + bar.addTab(bar.newTab() | |
54 | + .setText("Throttle") | |
55 | + .setTabListener(new TabListener<LoaderThrottle.ThrottledLoaderListFragment>( | |
56 | + this, "throttle", LoaderThrottle.ThrottledLoaderListFragment.class))); | |
57 | + | |
58 | + if (savedInstanceState != null) { | |
59 | + bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); | |
60 | + } | |
61 | + } | |
62 | + | |
63 | + @Override | |
64 | + protected void onSaveInstanceState(Bundle outState) { | |
65 | + super.onSaveInstanceState(outState); | |
66 | + outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); | |
67 | + } | |
68 | + | |
69 | + public static class TabListener<T extends Fragment> implements ActionBar.TabListener { | |
70 | + private final Activity mActivity; | |
71 | + private final String mTag; | |
72 | + private final Class<T> mClass; | |
73 | + private final Bundle mArgs; | |
74 | + private Fragment mFragment; | |
75 | + | |
76 | + public TabListener(Activity activity, String tag, Class<T> clz) { | |
77 | + this(activity, tag, clz, null); | |
78 | + } | |
79 | + | |
80 | + public TabListener(Activity activity, String tag, Class<T> clz, Bundle args) { | |
81 | + mActivity = activity; | |
82 | + mTag = tag; | |
83 | + mClass = clz; | |
84 | + mArgs = args; | |
85 | + | |
86 | + // Check to see if we already have a fragment for this tab, probably | |
87 | + // from a previously saved state. If so, deactivate it, because our | |
88 | + // initial state is that a tab isn't shown. | |
89 | + mFragment = mActivity.getFragmentManager().findFragmentByTag(mTag); | |
90 | + if (mFragment != null && !mFragment.isDetached()) { | |
91 | + FragmentTransaction ft = mActivity.getFragmentManager().beginTransaction(); | |
92 | + ft.detach(mFragment); | |
93 | + ft.commit(); | |
94 | + } | |
95 | + } | |
96 | + | |
97 | + public void onTabSelected(Tab tab, FragmentTransaction ft) { | |
98 | + if (mFragment == null) { | |
99 | + mFragment = Fragment.instantiate(mActivity, mClass.getName(), mArgs); | |
100 | + ft.add(android.R.id.content, mFragment, mTag); | |
101 | + } else { | |
102 | + ft.attach(mFragment); | |
103 | + } | |
104 | + } | |
105 | + | |
106 | + public void onTabUnselected(Tab tab, FragmentTransaction ft) { | |
107 | + if (mFragment != null) { | |
108 | + ft.detach(mFragment); | |
109 | + } | |
110 | + } | |
111 | + | |
112 | + public void onTabReselected(Tab tab, FragmentTransaction ft) { | |
113 | + Toast.makeText(mActivity, "Reselected!", Toast.LENGTH_SHORT).show(); | |
114 | + } | |
115 | + } | |
116 | +} |
@@ -159,7 +159,11 @@ public class LoaderCursor extends Activity { | ||
159 | 159 | mAdapter.swapCursor(data); |
160 | 160 | |
161 | 161 | // The list should now be shown. |
162 | - setListShown(true); | |
162 | + if (isResumed()) { | |
163 | + setListShown(true); | |
164 | + } else { | |
165 | + setListShownNoAnimation(true); | |
166 | + } | |
163 | 167 | } |
164 | 168 | |
165 | 169 | public void onLoaderReset(Loader<Cursor> loader) { |
@@ -1,5 +1,5 @@ | ||
1 | 1 | /* |
2 | - * Copyright (C) 2010 The Android Open Source Project | |
2 | + * Copyright (C) 2011 The Android Open Source Project | |
3 | 3 | * |
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | 5 | * you may not use this file except in compliance with the License. |
@@ -466,7 +466,11 @@ public class LoaderCustom extends Activity { | ||
466 | 466 | mAdapter.setData(data); |
467 | 467 | |
468 | 468 | // The list should now be shown. |
469 | - setListShown(true); | |
469 | + if (isResumed()) { | |
470 | + setListShown(true); | |
471 | + } else { | |
472 | + setListShownNoAnimation(true); | |
473 | + } | |
470 | 474 | } |
471 | 475 | |
472 | 476 | @Override public void onLoaderReset(Loader<List<AppEntry>> loader) { |
@@ -409,6 +409,9 @@ public class LoaderThrottle extends Activity { | ||
409 | 409 | new int[] { android.R.id.text1 }, 0); |
410 | 410 | setListAdapter(mAdapter); |
411 | 411 | |
412 | + // Start out with a progress indicator. | |
413 | + setListShown(false); | |
414 | + | |
412 | 415 | // Prepare the loader. Either re-connect with an existing one, |
413 | 416 | // or start a new one. |
414 | 417 | getLoaderManager().initLoader(0, null, this); |
@@ -492,6 +495,13 @@ public class LoaderThrottle extends Activity { | ||
492 | 495 | |
493 | 496 | public void onLoadFinished(Loader<Cursor> loader, Cursor data) { |
494 | 497 | mAdapter.swapCursor(data); |
498 | + | |
499 | + // The list should now be shown. | |
500 | + if (isResumed()) { | |
501 | + setListShown(true); | |
502 | + } else { | |
503 | + setListShownNoAnimation(true); | |
504 | + } | |
495 | 505 | } |
496 | 506 | |
497 | 507 | public void onLoaderReset(Loader<Cursor> loader) { |
@@ -134,6 +134,10 @@ | ||
134 | 134 | <dd>Demonstrates creating a stack of Fragment instances similar to the |
135 | 135 | traditional stack of activities.</dd> |
136 | 136 | |
137 | + <dt><a href="FragmentTabs.html">Fragment Tabs</a></dt> | |
138 | + <dd>Demonstrates implementing ActionBar tabs by switching between | |
139 | + Fragments.</dd> | |
140 | + | |
137 | 141 | </dl> |
138 | 142 | |
139 | 143 |
@@ -145,9 +149,10 @@ menu. This demo is for informative purposes only; see Usage for an example of us | ||
145 | 149 | Action Bar in a more idiomatic manner.</dd> |
146 | 150 | <dt><a href="ActionBarTabs.html">Action Bar Tabs</a></dt> |
147 | 151 | <dd>Demonstrates the use of Action Bar tabs and how they interact with other action bar |
148 | -features.</dd> | |
152 | +features. Also see the <a href="FragmentTabs.html">Fragment Tabs</a> for a more | |
153 | +complete example of how to switch between fragments.</dd> | |
149 | 154 | <dt><a href="ActionBarUsage.html">Action Bar Usage</a></dt> |
150 | - <dd>Demonstrates imple usage of the Action Bar, including a SearchView as an action item. The | |
155 | + <dd>Demonstrates simple usage of the Action Bar, including a SearchView as an action item. The | |
151 | 156 | default Honeycomb theme includes the Action Bar by default and a menu resource is used to populate |
152 | 157 | the menu data itself. If you'd like to see how these things work under the hood, see |
153 | 158 | Mechanics.</dd> |
@@ -162,6 +167,10 @@ Mechanics.</dd> | ||
162 | 167 | <dd>Demonstrates use of LoaderManager to perform a query for a Cursor that |
163 | 168 | populates a ListFragment.</dd> |
164 | 169 | |
170 | + <dt><a href="LoaderCustom.html">Loader Custom</a></dt> | |
171 | + <dd>Demonstrates implementation and use of a custom Loader class. The | |
172 | + custom class here "loads" the currently installed applications.</dd> | |
173 | + | |
165 | 174 | <dt><a href="LoaderThrottle.html">Loader Throttle</a></dt> |
166 | 175 | <dd>Complete end-to-end demonstration of a simple content provider that |
167 | 176 | populates data in a list through a cursor loader. The UI allows the list |
@@ -26,7 +26,10 @@ | ||
26 | 26 | |
27 | 27 | <uses-sdk android:minSdkVersion="13" /> |
28 | 28 | |
29 | - <!-- This app has not been optimized for large screens. --> | |
29 | + <!-- The smallest screen this app works on is a phone. The app will | |
30 | + scale its UI to larger screens but doesn't make good use of them | |
31 | + so allow the compatibility mode button to be shown (mostly because | |
32 | + this is just convenient for testing). --> | |
30 | 33 | <supports-screens android:requiresSmallestWidthDp="320" |
31 | 34 | android:compatibleWidthLimitDp="480" /> |
32 | 35 |
@@ -60,5 +63,13 @@ | ||
60 | 63 | </intent-filter> |
61 | 64 | </activity> |
62 | 65 | |
66 | + <activity android:name=".app.ActionBarTabsPager" | |
67 | + android:label="@string/action_bar_tabs_pager"> | |
68 | + <intent-filter> | |
69 | + <action android:name="android.intent.action.MAIN" /> | |
70 | + <category android:name="com.example.android.supportv13.SUPPORT13_SAMPLE_CODE" /> | |
71 | + </intent-filter> | |
72 | + </activity> | |
73 | + | |
63 | 74 | </application> |
64 | 75 | </manifest> |
@@ -29,4 +29,5 @@ | ||
29 | 29 | |
30 | 30 | <string name="fragment_state_pager_support">Fragment/State Pager</string> |
31 | 31 | |
32 | + <string name="action_bar_tabs_pager">Fragment/Action Bar Tabs Pager</string> | |
32 | 33 | </resources> |
@@ -0,0 +1,155 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2011 The Android Open Source Project | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package com.example.android.supportv13.app; | |
17 | + | |
18 | +import java.util.ArrayList; | |
19 | + | |
20 | +import com.example.android.supportv13.R; | |
21 | + | |
22 | +import android.app.ActionBar; | |
23 | +import android.app.ActionBar.Tab; | |
24 | +import android.app.Activity; | |
25 | +import android.app.Fragment; | |
26 | +import android.app.FragmentTransaction; | |
27 | +import android.content.Context; | |
28 | +import android.os.Bundle; | |
29 | +import android.support.v13.app.FragmentPagerAdapter; | |
30 | +import android.support.v4.view.ViewPager; | |
31 | + | |
32 | +/** | |
33 | + * This demonstrates the use of action bar tabs and how they interact | |
34 | + * with other action bar features. | |
35 | + */ | |
36 | +public class ActionBarTabsPager extends Activity { | |
37 | + ViewPager mViewPager; | |
38 | + TabsAdapter mTabsAdapter; | |
39 | + | |
40 | + @Override | |
41 | + protected void onCreate(Bundle savedInstanceState) { | |
42 | + super.onCreate(savedInstanceState); | |
43 | + | |
44 | + mViewPager = new ViewPager(this); | |
45 | + mViewPager.setId(R.id.pager); | |
46 | + setContentView(mViewPager); | |
47 | + | |
48 | + final ActionBar bar = getActionBar(); | |
49 | + bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); | |
50 | + bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE); | |
51 | + | |
52 | + mTabsAdapter = new TabsAdapter(this, mViewPager); | |
53 | + mTabsAdapter.addTab(bar.newTab().setText("Simple"), | |
54 | + CountingFragment.class, null); | |
55 | + mTabsAdapter.addTab(bar.newTab().setText("List"), | |
56 | + FragmentPagerSupport.ArrayListFragment.class, null); | |
57 | + mTabsAdapter.addTab(bar.newTab().setText("Cursor"), | |
58 | + CursorFragment.class, null); | |
59 | + | |
60 | + if (savedInstanceState != null) { | |
61 | + bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); | |
62 | + } | |
63 | + } | |
64 | + | |
65 | + @Override | |
66 | + protected void onSaveInstanceState(Bundle outState) { | |
67 | + super.onSaveInstanceState(outState); | |
68 | + outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); | |
69 | + } | |
70 | + | |
71 | + /** | |
72 | + * This is a helper class that implements the management of tabs and all | |
73 | + * details of connecting a ViewPager with associated TabHost. It relies on a | |
74 | + * trick. Normally a tab host has a simple API for supplying a View or | |
75 | + * Intent that each tab will show. This is not sufficient for switching | |
76 | + * between pages. So instead we make the content part of the tab host | |
77 | + * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy | |
78 | + * view to show as the tab content. It listens to changes in tabs, and takes | |
79 | + * care of switch to the correct paged in the ViewPager whenever the selected | |
80 | + * tab changes. | |
81 | + */ | |
82 | + public static class TabsAdapter extends FragmentPagerAdapter | |
83 | + implements ActionBar.TabListener, ViewPager.OnPageChangeListener { | |
84 | + private final Context mContext; | |
85 | + private final ActionBar mActionBar; | |
86 | + private final ViewPager mViewPager; | |
87 | + private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); | |
88 | + | |
89 | + static final class TabInfo { | |
90 | + private final Class<?> clss; | |
91 | + private final Bundle args; | |
92 | + | |
93 | + TabInfo(Class<?> _class, Bundle _args) { | |
94 | + clss = _class; | |
95 | + args = _args; | |
96 | + } | |
97 | + } | |
98 | + | |
99 | + public TabsAdapter(Activity activity, ViewPager pager) { | |
100 | + super(activity.getFragmentManager()); | |
101 | + mContext = activity; | |
102 | + mActionBar = activity.getActionBar(); | |
103 | + mViewPager = pager; | |
104 | + mViewPager.setAdapter(this); | |
105 | + mViewPager.setOnPageChangeListener(this); | |
106 | + } | |
107 | + | |
108 | + public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) { | |
109 | + TabInfo info = new TabInfo(clss, args); | |
110 | + tab.setTag(info); | |
111 | + tab.setTabListener(this); | |
112 | + mTabs.add(info); | |
113 | + mActionBar.addTab(tab); | |
114 | + notifyDataSetChanged(); | |
115 | + } | |
116 | + | |
117 | + @Override | |
118 | + public int getCount() { | |
119 | + return mTabs.size(); | |
120 | + } | |
121 | + | |
122 | + @Override | |
123 | + public Fragment getItem(int position) { | |
124 | + TabInfo info = mTabs.get(position); | |
125 | + return Fragment.instantiate(mContext, info.clss.getName(), info.args); | |
126 | + } | |
127 | + | |
128 | + @Override | |
129 | + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { | |
130 | + } | |
131 | + | |
132 | + @Override | |
133 | + public void onPageSelected(int position) { | |
134 | + mActionBar.setSelectedNavigationItem(position); | |
135 | + } | |
136 | + | |
137 | + @Override | |
138 | + public void onTabSelected(Tab tab, FragmentTransaction ft) { | |
139 | + Object tag = tab.getTag(); | |
140 | + for (int i=0; i<mTabs.size(); i++) { | |
141 | + if (mTabs.get(i) == tag) { | |
142 | + mViewPager.setCurrentItem(i); | |
143 | + } | |
144 | + } | |
145 | + } | |
146 | + | |
147 | + @Override | |
148 | + public void onTabUnselected(Tab tab, FragmentTransaction ft) { | |
149 | + } | |
150 | + | |
151 | + @Override | |
152 | + public void onTabReselected(Tab tab, FragmentTransaction ft) { | |
153 | + } | |
154 | + } | |
155 | +} |
@@ -0,0 +1,67 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2011 The Android Open Source Project | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package com.example.android.supportv13.app; | |
17 | + | |
18 | +import com.example.android.supportv13.R; | |
19 | + | |
20 | +import android.app.Fragment; | |
21 | +import android.os.Bundle; | |
22 | +import android.view.LayoutInflater; | |
23 | +import android.view.View; | |
24 | +import android.view.ViewGroup; | |
25 | +import android.widget.TextView; | |
26 | + | |
27 | +public class CountingFragment extends Fragment { | |
28 | + int mNum; | |
29 | + | |
30 | + /** | |
31 | + * Create a new instance of CountingFragment, providing "num" | |
32 | + * as an argument. | |
33 | + */ | |
34 | + static CountingFragment newInstance(int num) { | |
35 | + CountingFragment f = new CountingFragment(); | |
36 | + | |
37 | + // Supply num input as an argument. | |
38 | + Bundle args = new Bundle(); | |
39 | + args.putInt("num", num); | |
40 | + f.setArguments(args); | |
41 | + | |
42 | + return f; | |
43 | + } | |
44 | + | |
45 | + /** | |
46 | + * When creating, retrieve this instance's number from its arguments. | |
47 | + */ | |
48 | + @Override | |
49 | + public void onCreate(Bundle savedInstanceState) { | |
50 | + super.onCreate(savedInstanceState); | |
51 | + mNum = getArguments() != null ? getArguments().getInt("num") : 1; | |
52 | + } | |
53 | + | |
54 | + /** | |
55 | + * The Fragment's UI is just a simple text view showing its | |
56 | + * instance number. | |
57 | + */ | |
58 | + @Override | |
59 | + public View onCreateView(LayoutInflater inflater, ViewGroup container, | |
60 | + Bundle savedInstanceState) { | |
61 | + View v = inflater.inflate(R.layout.hello_world, container, false); | |
62 | + View tv = v.findViewById(R.id.text); | |
63 | + ((TextView)tv).setText("Fragment #" + mNum); | |
64 | + tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb)); | |
65 | + return v; | |
66 | + } | |
67 | +} |
@@ -0,0 +1,154 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2011 The Android Open Source Project | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +package com.example.android.supportv13.app; | |
18 | + | |
19 | +import android.app.ListFragment; | |
20 | +import android.app.LoaderManager; | |
21 | +import android.content.CursorLoader; | |
22 | +import android.content.Loader; | |
23 | +import android.database.Cursor; | |
24 | +import android.net.Uri; | |
25 | +import android.os.Bundle; | |
26 | +import android.provider.ContactsContract.Contacts; | |
27 | +import android.text.TextUtils; | |
28 | +import android.util.Log; | |
29 | +import android.view.Menu; | |
30 | +import android.view.MenuInflater; | |
31 | +import android.view.MenuItem; | |
32 | +import android.view.View; | |
33 | +import android.widget.ListView; | |
34 | +import android.widget.SearchView; | |
35 | +import android.widget.SimpleCursorAdapter; | |
36 | +import android.widget.SearchView.OnQueryTextListener; | |
37 | + | |
38 | + | |
39 | +public class CursorFragment extends ListFragment | |
40 | + implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { | |
41 | + | |
42 | + // This is the Adapter being used to display the list's data. | |
43 | + SimpleCursorAdapter mAdapter; | |
44 | + | |
45 | + // If non-null, this is the current filter the user has provided. | |
46 | + String mCurFilter; | |
47 | + | |
48 | + @Override public void onActivityCreated(Bundle savedInstanceState) { | |
49 | + super.onActivityCreated(savedInstanceState); | |
50 | + | |
51 | + // Give some text to display if there is no data. In a real | |
52 | + // application this would come from a resource. | |
53 | + setEmptyText("No phone numbers"); | |
54 | + | |
55 | + // We have a menu item to show in action bar. | |
56 | + setHasOptionsMenu(true); | |
57 | + | |
58 | + // Create an empty adapter we will use to display the loaded data. | |
59 | + mAdapter = new SimpleCursorAdapter(getActivity(), | |
60 | + android.R.layout.simple_list_item_2, null, | |
61 | + new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, | |
62 | + new int[] { android.R.id.text1, android.R.id.text2 }, 0); | |
63 | + setListAdapter(mAdapter); | |
64 | + | |
65 | + // Start out with a progress indicator. | |
66 | + setListShown(false); | |
67 | + | |
68 | + // Prepare the loader. Either re-connect with an existing one, | |
69 | + // or start a new one. | |
70 | + getLoaderManager().initLoader(0, null, this); | |
71 | + } | |
72 | + | |
73 | + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | |
74 | + // Place an action bar item for searching. | |
75 | + MenuItem item = menu.add("Search"); | |
76 | + item.setIcon(android.R.drawable.ic_menu_search); | |
77 | + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | |
78 | + SearchView sv = new SearchView(getActivity()); | |
79 | + sv.setOnQueryTextListener(this); | |
80 | + item.setActionView(sv); | |
81 | + } | |
82 | + | |
83 | + public boolean onQueryTextChange(String newText) { | |
84 | + // Called when the action bar search text has changed. Update | |
85 | + // the search filter, and restart the loader to do a new query | |
86 | + // with this filter. | |
87 | + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; | |
88 | + getLoaderManager().restartLoader(0, null, this); | |
89 | + return true; | |
90 | + } | |
91 | + | |
92 | + @Override public boolean onQueryTextSubmit(String query) { | |
93 | + // Don't care about this. | |
94 | + return true; | |
95 | + } | |
96 | + | |
97 | + @Override public void onListItemClick(ListView l, View v, int position, long id) { | |
98 | + // Insert desired behavior here. | |
99 | + Log.i("FragmentComplexList", "Item clicked: " + id); | |
100 | + } | |
101 | + | |
102 | + // These are the Contacts rows that we will retrieve. | |
103 | + static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { | |
104 | + Contacts._ID, | |
105 | + Contacts.DISPLAY_NAME, | |
106 | + Contacts.CONTACT_STATUS, | |
107 | + Contacts.CONTACT_PRESENCE, | |
108 | + Contacts.PHOTO_ID, | |
109 | + Contacts.LOOKUP_KEY, | |
110 | + }; | |
111 | + | |
112 | + public Loader<Cursor> onCreateLoader(int id, Bundle args) { | |
113 | + // This is called when a new Loader needs to be created. This | |
114 | + // sample only has one Loader, so we don't care about the ID. | |
115 | + // First, pick the base URI to use depending on whether we are | |
116 | + // currently filtering. | |
117 | + Uri baseUri; | |
118 | + if (mCurFilter != null) { | |
119 | + baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, | |
120 | + Uri.encode(mCurFilter)); | |
121 | + } else { | |
122 | + baseUri = Contacts.CONTENT_URI; | |
123 | + } | |
124 | + | |
125 | + // Now create and return a CursorLoader that will take care of | |
126 | + // creating a Cursor for the data being displayed. | |
127 | + String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" | |
128 | + + Contacts.HAS_PHONE_NUMBER + "=1) AND (" | |
129 | + + Contacts.DISPLAY_NAME + " != '' ))"; | |
130 | + return new CursorLoader(getActivity(), baseUri, | |
131 | + CONTACTS_SUMMARY_PROJECTION, select, null, | |
132 | + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); | |
133 | + } | |
134 | + | |
135 | + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { | |
136 | + // Swap the new cursor in. (The framework will take care of closing the | |
137 | + // old cursor once we return.) | |
138 | + mAdapter.swapCursor(data); | |
139 | + | |
140 | + // The list should now be shown. | |
141 | + if (isResumed()) { | |
142 | + setListShown(true); | |
143 | + } else { | |
144 | + setListShownNoAnimation(true); | |
145 | + } | |
146 | + } | |
147 | + | |
148 | + public void onLoaderReset(Loader<Cursor> loader) { | |
149 | + // This is called when the last Cursor provided to onLoadFinished() | |
150 | + // above is about to be closed. We need to make sure we are no | |
151 | + // longer using it. | |
152 | + mAdapter.swapCursor(null); | |
153 | + } | |
154 | +} |
@@ -8,10 +8,16 @@ package features of the static support library fir API 13 or later. | ||
8 | 8 | |
9 | 9 | <h3 id="Fragment">Fragment</h3> |
10 | 10 | <dl> |
11 | + <dt><a href="ActionBarTabsPager.html">Action Bar Tabs Pager</a></dt> | |
12 | + <dd>Demonstrates the use of fragments to implement switching between | |
13 | + ActionBar tabs, using a ViewPager to manager the fragments so that | |
14 | + the user can also fling left and right to switch tabs.</dd> | |
15 | + | |
11 | 16 | <dt><a href="FragmentPagerSupport.html">Fragment Pager Support</a></dt> |
12 | 17 | <dd>Demonstrates the use of the v4 support class ViewPager with a |
13 | 18 | FragmentPagerAdapter to build a user interface where the user can fling |
14 | 19 | left or right to switch between fragments.</dd> |
20 | + | |
15 | 21 | <dt><a href="FragmentStatePagerSupport.html">Fragment State Pager Support</a></dt> |
16 | 22 | <dd>Demonstrates the use of the v4 support class ViewPager with a |
17 | 23 | FragmentStatePagerAdapter to build a user interface where the user can fling |
@@ -26,7 +26,10 @@ | ||
26 | 26 | |
27 | 27 | <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="13" /> |
28 | 28 | |
29 | - <!-- This app has not been optimized for large screens. --> | |
29 | + <!-- The smallest screen this app works on is a phone. The app will | |
30 | + scale its UI to larger screens but doesn't make good use of them | |
31 | + so allow the compatibility mode button to be shown (mostly because | |
32 | + this is just convenient for testing). --> | |
30 | 33 | <supports-screens android:requiresSmallestWidthDp="320" |
31 | 34 | android:compatibleWidthLimitDp="480" /> |
32 | 35 |
@@ -147,6 +150,22 @@ | ||
147 | 150 | </intent-filter> |
148 | 151 | </activity> |
149 | 152 | |
153 | + <activity android:name=".app.FragmentTabs" | |
154 | + android:label="@string/fragment_tabs"> | |
155 | + <intent-filter> | |
156 | + <action android:name="android.intent.action.MAIN" /> | |
157 | + <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" /> | |
158 | + </intent-filter> | |
159 | + </activity> | |
160 | + | |
161 | + <activity android:name=".app.FragmentTabsPager" | |
162 | + android:label="@string/fragment_tabs_pager"> | |
163 | + <intent-filter> | |
164 | + <action android:name="android.intent.action.MAIN" /> | |
165 | + <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" /> | |
166 | + </intent-filter> | |
167 | + </activity> | |
168 | + | |
150 | 169 | <activity android:name=".app.FragmentPagerSupport" |
151 | 170 | android:label="@string/fragment_pager_support"> |
152 | 171 | <intent-filter> |
@@ -171,6 +190,14 @@ | ||
171 | 190 | </intent-filter> |
172 | 191 | </activity> |
173 | 192 | |
193 | + <activity android:name=".app.LoaderCustomSupport" | |
194 | + android:label="@string/loader_custom_support"> | |
195 | + <intent-filter> | |
196 | + <action android:name="android.intent.action.MAIN" /> | |
197 | + <category android:name="com.example.android.supportv4.SUPPORT4_SAMPLE_CODE" /> | |
198 | + </intent-filter> | |
199 | + </activity> | |
200 | + | |
174 | 201 | <activity android:name=".app.LoaderThrottleSupport" |
175 | 202 | android:label="@string/loader_throttle_support"> |
176 | 203 | <intent-filter> |
@@ -0,0 +1,52 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
2 | +<!-- | |
3 | +/* //device/apps/common/assets/res/layout/tab_content.xml | |
4 | +** | |
5 | +** Copyright 2011, The Android Open Source Project | |
6 | +** | |
7 | +** Licensed under the Apache License, Version 2.0 (the "License"); | |
8 | +** you may not use this file except in compliance with the License. | |
9 | +** You may obtain a copy of the License at | |
10 | +** | |
11 | +** http://www.apache.org/licenses/LICENSE-2.0 | |
12 | +** | |
13 | +** Unless required by applicable law or agreed to in writing, software | |
14 | +** distributed under the License is distributed on an "AS IS" BASIS, | |
15 | +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
16 | +** See the License for the specific language governing permissions and | |
17 | +** limitations under the License. | |
18 | +*/ | |
19 | +--> | |
20 | + | |
21 | +<TabHost | |
22 | + xmlns:android="http://schemas.android.com/apk/res/android" | |
23 | + android:id="@android:id/tabhost" | |
24 | + android:layout_width="match_parent" | |
25 | + android:layout_height="match_parent"> | |
26 | + | |
27 | + <LinearLayout | |
28 | + android:orientation="vertical" | |
29 | + android:layout_width="match_parent" | |
30 | + android:layout_height="match_parent"> | |
31 | + | |
32 | + <TabWidget | |
33 | + android:id="@android:id/tabs" | |
34 | + android:orientation="horizontal" | |
35 | + android:layout_width="match_parent" | |
36 | + android:layout_height="wrap_content" | |
37 | + android:layout_weight="0"/> | |
38 | + | |
39 | + <FrameLayout | |
40 | + android:id="@android:id/tabcontent" | |
41 | + android:layout_width="0dp" | |
42 | + android:layout_height="0dp" | |
43 | + android:layout_weight="0"/> | |
44 | + | |
45 | + <FrameLayout | |
46 | + android:id="@+android:id/realtabcontent" | |
47 | + android:layout_width="match_parent" | |
48 | + android:layout_height="0dp" | |
49 | + android:layout_weight="1"/> | |
50 | + | |
51 | + </LinearLayout> | |
52 | +</TabHost> |
@@ -0,0 +1,52 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
2 | +<!-- | |
3 | +/* //device/apps/common/assets/res/layout/tab_content.xml | |
4 | +** | |
5 | +** Copyright 2011, The Android Open Source Project | |
6 | +** | |
7 | +** Licensed under the Apache License, Version 2.0 (the "License"); | |
8 | +** you may not use this file except in compliance with the License. | |
9 | +** You may obtain a copy of the License at | |
10 | +** | |
11 | +** http://www.apache.org/licenses/LICENSE-2.0 | |
12 | +** | |
13 | +** Unless required by applicable law or agreed to in writing, software | |
14 | +** distributed under the License is distributed on an "AS IS" BASIS, | |
15 | +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
16 | +** See the License for the specific language governing permissions and | |
17 | +** limitations under the License. | |
18 | +*/ | |
19 | +--> | |
20 | + | |
21 | +<TabHost | |
22 | + xmlns:android="http://schemas.android.com/apk/res/android" | |
23 | + android:id="@android:id/tabhost" | |
24 | + android:layout_width="match_parent" | |
25 | + android:layout_height="match_parent"> | |
26 | + | |
27 | + <LinearLayout | |
28 | + android:orientation="vertical" | |
29 | + android:layout_width="match_parent" | |
30 | + android:layout_height="match_parent"> | |
31 | + | |
32 | + <TabWidget | |
33 | + android:id="@android:id/tabs" | |
34 | + android:orientation="horizontal" | |
35 | + android:layout_width="match_parent" | |
36 | + android:layout_height="wrap_content" | |
37 | + android:layout_weight="0"/> | |
38 | + | |
39 | + <FrameLayout | |
40 | + android:id="@android:id/tabcontent" | |
41 | + android:layout_width="0dp" | |
42 | + android:layout_height="0dp" | |
43 | + android:layout_weight="0"/> | |
44 | + | |
45 | + <android.support.v4.view.ViewPager | |
46 | + android:id="@+id/pager" | |
47 | + android:layout_width="match_parent" | |
48 | + android:layout_height="0dp" | |
49 | + android:layout_weight="1"/> | |
50 | + | |
51 | + </LinearLayout> | |
52 | +</TabHost> |
@@ -0,0 +1,32 @@ | ||
1 | +<?xml version="1.0" encoding="utf-8"?> | |
2 | +<!-- Copyright (C) 2007 The Android Open Source Project | |
3 | + | |
4 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + you may not use this file except in compliance with the License. | |
6 | + You may obtain a copy of the License at | |
7 | + | |
8 | + http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + | |
10 | + Unless required by applicable law or agreed to in writing, software | |
11 | + distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + See the License for the specific language governing permissions and | |
14 | + limitations under the License. | |
15 | +--> | |
16 | + | |
17 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
18 | + android:orientation="horizontal" | |
19 | + android:layout_width="match_parent" | |
20 | + android:layout_height="match_parent"> | |
21 | + | |
22 | + <ImageView android:id="@+id/icon" | |
23 | + android:layout_width="48dip" | |
24 | + android:layout_height="48dip" /> | |
25 | + | |
26 | + <TextView android:id="@+id/text" | |
27 | + android:layout_gravity="center_vertical" | |
28 | + android:layout_width="0dip" | |
29 | + android:layout_weight="1.0" | |
30 | + android:layout_height="wrap_content" /> | |
31 | + | |
32 | +</LinearLayout> |
@@ -77,6 +77,10 @@ | ||
77 | 77 | <string name="fragment_stack_support">Fragment/Stack</string> |
78 | 78 | <string name="new_fragment">New fragment</string> |
79 | 79 | |
80 | + <string name="fragment_tabs">Fragment/Tabs</string> | |
81 | + | |
82 | + <string name="fragment_tabs_pager">Fragment/Tabs and Pager</string> | |
83 | + | |
80 | 84 | <string name="fragment_pager_support">Fragment/Pager</string> |
81 | 85 | <string name="first">First</string> |
82 | 86 | <string name="last">Last</string> |
@@ -85,6 +89,8 @@ | ||
85 | 89 | |
86 | 90 | <string name="loader_cursor_support">Loader/Cursor</string> |
87 | 91 | |
92 | + <string name="loader_custom_support">Loader/Custom</string> | |
93 | + | |
88 | 94 | <string name="loader_throttle_support">Loader/Throttle</string> |
89 | 95 | |
90 | 96 | </resources> |
@@ -0,0 +1,169 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2011 The Android Open Source Project | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package com.example.android.supportv4.app; | |
17 | + | |
18 | +import java.util.HashMap; | |
19 | + | |
20 | +import com.example.android.supportv4.R; | |
21 | + | |
22 | +import android.content.Context; | |
23 | +import android.os.Bundle; | |
24 | +import android.support.v4.app.Fragment; | |
25 | +import android.support.v4.app.FragmentActivity; | |
26 | +import android.support.v4.app.FragmentTransaction; | |
27 | +import android.view.View; | |
28 | +import android.widget.TabHost; | |
29 | + | |
30 | +/** | |
31 | + * This demonstrates how you can implement switching between the tabs of a | |
32 | + * TabHost through fragments. It uses a trick (see the code below) to allow | |
33 | + * the tabs to switch between fragments instead of simple views. | |
34 | + */ | |
35 | +public class FragmentTabs extends FragmentActivity { | |
36 | + TabHost mTabHost; | |
37 | + TabManager mTabManager; | |
38 | + | |
39 | + @Override | |
40 | + protected void onCreate(Bundle savedInstanceState) { | |
41 | + super.onCreate(savedInstanceState); | |
42 | + | |
43 | + setContentView(R.layout.fragment_tabs); | |
44 | + mTabHost = (TabHost)findViewById(android.R.id.tabhost); | |
45 | + mTabHost.setup(); | |
46 | + | |
47 | + mTabManager = new TabManager(this, mTabHost, R.id.realtabcontent); | |
48 | + | |
49 | + mTabManager.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"), | |
50 | + FragmentStackSupport.CountingFragment.class, null); | |
51 | + mTabManager.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"), | |
52 | + LoaderCursorSupport.CursorLoaderListFragment.class, null); | |
53 | + mTabManager.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"), | |
54 | + LoaderCustomSupport.AppListFragment.class, null); | |
55 | + mTabManager.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), | |
56 | + LoaderThrottleSupport.ThrottledLoaderListFragment.class, null); | |
57 | + | |
58 | + if (savedInstanceState != null) { | |
59 | + mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab")); | |
60 | + } | |
61 | + } | |
62 | + | |
63 | + @Override | |
64 | + protected void onSaveInstanceState(Bundle outState) { | |
65 | + super.onSaveInstanceState(outState); | |
66 | + outState.putString("tab", mTabHost.getCurrentTabTag()); | |
67 | + } | |
68 | + | |
69 | + /** | |
70 | + * This is a helper class that implements a generic mechanism for | |
71 | + * associating fragments with the tabs in a tab host. It relies on a | |
72 | + * trick. Normally a tab host has a simple API for supplying a View or | |
73 | + * Intent that each tab will show. This is not sufficient for switching | |
74 | + * between fragments. So instead we make the content part of the tab host | |
75 | + * 0dp high (it is not shown) and the TabManager supplies its own dummy | |
76 | + * view to show as the tab content. It listens to changes in tabs, and takes | |
77 | + * care of switch to the correct fragment shown in a separate content area | |
78 | + * whenever the selected tab changes. | |
79 | + */ | |
80 | + public static class TabManager implements TabHost.OnTabChangeListener { | |
81 | + private final FragmentActivity mActivity; | |
82 | + private final TabHost mTabHost; | |
83 | + private final int mContainerId; | |
84 | + private final HashMap<String, TabInfo> mTabs = new HashMap<String, TabInfo>(); | |
85 | + TabInfo mLastTab; | |
86 | + | |
87 | + static final class TabInfo { | |
88 | + private final String tag; | |
89 | + private final Class<?> clss; | |
90 | + private final Bundle args; | |
91 | + private Fragment fragment; | |
92 | + | |
93 | + TabInfo(String _tag, Class<?> _class, Bundle _args) { | |
94 | + tag = _tag; | |
95 | + clss = _class; | |
96 | + args = _args; | |
97 | + } | |
98 | + } | |
99 | + | |
100 | + static class DummyTabFactory implements TabHost.TabContentFactory { | |
101 | + private final Context mContext; | |
102 | + | |
103 | + public DummyTabFactory(Context context) { | |
104 | + mContext = context; | |
105 | + } | |
106 | + | |
107 | + @Override | |
108 | + public View createTabContent(String tag) { | |
109 | + View v = new View(mContext); | |
110 | + v.setMinimumWidth(0); | |
111 | + v.setMinimumHeight(0); | |
112 | + return v; | |
113 | + } | |
114 | + } | |
115 | + | |
116 | + public TabManager(FragmentActivity activity, TabHost tabHost, int containerId) { | |
117 | + mActivity = activity; | |
118 | + mTabHost = tabHost; | |
119 | + mContainerId = containerId; | |
120 | + mTabHost.setOnTabChangedListener(this); | |
121 | + } | |
122 | + | |
123 | + public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) { | |
124 | + tabSpec.setContent(new DummyTabFactory(mActivity)); | |
125 | + String tag = tabSpec.getTag(); | |
126 | + | |
127 | + TabInfo info = new TabInfo(tag, clss, args); | |
128 | + | |
129 | + // Check to see if we already have a fragment for this tab, probably | |
130 | + // from a previously saved state. If so, deactivate it, because our | |
131 | + // initial state is that a tab isn't shown. | |
132 | + info.fragment = mActivity.getSupportFragmentManager().findFragmentByTag(tag); | |
133 | + if (info.fragment != null && !info.fragment.isDetached()) { | |
134 | + FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction(); | |
135 | + ft.detach(info.fragment); | |
136 | + ft.commit(); | |
137 | + } | |
138 | + | |
139 | + mTabs.put(tag, info); | |
140 | + mTabHost.addTab(tabSpec); | |
141 | + } | |
142 | + | |
143 | + @Override | |
144 | + public void onTabChanged(String tabId) { | |
145 | + TabInfo newTab = mTabs.get(tabId); | |
146 | + if (mLastTab != newTab) { | |
147 | + FragmentTransaction ft = mActivity.getSupportFragmentManager().beginTransaction(); | |
148 | + if (mLastTab != null) { | |
149 | + if (mLastTab.fragment != null) { | |
150 | + ft.detach(mLastTab.fragment); | |
151 | + } | |
152 | + } | |
153 | + if (newTab != null) { | |
154 | + if (newTab.fragment == null) { | |
155 | + newTab.fragment = Fragment.instantiate(mActivity, | |
156 | + newTab.clss.getName(), newTab.args); | |
157 | + ft.add(mContainerId, newTab.fragment, newTab.tag); | |
158 | + } else { | |
159 | + ft.attach(newTab.fragment); | |
160 | + } | |
161 | + } | |
162 | + | |
163 | + mLastTab = newTab; | |
164 | + ft.commit(); | |
165 | + mActivity.getSupportFragmentManager().executePendingTransactions(); | |
166 | + } | |
167 | + } | |
168 | + } | |
169 | +} |
@@ -0,0 +1,165 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2011 The Android Open Source Project | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | +package com.example.android.supportv4.app; | |
17 | + | |
18 | +import java.util.ArrayList; | |
19 | + | |
20 | +import com.example.android.supportv4.R; | |
21 | + | |
22 | +import android.content.Context; | |
23 | +import android.os.Bundle; | |
24 | +import android.support.v4.app.Fragment; | |
25 | +import android.support.v4.app.FragmentActivity; | |
26 | +import android.support.v4.app.FragmentPagerAdapter; | |
27 | +import android.support.v4.view.ViewPager; | |
28 | +import android.view.View; | |
29 | +import android.widget.TabHost; | |
30 | + | |
31 | +/** | |
32 | + * Demonstrates combining a TabHost with a ViewPager to implement a tab UI | |
33 | + * that switches between tabs and also allows the user to perform horizontal | |
34 | + * flicks to move between the tabs. | |
35 | + */ | |
36 | +public class FragmentTabsPager extends FragmentActivity { | |
37 | + TabHost mTabHost; | |
38 | + ViewPager mViewPager; | |
39 | + TabsAdapter mTabsAdapter; | |
40 | + | |
41 | + @Override | |
42 | + protected void onCreate(Bundle savedInstanceState) { | |
43 | + super.onCreate(savedInstanceState); | |
44 | + | |
45 | + setContentView(R.layout.fragment_tabs_pager); | |
46 | + mTabHost = (TabHost)findViewById(android.R.id.tabhost); | |
47 | + mTabHost.setup(); | |
48 | + | |
49 | + mViewPager = (ViewPager)findViewById(R.id.pager); | |
50 | + | |
51 | + mTabsAdapter = new TabsAdapter(this, mTabHost, mViewPager); | |
52 | + | |
53 | + mTabsAdapter.addTab(mTabHost.newTabSpec("simple").setIndicator("Simple"), | |
54 | + FragmentStackSupport.CountingFragment.class, null); | |
55 | + mTabsAdapter.addTab(mTabHost.newTabSpec("contacts").setIndicator("Contacts"), | |
56 | + LoaderCursorSupport.CursorLoaderListFragment.class, null); | |
57 | + mTabsAdapter.addTab(mTabHost.newTabSpec("custom").setIndicator("Custom"), | |
58 | + LoaderCustomSupport.AppListFragment.class, null); | |
59 | + mTabsAdapter.addTab(mTabHost.newTabSpec("throttle").setIndicator("Throttle"), | |
60 | + LoaderThrottleSupport.ThrottledLoaderListFragment.class, null); | |
61 | + | |
62 | + if (savedInstanceState != null) { | |
63 | + mTabHost.setCurrentTabByTag(savedInstanceState.getString("tab")); | |
64 | + } | |
65 | + } | |
66 | + | |
67 | + @Override | |
68 | + protected void onSaveInstanceState(Bundle outState) { | |
69 | + super.onSaveInstanceState(outState); | |
70 | + outState.putString("tab", mTabHost.getCurrentTabTag()); | |
71 | + } | |
72 | + | |
73 | + /** | |
74 | + * This is a helper class that implements the management of tabs and all | |
75 | + * details of connecting a ViewPager with associated TabHost. It relies on a | |
76 | + * trick. Normally a tab host has a simple API for supplying a View or | |
77 | + * Intent that each tab will show. This is not sufficient for switching | |
78 | + * between pages. So instead we make the content part of the tab host | |
79 | + * 0dp high (it is not shown) and the TabsAdapter supplies its own dummy | |
80 | + * view to show as the tab content. It listens to changes in tabs, and takes | |
81 | + * care of switch to the correct paged in the ViewPager whenever the selected | |
82 | + * tab changes. | |
83 | + */ | |
84 | + public static class TabsAdapter extends FragmentPagerAdapter | |
85 | + implements TabHost.OnTabChangeListener, ViewPager.OnPageChangeListener { | |
86 | + private final Context mContext; | |
87 | + private final TabHost mTabHost; | |
88 | + private final ViewPager mViewPager; | |
89 | + private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); | |
90 | + | |
91 | + static final class TabInfo { | |
92 | + private final String tag; | |
93 | + private final Class<?> clss; | |
94 | + private final Bundle args; | |
95 | + | |
96 | + TabInfo(String _tag, Class<?> _class, Bundle _args) { | |
97 | + tag = _tag; | |
98 | + clss = _class; | |
99 | + args = _args; | |
100 | + } | |
101 | + } | |
102 | + | |
103 | + static class DummyTabFactory implements TabHost.TabContentFactory { | |
104 | + private final Context mContext; | |
105 | + | |
106 | + public DummyTabFactory(Context context) { | |
107 | + mContext = context; | |
108 | + } | |
109 | + | |
110 | + @Override | |
111 | + public View createTabContent(String tag) { | |
112 | + View v = new View(mContext); | |
113 | + v.setMinimumWidth(0); | |
114 | + v.setMinimumHeight(0); | |
115 | + return v; | |
116 | + } | |
117 | + } | |
118 | + | |
119 | + public TabsAdapter(FragmentActivity activity, TabHost tabHost, ViewPager pager) { | |
120 | + super(activity.getSupportFragmentManager()); | |
121 | + mContext = activity; | |
122 | + mTabHost = tabHost; | |
123 | + mViewPager = pager; | |
124 | + mTabHost.setOnTabChangedListener(this); | |
125 | + mViewPager.setAdapter(this); | |
126 | + mViewPager.setOnPageChangeListener(this); | |
127 | + } | |
128 | + | |
129 | + public void addTab(TabHost.TabSpec tabSpec, Class<?> clss, Bundle args) { | |
130 | + tabSpec.setContent(new DummyTabFactory(mContext)); | |
131 | + String tag = tabSpec.getTag(); | |
132 | + | |
133 | + TabInfo info = new TabInfo(tag, clss, args); | |
134 | + mTabs.add(info); | |
135 | + mTabHost.addTab(tabSpec); | |
136 | + notifyDataSetChanged(); | |
137 | + } | |
138 | + | |
139 | + @Override | |
140 | + public int getCount() { | |
141 | + return mTabs.size(); | |
142 | + } | |
143 | + | |
144 | + @Override | |
145 | + public Fragment getItem(int position) { | |
146 | + TabInfo info = mTabs.get(position); | |
147 | + return Fragment.instantiate(mContext, info.clss.getName(), info.args); | |
148 | + } | |
149 | + | |
150 | + @Override | |
151 | + public void onTabChanged(String tabId) { | |
152 | + int position = mTabHost.getCurrentTab(); | |
153 | + mViewPager.setCurrentItem(position); | |
154 | + } | |
155 | + | |
156 | + @Override | |
157 | + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { | |
158 | + } | |
159 | + | |
160 | + @Override | |
161 | + public void onPageSelected(int position) { | |
162 | + mTabHost.setCurrentTab(position); | |
163 | + } | |
164 | + } | |
165 | +} |
@@ -81,6 +81,9 @@ public class LoaderCursorSupport extends FragmentActivity { | ||
81 | 81 | new int[] { android.R.id.text1, android.R.id.text2 }, 0); |
82 | 82 | setListAdapter(mAdapter); |
83 | 83 | |
84 | + // Start out with a progress indicator. | |
85 | + setListShown(false); | |
86 | + | |
84 | 87 | // Prepare the loader. Either re-connect with an existing one, |
85 | 88 | // or start a new one. |
86 | 89 | getLoaderManager().initLoader(0, null, this); |
@@ -147,6 +150,13 @@ public class LoaderCursorSupport extends FragmentActivity { | ||
147 | 150 | // Swap the new cursor in. (The framework will take care of closing the |
148 | 151 | // old cursor once we return.) |
149 | 152 | mAdapter.swapCursor(data); |
153 | + | |
154 | + // The list should now be shown. | |
155 | + if (isResumed()) { | |
156 | + setListShown(true); | |
157 | + } else { | |
158 | + setListShownNoAnimation(true); | |
159 | + } | |
150 | 160 | } |
151 | 161 | |
152 | 162 | public void onLoaderReset(Loader<Cursor> loader) { |
@@ -0,0 +1,482 @@ | ||
1 | +/* | |
2 | + * Copyright (C) 2010 The Android Open Source Project | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +package com.example.android.supportv4.app; | |
18 | + | |
19 | +import com.example.android.supportv4.R; | |
20 | + | |
21 | +import java.io.File; | |
22 | +import java.text.Collator; | |
23 | +import java.util.ArrayList; | |
24 | +import java.util.Collections; | |
25 | +import java.util.Comparator; | |
26 | +import java.util.List; | |
27 | + | |
28 | +import android.content.BroadcastReceiver; | |
29 | +import android.content.Context; | |
30 | +import android.content.Intent; | |
31 | +import android.content.IntentFilter; | |
32 | +import android.content.pm.ActivityInfo; | |
33 | +import android.content.pm.ApplicationInfo; | |
34 | +import android.content.pm.PackageManager; | |
35 | +import android.content.res.Configuration; | |
36 | +import android.content.res.Resources; | |
37 | +import android.graphics.drawable.Drawable; | |
38 | +import android.os.Bundle; | |
39 | +import android.support.v4.app.FragmentActivity; | |
40 | +import android.support.v4.app.FragmentManager; | |
41 | +import android.support.v4.app.ListFragment; | |
42 | +import android.support.v4.app.LoaderManager; | |
43 | +import android.support.v4.content.AsyncTaskLoader; | |
44 | +import android.support.v4.content.Loader; | |
45 | +import android.text.TextUtils; | |
46 | +import android.util.Log; | |
47 | +import android.view.LayoutInflater; | |
48 | +import android.view.Menu; | |
49 | +import android.view.MenuInflater; | |
50 | +import android.view.MenuItem; | |
51 | +import android.view.View; | |
52 | +import android.view.ViewGroup; | |
53 | +import android.widget.ArrayAdapter; | |
54 | +import android.widget.ImageView; | |
55 | +import android.widget.ListView; | |
56 | +import android.widget.SearchView; | |
57 | +import android.widget.TextView; | |
58 | +import android.widget.SearchView.OnQueryTextListener; | |
59 | + | |
60 | +/** | |
61 | + * Demonstration of the implementation of a custom Loader. | |
62 | + */ | |
63 | +public class LoaderCustomSupport extends FragmentActivity { | |
64 | + | |
65 | + @Override | |
66 | + protected void onCreate(Bundle savedInstanceState) { | |
67 | + super.onCreate(savedInstanceState); | |
68 | + | |
69 | + FragmentManager fm = getSupportFragmentManager(); | |
70 | + | |
71 | + // Create the list fragment and add it as our sole content. | |
72 | + if (fm.findFragmentById(android.R.id.content) == null) { | |
73 | + AppListFragment list = new AppListFragment(); | |
74 | + fm.beginTransaction().add(android.R.id.content, list).commit(); | |
75 | + } | |
76 | + } | |
77 | + | |
78 | +//BEGIN_INCLUDE(loader) | |
79 | + /** | |
80 | + * This class holds the per-item data in our Loader. | |
81 | + */ | |
82 | + public static class AppEntry { | |
83 | + public AppEntry(AppListLoader loader, ApplicationInfo info) { | |
84 | + mLoader = loader; | |
85 | + mInfo = info; | |
86 | + mApkFile = new File(info.sourceDir); | |
87 | + } | |
88 | + | |
89 | + public ApplicationInfo getApplicationInfo() { | |
90 | + return mInfo; | |
91 | + } | |
92 | + | |
93 | + public String getLabel() { | |
94 | + return mLabel; | |
95 | + } | |
96 | + | |
97 | + public Drawable getIcon() { | |
98 | + if (mIcon == null) { | |
99 | + if (mApkFile.exists()) { | |
100 | + mIcon = mInfo.loadIcon(mLoader.mPm); | |
101 | + return mIcon; | |
102 | + } else { | |
103 | + mMounted = false; | |
104 | + } | |
105 | + } else if (!mMounted) { | |
106 | + // If the app wasn't mounted but is now mounted, reload | |
107 | + // its icon. | |
108 | + if (mApkFile.exists()) { | |
109 | + mMounted = true; | |
110 | + mIcon = mInfo.loadIcon(mLoader.mPm); | |
111 | + return mIcon; | |
112 | + } | |
113 | + } else { | |
114 | + return mIcon; | |
115 | + } | |
116 | + | |
117 | + return mLoader.getContext().getResources().getDrawable( | |
118 | + android.R.drawable.sym_def_app_icon); | |
119 | + } | |
120 | + | |
121 | + @Override public String toString() { | |
122 | + return mLabel; | |
123 | + } | |
124 | + | |
125 | + void loadLabel(Context context) { | |
126 | + if (mLabel == null || !mMounted) { | |
127 | + if (!mApkFile.exists()) { | |
128 | + mMounted = false; | |
129 | + mLabel = mInfo.packageName; | |
130 | + } else { | |
131 | + mMounted = true; | |
132 | + CharSequence label = mInfo.loadLabel(context.getPackageManager()); | |
133 | + mLabel = label != null ? label.toString() : mInfo.packageName; | |
134 | + } | |
135 | + } | |
136 | + } | |
137 | + | |
138 | + private final AppListLoader mLoader; | |
139 | + private final ApplicationInfo mInfo; | |
140 | + private final File mApkFile; | |
141 | + private String mLabel; | |
142 | + private Drawable mIcon; | |
143 | + private boolean mMounted; | |
144 | + } | |
145 | + | |
146 | + /** | |
147 | + * Perform alphabetical comparison of application entry objects. | |
148 | + */ | |
149 | + public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { | |
150 | + private final Collator sCollator = Collator.getInstance(); | |
151 | + @Override | |
152 | + public int compare(AppEntry object1, AppEntry object2) { | |
153 | + return sCollator.compare(object1.getLabel(), object2.getLabel()); | |
154 | + } | |
155 | + }; | |
156 | + | |
157 | + /** | |
158 | + * Helper for determining if the configuration has changed in an interesting | |
159 | + * way so we need to rebuild the app list. | |
160 | + */ | |
161 | + public static class InterestingConfigChanges { | |
162 | + final Configuration mLastConfiguration = new Configuration(); | |
163 | + int mLastDensity; | |
164 | + | |
165 | + boolean applyNewConfig(Resources res) { | |
166 | + int configChanges = mLastConfiguration.updateFrom(res.getConfiguration()); | |
167 | + boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi; | |
168 | + if (densityChanged || (configChanges&(ActivityInfo.CONFIG_LOCALE | |
169 | + |ActivityInfo.CONFIG_UI_MODE|ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) { | |
170 | + mLastDensity = res.getDisplayMetrics().densityDpi; | |
171 | + return true; | |
172 | + } | |
173 | + return false; | |
174 | + } | |
175 | + } | |
176 | + | |
177 | + /** | |
178 | + * Helper class to look for interesting changes to the installed apps | |
179 | + * so that the loader can be updated. | |
180 | + */ | |
181 | + public static class PackageIntentReceiver extends BroadcastReceiver { | |
182 | + final AppListLoader mLoader; | |
183 | + | |
184 | + public PackageIntentReceiver(AppListLoader loader) { | |
185 | + mLoader = loader; | |
186 | + IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); | |
187 | + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); | |
188 | + filter.addAction(Intent.ACTION_PACKAGE_CHANGED); | |
189 | + filter.addDataScheme("package"); | |
190 | + mLoader.getContext().registerReceiver(this, filter); | |
191 | + // Register for events related to sdcard installation. | |
192 | + IntentFilter sdFilter = new IntentFilter(); | |
193 | + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); | |
194 | + sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); | |
195 | + mLoader.getContext().registerReceiver(this, sdFilter); | |
196 | + } | |
197 | + | |
198 | + @Override public void onReceive(Context context, Intent intent) { | |
199 | + // Tell the loader about the change. | |
200 | + mLoader.onContentChanged(); | |
201 | + } | |
202 | + } | |
203 | + | |
204 | + /** | |
205 | + * A custom Loader that loads all of the installed applications. | |
206 | + */ | |
207 | + public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> { | |
208 | + final InterestingConfigChanges mLastConfig = new InterestingConfigChanges(); | |
209 | + final PackageManager mPm; | |
210 | + | |
211 | + List<AppEntry> mApps; | |
212 | + PackageIntentReceiver mPackageObserver; | |
213 | + | |
214 | + public AppListLoader(Context context) { | |
215 | + super(context); | |
216 | + | |
217 | + // Retrieve the package manager for later use; note we don't | |
218 | + // use 'context' directly but instead the save global application | |
219 | + // context returned by getContext(). | |
220 | + mPm = getContext().getPackageManager(); | |
221 | + } | |
222 | + | |
223 | + /** | |
224 | + * This is where the bulk of our work is done. This function is | |
225 | + * called in a background thread and should generate a new set of | |
226 | + * data to be published by the loader. | |
227 | + */ | |
228 | + @Override public List<AppEntry> loadInBackground() { | |
229 | + // Retrieve all known applications. | |
230 | + List<ApplicationInfo> apps = mPm.getInstalledApplications( | |
231 | + PackageManager.GET_UNINSTALLED_PACKAGES | | |
232 | + PackageManager.GET_DISABLED_COMPONENTS); | |
233 | + if (apps == null) { | |
234 | + apps = new ArrayList<ApplicationInfo>(); | |
235 | + } | |
236 | + | |
237 | + final Context context = getContext(); | |
238 | + | |
239 | + // Create corresponding array of entries and load their labels. | |
240 | + List<AppEntry> entries = new ArrayList<AppEntry>(apps.size()); | |
241 | + for (int i=0; i<apps.size(); i++) { | |
242 | + AppEntry entry = new AppEntry(this, apps.get(i)); | |
243 | + entry.loadLabel(context); | |
244 | + entries.add(entry); | |
245 | + } | |
246 | + | |
247 | + // Sort the list. | |
248 | + Collections.sort(entries, ALPHA_COMPARATOR); | |
249 | + | |
250 | + // Done! | |
251 | + return entries; | |
252 | + } | |
253 | + | |
254 | + /** | |
255 | + * Called when there is new data to deliver to the client. The | |
256 | + * super class will take care of delivering it; the implementation | |
257 | + * here just adds a little more logic. | |
258 | + */ | |
259 | + @Override public void deliverResult(List<AppEntry> apps) { | |
260 | + if (isReset()) { | |
261 | + // An async query came in while the loader is stopped. We | |
262 | + // don't need the result. | |
263 | + if (apps != null) { | |
264 | + onReleaseResources(apps); | |
265 | + } | |
266 | + } | |
267 | + List<AppEntry> oldApps = apps; | |
268 | + mApps = apps; | |
269 | + | |
270 | + if (isStarted()) { | |
271 | + // If the Loader is currently started, we can immediately | |
272 | + // deliver its results. | |
273 | + super.deliverResult(apps); | |
274 | + } | |
275 | + | |
276 | + // At this point we can release the resources associated with | |
277 | + // 'oldApps' if needed; now that the new result is delivered we | |
278 | + // know that it is no longer in use. | |
279 | + if (oldApps != null) { | |
280 | + onReleaseResources(oldApps); | |
281 | + } | |
282 | + } | |
283 | + | |
284 | + /** | |
285 | + * Handles a request to start the Loader. | |
286 | + */ | |
287 | + @Override protected void onStartLoading() { | |
288 | + if (mApps != null) { | |
289 | + // If we currently have a result available, deliver it | |
290 | + // immediately. | |
291 | + deliverResult(mApps); | |
292 | + } | |
293 | + | |
294 | + // Start watching for changes in the app data. | |
295 | + if (mPackageObserver == null) { | |
296 | + mPackageObserver = new PackageIntentReceiver(this); | |
297 | + } | |
298 | + | |
299 | + // Has something interesting in the configuration changed since we | |
300 | + // last built the app list? | |
301 | + boolean configChange = mLastConfig.applyNewConfig(getContext().getResources()); | |
302 | + | |
303 | + if (takeContentChanged() || mApps == null || configChange) { | |
304 | + // If the data has changed since the last time it was loaded | |
305 | + // or is not currently available, start a load. | |
306 | + forceLoad(); | |
307 | + } | |
308 | + } | |
309 | + | |
310 | + /** | |
311 | + * Handles a request to stop the Loader. | |
312 | + */ | |
313 | + @Override protected void onStopLoading() { | |
314 | + // Attempt to cancel the current load task if possible. | |
315 | + cancelLoad(); | |
316 | + } | |
317 | + | |
318 | + /** | |
319 | + * Handles a request to cancel a load. | |
320 | + */ | |
321 | + @Override public void onCanceled(List<AppEntry> apps) { | |
322 | + super.onCanceled(apps); | |
323 | + | |
324 | + // At this point we can release the resources associated with 'apps' | |
325 | + // if needed. | |
326 | + onReleaseResources(apps); | |
327 | + } | |
328 | + | |
329 | + /** | |
330 | + * Handles a request to completely reset the Loader. | |
331 | + */ | |
332 | + @Override protected void onReset() { | |
333 | + super.onReset(); | |
334 | + | |
335 | + // Ensure the loader is stopped | |
336 | + onStopLoading(); | |
337 | + | |
338 | + // At this point we can release the resources associated with 'apps' | |
339 | + // if needed. | |
340 | + if (mApps != null) { | |
341 | + onReleaseResources(mApps); | |
342 | + mApps = null; | |
343 | + } | |
344 | + | |
345 | + // Stop monitoring for changes. | |
346 | + if (mPackageObserver != null) { | |
347 | + getContext().unregisterReceiver(mPackageObserver); | |
348 | + mPackageObserver = null; | |
349 | + } | |
350 | + } | |
351 | + | |
352 | + /** | |
353 | + * Helper function to take care of releasing resources associated | |
354 | + * with an actively loaded data set. | |
355 | + */ | |
356 | + protected void onReleaseResources(List<AppEntry> apps) { | |
357 | + // For a simple List<> there is nothing to do. For something | |
358 | + // like a Cursor, we would close it here. | |
359 | + } | |
360 | + } | |
361 | +//END_INCLUDE(loader) | |
362 | + | |
363 | +//BEGIN_INCLUDE(fragment) | |
364 | + public static class AppListAdapter extends ArrayAdapter<AppEntry> { | |
365 | + private final LayoutInflater mInflater; | |
366 | + | |
367 | + public AppListAdapter(Context context) { | |
368 | + super(context, android.R.layout.simple_list_item_2); | |
369 | + mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); | |
370 | + } | |
371 | + | |
372 | + public void setData(List<AppEntry> data) { | |
373 | + clear(); | |
374 | + if (data != null) { | |
375 | + addAll(data); | |
376 | + } | |
377 | + } | |
378 | + | |
379 | + /** | |
380 | + * Populate new items in the list. | |
381 | + */ | |
382 | + @Override public View getView(int position, View convertView, ViewGroup parent) { | |
383 | + View view; | |
384 | + | |
385 | + if (convertView == null) { | |
386 | + view = mInflater.inflate(R.layout.list_item_icon_text, parent, false); | |
387 | + } else { | |
388 | + view = convertView; | |
389 | + } | |
390 | + | |
391 | + AppEntry item = getItem(position); | |
392 | + ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon()); | |
393 | + ((TextView)view.findViewById(R.id.text)).setText(item.getLabel()); | |
394 | + | |
395 | + return view; | |
396 | + } | |
397 | + } | |
398 | + | |
399 | + public static class AppListFragment extends ListFragment | |
400 | + implements OnQueryTextListener, LoaderManager.LoaderCallbacks<List<AppEntry>> { | |
401 | + | |
402 | + // This is the Adapter being used to display the list's data. | |
403 | + AppListAdapter mAdapter; | |
404 | + | |
405 | + // If non-null, this is the current filter the user has provided. | |
406 | + String mCurFilter; | |
407 | + | |
408 | + @Override public void onActivityCreated(Bundle savedInstanceState) { | |
409 | + super.onActivityCreated(savedInstanceState); | |
410 | + | |
411 | + // Give some text to display if there is no data. In a real | |
412 | + // application this would come from a resource. | |
413 | + setEmptyText("No applications"); | |
414 | + | |
415 | + // We have a menu item to show in action bar. | |
416 | + setHasOptionsMenu(true); | |
417 | + | |
418 | + // Create an empty adapter we will use to display the loaded data. | |
419 | + mAdapter = new AppListAdapter(getActivity()); | |
420 | + setListAdapter(mAdapter); | |
421 | + | |
422 | + // Start out with a progress indicator. | |
423 | + setListShown(false); | |
424 | + | |
425 | + // Prepare the loader. Either re-connect with an existing one, | |
426 | + // or start a new one. | |
427 | + getLoaderManager().initLoader(0, null, this); | |
428 | + } | |
429 | + | |
430 | + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { | |
431 | + // Place an action bar item for searching. | |
432 | + MenuItem item = menu.add("Search"); | |
433 | + item.setIcon(android.R.drawable.ic_menu_search); | |
434 | + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); | |
435 | + SearchView sv = new SearchView(getActivity()); | |
436 | + sv.setOnQueryTextListener(this); | |
437 | + item.setActionView(sv); | |
438 | + } | |
439 | + | |
440 | + @Override public boolean onQueryTextChange(String newText) { | |
441 | + // Called when the action bar search text has changed. Since this | |
442 | + // is a simple array adapter, we can just have it do the filtering. | |
443 | + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; | |
444 | + mAdapter.getFilter().filter(mCurFilter); | |
445 | + return true; | |
446 | + } | |
447 | + | |
448 | + @Override public boolean onQueryTextSubmit(String query) { | |
449 | + // Don't care about this. | |
450 | + return true; | |
451 | + } | |
452 | + | |
453 | + @Override public void onListItemClick(ListView l, View v, int position, long id) { | |
454 | + // Insert desired behavior here. | |
455 | + Log.i("LoaderCustom", "Item clicked: " + id); | |
456 | + } | |
457 | + | |
458 | + @Override public Loader<List<AppEntry>> onCreateLoader(int id, Bundle args) { | |
459 | + // This is called when a new Loader needs to be created. This | |
460 | + // sample only has one Loader with no arguments, so it is simple. | |
461 | + return new AppListLoader(getActivity()); | |
462 | + } | |
463 | + | |
464 | + @Override public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> data) { | |
465 | + // Set the new data in the adapter. | |
466 | + mAdapter.setData(data); | |
467 | + | |
468 | + // The list should now be shown. | |
469 | + if (isResumed()) { | |
470 | + setListShown(true); | |
471 | + } else { | |
472 | + setListShownNoAnimation(true); | |
473 | + } | |
474 | + } | |
475 | + | |
476 | + @Override public void onLoaderReset(Loader<List<AppEntry>> loader) { | |
477 | + // Clear the data in the adapter. | |
478 | + mAdapter.setData(null); | |
479 | + } | |
480 | + } | |
481 | +//END_INCLUDE(fragment) | |
482 | +} |
@@ -410,6 +410,9 @@ public class LoaderThrottleSupport extends FragmentActivity { | ||
410 | 410 | new int[] { android.R.id.text1 }, 0); |
411 | 411 | setListAdapter(mAdapter); |
412 | 412 | |
413 | + // Start out with a progress indicator. | |
414 | + setListShown(false); | |
415 | + | |
413 | 416 | // Prepare the loader. Either re-connect with an existing one, |
414 | 417 | // or start a new one. |
415 | 418 | getLoaderManager().initLoader(0, null, this); |
@@ -493,6 +496,13 @@ public class LoaderThrottleSupport extends FragmentActivity { | ||
493 | 496 | |
494 | 497 | public void onLoadFinished(Loader<Cursor> loader, Cursor data) { |
495 | 498 | mAdapter.swapCursor(data); |
499 | + | |
500 | + // The list should now be shown. | |
501 | + if (isResumed()) { | |
502 | + setListShown(true); | |
503 | + } else { | |
504 | + setListShownNoAnimation(true); | |
505 | + } | |
496 | 506 | } |
497 | 507 | |
498 | 508 | public void onLoaderReset(Loader<Cursor> loader) { |
@@ -67,6 +67,15 @@ and loaders.</p> | ||
67 | 67 | <dd>Demonstrates creating a stack of Fragment instances similar to the |
68 | 68 | traditional stack of activities.</dd> |
69 | 69 | |
70 | + <dt><a href="FragmentTabs.html">Fragment Tabs</a></dt> | |
71 | + <dd>Demonstrates the use of fragments to implement switching between | |
72 | + tabs in a TabHost.</dd> | |
73 | + | |
74 | + <dt><a href="FragmentTabsPager.html">Fragment Tabs Pager</a></dt> | |
75 | + <dd>Demonstrates the use of fragments to implement switching between | |
76 | + tabs in a TabHost, using a ViewPager to manager the fragments so that | |
77 | + the user can also fling left and right to switch tabs.</dd> | |
78 | + | |
70 | 79 | </dl> |
71 | 80 | |
72 | 81 | <h3 id="LoaderManager">LoaderManager</h3> |
@@ -74,6 +83,10 @@ and loaders.</p> | ||
74 | 83 | <dt><a href="LoaderCursorSupport.html">Loader Cursor</a></dt> |
75 | 84 | <dd>Demonstrates use of LoaderManager to perform a query for a Cursor that |
76 | 85 | populates a ListFragment.</dd> |
86 | + | |
87 | + <dt><a href="LoaderCustomSupport.html">Loader Custom</a></dt> | |
88 | + <dd>Demonstrates implementation and use of a custom Loader class. The | |
89 | + custom class here "loads" the currently installed applications.</dd> | |
77 | 90 | |
78 | 91 | <dt><a href="LoaderThrottleSupport.html">Loader Throttle</a></dt> |
79 | 92 | <dd>Complete end-to-end demonstration of a simple content provider that |