Android支持设计TabLayout:重力中心和模式可滚动


98

我正在尝试在项目中使用新的Design TabLayout。我希望布局能够适应每种屏幕尺寸和方向,但是可以在一个方向上正确看到它。

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

    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获得的

在此处输入图片说明

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

这是我对景观的期望;但是要拥有它,我需要设置MODE_FIXED,所以我得到的肖像是:

在此处输入图片说明

如果tabLayout适合屏幕,为什么GRAVITY_CENTER无法用于SCROLLABLE?有没有办法动态设置重力和模式(并查看我的期望)?

非常感谢你!

编辑:这是我的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您是否找到任何解决方案?我正面临着同样的问题。
Spynet

我发现的唯一方法是(手动)获取选项卡的大小,然后根据是否合适来动态配置布局参数。
哈维尔·德尔加多

@ Fighter42,可以为正在使用的解决方案发布一些示例代码吗?
萨米斯,2015年

1
我知道我的回答不利于逻辑,但对我有帮助。在文本前后放置一些空格。然后它将是可滚动的。在两种模式下。
2015年

Answers:


127

制表符只有重力作用MODE_FIXED

一种可能的解决方案是将设置layout_widthwrap_content和设置layout_gravitycenter_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。我遵循了androidhive.info/2015/09/中的示例。Tab scrollable在Kitkat和Jelly bean设备中不起作用。在棒棒糖中,我可以滚动Tab,但在棒棒糖设备之外,我无法滚动Tab,但可滑动正常。我可以知道会是什么问题。
user2681579

在API 15 / support.design:25.1.0上,这对我不起作用。当标签宽度小于窗口宽度时,标签会对齐。设置 app:tabMaxWidth="0dp"android:layout_centerHorizontal="true"工作到中心标签。
贝克

1
它为我工作。制表符始终居中,但是当它们实际上不可滚动时,它们不会填满所有宽度。
何塞Augustinho

14

这就是我做到的

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"
            />

创建时

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

我在可运行部分对@Mario Velasco的解决方案做了一些小的更改


该解决方案与我在Tiago解决方案中提到的问题相同。
索提(Sotti)

此解决方案在不带图标的选项卡上工作,如果我的图标带有文本怎么办?:/
Emre Tekin

@EmreTekin是的,它可以使用图标,我的选项卡上带有文本的图标
Flux

tabLayout.getWidth()总是为我返回零
ishandutta2007 2007年

9

保持简单,只需添加app:tabMode =“ scrollable”和android:layout_gravity =“”底部“

像这样

<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" />

在此处输入图片说明


7
这不是创作者所要求的。这种方法的问题在于,在手机中有2个标签页或3个三个标签页且文字较小的情况下,您的布局将类似于Google Play商店,标签页位于左侧。创建者希望选项卡在可能的情况下从另一边到达,否则滚动。
索蒂

同样的问题。你能解决吗?
Hoang Duc Tuan

8

由于我没有找到为什么会发生这种现象,因此我使用了以下代码:

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

基本上,我必须手动计算tabLayout的宽度,然后根据tabLayout是否适合设备来设置Tab模式。

我之所以要手动获取布局的大小,是因为并非所有选项卡在Scrollable模式下都具有相同的宽度,这可能会招致某些名称使用2行,就像在示例中遇到的那样。


使用此解决方案,文本可能会缩小,并且也可以使用2行。有关此主题,请参见此线程中的其他答案和解决方案。您正在减少出现这种情况的风险,但是当TabLayout小于screenWidth时,无论如何都会经常出现。
索提

7

看看android-tablayouthelper

自动切换TabLayout.MODE_FIXED和TabLayout.MODE_SCROLLABLE取决于选项卡的总宽度。


但是当标签标题是动态的时。文本进入下一行。如果使用自定义布局选项卡。末尾的文本为椭圆形,例如
AndroidGeek

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应该在选项卡式活动的默认模板中实现此功能。
某处某人2015年

这个答案与我在Tiago解决方案中提到的问题相同。
索提(Sotti)'16

4

我创建了AdaptiveTabLayout类来实现此目的。这是我发现真正解决问题,回答问题并避免/解决其他问题所没有的唯一方法。

