安卓支持设计的TabLayout:居中对齐和可滚动模式

112

我正在尝试在我的项目中使用新的Design TabLayout。我希望该布局适应每个屏幕大小和方向,但只能在一个方向上正确显示。

我正在处理将Gravity和Mode设置为我的tabLayout:

    tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER);
    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);

因此,我期望如果没有空间,tabLayout 可以滚动,但如果有空间,则居中显示。

从指南中可以看到:

public static final int GRAVITY_CENTER 表示在 TabLayout 中居中布局选项卡的重力。

public static final int GRAVITY_FILL 表示尽可能填充 TabLayout 的重力。仅在 MODE_FIXED 模式下使用此选项有效。

public static final int MODE_FIXED 固定标签同时显示所有选项卡,并且最适合用于需要快速在选项卡之间进行切换的内容。选项卡的最大数量受视图宽度限制。 固定选项卡的宽度相等,基于最宽的选项卡标签。

public static final int MODE_SCROLLABLE 可滚动选项卡在任何给定时刻只显示一部分选项卡,并且可以包含更长的选项卡标签和更多选项卡。当用户不需要直接比较选项卡标签时,在触摸界面中浏览上下文时最好使用它们。

因此 GRAVITY_FILL 仅与 MODE_FIXED 兼容,但是由于没有指定 GRAVITY_CENTER 的情况下,我期望它与 MODE_SCROLLABLE 兼容,但是这是我在使用 GRAVITY_CENTER 和 MODE_SCROLLABLE 时看到的结果:

enter image description here

因此,在两个方向上都使用了 SCROLLABLE,但未使用 GRAVITY_CENTER。

这是我期望的横向布局;但是要实现这一点,我需要设置 MODE_FIXED,因此在纵向布局中得到以下结果:

enter image description here

如果 TabLayout 可以适应屏幕大小,为什么 GRAVITY_CENTER 不起作用?有没有办法动态设置重力和模式(并查看我期望的效果)?

非常感谢!

编辑:这是我的 TabLayout 布局:

<android.support.design.widget.TabLayout
    android:id="@+id/sliding_tabs"
    android:layout_width="match_parent"
    android:background="@color/orange_pager"
    android:layout_height="wrap_content" />

@Fighter42,你找到解决办法了吗?我也遇到了同样的问题。 - Akhil
我找到的唯一方法是手动获取选项卡的大小,然后根据其是否适合动态配置布局参数。 - Javier Delgado
@Fighter42,你能发一些你正在使用的解决方案的示例代码吗? - Sammys
1
我知道我的答案在逻辑上不是很好,但它对我有帮助。在文本前后加上一些空格,这样它就可以在两种模式下滚动。 - AmmY
16个回答

146

选项卡重力只影响MODE_FIXED

一种可能的解决方案是将您的layout_width设置为wrap_content,并将layout_gravity设置为center_horizontal

<android.support.design.widget.TabLayout
    android:id="@+id/sliding_tabs"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    app:tabMode="scrollable" />
如果选项卡比屏幕宽度小,则TabLayout本身也会更小,并且由于重力而居中。如果选项卡比屏幕宽度大,则TabLayout将与屏幕宽度匹配,滚动将被激活。

1
我正在使用Material Design来开发我的应用程序。我按照http://www.androidhive.info/2015/09/android-material-design-working-with-tabs/上的示例进行操作。在Kitkat和Jelly Bean设备上,选项卡可滚动功能无法正常工作。在Lollipop上,我可以滚动选项卡,但在其他版本的设备上,选项卡的滚动功能无法正常工作,但是滑动功能正常。请问可能是什么问题呢? - user2681579
这在 API 15 / support.design:25.1.0 上对我无效。当选项卡宽度小于窗口宽度时,选项卡仍然左对齐。设置 app:tabMaxWidth =“0dp”android:layout_centerHorizontal =“true” 可以将选项卡居中对齐。 - Baker
1
它对我起作用了。选项卡始终居中,但当它们实际上不可滚动时,它们不会填充所有宽度。 - José Augustinho

20

我在@Mario Velasco的解决方案中对可运行部分进行了小修改:

TabLayout.xml

<android.support.design.widget.TabLayout
            android:id="@+id/tab_layout"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            android:background="@android:color/transparent"
            app:tabGravity="fill"
            app:tabMode="scrollable"
            app:tabTextAppearance="@style/TextAppearance.Design.Tab"
            app:tabSelectedTextColor="@color/myPrimaryColor"
            app:tabIndicatorColor="@color/myPrimaryColor"
            android:overScrollMode="never"
            />

