Swipe Views are an efficient way to allow the user to laterally navigate among related data items spread across multiple panes using a simple touch gesture (swipes/flings), making access to data faster and much more enjoyable. Think of the android home screen where you can swipe across multiple sibling screens or the facebook or twitter app with multiple screens (and their respective tabs) where you can just swipe to navigate through them. The entire experience is super interactive and fun. They’re basically equivalent to slideshows/carousels which has sections with/without tabs (or similar controls to navigate) in the web development arena.
Android provides us with the ViewPager
class that allows the user to flip left and right through pages of data. To use it, you’ll have to put the <ViewPager>
element (from the support library) inside your XML layout file that’ll contain multiple child views (which will be the different pages). This is how it’s done:
What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent" />
Once this code is placed in say res/layout/activity_main.xml
, we’ll have to hook the layout to a PagerAdapter
which will populate our ViewPager
with pages. Basically a PagerAdapter
is responsible for creating the content for each page. You’ll most likely want to use one of the following two implementation of PagerAdapter
:
-
FragmentPagerAdapter
– Each page is a fragment and all of them are kept in memory, hence best to use when there’s a fixed small set of screens to be navigated through. If loads of fragments are shoved in using this adapter then it could lead to a laggy user experience. It works even better when the content of the fragments are static than something that constantly changes or gets updated. -
FragmentStatePagerAdapter
– Each page is a fragment which gets destroyed as soon as it’s not visible to the user, i.e., the user navigates to another one. Due to this behaviour it consumes much less memory but then doesn’t perform as well as its counterpart (FragmentPagerAdapter
). Hence, it’s perfect in the cases where the number of pages is high or undetermined.
FragmentPagerAdapter
Let’s see how our Activity class will look like where we’ll get the ViewPager
and hook a FragmentPagerAdapter
to it.
public class MainActivity extends Activity { CustomPagerAdapter mCustomPagerAdapter; ViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // == Setting up the ViewPager == mCustomPagerAdapter = new CustomPagerAdapter(getFragmentManager(), this); mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mCustomPagerAdapter); } }
Here’s our CustomPagerAdapter
which is a FragmentPagerAdapter
implementation:
public class CustomPagerAdapter extends FragmentPagerAdapter { protected Context mContext; public CustomPagerAdapter(FragmentManager fm, Context context) { super(fm); mContext = context; } @Override // This method returns the fragment associated with // the specified position. // // It is called when the Adapter needs a fragment // and it does not exists. public Fragment getItem(int position) { // Create fragment object Fragment fragment = new DemoFragment(); // Attach some data to it that we'll // use to populate our fragment layouts Bundle args = new Bundle(); args.putInt("page_position", position + 1); // Set the arguments on the fragment // that will be fetched in DemoFragment@onCreateView fragment.setArguments(args); return fragment; } @Override public int getCount() { return 3; } }
The getCount()
method specifies how many views to show in the ViewPager
and getItem()
generates the views at the specified positions (passed in as an argument).
What we basically do in the getItem()
method is instantiate our Fragment and pass it some arguments data that’ll get used in the fragment’s (DemoFragment
) onCreateView()
method to populate its XML layout. Here’s how our Fragment class will look like:
public class DemoFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout resource that'll be returned View rootView = inflater.inflate(R.layout.fragment_demo, container, false); // Get the arguments that was supplied when // the fragment was instantiated in the // CustomPagerAdapter Bundle args = getArguments(); ((TextView) rootView.findViewById(R.id.text)).setText("Page " + args.getInt("page_position")); return rootView; } }
The onCreateView()
method is the one that inflates the layout resource and hence instantiates the user interface for that fragment. Here’s how our res/layout/fragment_demo.xml
will look like:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="match_parent" android:layout_height="match_parent" android:text="Page 0" android:id="@+id/text" /> </FrameLayout>
Pretty simple! All you need to do now is test your app and play with the ViewPager
by swiping the different screens/pages in it. They should display “Page 1”, “Page 2” and “Page 3” as texts.
Tip: At times you might have multiple fragments that you’ll want to return in the getItem()
method of the FragmentPagerAdapter
implementation. Then you could do something like this:
switch (position) { case 0: return new FirstFragment(); case 1: return new SecondFragment(); case 2: return new ThirdFragment(); }
… and all of them having different layout resources that’d display different sorts of data like photo gallery, activity feed, list of messages, etc.
FragmentStatePagerAdapter
I think I’ve already explained why this PagerAdapter
is useful and when it should be used. Its main advantage is low memory consumption due to the destruction of fragments as soon as they go off the screen. So as soon as the user navigates to a particular screen the one next to it is generated/rebuilt by calling getItem()
in the adapter implementation. When the number of the pages are undetermined or are too many (for example in a book reader) then this adapter makes more sense than the previous one explained.
In order to understand how to work with it, all you need to do is use the previous example and instead of making the CustomPagerAdapter
extend FragmentPagerAdapter
, make it subclass FragmentStatePagerAdapter
. Then in getItem()
you could add a Log.d()
to see when its called while swiping through the views inside the view pager.
By now you’ll know the differences between FragmentPagerAdapter and FragmentStatePagerAdapter and which one to use when.
Adding Tabs to Action Bar
Along with having our swiping views there has to be something that hints the user that he can swipe through for a much better user experience. Tabs can do a great job for that purpose and in Android we can use the ActionBar
to quickly create them and integrate with our ViewPager
making them work in conjunction. To specify that tabs should be displayed in the action bar we’ll have to set the navigation mode on the action bar object in onCreate()
method like this:
final ActionBar actionBar = getActionBar(); // specify that action bar should display tabs actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
Next, we’ll have to add the tabs, specify their text and add various listeners for them.
// == Setting up the tabs == String[] tabs = { "Tab One", "Tab Two", "Tab Three" }; ActionBar.Tab tab; for (int i = 0; i < tabs.length; i++) { tab = actionBar .newTab() // create new tab .setText(tabs[i]) // set text .setTabListener(tabListener); // set tab listeners // add the tab to the action bar finally actionBar.addTab(tab); }
Pretty straightforward piece of code. Notice the part where we set the tab listener using setTabListener(tabListener)
. It’s time to define the tabListener
object now, whose methods will be called whenever the user changes tabs.
ActionBar.TabListener tabListener = new ActionBar.TabListener() { @Override public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { // show the tab int position = tab.getPosition(); mViewPager.setCurrentItem(position); } @Override public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { // hide the tab, we don't need this in this example } @Override public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { // when tab is reselected, we don't really need this } };
Super simple and easy! When a tab is selected, we show the respective screen in our ViewPager
by calling its setCurrentItem()
method.
Instead of creating this tabListener
object inside the onCreate()
method we could have also made our Activity class implement ActionBar.TabListener
and then overriden the methods as our activity class’s methods. The only change then required would be to modify setTabListener(tabListener)
to setTabListener(this)
.
Likewise, when the user changes the screen inside the ViewPager
with a swipe touch gesture, we should select the corresponding tab. We can do this with the help of ViewPager.OnPageChangeListener
interface or the ViewPager.SimpleOnPageChangeListener
class. We’ll use the latter in this case so that we don’t have to override all the methods from the interface since when we won’t be using most of them.
// When swiping between different sections, select the corresponding tab mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { actionBar.setSelectedNavigationItem(position); } });
So the entire onCreate()
method will somewhat look like this:
CustomPagerAdapter mCustomPagerAdapter; ViewPager mViewPager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Setup the action bar for tabs final ActionBar actionBar = getActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); actionBar.setTitle("App Title"); // If you want to set an app title // == Setting up the ViewPager == mCustomPagerAdapter = new CustomPagerAdapter(getFragmentManager(), this); mViewPager = (ViewPager) findViewById(R.id.pager); mViewPager.setAdapter(mCustomPagerAdapter); // When swiping between different sections, select the corresponding tab mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { actionBar.setSelectedNavigationItem(position); } }); // == Setting up the tabs == ActionBar.TabListener tabListener = new ActionBar.TabListener() { @Override public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { // show the tab int position = tab.getPosition(); mViewPager.setCurrentItem(position); } @Override public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { // hide the tab, we don't need this in this example } @Override public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { // when tab is reselected, we don't really need this } }; String[] tabs = { "Tab One", "Tab Two", "Tab Three" }; ActionBar.Tab tab; for (int i = 0; i < tabs.length; i++) { tab = actionBar .newTab() // create new tab .setText(tabs[i]) // set text .setTabListener(tabListener); // set tab listener actionBar.addTab(tab); } }
If for some reason you don’t want to include tabs in the action bar but have it in the ViewPager
itself as scrollable tabs then we can make use of PagerTitleStrip
or PagerTabStrip
that we’ll cover in another tutorial.
Conclusion
So we covered quite a bit in this tutorial. We learnt how to create a swipeable UI element with multiple screens using ViewPager
and injecting the screens/pages into it through PagerAdapter
implementations like FragmentPagerAdapter
and FragmentStatePagerAdapter
. We also saw how we can integrate tabs to the entire Activity in the action bar that works in correspondence with the ViewPager
.
It’s quite interesting to note that ViewPager
is not only restricted to be used with fragments but can be used with any other View like ImageView
to create an image based slideshow. In that case the custom pager adapter implementation has to be of PagerAdapter
directly. We’ll see how to do this in another post.
If you have any questions or want to discuss something regarding this topic, then feel free to comment below.
Very good tutorial!
Here is a little mistake:
In the CustomPagerAdapter codeblock on line 5, the “HomePagerAdapter” should be “CustomPagerAdapter”.
Thanks for pointing that out! I’ve fixed it.
Where to download your project? The past 2 hours I copy&pasted your code into my project but it’s not working. A sample project would be helpful.
Thanks
Hi Samuel,
I haven’t put up the project anywhere like github. I think based on the instructions if you just copy paste the code it should definitely work. If it’s not working look at the error and tell me about it. We’ll try to fix it together! 🙂
Will it be better to use getSuportActionBar() instead of getActionBar() because sometimes action bar can be null?
Depends upon the API level version you’re targeting. With API level 11 and higher you can get the
ActionBar
by just usinggetActionBar()
. For lower API levels, yes you’ll need to use the support library version, i.e.,getSupportActionBar()
and also extendActionBarActivity
, not justActivity
.Actually it also depends upon the theme in use. For example what I just said works perfectly with the Holo themes but if you’re using the AppCompat themes (for instance to support Material design in API level < 21) then ActionBarActivity has to be used along with
getSupportActionBar()
to show up an Action Bar in your UI. Although you can bypass this usage by modifying your style like this (styles.xml
):<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowNoTitle">false</item>
<item name="android:windowActionBar">true</item>
</style>
But then that just seems like a hack and is more work.
You can add Demo Video & Images (Snaps) for better understanding of users..
It will help your blog to get more visitors…