笔记:

  • 处理电话/平板电脑的布局。
  • 处理有足够空间MODE_SCROLLABLE但没有足够空间容纳的情况MODE_FIXED。如果您不处理这种情况,它将在某些设备上发生,您会看到不同的文本大小,或者在某些选项卡中看到两行文本,这看起来很糟糕。
  • 它得到了真正的衡量,并且没有做任何假设(例如,屏幕为360dp宽或其他大小)。这适用于实际的屏幕尺寸和实际的标签尺寸。这意味着与翻译效果很好,因为不假定任何制表符大小,制表符即可度量。
  • 在onLayout阶段处理不同的过程,以避免额外的工作。
  • 布局宽度必须wrap_content在xml上。不要在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>

要旨

问题/寻求帮助:

  • 您会看到以下内容:

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

我不确定如何解决。有什么建议?

  • 为了使TabLayout成为子度量,我进行了一些强制转换和假设(就像该子项是包含其他视图的LinearLayout一样。)这可能会导致进一步的设计支持库更新出现问题。更好的方法/建议?

4
 <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"/>


1

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

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
快到了,但是有一些问题。最明显的问题是,在某些设备(和大多数慢速设备)中,您实际上会看到如何修改和更改布局。
索蒂

1
还有另一个问题,这种方法测量TabLayout的总宽度来确定是否可滚动。那不是很准确。在某些情况下,TabLayout在滚动模式下无法同时到达两个边缘,但是在不缩小滥用选项卡上的行数的文本(通常为2)的情况下,固定模式没有足够的空间。这是固定模式下的选项卡具有固定宽度(screenWidth / numberOfTabs)的直接结果,而在可滚动模式下,它们将包裹文本+填充。
索提

1

您所需要做的就是将以下内容添加到TabLayout中

custom:tabGravity="fill"

因此,您将拥有:

xmlns:custom="http://schemas.android.com/apk/res-auto"
<android.support.design.widget.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        custom:tabGravity="fill"
        />

这不是创作者所要求的。这里的问题是选项卡将永远无法滚动,并且您将摆脱屏幕上的空间。选项卡将开始缩小其文本,最终它们将有两行。
索提(Sotti)

1

我使用以下解决了这个问题

if(tabLayout_chemistCategory.getTabCount()<4)
    {
        tabLayout_chemistCategory.setTabGravity(TabLayout.GRAVITY_FILL);
    }else
    {
        tabLayout_chemistCategory.setTabMode(TabLayout.MODE_SCROLLABLE);

    }

1

非常简单的示例,它始终有效。

/**
 * Setup stretch and scrollable TabLayout.
 * The TabLayout initial parameters in layout must be:
 * android:layout_width="wrap_content"
 * app:tabMaxWidth="0dp"
 * app:tabGravity="fill"
 * app:tabMode="fixed"
 *
 * @param context   your Context
 * @param tabLayout your TabLayout
 */
public static void setupStretchTabLayout(Context context, TabLayout tabLayout) {
    tabLayout.post(() -> {

        ViewGroup.LayoutParams params = tabLayout.getLayoutParams();
        if (params.width == ViewGroup.LayoutParams.MATCH_PARENT) { // is already set up for stretch
            return;
        }

        int deviceWidth = context.getResources()
                                 .getDisplayMetrics().widthPixels;

        if (tabLayout.getWidth() < deviceWidth) {
            tabLayout.setTabMode(TabLayout.MODE_FIXED);
            params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        } else {
            tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
            params.width = ViewGroup.LayoutParams.WRAP_CONTENT;
        }
        tabLayout.setLayoutParams(params);

    });

}

谢谢你的主意。我的案子有了一点点变化,就像魅力一样工作
ErenTüfekçi

1

我的最终解决方案

class DynamicModeTabLayout : 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 setupWithViewPager(viewPager: ViewPager?) {
        super.setupWithViewPager(viewPager)

        val view = getChildAt(0) ?: return
        view.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
        val size = view.measuredWidth

        if (size > measuredWidth) {
            tabMode = MODE_SCROLLABLE
            tabGravity = GRAVITY_CENTER
        } else {
            tabMode = MODE_FIXED
            tabGravity = GRAVITY_FILL
        }
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.