Oncreate

的翻译是

Oncreate

@Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mToolbar = (Toolbar) findViewById(R.id.toolbar_actionbar);
        mTabLayout = (TabLayout)findViewById(R.id.tab_layout);
        mTabLayout.setOnTabSelectedListener(this);
        setSupportActionBar(mToolbar);

        mTabLayout.addTab(mTabLayout.newTab().setText("Dashboard"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Signature"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Booking/Sampling"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Calendar"));
        mTabLayout.addTab(mTabLayout.newTab().setText("Customer Detail"));

        mTabLayout.post(mTabLayout_config);
    }

    Runnable mTabLayout_config = new Runnable()
    {
        @Override
        public void run()
        {
              
           if(mTabLayout.getWidth() < MainActivity.this.getResources().getDisplayMetrics().widthPixels)
            {
                mTabLayout.setTabMode(TabLayout.MODE_FIXED);
                ViewGroup.LayoutParams mParams = mTabLayout.getLayoutParams();
                mParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
                mTabLayout.setLayoutParams(mParams);

            }
            else
            {
                mTabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            }
        }
    };

这个解决方案存在我在 Tiago 的解决方案中提到的相同问题。 - Sotti
这个解决方案适用于没有图标的选项卡,如果我有带文本的图标怎么办?:/ - Emre Tekin
@EmreTekin 是的,它可以使用图标,我的选项卡带有图标和文本。 - Flux
tabLayout.getWidth() 对我总是返回零。 - ishandutta2007

14

由于我没有找到这种行为发生的原因,因此我使用了以下代码:

float myTabLayoutSize = 360;
if (DeviceInfo.getWidthDP(this) >= myTabLayoutSize ){
    tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
    tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}

基本上,我需要手动计算我的tabLayout的宽度,然后根据tabLayout是否适合设备来设置Tab Mode。

我手动获取布局大小的原因是因为在可滚动模式下,并不是所有选项卡都具有相同的宽度,这可能会导致一些名称使用2行,就像在示例中发生的那样。


通过这个解决方案,文本可能会被缩小并使用2行。请参阅此线程中的其他答案和解决方案。虽然TabLayout比屏幕宽度小一点时,您正在减少出现此问题的风险,但它仍然经常出现。 - Sotti

8

将App的选项卡模式设置为可滚动,只需添加app:tabMode="scrollable"和android:layout_gravity= "bottom"。就像这样:

就像这样

<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="bottom"
app:tabMode="scrollable"
app:tabIndicatorColor="@color/colorAccent" />

enter image description here


7
这不是创作者要求的。这种方法的问题在于,当你在手机上有2个或3个选项卡且文本很小的情况下,你会像Google Play商店那样,将选项卡放在左侧,布局会受到影响。创作者希望在可能的情况下,选项卡能够从边缘到边缘延伸,并在需要时滚动。 - Sotti
同样的问题。你可以解决吗? - Hoang Duc Tuan

7

请查看android-tablayouthelper

根据标签总宽度自动切换TabLayout.MODE_FIXED和TabLayout.MODE_SCROLLABLE。


1
但是当选项卡标题是动态的时候,文本会进入下一行。如果使用自定义布局来制作选项卡,则文本将在末尾省略号。 - Zar E Ahmer

5
我创建了一个AdaptiveTabLayout类来解决这个问题。这是我找到的唯一解决问题,回答问题并避免/解决其他答案中存在的问题的方法。
备注:
- 处理手机/平板电脑布局。 - 处理有足够空间使用MODE_SCROLLABLE但没有足够空间使用MODE_FIXED的情况。如果您不处理此情况,则在某些设备上会出现不同的文本大小或甚至在某些选项卡中有两行文本的情况,这看起来很糟糕。 - 它获取真实的尺寸并且不做任何假设(如屏幕宽度为360dp或其他...)。这适用于真实的屏幕尺寸和真实的选项卡尺寸。这意味着可以很好地处理翻译,因为它不会假设任何选项卡大小,而是测量选项卡大小。 - 处理onLayout阶段的不同传递,以避免额外的工作。 - 布局宽度需要在xml上设置wrap_content。不要在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;
    }

}

这是DeviceUtils类:

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

}

使用例:

<?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

问题/寻求帮助:

  • 您将看到以下内容:

日志记录:

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

