Monday, September 12, 2016

Using PagerTabStrip with ViewPager

A simple Android example of implementing tabs using PagerTabStrip with ViewPager.


The release of Android API level 21 (Lollipop) deprecated a number of APIs — in particular, anything to do with ActionBar tabs.

This means a common way of implementing tabbed navigation is now discouraged, but things have not been particularly clear as to what replaces these deprecated APIs. Of course, deprecated doesn’t mean unusable. Deprecated though they are, ActionBar tabs will still work.

But anyone starting a new long term Android project might reasonably baulk at firing up the Android Studio "New Project" code wizard only to be greeted with a whole lot of deprecation warnings in the generated code for anything with tabs. Even if the resulting code does actually work. For now.

So what are our options if we want tabs, but don’t want to use any of these deprecated APIs?

Roll your own tabs implementation (Obviously not helpful to any developers dealing with onerous deadlines.)
Use a third party implementation.
Incorporate or adapt the implementation of tabs in either the Android SDK Sliding Tabs sample or the Google iosched app.
Use the venerable PagerTabStrip.
Use TabLayout from Google’s newish Android Design Support Library.
I’ll explore the last two options in this tutorial. In this post I’ll show a simple implementation of tabs using PagerTabStrip. In the next post I’ll look at TabLayout.

PagerTabStrip works in conjunction with ViewPager, and plays well with AppCompatActivity (although the results aren’t exactly Material Design friendly).

For this example project we’ll use AppCompatActivity as our Activity class, and Theme.AppCompat.Light.DarkActionBar as the application theme.

We’ll start with the layout for our activity (activity_pager_tab_strip.xml), which in this case will just be a ViewPager containing a PagerTabStrip:
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="net.voidynullness.android.tabitytabs.PagerTabStripActivity">
    <android.support.v4.view.PagerTabStrip
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="top"/>
</android.support.v4.view.ViewPager>

The layout_gravity is set to "top" so that the tabs are displayed at the top of the ViewPager.

The activity code (PagerTabStripActivity.java) is fairly straightforward:
package net.voidynullness.android.tabitytabs;
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
public class PagerTabStripActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pager_tab_strip);
        TabsPagerAdapter adapter = new TabsPagerAdapter(getSupportFragmentManager());
        ViewPager pager = (ViewPager) findViewById(R.id.pager);
        pager.setAdapter(adapter);
    }
}
There’s not much going on in the Activity — it’s basically just configuring the ViewPager by giving it a suitable adapter object. In this case the adapter class is TabsPagerAdapter, which is our subclass of FragmentPagerAdapter (see below). Notice that we need to pass a FragmentManager to the adapter, and here we’re passing the support library fragment manager, since we’re using AppCompatActivity.

TabsPagerAdapter is a subclass of FragmentPagerAdapter, and it’s here that the fragments corresponding to each tab page are instantiated, and tab title text is defined (TabsPagerAdapter.java):
package net.voidynullness.android.tabitytabs;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

public class TabsPagerAdapter extends FragmentPagerAdapter {
    public TabsPagerAdapter(FragmentManager fm) {
        super(fm);
    }
    @Override
    public Fragment getItem(int position) {
        return PageFragment.newInstance(position + 1);
    }
    @Override
    public int getCount() {
        return 3;
    }
    @Override
    public CharSequence getPageTitle(int position) {
        return "TAB " + (position + 1);
    }
}
It’s about as minimal as a FragmentPagerAdapter can be. At the very least, subclasses of FragmentPagerAdapter must implement getItem() and getCount().

getItem() is expected to return the fragment for the specified position. Internally, FragmentPagerAdapter calls getItem() to instantiate fragments, and uses the FragmentManager passed into the constructor to manage these fragments.

getCount() should return the total number of fragments.

We’re also overriding getPageTitle(), which is used to set the text for each tab position.

So we have our ViewPager and FragmentPagerAdapter, now we need a fragment to actually display something. We’ll call our simple placeholder fragment class PageFragment. It will just display a text string on the screen to indicate which fragment "page" is currently selected (PageFragment.java):
package net.voidynullness.android.tabitytabs;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class PageFragment extends Fragment {
    private static final String ARG_PAGE_NUMBER = "page_number";
    public PageFragment() {
    }
    public static PageFragment newInstance(int page) {
        PageFragment fragment = new PageFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_PAGE_NUMBER, page);
        fragment.setArguments(args);
        return fragment;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_page_layout, container, false);
        TextView txt = (TextView) rootView.findViewById(R.id.page_number_label);
        int page = getArguments().getInt(ARG_PAGE_NUMBER, -1);
        txt.setText(String.format("Page %d", page));
        return rootView;
    }
}
The newInstance() idiom of a static factory method is used to create and initialise new fragments.

As shown above, our fragment adapter (TabsPagerAdapter) creates PageFragment fragments by calling the static newInstance() method and passing in the page/tab number of that particular instance. Inside newInstance(), the page number is saved as an argument bundle.

In onCreateView(), the TextView contents are updated to display the page number, which is retrieved from the arguments.

The corresponding layout file for our PageFragment class simply contains a single TextView to display the page number (fragment_page_layout.xml):
<RelativeLayout 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" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context="net.voidynullness.android.tabitytabs.PageFragment">
    <TextView android:id="@+id/page_number_label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textAppearance="@android:style/TextAppearance.Large"
        android:gravity="center"
        android:layout_centerVertical="true" />
</RelativeLayout>

No comments:

Post a Comment