I have made a custom TabLayout with a ViewPager
and am using the TabLayout in scrollable mode:
I need it to be scrollable as the number of dates can var
I looked all over for an answer to this exact problem. In my case I was dynamically adding and removing tabs, so I wanted it to fill the screen when there were only a few tabs, but start scrolling when there were too many rather than shrinking them or putting the titles on two lines. Using the following custom tab layout finally got it working for me. It was key to set the minimum width before calling super.onMeasure().
public class CustomTabLayout extends TabLayout {
public CustomTabLayout(Context context) {
super(context);
}
public CustomTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
ViewGroup tabLayout = (ViewGroup)getChildAt(0);
int childCount = tabLayout.getChildCount();
if( childCount != 0 ) {
DisplayMetrics displayMetrics = getContext().getResources().getDisplayMetrics();
int tabMinWidth = displayMetrics.widthPixels/childCount;
for(int i = 0; i < childCount; ++i){
tabLayout.getChildAt(i).setMinimumWidth(tabMinWidth);
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
Set the tab mode to scrollable in the xml.
<com.package.name.CustomTabLayout
android:id="@+id/my_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable">
Here is my solution.
public class MyTabLayout extends TabLayout {
public MyTabLayout(Context context) {
super(context);
}
public MyTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
ViewGroup tabLayout = (ViewGroup)getChildAt(0);
int childCount = tabLayout.getChildCount();
int widths[] = new int[childCount+1];
for(int i = 0; i < childCount; i++){
widths[i] = tabLayout.getChildAt(i).getMeasuredWidth();
widths[childCount] += widths[i];
}
int measuredWidth = getMeasuredWidth();
for(int i = 0; i < childCount; i++){
tabLayout.getChildAt(i).setMinimumWidth(measuredWidth*widths[i]/widths[childCount]);
}
}
}
My solution is slightly different, since I tried the one above and it didn't work on some devices. I've noticed that if all tabs are visible on the screen and if we set tabMode
to fixed
, TabLayout
fills its given width. When we use scrollable
, TabLayout
acts like its tabGravity
is set to center
.
So, I calculate the sum of widths of all tabs in TabLayout
and if it is lower than measured width I set its tabMode
to MODE_FIXED
, otherwise to MODE_SCROLLABLE
:
public class CustomTabLayout extends TabLayout {
private static final String TAG = CustomTabLayout.class.getSimpleName();
public CustomTabLayout(Context context) {
super(context);
}
public CustomTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (getTabCount() == 0)
return;
try {
ViewGroup tabLayout = (ViewGroup)getChildAt(0);
int widthOfAllTabs = 0;
for (int i = 0; i < tabLayout.getChildCount(); i++) {
widthOfAllTabs += tabLayout.getChildAt(i).getMeasuredWidth();
}
setTabMode(widthOfAllTabs <= getMeasuredWidth() ? MODE_FIXED : MODE_SCROLLABLE);
} catch (Exception e) {
e.printStackTrace();
}
}
}