Quote

Using FragmentTabHost with ViewPager

On my last project, I wanted to design one of the screens to look like Google Newsstand’s swiping tabs that were also scrollable and had an image between the ActionBar and the tabs.

Google News Screenshot

Google News Screenshot

 

I did not find any simple and useful solution to this that I could easily follow and implement in my app. So I created my own. Here’s how it looks like.

FragmentTabHost 1

Google recommends using FragmentTabHost in place of deprecated TabHost. FragmentTabHost usually has two layout components, TabWidget (for tabs) and FrameLayout (for tabContent). To use ViewPager, I added the Tab Content layout with height 0dp and added another FrameLayout that will host the ViewPager, like this:

 

Activity Layout

 

The Fragments

Let’s start by creating a fragment that we will reuse for our tabs. Here’s the fragment layout

<?xml version="1.0" encoding="utf-8"?>
<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"
    tools:context="com.intelliabb.hnabbasi.fragmenthostviewpager.Fragments.MyFragment">

    <TextView android:id="@+id/label"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:textSize="20dp"/>

</FrameLayout>

The Fragment class that uses this layout,

public class MyFragment extends Fragment {

    String label;

    public static MyFragment newInstance(String fragmentLabel) {
        MyFragment fragment = new MyFragment();
        Bundle args = new Bundle();
        args.putString("label", fragmentLabel);
        fragment.setArguments(args);
        return fragment;
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_item, container, false);
        TextView tv = (TextView) view.findViewById(R.id.label);

        //If the fragment was created by the TabHost, return empty view
        if(getArguments() == null) return view;

        label = getArguments().getString("label", "");

        tv.setText(label);
        return view;
    }
}

Since we are using two different methods to create this fragment (one from the FragmentTabHost and one from the ViewPager), we will check if the bundle passed to the fragment while creating it’s instance is null. If it is, then we will simply return the view without populating it with any content.

 

FragmentTabHost Layout

Now that we have the fragments ready, let’s configure the FragmentHost to use these fragments. In this layout, we will have FragmentTabHost with tabs, tabcontent, and realtabcontent components. Since we want our ViewPager to actually create the views and use the fragments inside realtabcontent, we will set the height of tabscontent to 0dp. The fragments will return an empty view if the bundle is empty and hence we will not have any view created on the screen.

<android.support.v4.app.FragmentTabHost
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:id="@android:id/tabhost"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_below="@id/header_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TabWidget
            android:id="@android:id/tabs"
            android:showDividers="none"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@android:id/tabcontent"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_weight="0"/>

        <FrameLayout
            android:id="@+id/realTabContent"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="0"/>

    </LinearLayout>
</android.support.v4.app.FragmentTabHost>

ViewPager Layout

Now, the ViewPager where all the action is. We will use the ViewPagerAdapter to request and new instances of the Fragments and pass the appropriate bundle. ViewPager will reside inside the FragmentTabHost and has a simple layout,

<android.support.v4.view.ViewPager
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
</android.support.v4.view.ViewPager>

ViewPagerAdapter

ViewPager get’s its content (sliding views) from a PagerAdapter. In our example, we will use a custom adapter called ViewPagerAdapter that extends FragmentPagerAdapter,

public class ViewPagerAdapter extends FragmentPagerAdapter {

    String[] tabs;

    public ViewPagerAdapter(FragmentManager fm, String[] tabs) {
        super(fm);
        this.tabs = tabs;
    }

    @Override
    public Fragment getItem(int i) {
        return MyFragment.newInstance(String.format("%s Fragment", tabs[i]));
    }

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

The Activity

Now that we have all the individual parts ready, we will start linking them in the activity. Our MainActivity extends FragmentActivity so we can get the Fragment manager for our ViewPager.

public class MainActivity extends FragmentActivity

 

In the onCreate() method of our activity, we will initialize the ViewPager, TabHost, and TabHost

viewPager = (ViewPager) findViewById(R.id.pager);
tabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
tabWidget = (TabWidget) findViewById(android.R.id.tabs);
tabHost.setup(this, getSupportFragmentManager(), R.id.realTabContent);

We will get the data in our activity and pass each fragment it’s data to display.

Tabs:

TV Shows

Movies

Music

News

Weather

Pass each tab’s content via ViewPagerTabAdapter. We will initialize the tabs,

private void initializeTabs() {
    tabs = new String[] { "TV Shows", "Movies", "Music", "News", "Weather" };
}

Now, we will setup the TabHost,

private void setupTabHost() {

    for(int i=0; i<tabs.length; i++) {
        tabHost.addTab(tabHost.newTabSpec(String.format("%sTab", tabs[i].replace(" ","").toLowerCase())).setIndicator(tabs[i]), MyFragment.class, null);
    }
}

Now that we have the tabs ready. We will initialize the PagerAdapter and set it to ViewPager,

pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), tabs);
viewPager.setAdapter(pagerAdapter);

We will handle setOnPageChangedListener() of the ViewPager to communicate to the TabHost to switch to the corresponding tab when swiping through pages,

viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
        invalidateOptionsMenu();
        tabHost.setCurrentTab(position);
    }
});

Similarly, we have to handle the TabHost setOnTabChagedListener() to communicate to the ViewPager to swipe to corresponding page,

tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
    @Override
    public void onTabChanged(String tabId) {
        viewPager.setCurrentItem(tabHost.getCurrentTab());
    }
});

This completes our setup. But, we are not done quite yet…

Scrolling Tabs

To let the user scroll the tabs and center the tab on selection, we will do a minor tweaking using ScrollTo() in HorizontalScrollableView.

private void scrollToCurrentTab() {
    final int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
    final int leftX = tabWidget.getChildAt(tabHost.getCurrentTab()).getLeft();
    int newX = 0;

    newX = leftX + (tabWidget.getChildAt(tabHost.getCurrentTab()).getWidth() / 2) - (screenWidth / 2);
    if (newX < 0) {
        newX = 0;
    }
    horizontalScrollView.scrollTo(newX, 0);
}

Now, we are done with our setup. This is how the completed MainActivity looks like,

/*
 * FragmentHost with ViewPager
 * By Hussain Abbasi at intelliAbb.com
 */
package com.intelliabb.hnabbasi.fragmenthostviewpager;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTabHost;
import android.support.v4.view.ViewPager;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.LinearLayout;
import android.widget.TabHost;
import android.widget.TabWidget;
import com.intelliabb.hnabbasi.fragmenthostviewpager.Adapters.ViewPagerAdapter;
import com.intelliabb.hnabbasi.fragmenthostviewpager.Fragments.MyFragment;


public class MainActivity extends FragmentActivity {
    private String[] tabs;
    FragmentTabHost tabHost;
    ViewPagerAdapter pagerAdapter;
    ViewPager viewPager;
    private TabWidget tabWidget;
    private HorizontalScrollView horizontalScrollView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.requestWindowFeature(getWindow().FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        viewPager = (ViewPager) findViewById(R.id.pager);
        tabHost = (FragmentTabHost) findViewById(android.R.id.tabhost);
        tabWidget = (TabWidget) findViewById(android.R.id.tabs);
        tabHost.setup(this, getSupportFragmentManager(), R.id.realTabContent);

        initializeHorizontalTabs();
        initializeTabs();
        setupTabHost();

        pagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), tabs);
        viewPager.setAdapter(pagerAdapter);

        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

            /**
             * on swipe select the respective tab
             * */
            @Override
            public void onPageSelected(int position) {
                invalidateOptionsMenu();
                tabHost.setCurrentTab(position);
            }

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
            }

            @Override
            public void onPageScrollStateChanged(int arg0) {
                invalidateOptionsMenu();
            }
        });

        tabHost.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
            @Override
            public void onTabChanged(String tabId) {
                viewPager.setCurrentItem(tabHost.getCurrentTab());
                scrollToCurrentTab();
            }
        });
    }

    private void initializeHorizontalTabs() {
        LinearLayout ll = (LinearLayout) tabWidget.getParent();
        horizontalScrollView = new HorizontalScrollView(this);
        horizontalScrollView.setLayoutParams(new FrameLayout.LayoutParams(
                FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT));
        ll.addView(horizontalScrollView, 0);
        ll.removeView(tabWidget);
        horizontalScrollView.addView(tabWidget);
        horizontalScrollView.setHorizontalScrollBarEnabled(false);
    }

    private void scrollToCurrentTab() {
        final int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
        final int leftX = tabWidget.getChildAt(tabHost.getCurrentTab()).getLeft();
        int newX = 0;

        newX = leftX + (tabWidget.getChildAt(tabHost.getCurrentTab()).getWidth() / 2) - (screenWidth / 2);
        if (newX < 0) {
            newX = 0;
        }
        horizontalScrollView.scrollTo(newX, 0);
    }
    private void initializeTabs() {
        tabs = new String[] { "TV Shows", "Movies", "Music", "News", "Weather" };
    }

    private void setupTabHost() {

        for(int i=0; i<tabs.length; i++) {
            tabHost.addTab(tabHost.newTabSpec(String.format("%sTab", tabs[i].replace(" ","").toLowerCase())).setIndicator(tabs[i]), MyFragment.class, null);
        }
    }
}

 

FragementHost ViewPager

 

Wrap Up

I am sure there may be a better way to implement this, but after searching for one online and in Android docs, I could not find one. This is simple and gets the job done.

GitHub: https://github.com/Intelliabb/android/tree/master/FragmentHostViewPager

Leave your suggestions and comments below in the comment section. Enjoy.

Advertisements

4 thoughts on “Using FragmentTabHost with ViewPager

    • In this example, I am reusing MyFragment for all tabs. To get a different fragment for each tab, you would initialize it when adding tabs to TabHost.

      For example,

      tabHost.addTab(tabHost.newTabSpec().setIndicator("Tab A"), MyFragmentA.class, null);
      tabHost.addTab(tabHost.newTabSpec().setIndicator("Tab B"), MyFragmentB.class, null);
      tabHost.addTab(tabHost.newTabSpec().setIndicator("Tab C"), MyFragmentC.class, null);
      
  1. Bhargav B.L says:

    I am getting a nullpointerException at this line.

    tabHost.setup(this, getSupportFragmentManager(), R.id.realTabContent);

    Please help me. I just copied your code from github and tried to run in my system. Thanking you.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s