Android – Build tab layout with FragmentPagerAdapter, ViewPager and ActionBar

Tab control is a popular component in desktop application. I think, most of desktop application contains at least a tab control in one way or another. In Android environment, we also have a tab control named “TabHost”, but since Android 3.0, it was replaced by ActionBar. In this post I would like to show how we can make a “fake” tab layout with ActionBar so that we can give user the same experience like TabHost. This control is built up by a combination of ActionBar and FragmentPagerAdapter. The labels will be located at the top, switching between tabs will load the content of the tabs. Moreover thanks to touch screen of smartphone, user can also swipe to navigate between tabs. The final result of our “fake” tab layout looks like following image (Please note that we aren’t going to make a “tab control” but a “tab layout”, they are different concepts)

Android tab layout

As you can see, I displayed the available categories of this website as labels of tabs such as programming, computer security, tutorial and everything else. Under each tab, the app will load and show the articles categorized to that category. It works exactly like a RSS reader but this demo app works only with the feeds from this website.

1. Prerequisites

Before getting started, I would like to list some prerequisites or notices so that you won’t get shocked when reading the source code later.

First, as I mentioned many times in previous post before, I use roboguice for Dependency Injection. Please read this post Android, Dependency Injection (IOC) with roboguice and MVVM (Model-View-ViewModel) pattern to understand how it works.

Second, as I said above, we will combine FragmentPagerAdapter and ActionBar to make a tab layout. For developers who use Intellij IDEA as his IDE like me, remember to make reference to android-support-v4.jar (found at %USERPROFILE%\android-sdks\extras\android\support\v4) so that you will have FragmentActivity/Fragment available for reference later.

Third, it’s now really cold outside, winter already came , a cup of tea is always good idea to keep us lucid. :).

2. UI Layer

In this part, we’ll focus only on how to build up the user interface. I only explain the code for building up the tab layout, no code for business logic right now.
Android tab layout

In the picture of demo tab layout above, the tab layout contains 2 components: ActionBar and ViewPager. The ActionBar is framed with blue and ViewPager is framed with yellow. While the ViewPager can be inserted in resource layout file, the ActionBar can only be initialized with code.

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" android:layout_width="fill_parent" android:id="@+id/viewPager">
</android.support.v4.view.ViewPager>
public class MainActivity extends RoboFragmentActivity {
    
    private ActionBar actionBar;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        initializeComponents();
    }

    private void initializeComponents() {
		...
        actionBar=getActionBar();
        actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);       
	for(int index=0;index < tabFragmentPagerAdapter.getCount();index++)
        {
            actionBar.addTab(actionBar.newTab().setText(tabFragmentPagerAdapter.getPageTitle(index)).setTabListener(MainActivityTabListener));
        }
    }
}

Setting the navigation mode of action bar on NAVIGATION_MODE_TABS does the trick. It helps us to create a navigation menu like a tab control. When the ActionBar is loaded, he will check how many pages are defined in our custom TabFragmentPagerAdapter, make a loop through the list of pages and create for each page one tab and register tab changed event handler for each one.

ActionBar.TabListener MainActivityTabListener = new ActionBar.TabListener() {
	@Override
	public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) {
		//To change body of implemented methods use File | Settings | File Templates.
		viewPager.setCurrentItem(tab.getPosition());
	}

	...
};

This event handler will be triggered when user changes tab by clicking on tab label. In the event handler function, we’ll set the appropriate content of tab by setting the current item of viewPager. The viewPager is intialized in initializeComponents() function.

private void initializeComponents() {
	tabFragmentPagerAdapter = new TabFragmentPagerAdapter(getSupportFragmentManager());
	viewPager.setAdapter(tabFragmentPagerAdapter);
	viewPager.setOnPageChangeListener(ViewPagerOnPageChangeListener);
	...
}

We have many tabs, each tab has its own content. That means our viewPager must be able to handle or provide many contents according to selected tab. That is dynamic. To support dynamic content in Android, there is no better way than using adapter. So for ViewPager, we can create a custom adapter of FragmentPagerAdapter, define how our custom adapter works and customize the content as we want.
In our demo, I define a custom adapter TabFragmentPagerAdapter (extending FragmentPagerAdapter) to “supply” the content for ViewPager depending on selected tab.

public class TabFragmentPagerAdapter  extends FragmentPagerAdapter{

    String[] tabs = {"Programming", "Computer security","Tutorial","Everything Else"};
    public TabFragmentPagerAdapter(FragmentManager fm) {
        super(fm);    //To change body of overridden methods use File | Settings | File Templates.
    }

    @Override
    public Fragment getItem(int index) {
        switch (index)
        {
            case 0:
                return new ProgrammingFragment();
            case 1:
                return new ComputerSecurityFragment();
            case 2:
                return new TutorialFragment();
            case 3:
                return new EverythingElseFragment();
        }
        return null;
    }

    @Override
    public int getCount() {
        return tabs.length;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return tabs[position];    //To change body of overridden methods use File | Settings | File Templates.
    }
}

As you can see, the custom adapter is really simple. I just define an array of tabs I would like to have, override some functions for using with ActionBar (the first code listing above) and define which fragment should be loaded for selected tab basing on its position. As in the code listing above, the first tab is Programming category, the next one is Computer Security, then Tutorial and the last is Everything Else.

Now we have already a tab layout to use. When you run your app, you’ll get a tab layout in main activity. When you click on the labels, you’ll get the content of that tab. However because we have nothing to show in our fragment, we won’t see anything in content area. The content should be filled in business logic layer.

3. Bussiness Layer

