'Android Tab layout: Wrap tab indicator width with respect to tab title

Is there any way to wrap tab indicator width with respect to tab title ?

Current

Required



Solution 1:[1]

Yes, it's possible to wrap tab indicator as title setting padding to 0

 public void wrapTabIndicatorToTitle(TabLayout tabLayout, int externalMargin, int internalMargin) {
        View tabStrip = tabLayout.getChildAt(0);
        if (tabStrip instanceof ViewGroup) {
            ViewGroup tabStripGroup = (ViewGroup) tabStrip;
            int childCount = ((ViewGroup) tabStrip).getChildCount();
            for (int i = 0; i < childCount; i++) {
                View tabView = tabStripGroup.getChildAt(i);
                //set minimum width to 0 for instead for small texts, indicator is not wrapped as expected
                tabView.setMinimumWidth(0);
                // set padding to 0 for wrapping indicator as title
                tabView.setPadding(0, tabView.getPaddingTop(), 0, tabView.getPaddingBottom());
                // setting custom margin between tabs
                if (tabView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
                    ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) tabView.getLayoutParams();
                    if (i == 0) {
                        // left
                        settingMargin(layoutParams, externalMargin, internalMargin);
                    } else if (i == childCount - 1) {
                        // right
                        settingMargin(layoutParams, internalMargin, externalMargin);
                    } else {
                        // internal
                        settingMargin(layoutParams, internalMargin, internalMargin);
                    }
                }
            }

            tabLayout.requestLayout();
        }
}

private void settingMargin(ViewGroup.MarginLayoutParams layoutParams, int start, int end) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        layoutParams.setMarginStart(start);
        layoutParams.setMarginEnd(end);
        layoutParams.leftMargin = start;
        layoutParams.rightMargin = end;
    } else {
        layoutParams.leftMargin = start;
        layoutParams.rightMargin = end;
    }
}

Result

EDIT: As of com.android.support:design:28.0.0, you can adjust the indicator as label easily now setting:

app:tabIndicatorFullWidth="false"

EDIT July 2019: Use the material dependency com.google.android.material:material:x.x.x

Solution 2:[2]

As of support library 28 you can do the following:

app:tabIndicatorFullWidth="false"
app:tabPaddingStart="25dp"
app:tabPaddingEnd="25dp"

You can set the desired padding that affects the tab indicator.

Also you can now do this:

app:tabIndicator="@drawable/tab_indicator"

this will set a custom drawable as indicator.

example of custom drawable:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <solid android:color="@color/colorPrimary"/>
    <corners android:radius="5dp"/>
</shape>

Solution 3:[3]

enter image description here

If you don't need the strip to be smaller than the text, then this should work:

public static void reduceMarginsInTabs(TabLayout tabLayout, int marginOffset) {

    View tabStrip = tabLayout.getChildAt(0);
    if (tabStrip instanceof ViewGroup) {
        ViewGroup tabStripGroup = (ViewGroup) tabStrip;
        for (int i = 0; i < ((ViewGroup) tabStrip).getChildCount(); i++) {
            View tabView = tabStripGroup.getChildAt(i);
            if (tabView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
                ((ViewGroup.MarginLayoutParams) tabView.getLayoutParams()).leftMargin = marginOffset;
                ((ViewGroup.MarginLayoutParams) tabView.getLayoutParams()).rightMargin = marginOffset;
            }
        }

        tabLayout.requestLayout();
    }
}

For added points you could check the text size of each title.

Solution 4:[4]

One simple solution is:

tabLayout.setTabIndicatorFullWidth(false);

But it works with latest dependency like

implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:design:28.0.0'

Solution 5:[5]

Short answer is "no". Here is explanation.

There is private class SlidingTabStrip within TabLayout that draws indicator

    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        // Thick colored underline below the current selection
        if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
            canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
                    mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
        }
    }

I believe mIndicatorLeft and mIndicatorRight is what you need. These fields are set in the same class:

    private void setIndicatorPosition(int left, int right) {
        if (left != mIndicatorLeft || right != mIndicatorRight) {
            // If the indicator's left/right has changed, invalidate
            mIndicatorLeft = left;
            mIndicatorRight = right;
            ViewCompat.postInvalidateOnAnimation(this);
        }
    }

where left and right parameters are calculated in next method:

