Material Design Tabs with Android Design Support Library
This is my third post where I continue to bring you more from the Design Support Library. Let’s look at adding a Material Design Tab strip for our apps with on scroll animations.
Tabs make it easy to explore and switch between different views. Provides a way for displaying grouped content.
– Material Design, Tab Component
This article has been updated for AndroidX!
Material Design Tabs made easy
You can see Material Design tabs popularly on the Play Store app, and more recently on WhatsApp as well (after their Material Design update).
To implement Tabs earlier, we had to rely on external libraries. Also, let’s not forget the hide on scroll animations which we had to make ourselves!
When you’re done reading this post, you’ll be able to create amazing Material Design tabs for your apps. Like the ones above.
So let’s get to it!
Also read:
- Parallax Scrolling Tabs with Design Support Library
- Flexible space with header image pattern
- Quick Material Design Navigation Drawer with Design Support
Layout
Have you closely observed how Tabs actually behave?
When you click a Tab, it becomes active and slides in a new screen. Rather a new sub-screen called Fragment
. We either tap a Tab, or swipe left or right to access adjacent Tabs.
Tabs are typically found anchored to the bottom of the Toolbar
.
In essence, the entirety of those Tab screens are Fragments, which will be handled by a ViewPager
. Think of it like a gallery slideshow.
But before we dive into all of that, let’s setup our main layout.
Here’s a skeleton of the layout.
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout>
<androidx.appcompat.widget.Toolbar />
<!-- The Tab rests directly below the Toolbar, attached below it -->
<com.google.android.material.tabs.TabLayout />
</com.google.android.material.appbar.AppBarLayout>
<!-- Helps handing the Fragments to load for each Tab -->
<android.support.v4.view.ViewPager />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
For creating Tabs, we use the TabLayout
widget class. This is a new widget, part of the Design Support Library.
Additionally, you might notice other new widgets as well. If you’re not familiar with them, I strongly suggest you go through this post first.
That’s the skeleton of our Activity
layout. Let’s start defining it now. Open your Activity’s layout.xml
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
<androidx.appcompat.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
<com.google.android.material.tabs.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
The scroll flags for the Toolbar
specify how it must behave upon scroll (i.e. its animation).
scroll|enterAlways
says make my Toolbar
react to scroll events. Hide it when scrolling down, and reveal it when scrolling back up.
This behavior will be indicated by our ViewPager
which will be hold our Fragments. The Fragments hold our scrollable view. So when we scroll down a list, the Toolbar
knows when to react.
Setting up TabLayout
That’s our main layout. Now open up YourActivity.java.
final Toolbar toolbar = findViewById(R.id.tabanim_toolbar);
setSupportActionBar(toolbar);
final ViewPager viewPager = findViewById(R.id.tabanim_viewpager);
setupViewPager(viewPager);
TabLayout tabLayout = findViewById(R.id.tabanim_tabs);
tabLayout.setupWithViewPager(viewPager);
First, start off by setting up your Toolbar
. Then define the ViewPager
. We’ll attach an Adapter to in the setupViewPager()
method. Finally define the TabLayout
, and attach the ViewPager
to it.
Remember that we must attach an adapter to the ViewPager
first. Then attach the ViewPager
to TabLayout
.
setupViewPager()
method
Here I will simply initialize a ViewPagerAdapter (our custom adapter class which I will explain in a moment) and add 3 fragments to it.
For simplicity’s sake, I will be attached the same Fragment
thrice which different backgrounds. However in a real app scenario, you would be attaching different once.
Let’s assume you have 3 tabs. Games, Movies, Books. Then you should be loading their respective fragments here in ORDER of the tabs.
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager(), FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
adapter.addFrag(new DummyFragment(
ContextCompat.getColor(this, R.color.blue_grey)), "CAT");
adapter.addFrag(new DummyFragment(
ContextCompat.getColor(this, R.color.amber)), "DOG");
adapter.addFrag(new DummyFragment(
ContextCompat.getColor(this, R.color.cyan)), "MOUSE");
viewPager.setAdapter(adapter);
}
So let’s define our Tabs and give each one a name. Cat, Dog and Mouse (for the lack of better names) will be my 3 Tabs.
Defining our Adapter
Remember that ViewPagerAdapter I told you about? Let’s create a class for it now, extend FragmentPagerAdapter
and implement the required methods.
We need to store, first our Fragments itself, and then the Tab titles. We’ll do this with a List
.
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
Our entire Adapter class will look like this:
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List < Fragment > mFragmentList = new ArrayList < > ();
private final List < String > mFragmentTitleList = new ArrayList < > ();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFrag(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Note that addFrag()
is my own custom method. I’m using it to add Fragments and its titles to the ViewPager
adapter.
Tab Fragment
Now that we’ve done all this, you might be wondering what is ‘DummyFragment‘ and why its showing an error?
DummyFragment is to be your Fragment
(obvious from the name), which holds the layout content. This is what we see when we swipe to each Tab. Ideally, you should be loading a different Fragment
for each tab.
The layout could be anything. But I don’t want to complicate things.
Just be sure for testing, to make a list long enough so that its scrollable. This will allow the Quick Return scroll animation to work.
I encourage you to create your own layouts for the Tab Fragment
. You could refer mine, but if you’re like “Hey, I can do this!“, then good job! Scroll down to the next section.
public static class DummyFragment extends Fragment {
int color;
SimpleRecyclerAdapter adapter;
public DummyFragment() {
}
@SuppressLint("ValidFragment")
public DummyFragment(int color) {
this.color = color;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.dummy_fragment, container, false);
final FrameLayout frameLayout = view.findViewById(R.id.dummyfrag_bg);
frameLayout.setBackgroundColor(color);
RecyclerView recyclerView = view.findViewById(R.id.dummyfrag_scrollableview);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity().getBaseContext());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setHasFixedSize(true);
List<String> list = Arrays.asList(VersionModel.data);
adapter = new SimpleRecyclerAdapter(list);
recyclerView.setAdapter(adapter);
return view;
}
}
Tab Listener
We can interact with the TabLayout
and dictate what must happen when selecting a Tab, unselecting, reselecting and so forth.
While we’re staying on topic, let me show you how we can add such a listener to it.
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
switch (tab.getPosition()) {
case 0:
showToast("One");
break;
case 1:
showToast("Two");
break;
case 2:
showToast("Three");
break;
}
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {}
@Override
public void onTabReselected(TabLayout.Tab tab) {}
});
Note viewPager.setCurrentItem(tab.getPosition())
handles changing the Fragment
based on Tab click.
Yes, all it takes is to use setOnTabSelectedListener
with TabLayout.OnTabSelectedListener()
.
Just for demonstration, I’ll show a simple Toast
when you tap a Tab item. This works not only when tapping, but also when swiping left or right! In essence, a Toast
pops each time the Fragment
loads into view.
v23.0.0 Tab Listener Fix:
With the release of Android Marshmallow, libraries got updated to 23.0.0 including the Design Support Library. This update somehow broke the tab listener in a viewpager + tab combination. So if click listener for the tabs stops working for you, here is a suggested workaround for it, suggested by Chris Banes himself.
Material Design Tabs in Action
Let’s take a breath and break down what we’ve accomplished so far:
- Created our main
TabLayout
- Initialized a
ViewPager
and attached an Adapter (ViewPagerAdapter) to it
- Attached the
ViewPager
to our Tabs - Created the
Fragment
content to supply ourViewPager
with
Finally with all that code behind us, lets run our app and see how it looks like.
That’s it! We’ve got some great looking Material Design Tabs (without a third party library). Created the needed adapter and even got those snazzy on scroll animations for our Toolbar
.
Note the Tab underline highlight takes colorAccent by default. Also, did you just notice we got those animations to work just by defining it in XML? No Java. Cheers!
Thats what Android Design Support library allows us to do. Making the easy stuff easier.
How are you using TabLayout in your apps? Have anything else to add? Let me know in the comments.
Product Designer who occasionally writes code.
For me whenever I swipe slowly from one tab to another, in between if I stop swiping the tab indicator get struck in between the Tabs. Is there any solutions to fix this?
Hi Kevin,
That behavior which you’re describing is weird. If you take a look at my Youtube video, I swipe past the Tabs slowly and it doesn’t get stuck in between. Could you try doing a clean rebuild? Make sure your updated all build tools and libraries to their respective latest versions as well.
Its happening like attached Image. These are the version I’m using. All are latest
compile ‘com.android.support:appcompat-v7:22.2.0’
compile ‘com.android.support:support-v4:22.2.0’
compile ‘com.android.support:design:22.2.0’
compile ‘com.android.support:cardview-v7:21.0.+’
compile ‘com.android.support:recyclerview-v7:21.0.+’
Well, you certainly didn’t cross check against mine. Remove support v4. The card and recycler libraries are version 22.
check mine
https://github.com/Suleiman19/Android-Material-Design-for-pre-Lollipop/blob/master/MaterialSample/app/build.gradle
Its happening in my scenario! Couldn’t resolve still
Have you tried what I suggested?
Me too,But I resolved this problem,I setId for viewPager like this:viewPager.setId(View.generateViewId()); you can try.My English is very poor.Hope useful to you.
Thank you for that Susie. I’ve never faced this issue. Not able to reproduce it either, so I’m afraid I can’t be of help here.
However, I’m sure this can help those who have this issue 🙂
The last update of Support Lib (23.1.0) doesn’t fix this issue
While I honestly haven’t experience this myself, nor have I been successful in recreating the issue. I suggest you try the solution suggested here: https://code.google.com/p/android/issues/detail?id=180462
Me too! Have you resolve?
The issue is seems to because of not created Fragments (empty) in ViewPager. You should clear ViewPager items before add new fragments. See this solution: http://stackoverflow.com/a/11442623/1621111
I have a problem, When ever i slide the view pager slowly and if I take my hands in between the tabs indicator get stuck between the both tabs. I mean it is in middle. How to resolve this
Let’s say that I have a navigation drawer that selects either Frag1 or Frag2. How can I adapt this code so that Frag1 has the sliding tabs (like in this post), but Frag2 doesn’t?
I have a StackOverflow question about this: http://stackoverflow.com/questions/31091644/implementing-sliding-tabs-in-a-fragment
Edit: Never mind, I figured it out! My xml layout wasn’t putting the ViewPager on that screen, so I couldn’t swipe!
Great. Glad to know you figured it out!
The best tutorial of Tabs using material design lib!
Thank you for the kind words Rafael 🙂
If in your fragment, you are not using RecyclerView, but you have simple ScollView – in this case hiding toolbar won’t work. In this case please use – android.support.v4.widget.NestedScrollView
Correct. Thanks Mateusz for sharing a useful pointer!
Hi, I was following the example and Yes I dont have a RecyclerView. I just have a simple Listview in the fragment. How do I make the hiding work when I scroll ?
Hi Sayooj,
Please migrate to RecyclerView
yo can use NestedListView
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListAdapter;
import android.widget.ListView;
public class NestedListView extends ListView {
private static final int MAXIMUM_LIST_ITEMS_VIEWABLE = 99;
public NestedListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int newHeight = 0;
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode != MeasureSpec.EXACTLY) {
ListAdapter listAdapter = getAdapter();
if (listAdapter != null && !listAdapter.isEmpty()) {
int listPosition = 0;
for (listPosition = 0; listPosition < listAdapter.getCount()
&& listPosition heightSize)) {
if (newHeight > heightSize) {
newHeight = heightSize;
}
}
} else {
newHeight = getMeasuredHeight();
}
setMeasuredDimension(getMeasuredWidth(), newHeight);
}
}
Hi Akshay,
While that is a great and viable solution, I urge you to consider the advantages RecyclerView offers. Maybe giving this a read can help? http://stackoverflow.com/questions/28525112/android-recyclerview-vs-listview-with-viewholder
Thank you @mateuszpryczkowski:disqus
You saved my life.
how to do with icons in tabs without text???
Use this:
tabLayout.getTabAt(index).setIcon(icon).setText(“”);
Thanks sir, and what if i want both txt and image then?
Its there in the same code snippet. Use setText(“YourTabName”);
Thanks a lot sir ????
Thanks a lot sir it working fine now.Now i have one more doubt.I have differnt images when the user is on particular page of view pager.So how will i change the icon on page scrolled?
It is not recommended to keep changing the tab icon/text during use, as it would only confuse the user.
Ok. I’ve spent 1 day to realize that Google silently updated recyclerview!!! So I’d used 22.0.0 and even imagine that it can cause problem with material hiding. When I changed to 22.2.0 everything seems to work.
PS: Great article, but it would be great if you can point at such kind of misunderstanding
Sorry to hear you wasted a day finding that out. But glad you finally got it to work! Thanks for the pointer. Will make it clear for future readers.
PS: Make sure you update ALL respective libraries together 😉
Oh, I’m sorry for previous post – i’ve tried just copy and paste main classes needed for tabs – and it didn’t work. However. in your project everything works fine, but I can’t understand where I could make mistake, because I’ve copied all java, resources and AndroidManifest files to brand new project
Hello!! I’ve just copy your repository and try to launch and I noticed that neither “Toolbar Animation wih image” nor “Material Style Tabs” works. I’ve tested on Genymotion 5.1 and Android 4.4. This is dependencies I’m using
compile ‘com.android.support:appcompat-v7:22.2.0’
compile (‘com.android.support:design:22.2.0’){
}
compile ‘com.android.support:appcompat-v7:22.2.0’
compile ‘com.android.support:palette-v7:22.2.0’
compile(‘com.android.support:recyclerview-v7:22.0.0’) {
}
compile ‘com.android.support:cardview-v7:22.0.0’
In your codes, when tabs are clicked, ViewPager won’t change pages even you have set OnTabSelectedListener . You should set ViewPagerOnTabSelectedListener on your TabLayout. BTW, do you think it’s a bug inside TabLayout?
Hi Peter,
Thanks for pointing it out. While the listener itself needn’t be changed, just add this line in the onTabSelected method, before the switch case:
viewPager.setCurrentItem(tab.getPosition());
I’ve updated the post and GitHub repository. Thanks for following the post with a watchful eye.