Earlier to make tabs in Android, ActionBar
was used. But now with API 21 onwards it has been deprecated. The new Design Support Library has many new UI components like floating action buttons, snackbars and tabs.
The base componets of this tutorial are
ViewPager
are managed by an instance of the FragmentStatePagerAdapter
class. At a minimum the pager adapter assigned to a ViewPager
must implement two methods. The first, named getCount()
, must return the total number of page fragments available to be displayed to the user. The second method, getItem()
, is passed a page number and must return the corresponding fragment object ready to be presented to the user.TabLayout
is to present the user with a row of tabs which can be selected to display different pages to the user.To start off include following libraries in the dependencies section of your build.gradle file
dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:26.1.0' compile 'com.android.support:design:26.1.0' }
We will be using Android Toolbar
and TabLayout
classes to show tabs, lets remove the ActionBar
from layout by using styles. In order to change the theme, go to app - res – values – styles and change the default theme Theme.AppCompat.Light.DarkActionBar to Theme.AppCompat.Light.NoActionBar.
# file styles.xml <resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">#3F51B5</item> <item name="colorPrimaryDark">#303F9F</item> <item name="colorAccent">#FF4081</item> </style> </resources>
Next, to display Android tabs with fragment and ViewPager
, lets define simple fragment
# file TabFragment.java import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.Toast; public class TabFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.tab_fragment, container, false); Button btnIntroduce = (Button) view.findViewById(R.id.btnIntroduce); btnIntroduce.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(getActivity(), "Fragment", Toast.LENGTH_LONG).show(); } }); return view; } }
and layout
# file tab_fragment.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Tab 1" android:textAppearance="?android:attr/textAppearanceLarge"/> <Button android:id="@+id/btnIntroduce" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Who I am?" android:layout_below="@+id/textView" android:layout_centerHorizontal="true" /> </RelativeLayout>
Now that we have all the tab fragments defined, lets define a ViewPager
adapter for the swipe tabs feature
# file PagerAdapter.java import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentStatePagerAdapter; public class PagerAdapter extends FragmentStatePagerAdapter { int numTabs; public PagerAdapter(FragmentManager fm, int numTabs) { super(fm); this.numTabs = numTabs; } @Override public Fragment getItem(int position) { switch (position) { case 0: TabFragment tab1 = new TabFragment(); return tab1; case 1: TabFragment tab2 = new TabFragment(); return tab2; case 2: TabFragment tab3 = new TabFragment(); return tab3; default: return null; } } @Override public int getCount() { return numTabs; } }
At the very least, subclasses of FragmentStatePagerAdapter
must implement getItem()
and getCount()
.
getItem()
is expected to return the fragment for the specified position. Internally, FragmentStatePagerAdapter
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 can override getPageTitle()
, which is used to set the text for each tab position.
Next lets define the layout for main activity where all these tabs would be displayed.
# file activity_main.xml <RelativeLayout android:id="@+id/main_layout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:background="?attr/colorPrimary" android:elevation="6dp" android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/> <android.support.design.widget.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@+id/toolbar" android:background="?attr/colorPrimary" android:elevation="6dp" android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="fill_parent" android:layout_below="@id/tab_layout"/> </RelativeLayout>
The remaining tasks involve initializing the TabLayout
, ViewPager
and PagerAdapter
instances. All of these tasks will be performed in the onCreate()
method of the MainActivity.java
file.
# file MainActivity.java import android.os.Bundle; import android.support.design.widget.TabLayout; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.view.Menu; import android.view.MenuItem; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(toolbar); TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout); tabLayout.addTab(tabLayout.newTab().setText("Tab 1")); tabLayout.addTab(tabLayout.newTab().setText("Tab 2")); tabLayout.addTab(tabLayout.newTab().setText("Tab 3")); tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); final ViewPager viewPager = (ViewPager) findViewById(R.id.pager); final PagerAdapter adapter = new PagerAdapter (getSupportFragmentManager(), tabLayout.getTabCount()); viewPager.setAdapter(adapter); viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout)); // This method ensures that tab selection events update the ViewPager and page changes update the selected tab. // tabLayout.setupWithViewPager(viewPager); tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { viewPager.setCurrentItem(tab.getPosition()); } @Override public void onTabUnselected(TabLayout.Tab tab) {} @Override public void onTabReselected(TabLayout.Tab tab) {} }); } }
Code on github.
Changing size of tab title
In an effort to fit the tabs into the available display width the TabLayout
has used multiple lines of text. Even so, the second line is clearly truncated making it impossible to see the full title. The best solution to this problem is to switch the TabLayout
to scrollable mode. In this mode the titles appear in full length, single line format allowing the user to swipe to scroll horizontally through the available items.
To switch a TabLayout
to scrollable mode, simply change the app:tabMode
property in the activity_main.xml
layout resource file from fixed
to scrollable
<android.support.design.widget.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" app:tabMode="scrollable" app:tabGravity="fill"/> </android.support.design.widget.AppBarLayout>
When in fixed
mode, the TabLayout
may be configured to control how the tab items are displayed to take up the available space on the screen. This is controlled via the app:tabGravity
property, the results of which are more noticeable on wider displays such as tablets in landscape orientation. When set to fill
, for example, the items will be distributed evenly across the width of the TabLayout
.
Changing text size of tab items
To replace the text size of tabs items create new style in style.xml
<style name="CustomTabText" parent="TextAppearance.Design.Tab"> <item name="android:textSize">16sp</item> </style>
Use is in TabLayout
like this
<android.support.design.widget.TabLayout app:tabTextAppearance="@style/CustomTabText" ... />
Displaying icon tab items
To replace the text based tabs with icons, modify the onCreate()
method in the MainActivity.java
file to assign some built-in drawable icons to the tab items
@Override protected void onCreate(Bundle savedInstanceState) { ... TabLayout tabLayout = (TabLayout) findViewById(R.id.tab_layout); tabLayout.addTab(tabLayout.newTab().setIcon( android.R.drawable.ic_dialog_email)); tabLayout.addTab(tabLayout.newTab().setIcon( android.R.drawable.ic_dialog_dialer)); tabLayout.addTab(tabLayout.newTab().setIcon( android.R.drawable.ic_dialog_map)); ... }
TabLayout and Kotlin
In the following sections, we'll dive into coding a simple app that makes use of TabLayout
with a ViewPager
.
To be able to follow this section, you'll need:
You can also learn all the ins and outs of the Kotlin language here.
We're going to create a TabLayout
with just three tabs. When each of the tabs is selected, it displays a different Android fragment or page. So let's now create the three Android fragments for each of the tabs. We'll start with the first fragment class, and you should follow a similar process for the remaining two fragment classes - FragmentTwo.kt
and FragmentThree.kt
.
Here is my FragmentOne.kt
class FragmentOne : Fragment() { companion object { fun newInstance(): FragmentOne = FragmentOne() } override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater!!.inflate(R.layout.fragment_one, container, false) }
Here is also my R.layout.fragment_one
.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="Tab 1" android:textAppearance="?android:attr/textAppearanceLarge"/> </RelativeLayout>
Also, visit your res/layout/activlty_main.xml file to include both the TabLayout
widget and the ViewPager
view.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout android:id="@+id/main_layout" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.design.widget.TabLayout android:id="@+id/tab_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:background="?attr/colorPrimary" android:elevation="6dp" android:minHeight="?attr/actionBarSize" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> <android.support.v4.view.ViewPager android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="fill_parent" android:layout_below="@id/tab_layout"/> </RelativeLayout>
Here we created a simple TabLayout
with id tab_layout
.
We need to create a subclass in SampleAdapter.kt
that extends the FragmentPagerAdapter
. This class is responsible for managing the different fragments that will be displayed on the tabs.
class SampleAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { override fun getItem(position: Int): Fragment? = when (position) { 0 -> FragmentOne.newInstance() 1 -> FragmentTwo.newInstance() 2 -> FragmentThree.newInstance() else -> null } override fun getPageTitle(position: Int): CharSequence = when (position) { 0 -> "Tab 1" 1 -> "Tab 2" 2 -> "Tab 3" else -> "" } override fun getCount(): Int = 3 }
Here we override three methods from the parent class: getItem()
, getCount()
, and getPageTitle()
. Here are the explanations for the methods:
getItem()
: returns a Fragment
for a particular position within the ViewPager
.getCount()
: indicates how many pages will be in the ViewPager
.getPageTitle()
: this method is called by the ViewPager
to obtain a title string to describe the specified tab.Next, we are going to initialize instances of our TabLayout
, ViewPager
, and SampleAdapter
. Initialization is going to happen inside onCreate()
in MainActivity.kt
.
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val tabLayout: TabLayout = findViewById(R.id.tab_layout) val viewPager: ViewPager = findViewById(R.id.pager) val adapter = SampleAdapter(supportFragmentManager) viewPager.adapter = adapter tabLayout.setupWithViewPager(viewPager) } }
We got references to our TabLayout
and ViewPager
from R.layout.activity_main
and initialized them. We also created an instance of our SampleAdapter
- passing an instance of FragmentManager
as an argument. We need to supply the views for our ViewPager
, so we called setAdapter()
and passed in our created adapter to it. Finally, we called setupWithViewPager()
on an instance of TabLayout
to do some work: creation of the required tab for every page and setting up the required listeners.
When the user taps on a tab, it changes the pages in the ViewPager
and shows the required page (or Fragment
). Also, swiping between pages updates the selected tab. In other words, this method helps us take care of scroll state change and clicks on the tabs.
Note that if you want to explicitly create the tabs instead of using the helper method setUpWithViewPager()
, you can instead use newTab()
on a TabLayout
instance.
val tabLayout: TabLayout = findViewById(R.id.tab_layout) tabLayout.addTab(tabLayout.newTab().setText("Songs")) tabLayout.addTab(tabLayout.newTab().setText("Albums")) tabLayout.addTab(tabLayout.newTab().setText("Artists"))
Note also that we could explicitly create the tabs via XML instead of programmatically.
<android.support.design.widget.TabLayout android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.design.widget.TabItem android:id="@+id/tabItem" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Songs"/> <android.support.design.widget.TabItem android:id="@+id/tabItem2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Albums"/> <android.support.design.widget.TabItem android:id="@+id/tabItem3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Artists"/> </android.support.design.widget.TabLayout>
Useful links