private void updateIndicatorPosition() {
        final View selectedTitle = getChildAt(mSelectedPosition);
        int left, right;
        if (selectedTitle != null && selectedTitle.getWidth() > 0) {
            left = selectedTitle.getLeft();
            right = selectedTitle.getRight();
            if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
                // Draw the selection partway between the tabs
                View nextTitle = getChildAt(mSelectedPosition + 1);
                left = (int) (mSelectionOffset * nextTitle.getLeft() +
                        (1.0f - mSelectionOffset) * left);
                right = (int) (mSelectionOffset * nextTitle.getRight() +
                        (1.0f - mSelectionOffset) * right);
            }
        } else {
            left = right = -1;
        }
        setIndicatorPosition(left, right);
    }

And the worst thing is that SlidingTabStrip field in TabLayout is private and final.

private final SlidingTabStrip mTabStrip;

I don't see how it's possible to achieve what you need without creating totally new TabLayout.

Solution 6:[6]

A simple solution is to add this attribute to your tabLayout

app:tabIndicatorFullWidth="false"

Solution 7:[7]

You can do this:

layout/layout_demo.xml

<com.google.android.material.tabs.TabLayout
    app:tabIndicatorFullWidth="false"
    app:tabIndicator="@drawable/tab_indicator"
    .../>

res/tab_indicator.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:gravity="center">
    <shape>
        <size
            android:width="51dp"
            android:height="3dp" />
    </shape>
</item>
</layer-list>

Solution 8:[8]

You can do it using styles.

In the layout xml:

<android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        style="@style/AppTabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

in the styles.xml:

<style name="AppTabLayout" parent="Widget.Design.TabLayout">
    <item name="android:layout_marginLeft">40dp</item>
    <item name="android:layout_marginRight">40dp</item>
</style>

Solution 9:[9]

I'm note sure about the SDK Version needed, but you can do it directly in your XML file, adding the padding attribute using app namespace.

Just make your TabLayout looks like this:

        <android.support.design.widget.TabLayout
            xmlns:app="http://schemas.android.com/apk/res-auto"
            android:id="@+id/tab"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            ...
            app:tabPaddingEnd="0dp"
            app:tabPaddingStart="0dp"
        </android.support.design.widget.TabLayout>

You can move the xmlns:app="http://schemas.android.com/apk/res-auto" for your parent layou too, but its your choice :)

Thanks leon for his answer, that lead me to achieve that by XML.

Solution 10:[10]

I tried 2 hours for various solutions but none of those have a perfect effect.
The problem lies on the tabView's padding - even if I set its padding to all 0,
Different tabs still have different padding and therefore the text inside its TextView varies in size.

Then I found this library which perfectly solved it.
https://github.com/H07000223/FlycoTabLayout
As @Stas Melnychenko said, we probably can't do it unless we re-write a TabLayout. And this library does re-write a TabLayout...

Solution 11:[11]

There's a attribute called tabIndicatorFullWidth inside android.support.design.widget.TabLayout

<android.support.design.widget.TabLayout
    android:id="@+id/tablayout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabBackground="@color/colorPrimary"

    app:tabIndicatorFullWidth="false" // tested okay on 8.1 , 6.0.1 

    app:tabTextColor="@color/colorAccent"
    app:tabTextAppearance="@style/CampusTabText"
    android:minHeight="?attr/actionBarSize"
    app:tabMaxWidth="0dp"
    app:tabGravity="fill"
    app:tabMode="fixed" />

Solution 12:[12]

We can easily set the width of the Tab indicator with respect to its Tab Title. Because of the com.android.support:design:28.0.0, we can easily adjust it using below code in xml file:

app:tabIndicatorFullWidth="false"

Solution 13:[13]

For me app:tabIndicatorFullWidth="false" works perfectly. Here is my full code

<com.google.android.material.tabs.TabLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:tabIndicator="@drawable/my_tab_indicator"
                app:tabIndicatorColor="@color/tab_selected_unselected"
                app:tabIndicatorGravity="bottom"
                app:tabIndicatorHeight="@dimen/_3sdp"
                app:tabRippleColor="@color/transparent"
                app:tabMode="fixed"
                app:tabGravity="fill"
                app:tabIndicatorFullWidth="false"
                app:tabSelectedTextColor="@color/bottom_navigation_menu_selected"
                app:tabTextAppearance="@style/TabItemTextAppearance"
                app:tabTextColor="@color/title_1">

                <com.google.android.material.tabs.TabItem
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="?????????" />

                <com.google.android.material.tabs.TabItem
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="??????????" />