我不确定如何解决它。有任何建议吗?

  • 为了使TabLayout子元素测量,我进行了一些强制转换和假设(例如子元素是包含其他视图的LinearLayout....) 这可能会在进一步的Design Support Library更新中引起问题。有更好的方法或建议吗?

5
 <android.support.design.widget.TabLayout
                    android:id="@+id/tabList"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center"
                    app:tabMode="scrollable"/>


4
这是我用来自动在SCROLLABLEFIXED+FILL之间切换的解决方案。以下是@Fighter42解决方案的完整代码: (下面的代码显示了如果您使用了Google的选项卡活动模板应该放置修改的位置)
@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    // Create the adapter that will return a fragment for each of the three
    // primary sections of the activity.
    mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());

    // Set up the ViewPager with the sections adapter.
    mViewPager = (ViewPager) findViewById(R.id.container);
    mViewPager.setAdapter(mSectionsPagerAdapter);

    // Set up the tabs
    final TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
    tabLayout.setupWithViewPager(mViewPager);

    // Mario Velasco's code
    tabLayout.post(new Runnable()
    {
        @Override
        public void run()
        {
            int tabLayoutWidth = tabLayout.getWidth();

            DisplayMetrics metrics = new DisplayMetrics();
            ActivityMain.this.getWindowManager().getDefaultDisplay().getMetrics(metrics);
            int deviceWidth = metrics.widthPixels;

            if (tabLayoutWidth < deviceWidth)
            {
                tabLayout.setTabMode(TabLayout.MODE_FIXED);
                tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
            } else
            {
                tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            }
        }
    });
}

布局:

    <android.support.design.widget.TabLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />

如果您不需要填满宽度,最好使用@karaokyo的解决方案。


这个很好用;在横屏和竖屏之间切换正是你所期望的。Google 应该在选项卡活动的默认模板中实现这一点。 - Someone Somewhere
这个答案存在我在 Tiago 的解决方案中提到的相同问题。 - Sotti

3
我认为更好的方法是设置app:tabMode="auto"app:tabGravity="fill",因为将tabMode设置为fixed会使标题拥挤,并导致标题在其他屏幕尺寸上占据多行;而将其设置为scrollable可能会在某些屏幕尺寸上留下空白。手动设置tabMode时处理多个屏幕尺寸时会出现问题。
    <com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"       
    app:tabGravity="fill"
    android:textAlignment="center"
    app:tabMode="auto"
    />

2

这是唯一对我有效的代码:

public static void adjustTabLayoutBounds(final TabLayout tabLayout,
                                         final DisplayMetrics displayMetrics){

    final ViewTreeObserver vto = tabLayout.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {

            tabLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);

            int totalTabPaddingPlusWidth = 0;
            for(int i=0; i < tabLayout.getTabCount(); i++){

                final LinearLayout tabView = ((LinearLayout)((LinearLayout) tabLayout.getChildAt(0)).getChildAt(i));
                totalTabPaddingPlusWidth += (tabView.getMeasuredWidth() + tabView.getPaddingLeft() + tabView.getPaddingRight());
            }

            if (totalTabPaddingPlusWidth <= displayMetrics.widthPixels){

                tabLayout.setTabMode(TabLayout.MODE_FIXED);
                tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);

            }else{
                tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            }

            tabLayout.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
        }
    });
}

可以使用以下代码获取DisplayMetrics:

public DisplayMetrics getDisplayMetrics() {

    final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    final Display display = wm.getDefaultDisplay();
    final DisplayMetrics displayMetrics = new DisplayMetrics();

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
        display.getMetrics(displayMetrics);

    }else{
        display.getRealMetrics(displayMetrics);
    }

    return displayMetrics;
}

你的TabLayout XML应该像这样(不要忘记将tabMaxWidth设置为0):

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

1
这已经接近完成了,但还存在一些问题。最明显的问题是,在某些设备上(尤其是较慢的设备),您实际上会看到布局被修改和更改的情况。 - Sotti
1
还有一个问题,这种方法测量TabLayout的总宽度来决定是否可以滚动。这不是非常精确的。在可滚动模式下,有些情况下TabLayout并没有达到两侧,但是没有足够的空间进入固定模式而不缩小文本或滥用标签上的行数(通常为2)。这是固定模式下选项卡具有固定宽度(screenWidth/numberOfTabs),而在可滚动模式下它们正在包装文本和填充的直接结果。 - Sotti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接