I created an AdaptiveTabLayout class to achieve this. This was the only way I found to actually solve the problem, answer the question and avoid/workaround problems that other answers here don't.
Notes:
- Handles phone/tablet layouts.
- Handles cases where there's enough
room for
MODE_SCROLLABLE
but not enough room for MODE_FIXED
. If
you don't handle this case it's gonna happen on some devices you'll
see different text sizes or oven two lines of text in some tabs, which
looks bad.
- It gets real measures and doesn't make any assumptions (like screen is 360dp wide or whatever...). This works with real screen sizes and real tab sizes. This means works well with translations because doesn't assume any tab size, the tabs get measure.
- Deals with the different passes on the onLayout phase in order to
avoid extra work.
- Layout width needs to be
wrap_content
on the xml. Don't set any mode or gravity on the xml.
AdaptiveTabLayout.java
import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.TabLayout;
import android.util.AttributeSet;
import android.widget.LinearLayout;
public class AdaptiveTabLayout extends TabLayout
{
private boolean mGravityAndModeSeUpNeeded = true;
public AdaptiveTabLayout(@NonNull final Context context)
{
this(context, null);
}
public AdaptiveTabLayout(@NonNull final Context context, @Nullable final AttributeSet attrs)
{
this(context, attrs, 0);
}
public AdaptiveTabLayout
(
@NonNull final Context context,
@Nullable final AttributeSet attrs,
final int defStyleAttr
)
{
super(context, attrs, defStyleAttr);
setTabMode(MODE_SCROLLABLE);
}
@Override
protected void onLayout(final boolean changed, final int l, final int t, final int r, final int b)
{
super.onLayout(changed, l, t, r, b);
if (mGravityAndModeSeUpNeeded)
{
setModeAndGravity();
}
}
private void setModeAndGravity()
{
final int tabCount = getTabCount();
final int screenWidth = UtilsDevice.getScreenWidth();
final int minWidthNeedForMixedMode = getMinSpaceNeededForFixedMode(tabCount);
if (minWidthNeedForMixedMode == 0)
{
return;
}
else if (minWidthNeedForMixedMode < screenWidth)
{
setTabMode(MODE_FIXED);
setTabGravity(UtilsDevice.isBigTablet() ? GRAVITY_CENTER : GRAVITY_FILL) ;
}
else
{
setTabMode(TabLayout.MODE_SCROLLABLE);
}
setLayoutParams(new LinearLayout.LayoutParams
(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
mGravityAndModeSeUpNeeded = false;
}
private int getMinSpaceNeededForFixedMode(final int tabCount)
{
final LinearLayout linearLayout = (LinearLayout) getChildAt(0);
int widestTab = 0;
int currentWidth;
for (int i = 0; i < tabCount; i++)
{
currentWidth = linearLayout.getChildAt(i).getWidth();
if (currentWidth == 0) return 0;
if (currentWidth > widestTab)
{
widestTab = currentWidth;
}
}
return widestTab * tabCount;
}
}
And this is the DeviceUtils class:
import android.content.res.Resources;
public class UtilsDevice extends Utils
{
private static final int sWIDTH_FOR_BIG_TABLET_IN_DP = 720;
private UtilsDevice() {}
public static int pixelToDp(final int pixels)
{
return (int) (pixels / Resources.getSystem().getDisplayMetrics().density);
}
public static int getScreenWidth()
{
return Resources
.getSystem()
.getDisplayMetrics()
.widthPixels;
}
public static boolean isBigTablet()
{
return pixelToDp(getScreenWidth()) >= sWIDTH_FOR_BIG_TABLET_IN_DP;
}
}
Use example:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
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">
<com.com.stackoverflow.example.AdaptiveTabLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?colorPrimary"
app:tabIndicatorColor="@color/white"
app:tabSelectedTextColor="@color/text_white_primary"
app:tabTextColor="@color/text_white_secondary"
tools:layout_width="match_parent"/>
</FrameLayout>
Gist
Problems/Ask for help:
Logcat:
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$SlidingTabStrip{3e1ebcd6 V.ED.... ......ID 0,0-466,96} during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView{3423cb57 VFE...C. ..S...ID 0,0-144,96} during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView{377c4644 VFE...C. ......ID 144,0-322,96} during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView{19ead32d VFE...C. ......ID 322,0-466,96} during layout: running second layout pass
I'm not sure how to solve it. Any suggestions?
- To make the TabLayout child measures, I'm making some castings and assumptions (Like the child is a LinearLayout containing other views....)
This might cause problems with in further Design Support Library updates. A better approach/suggestions?