</com.google.android.material.tabs.TabLayout>

Solution 14:[14]

You just need to add app:tabMode="scrollable". It automatically resizes

Solution 15:[15]

Simply add spaces or spaces on either side of the text of the tab:

    tabLayout.addTab(tabLayout.newTab().setText("        Login        "));
    tabLayout.addTab(tabLayout.newTab().setText("        Sign Up        "));

and don't forget to set the attribute:

        app:tabIndicatorFullWidth="false"

in active.

here is the result: enter image description here

This is my code:

+) In .xml

<com.google.android.material.tabs.TabLayout
            android:id="@+id/tabLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabIndicatorColor="#D741C9EC"
            app:tabIndicatorHeight="3dp"
            app:tabSelectedTextColor="#D741C9EC"
            app:tabIndicatorFullWidth="false"

            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.0"
            tools:ignore="MissingConstraints"></com.google.android.material.tabs.TabLayout>

+) In adapter: LoginAdapter.java

public class LoginAdapter extends FragmentPagerAdapter {
    Context context;
    int tab;

    public LoginAdapter(@NonNull FragmentManager fm, Context context, int tab) {
        super(fm);
        this.context = context;
        this.tab = tab;
    }

    public LoginAdapter(@NonNull FragmentManager fm, int behavior, Context context, int tab) {
        super(fm, behavior);
        this.context = context;
        this.tab = tab;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
        switch (position){
            case 0:
                SignInFragment signInFragment = new SignInFragment();
                return signInFragment;
            case 1:
                SignUpFragment signUpFragment = new SignUpFragment();
                return signUpFragment;
            default:
                return null;
        }
    }

    @Override
    public int getCount() {
        return tab;
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
        switch (position) {
            case 0:
                return "                   Login                   ";
            case 1:
                return "                   Sign Up                   ";
        }
        return null;
    }
}

Solution 16:[16]

Wrap tab indicator width with respect to tab title and add right padding with tab title and right margin with tab indicator.

public class TabLayoutActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_tab_layout);

        TabLayout tabs = (TabLayout) findViewById(R.id.tabs);

        tabs.setTabTextColors(Color.parseColor("#727272"), Color.parseColor("#305b0d"));


        ViewPager pager = (ViewPager) findViewById(R.id.pager);
        TabsPagerAdapter adapter = new TabsPagerAdapter(getSupportFragmentManager());

        pager.setAdapter(adapter);
        tabs.setupWithViewPager(pager);

        wrapTabIndicatorToTitle(tabs,20,20);

    }
    public void wrapTabIndicatorToTitle(TabLayout tabLayout, int externalMargin, int internalMargin) {
        View tabStrip = tabLayout.getChildAt(0);
        if (tabStrip instanceof ViewGroup) {
            ViewGroup tabStripGroup = (ViewGroup) tabStrip;
            int childCount = ((ViewGroup) tabStrip).getChildCount();
            for (int i = 0; i < childCount; i++) {
                View tabView = tabStripGroup.getChildAt(i);


                //set minimum width to 0 for instead for small texts, indicator is not wrapped as expected
                tabView.setMinimumWidth(0);
                // set padding to 0 for wrapping indicator as title
                tabView.setPadding(0, tabView.getPaddingTop(), 40, tabView.getPaddingBottom());
                // setting custom margin between tabs
                if (tabView.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
                    ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) tabView.getLayoutParams();
                    if (i == 0) {
                        // left
                        setMargin(layoutParams, externalMargin, internalMargin);
                    } else if (i == childCount - 1) {
                        // right
                        setMargin(layoutParams, internalMargin, externalMargin);
                    } else {
                        // internal
                        setMargin(layoutParams, internalMargin, internalMargin);
                    }
                }
            }

            tabLayout.requestLayout();
        }
    }

    private void setMargin(ViewGroup.MarginLayoutParams layoutParams, int start, int end) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            layoutParams.setMarginStart(start);
            layoutParams.setMarginEnd(end);
        } else {
            layoutParams.leftMargin = start;
            layoutParams.rightMargin = end;
        }
    }
}