Tabs don't fit to screen with tabmode=scrollable, Even with a Custom Tab Layout
Asked Answered
S

4

5

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 vary to as many as 15-20:

<com.example.project.recommendedapp.CustomScrollableTabLayout
    android:id="@+id/tab_layout_movie"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?attr/colorPrimary"
    android:elevation="6dp"
    android:minHeight="?attr/actionBarSize"
    app:tabGravity="fill"
    app:tabMode="scrollable"
    app:tabTextAppearance="?android:textAppearanceMedium"
    android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/>

The custom class i used as per another similar question: TabLayout not filling width when tabMode set to 'scrollable'

The custom tablayout class is:

package com.example.project.recommendedapp;

import android.content.Context;
import android.graphics.Point;
import android.support.design.widget.TabLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;

import java.lang.reflect.Field;

public class CustomScrollableTabLayout extends TabLayout {
    private Context mContext;

    Point size;

    public CustomScrollableTabLayout(Context context) {
        super(context);
        mContext=context;
        size = new Point();
    }

    public CustomScrollableTabLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext=context;
        size = new Point();
    }

    public CustomScrollableTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext=context;
        size = new Point();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        try {
            if (getTabCount() == 0) {
                return;
            }else {
                Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
                display.getSize(size);
                int width = size.x;

                Field field = TabLayout.class.getDeclaredField("mScrollableTabMinWidth");
                field.setAccessible(true);
                field.set(this, (width / getTabCount()));

                Log.d("FragmentCreate",String.valueOf(width / getTabCount()));
            }
        } catch (Exception e) {
            Log.e("FragmentCreate","Error while setting width",e);
        }
    }
}

enter image description here

Skiplane answered 17/8, 2016 at 17:15 Comment(2)
This seems to be how Google wants it to be, unfortunately.Wolfy
You might try putting your TabLayout inside a layout and setting gravity to center_horizontal.Wolfy
C
23

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">
Castellano answered 17/5, 2018 at 3:20 Comment(3)
After spending 2 days, finally working. This custom tab layout working perfectly.Zepeda
yep. the onMeasure did the trick. I just wrap it with 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); } } cause it crashes my appDiazo
this works. be sure to set app:tabPaddingEnd="0dp" and app:tabPaddingStart="0dp" otherwise you do see some slight funky scrolling behaviour on the tab bar from the unaccounted for pixelsJulius
H
6

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]);
        }

    }

}
Hersh answered 26/2, 2017 at 9:30 Comment(0)
C
2

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();
        }
    }
}
Copyreader answered 3/7, 2017 at 21:34 Comment(0)
S
2

For those landing here from google like I did here is my 2021 Kotlin solution. It achieves the following which I couldn't do from other answers:

  • If not many tabs they expand to fill the whole width.
  • If lots of tabs they are scrollable so they aren't squished.
  • Works correctly even if tabLayout is not the full width of the screen.

To achieve this I created a subclass of TabLayout that overrides onMeasure

class ScalableTabLayout : TabLayout {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val tabLayout = getChildAt(0) as ViewGroup
        val childCount = tabLayout.childCount
        if (childCount != 0) {
            val widthPixels = MeasureSpec.getSize(widthMeasureSpec)
            val tabMinWidth = widthPixels / childCount
            var remainderPixels = widthPixels % childCount
            tabLayout.forEachChild {
                if (remainderPixels > 0) {
                    it.minimumWidth = tabMinWidth + 1
                    remainderPixels--
                } else {
                    it.minimumWidth = tabMinWidth
                }
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
}

And then used it in my layout file:

<com.package.name.ScalableTabLayout
    android:id="@+id/tabs"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabMaxWidth="0dp"
    app:tabMode="scrollable" />

both tabMaxWidth="0dp" and tabMode="scrollable" are required

Shayn answered 7/1, 2021 at 1:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.