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

时间:2015-06-03 09:45:12

标签: android android-5.0-lollipop material-design gravity

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

我正在处理重力和模式设置我的tabLayout为:

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

所以我希望如果没有空间,tabLayout是可滚动的,但如果有空间,它就会居中。

从指南中:

  

public static final int GRAVITY_CENTER Gravity用于布局   TabLayout中心的标签。

     

public static final int GRAVITY_FILL Gravity用于填充   TabLayout尽可能多。此选项仅在使用时生效   使用MODE_FIXED。

     

public static final int MODE_FIXED固定标签显示所有标签   同时并且最好与快速受益的内容一起使用   标签之间的枢轴。选项卡的最大数量受限于   视图的宽度。固定标签具有相同的宽度,基于最宽的标签   标签

     

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

所以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不能用于SCROLLABLE? 有没有办法动态设置重力和模式(并看看我期待什么)?

非常感谢!

EDITED:这是我的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" />

15 个答案:

答案 0 :(得分:107)

Tab重力仅影响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 :(得分:13)

这就是我做的方式

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

@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的解决方案做了一些小改动

答案 2 :(得分:7)

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

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

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

我手动获取布局大小的原因是因为并非所有选项卡在Scrollable模式下都具有相同的宽度,这可能会引发一些名称使用2行,因为它在示例中发生在我身上。

答案 3 :(得分:7)

查看android-tablayouthelper

  

自动切换TabLayout.MODE_FIXED和   TabLayout.MODE_SCROLLABLE取决于总标签宽度。

答案 4 :(得分:6)

保持简单只需添加app:tabMode =&#34;可滚动&#34; 和android:layout_gravity =&#34; bottom&#34;

就像这样

<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

答案 5 :(得分: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解决方案。

答案 6 :(得分:3)

我创建了一个AdaptiveTabLayout类来实现这一目标。这是我找到实际解决问题的唯一方法,回答问题并避免/解决其他答案不存在的问题。

注意:

  • 处理手机/平板电脑布局。
  • 处理足够的案例 MODE_SCROLLABLE的空间,但MODE_FIXED空间不足。如果 你不处理这种情况会发生在某些设备上你会发生的事情 在某些标签中查看不同的文字大小或烤箱两行文字 看起来很糟糕。
  • 它得到了真正的衡量标准,并没有做任何假设(比如屏幕宽360dp或其他......)。这适用于实际屏幕尺寸和实际标签尺寸。这意味着可以很好地处理翻译,因为不会假设任何标签大小,标签会被测量。
  • 为on来处理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

问题/寻求帮助:

  • 你会看到:

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 ......) 这可能会导致进一步的Design Support Library更新出现问题。更好的方法/建议?

答案 7 :(得分: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"/>

答案 8 :(得分: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"
        />

答案 9 :(得分:1)

我认为更好的方法是设置 app:tabMode="auto"app:tabGravity="fill" 因为将 tabMode 设置为 fixed 会使标题拥挤并导致标题在另一侧占据多行,将其设置为可滚动可能会使它们在某些屏幕尺寸的末尾留出空间。手动设置 tabMode 在处理多个屏幕尺寸时会出现问题

    <com.google.android.material.tabs.TabLayout
    android:id="@+id/tabLayout"       
    app:tabGravity="fill"
    android:textAlignment="center"
    app:tabMode="auto"
    />

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

/**
 * 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);

    });

}

答案 12 :(得分:0)

我的最终解决方案

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

答案 13 :(得分:0)

在 tablayout 中添加标签时,在您的活动中添加此行

 tabLayout.setTabGravity(TabLayout.GRAVITY_FILL); 

答案 14 :(得分:0)

Sotti's solution 很棒!它的工作原理与基础组件的工作原理完全一样。

在我的例子中,选项卡可以根据过滤器的变化动态发展,所以我做了一些小的调整,以允许使用 redraw() 方法更新选项卡模式。它也在 Kotlin 中

class AdaptiveTabLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : TabLayout(context, attrs, defStyleAttr) {
    private var gravityAndModeSeUpNeeded = true

    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        super.onLayout(changed, l, t, r, b)
        if (gravityAndModeSeUpNeeded) {
            setModeAndGravity()
        }
    }

    fun redraw() {
        post {
            tabMode = MODE_SCROLLABLE
            gravityAndModeSeUpNeeded = true
            invalidate()
        }
    }

    private fun setModeAndGravity() {
        val tabCount = tabCount
        val screenWidth = Utils.getScreenWidth()
        val minWidthNeedForMixedMode = getMinSpaceNeededForFixedMode(tabCount)
        if (minWidthNeedForMixedMode == 0) {
            return
        } else if (minWidthNeedForMixedMode < screenWidth) {
            tabMode = MODE_FIXED
            tabGravity = if (Utils.isBigTablet()) GRAVITY_CENTER else GRAVITY_FILL
        } else {
            tabMode = MODE_SCROLLABLE
        }
        gravityAndModeSeUpNeeded = false
    }

    private fun getMinSpaceNeededForFixedMode(tabCount: Int): Int {
        val linearLayout = getChildAt(0) as LinearLayout
        var widestTab = 0
        var currentWidth: Int
        for (i in 0 until tabCount) {
            currentWidth = linearLayout.getChildAt(i).width
            if (currentWidth == 0) return 0
            if (currentWidth > widestTab) {
                widestTab = currentWidth
            }
        }
        return widestTab * tabCount
    }

    init {
        tabMode = MODE_SCROLLABLE
    }
}

如果你喜欢这个片段,请点赞 Sotti 的解决方案,它为我节省了很多时间。