Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 1 | page.title=Creating Swipe Views with Tabs |
Scott Main | fb56b4f | 2013-07-31 10:58:48 -0700 | [diff] [blame] | 2 | page.tags="viewpager","horizontal","paging","swipe view","tabs" |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 3 | |
| 4 | trainingnavtop=true |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 5 | |
| 6 | @jd:body |
| 7 | |
| 8 | <div id="tb-wrapper"> |
| 9 | <div id="tb"> |
| 10 | |
| 11 | <h2>This lesson teaches you to</h2> |
| 12 | <ol> |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 13 | <li><a href="#horizontal-paging">Implement Swipe Views</a></li> |
| 14 | <li><a href="#tabs">Add Tabs to the Action Bar</a></li> |
| 15 | <li><a href="#swipe-tabs">Change Tabs with Swipe Views</a></li> |
| 16 | <li><a href="#PagerTitleStrip">Use a Title Strip Instead of Tabs</a></li> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 17 | </ol> |
| 18 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 19 | |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 20 | <h2>You should also read</h2> |
| 21 | <ul> |
| 22 | <li><a href="{@docRoot}training/design-navigation/descendant-lateral.html">Providing Descendant and Lateral Navigation</a></li> |
| 23 | <li><a href="{@docRoot}design/building-blocks/tabs.html">Android Design: Tabs</a></li> |
| 24 | <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li> |
| 25 | </ul> |
| 26 | |
| 27 | <h2>Try it out</h2> |
| 28 | |
| 29 | <div class="download-box"> |
| 30 | <a href="http://developer.android.com/shareables/training/EffectiveNavigation.zip" |
| 31 | class="button">Download the sample app</a> |
| 32 | <p class="filename">EffectiveNavigation.zip</p> |
| 33 | </div> |
| 34 | |
| 35 | </div> |
| 36 | </div> |
| 37 | |
| 38 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 39 | <p>Swipe views provide lateral navigation between sibling screens such as tabs with |
| 40 | a horizontal finger gesture (a pattern sometimes known as horizontal paging). This lesson teaches |
| 41 | you how to create a tab layout with swipe views for switching between tabs, or how to show |
| 42 | a title strip instead of tabs.</p> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 43 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 44 | <div class="note design"> |
| 45 | <p><strong>Swipe View Design</strong></p> |
| 46 | <p>Before implementing these features, you should understand the concepts and recommendations |
| 47 | as described in <a href="{@docRoot}training/design-navigation/descendant-lateral.html">Designing |
| 48 | Effective Navigation</a> and the <a href="{@docRoot}design/patterns/swipe-views.html">Swipe |
| 49 | Views</a> design guide.</p> |
| 50 | </div> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 51 | |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 52 | |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 53 | |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 54 | |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 55 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 56 | <h2 id="horizontal-paging">Implement Swipe Views</h2> |
| 57 | |
| 58 | <p>You can create swipe views in your app using the {@link android.support.v4.view.ViewPager} |
| 59 | widget, available in the |
Scott Main | 4e2c9dc | 2013-07-23 19:35:17 -0700 | [diff] [blame] | 60 | <a href="{@docRoot}tools/support-library/index.html">Support Library</a>. The |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 61 | {@link android.support.v4.view.ViewPager} is a layout widget in which each child view is |
| 62 | a separate page (a separate tab) in the layout.</p> |
| 63 | |
| 64 | <p>To set up your layout with {@link android.support.v4.view.ViewPager}, add a |
| 65 | {@code <ViewPager>} element to your XML layout. For example, if each page in the swipe view |
| 66 | should consume the entire layout, then your layout looks like this:</p> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 67 | |
| 68 | <pre> |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 69 | <?xml version="1.0" encoding="utf-8"?> |
| 70 | <android.support.v4.view.ViewPager |
| 71 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 72 | android:id="@+id/pager" |
| 73 | android:layout_width="match_parent" |
| 74 | android:layout_height="match_parent" /> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 75 | </pre> |
| 76 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 77 | <p>To insert child views that represent each page, |
| 78 | you need to hook this layout to a {@link android.support.v4.view.PagerAdapter}. |
| 79 | There are two kinds of adapter you can use:</p> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 80 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 81 | <dl> |
| 82 | <dt>{@link android.support.v4.app.FragmentPagerAdapter}</dt> |
| 83 | <dd>This is best when navigating between sibling screens representing a fixed, small |
| 84 | number of pages.</dd> |
| 85 | <dt>{@link android.support.v4.app.FragmentStatePagerAdapter}</dt> |
| 86 | <dd>This is best for paging across a collection of objects |
| 87 | for which the number of pages is undetermined. It destroys |
| 88 | fragments as the user navigates to other pages, minimizing memory usage.</dd> |
| 89 | </dl> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 90 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 91 | <p>For example, here's how you might use {@link android.support.v4.app.FragmentStatePagerAdapter} |
| 92 | to swipe across a collection of {@link android.app.Fragment} objects:</p> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 93 | |
| 94 | <pre> |
| 95 | public class CollectionDemoActivity extends FragmentActivity { |
| 96 | // When requested, this adapter returns a DemoObjectFragment, |
| 97 | // representing an object in the collection. |
| 98 | DemoCollectionPagerAdapter mDemoCollectionPagerAdapter; |
| 99 | ViewPager mViewPager; |
| 100 | |
| 101 | public void onCreate(Bundle savedInstanceState) { |
Scott Main | a15afd2 | 2013-03-12 09:25:22 -0700 | [diff] [blame] | 102 | super.onCreate(savedInstanceState); |
| 103 | setContentView(R.layout.activity_collection_demo); |
| 104 | |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 105 | // ViewPager and its adapters use support library |
| 106 | // fragments, so use getSupportFragmentManager. |
| 107 | mDemoCollectionPagerAdapter = |
| 108 | new DemoCollectionPagerAdapter( |
| 109 | getSupportFragmentManager()); |
| 110 | mViewPager = (ViewPager) findViewById(R.id.pager); |
| 111 | mViewPager.setAdapter(mDemoCollectionPagerAdapter); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // Since this is an object collection, use a FragmentStatePagerAdapter, |
| 116 | // and NOT a FragmentPagerAdapter. |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 117 | public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter { |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 118 | public DemoCollectionPagerAdapter(FragmentManager fm) { |
| 119 | super(fm); |
| 120 | } |
| 121 | |
| 122 | {@literal @}Override |
| 123 | public Fragment getItem(int i) { |
| 124 | Fragment fragment = new DemoObjectFragment(); |
| 125 | Bundle args = new Bundle(); |
| 126 | // Our object is just an integer :-P |
| 127 | args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1); |
| 128 | fragment.setArguments(args); |
| 129 | return fragment; |
| 130 | } |
| 131 | |
| 132 | {@literal @}Override |
| 133 | public int getCount() { |
| 134 | return 100; |
| 135 | } |
| 136 | |
| 137 | {@literal @}Override |
| 138 | public CharSequence getPageTitle(int position) { |
| 139 | return "OBJECT " + (position + 1); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | // Instances of this class are fragments representing a single |
| 144 | // object in our collection. |
| 145 | public static class DemoObjectFragment extends Fragment { |
| 146 | public static final String ARG_OBJECT = "object"; |
| 147 | |
| 148 | {@literal @}Override |
| 149 | public View onCreateView(LayoutInflater inflater, |
| 150 | ViewGroup container, Bundle savedInstanceState) { |
| 151 | // The last two arguments ensure LayoutParams are inflated |
| 152 | // properly. |
| 153 | View rootView = inflater.inflate( |
| 154 | R.layout.fragment_collection_object, container, false); |
| 155 | Bundle args = getArguments(); |
| 156 | ((TextView) rootView.findViewById(android.R.id.text1)).setText( |
| 157 | Integer.toString(args.getInt(ARG_OBJECT))); |
| 158 | return rootView; |
| 159 | } |
| 160 | } |
| 161 | </pre> |
| 162 | |
Scott Main | 7c84055 | 2013-03-26 18:53:01 -0700 | [diff] [blame] | 163 | <p>This example shows only the code necessary to create the swipe views. The following |
| 164 | sections show how you can add tabs to help facilitate navigation between pages.</p> |
| 165 | |
| 166 | |
| 167 | <h2 id="tabs">Add Tabs to the Action Bar</h2> |
| 168 | |
| 169 | <p>Action bar |
| 170 | <a href="{@docRoot}design/building-blocks/tabs.html">tabs</a> offer users a familiar interface |
| 171 | for navigating between and identifying sibling screens in your app.</p> |
| 172 | |
| 173 | <p>To create tabs using {@link android.app.ActionBar}, you need to enable |
| 174 | {@link android.app.ActionBar#NAVIGATION_MODE_TABS}, then create several instances of |
| 175 | {@link android.app.ActionBar.Tab} and supply an implementation of |
| 176 | the {@link android.app.ActionBar.TabListener} interface for each one. |
| 177 | For example, in your activity's {@link |
| 178 | android.app.Activity#onCreate onCreate()} method, you can use code similar to this:</p> |
| 179 | |
| 180 | <pre> |
| 181 | {@literal @}Override |
| 182 | public void onCreate(Bundle savedInstanceState) { |
| 183 | final ActionBar actionBar = getActionBar(); |
| 184 | ... |
| 185 | |
| 186 | // Specify that tabs should be displayed in the action bar. |
| 187 | actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); |
| 188 | |
| 189 | // Create a tab listener that is called when the user changes tabs. |
| 190 | ActionBar.TabListener tabListener = new ActionBar.TabListener() { |
| 191 | public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { |
| 192 | // show the given tab |
| 193 | } |
| 194 | |
| 195 | public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { |
| 196 | // hide the given tab |
| 197 | } |
| 198 | |
| 199 | public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { |
| 200 | // probably ignore this event |
| 201 | } |
| 202 | }; |
| 203 | |
| 204 | // Add 3 tabs, specifying the tab's text and TabListener |
| 205 | for (int i = 0; i < 3; i++) { |
| 206 | actionBar.addTab( |
| 207 | actionBar.newTab() |
| 208 | .setText("Tab " + (i + 1)) |
| 209 | .setTabListener(tabListener)); |
| 210 | } |
| 211 | } |
| 212 | </pre> |
| 213 | |
| 214 | <p>How you handle the {@link android.app.ActionBar.TabListener} callbacks to change tabs |
| 215 | depends on how you've constructed your content. But if you're using fragments for each tab with |
| 216 | {@link android.support.v4.view.ViewPager} as shown above, the following |
| 217 | section shows how to switch between pages when the user selects a tab and also update the selected |
| 218 | tab when the user swipes between pages.</p> |
| 219 | |
| 220 | |
| 221 | <h2 id="swipe-tabs">Change Tabs with Swipe Views</h2> |
| 222 | |
| 223 | <p>To switch between pages in a {@link android.support.v4.view.ViewPager} when the user selects |
| 224 | a tab, implement your {@link android.app.ActionBar.TabListener} to select the appropriate page |
| 225 | by calling {@link android.support.v4.view.ViewPager#setCurrentItem setCurrentItem()} on your |
| 226 | {@link android.support.v4.view.ViewPager}:</p> |
| 227 | |
| 228 | <pre> |
| 229 | {@literal @}Override |
| 230 | public void onCreate(Bundle savedInstanceState) { |
| 231 | ... |
| 232 | |
| 233 | // Create a tab listener that is called when the user changes tabs. |
| 234 | ActionBar.TabListener tabListener = new ActionBar.TabListener() { |
| 235 | public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { |
| 236 | // When the tab is selected, switch to the |
| 237 | // corresponding page in the ViewPager. |
| 238 | mViewPager.setCurrentItem(tab.getPosition()); |
| 239 | } |
| 240 | ... |
| 241 | }; |
| 242 | } |
| 243 | </pre> |
| 244 | |
| 245 | <p>Likewise, you should select the corresponding tab when the user swipes between pages with a |
| 246 | touch gesture. You can set up this behavior by implementing the |
| 247 | {@link android.support.v4.view.ViewPager.OnPageChangeListener} interface to change |
| 248 | the current tab each time the page changes. For example:</p> |
| 249 | |
| 250 | <pre> |
| 251 | {@literal @}Override |
| 252 | public void onCreate(Bundle savedInstanceState) { |
| 253 | ... |
| 254 | |
| 255 | mViewPager = (ViewPager) findViewById(R.id.pager); |
| 256 | mViewPager.setOnPageChangeListener( |
| 257 | new ViewPager.SimpleOnPageChangeListener() { |
| 258 | {@literal @}Override |
| 259 | public void onPageSelected(int position) { |
| 260 | // When swiping between pages, select the |
| 261 | // corresponding tab. |
| 262 | getActionBar().setSelectedNavigationItem(position); |
| 263 | } |
| 264 | }); |
| 265 | ... |
| 266 | } |
| 267 | </pre> |
| 268 | |
| 269 | |
| 270 | |
| 271 | <h2 id="PagerTitleStrip">Use a Title Strip Instead of Tabs</h2> |
| 272 | |
| 273 | <p>If you don't want to include action bar tabs and prefer to provide |
| 274 | <a href="{@docRoot}design/building-blocks/tabs.html#scrollable">scrollable tabs</a> for a shorter |
| 275 | visual profile, you can use {@link android.support.v4.view.PagerTitleStrip} with |
| 276 | your swipe views.</p> |
| 277 | |
| 278 | <p>Below is an example layout XML file for an |
| 279 | activity whose entire contents are a {@link android.support.v4.view.ViewPager} and a top-aligned |
| 280 | {@link android.support.v4.view.PagerTitleStrip} inside it. Individual pages (provided by the |
| 281 | adapter) occupy the remaining space inside the {@link android.support.v4.view.ViewPager}.</p> |
Roman Nurik | 19266f7 | 2012-03-12 21:48:47 -0700 | [diff] [blame] | 282 | |
| 283 | <pre> |
| 284 | <android.support.v4.view.ViewPager |
| 285 | xmlns:android="http://schemas.android.com/apk/res/android" |
| 286 | android:id="@+id/pager" |
| 287 | android:layout_width="match_parent" |
| 288 | android:layout_height="match_parent"> |
| 289 | |
| 290 | <android.support.v4.view.PagerTitleStrip |
| 291 | android:id="@+id/pager_title_strip" |
| 292 | android:layout_width="match_parent" |
| 293 | android:layout_height="wrap_content" |
| 294 | android:layout_gravity="top" |
| 295 | android:background="#33b5e5" |
| 296 | android:textColor="#fff" |
| 297 | android:paddingTop="4dp" |
| 298 | android:paddingBottom="4dp" /> |
| 299 | |
| 300 | </android.support.v4.view.ViewPager> |
Scott Main | 0c39152 | 2013-05-15 14:38:16 -0700 | [diff] [blame] | 301 | </pre> |