In previous section, we already built up the tab layout with ActionBar and ViewPager. In this part, I’ll show you how I fill the content of the fragments. As I mentioned at the beginning of the post, the demo app works like RSS reader. He will read the articles of this website according to the category and show them in app. Therefore the logic of our 4 fragments – ProgrammingFragment, ComputerSecurityFragment, TutorialFragment, EverythingElseFragment – are quite same, only the content are different.

So it’s reasonable to have one base class of fragment and the 4 fragment derive from it like code listing below

public class ProgrammingFragment extends HintDeskFragment {
    public ProgrammingFragment() {
        super(HintDeskCategory.Programming);
    }
}

public class ComputerSecurityFragment extends HintDeskFragment {
    public ComputerSecurityFragment()  {
        super(HintDeskCategory.ComputerSecurity);
    }
}

public class EverythingElseFragment extends HintDeskFragment
{
    public EverythingElseFragment() {
        super(HintDeskCategory.EverythingElse);
    }
}

public class TutorialFragment extends HintDeskFragment {
    public TutorialFragment() {
        super(HintDeskCategory.Tutorial);
    }
}

For example, if ProgrammingFragment is being called, an instance of HintDeskFragment will be initialized with parameter of enum value “Programming”. With this enum value, HintDeskFragment will decide which content from the website he should load and show it to user

import javax.inject.Inject;
public class HintDeskFragment extends RoboListFragment {

    private HintDeskCategory hintDeskCategory;
    private ListView listView;
    @Inject     private IRssReader rssReader;
    private ListViewRssItemAdapter adapter;
    private List<RssItem> rssItemList;

    public HintDeskFragment(HintDeskCategory hintDeskCategory) {
        this.hintDeskCategory = hintDeskCategory;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.rssitems,container,false );
   }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);    //To change body of overridden methods use File | Settings | File Templates.
        initializeComponents();
        String url = ConstantValues.RssMainUrl;
        switch (hintDeskCategory)
        {
            case Programming:
                url = String.format(url,"programming");
                break;
            case EverythingElse:
                url = String.format(url,"everything-else");
                break;
            case Tutorial:
                url=String.format(url,"tutorial");
                break;
            case ComputerSecurity:
                url=String.format(url,"computer-security");
                break;
        }

        new LoadRssItems().execute(url);
    }

    private void initializeComponents() {
        listView = getListView();
        switch (hintDeskCategory)
        {
            case ComputerSecurity:
                listView.setBackgroundColor(Color.parseColor("#009c00"));
                break;
            case EverythingElse:
                listView.setBackgroundColor(Color.parseColor("#a00098"));
                break;
            case Programming:
                listView.setBackgroundColor(Color.parseColor("#da542c"));
                break;
            case Tutorial:
                listView.setBackgroundColor(Color.parseColor("#b01a40"));
                break;

        }
        listView.setOnItemClickListener(listViewOnItemClickListener);

    }

    AdapterView.OnItemClickListener listViewOnItemClickListener = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String url = ((TextView)view.findViewById(R.id.textViewLink)).getText().toString();
            Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
            startActivity(browserIntent);

        }
    };

    class LoadRssItems extends AsyncTask<String,String,String> {
        private ProgressDialog progressDialog;
        @Override
        protected void onPreExecute() {
            super.onPreExecute();    //To change body of overridden methods use File | Settings | File Templates.
            progressDialog = new ProgressDialog(getActivity());
            progressDialog.setMessage("Loading RSS items. Please wait...");
            progressDialog.show();

        }



        @Override
        protected String doInBackground(String... params) {
            try {
                 rssItemList= rssReader.parse(params[0]);


            } catch (IOException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (XmlPullParserException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (ParseException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
            return null;  //To change body of implemented methods use File | Settings | File Templates.
        }

        @Override
        protected void onPostExecute(String s) {
            progressDialog.dismiss();
            if (rssItemList != null)
            {
            adapter = new ListViewRssItemAdapter(getActivity(),R.layout.list_item,rssItemList);
            setListAdapter(adapter);
            }
            super.onPostExecute(s);    //To change body of overridden methods use File | Settings | File Templates.

        }
    }
}

When this base fragment is loaded, he checks which category is set. Then he will create an AsyncTask to make HTTP request to this website with given URL. The reponse content will be parsed by a RSS reader class and shown to user. To make the tabs differ from each other, I also set the background of list view with different colors. When user taps on an item, he is able to open item in browser for reading the complete article by listViewOnItemClickListener() function.

So far so good, we reach now the end of post, the sample source code can be downloaded in following link Android – Build tab layout with FragmentPagerAdapter,ViewPager and ActionBar . In the source code, you’ll find more how RSS reader class handles the response content or how the adapter of list view looks like so that user can open an item with default browser.

I think it’s not necessary to explain every code row here, so just explore the code yourself. When you have any question, don’t hesitate to make a comment directly under.

4 thoughts on “Android – Build tab layout with FragmentPagerAdapter, ViewPager and ActionBar”

  1. @keith: Maybe there is an error with RSSReader class which can’t parse the feed. Debug the code when the parse() function is called and see what happens. I’ll take a look later when I have time.

  2. thank you so much ^_^ , it works , but how can i handle different rss-feeds from different websites? , i mean different tags, for example i will parse this xml

    and this one with additional tags

    ?

    i’ve just got a force close with this. Thanks in advance

  3. @keith: The post is a demo of concept. If you want to support more kinds of RSS feeds, then you have to do it yourself 🙂 or take a look if there is any open source for parsing RSS feeds then use it.

Leave a Reply

Your email address will not be published. Required fields are marked *