Android 支持设计 TabLayout: Gravity Center and Mode Scrollable
Posted
技术标签:
【中文标题】Android 支持设计 TabLayout: Gravity Center and Mode Scrollable【英文标题】:Android Support Design TabLayout: Gravity Center and Mode Scrollable 【发布时间】:2015-08-17 10:47:05 【问题描述】:我正在尝试在我的项目中使用新的 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 用于填充的重力 尽可能使用 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_
android:background="@color/orange_pager"
android:layout_ />
【问题讨论】:
@Fighter42 你找到解决办法了吗?我也面临同样的问题。 我发现的唯一方法是(手动)获取选项卡的大小,然后根据是否合适来动态配置布局参数。 @Fighter42,您可以为您正在使用的解决方案发布一些示例代码吗? 我知道我的回答对逻辑不好,但对我有帮助。在文本前后放置一些空格。然后它将是可滚动的。在这两种模式下。 【参考方案1】:制表符重力仅影响MODE_FIXED
。
一种可能的解决方案是将您的layout_width
设置为wrap_content
并将layout_gravity
设置为center_horizontal
:
<android.support.design.widget.TabLayout
android:id="@+id/sliding_tabs"
android:layout_
android:layout_
android:layout_gravity="center_horizontal"
app:tabMode="scrollable" />
如果选项卡小于屏幕宽度,TabLayout
本身也会更小,并且会因为重力而居中。如果选项卡大于屏幕宽度,TabLayout
将匹配屏幕宽度并激活滚动。
【讨论】:
我正在为我的应用程序使用材料设计。我遵循了来自androidhive.info/2015/09/… 的示例@ Tab scrollable is not working in Kitkat and Jelly bean devices。在棒棒糖中,我可以滚动选项卡,但在棒棒糖设备以外的选项卡可滚动不起作用,但滑动工作正常。我可以知道会是什么问题。 这对我在 API 15 / support.design:25.1.0 上不起作用。选项卡在窄于窗口宽度时左对齐。设置app:tabMaxWidth="0dp"
和 android:layout_centerHorizontal="true"
可以使标签居中。
它对我有用。选项卡始终居中,但当它们实际上不可滚动时,它们不会填满所有宽度。【参考方案2】:
我在可运行部分对@Mario Velasco's solution 做了一些小改动:
TabLayout.xml
<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_
android:layout_
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);
;
【讨论】:
这个解决方案和我在Tiago的解决方案中提到的问题一样。 此解决方案适用于没有图标的选项卡,如果我有带文字的图标怎么办? :// @EmreTekin 是的,它与图标一起使用,我的标签有带文本的图标tabLayout.getWidth()
总是为我返回零【参考方案3】:
由于我没有找到为什么会发生这种行为,我使用了以下代码:
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 小一点时经常出现。【参考方案4】:让事情变得简单,只需添加 app:tabMode="scrollable" 和 android:layout_gravity="bottom"
就这样
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_
android:layout_
android:layout_gravity="bottom"
app:tabMode="scrollable"
app:tabIndicatorColor="@color/colorAccent" />
【讨论】:
这不是创作者所要求的。这种方法的问题在于,在文本很小的手机中有 2 个标签或 3 个三个标签的情况下,您将拥有类似于 Google Play 商店的布局,其中标签位于左侧。创建者希望标签在可能的情况下从一个边缘延伸到另一个边缘,否则滚动。 同样的问题。可以修吗?【参考方案5】:看android-tablayouthelper
自动切换 TabLayout.MODE_FIXED 和 TabLayout.MODE_SCROLLABLE 取决于总标签宽度。
【讨论】:
但是当标签标题是动态的。文本进入下一行。如果对选项卡使用自定义布局。文本省略号结尾像【参考方案6】:我创建了一个 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_
android:layout_>
<com.com.***.example.AdaptiveTabLayout
android:layout_
android:layout_
android:background="?colorPrimary"
app:tabIndicatorColor="@color/white"
app:tabSelectedTextColor="@color/text_white_primary"
app:tabTextColor="@color/text_white_secondary"
tools:layout_/>
</FrameLayout>
Gist
问题/寻求帮助:
你会看到这个:Logcat:
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$SlidingTabStrip3e1ebcd6 V.ED.... ......ID 0,0-466,96 during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView3423cb57 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$TabView377c4644 VFE...C. ......ID 144,0-322,96 during layout: running second layout pass
W/View: requestLayout() improperly called by android.support.design.widget.TabLayout$TabView19ead32d VFE...C. ......ID 322,0-466,96 during layout: running second layout pass
我不知道如何解决它。有什么建议吗?
为了使 TabLayout 子度量,我进行了一些强制转换和假设(就像子是包含其他视图的 LinearLayout 一样......) 这可能会导致进一步的设计支持库更新出现问题。更好的方法/建议?【讨论】:
【参考方案7】: <android.support.design.widget.TabLayout
android:id="@+id/tabList"
android:layout_
android:layout_
android:layout_gravity="center"
app:tabMode="scrollable"/>
【讨论】:
【参考方案8】:这是我用来在SCROLLABLE
和FIXED
+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_
android:layout_
android:layout_gravity="center_horizontal" />
如果你不需要填充宽度,最好使用@karaokyo 解决方案。
【讨论】:
这很好用;在横向和纵向之间切换完全符合您的预期。 Google 应该在选项卡式活动的默认模板中实现这一点。 这个答案和我在Tiago的解决方案中提到的问题一样。【参考方案9】:这是唯一对我有用的代码:
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_
android:layout_
app:tabMaxWidth="0dp"/>
【讨论】:
快到了,但有一些问题。最明显的问题是,在某些设备(以及大多数速度较慢的设备)中,您实际上会看到布局是如何修改和更改的。 还有一个问题,这种方式通过测量 TabLayout 的总宽度来决定是否可以滚动。这不是很准确。有一些情况是 TabLayout 在可滚动模式下没有到达两个边缘,但是没有足够的空间用于固定模式而不缩小滥用选项卡上的行数(通常为 2)的文本。这是固定模式下的选项卡具有固定宽度(screenWidth/numberOfTabs)的直接结果,而在可滚动模式下,它们会包裹文本+填充。【参考方案10】:非常简单的例子,它总是有效的。
/**
* Setup stretch and scrollable TabLayout.
* The TabLayout initial parameters in layout must be:
* android:layout_
* 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);
);
【讨论】:
感谢您的想法。稍微改变一下我的情况,就像魅力一样【参考方案11】:我认为更好的方法是设置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"
/>
【讨论】:
【参考方案12】:您只需将以下内容添加到您的 TabLayout
custom:tabGravity="fill"
那么你将拥有:
xmlns:custom="http://schemas.android.com/apk/res-auto"
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_
android:layout_
custom:tabGravity="fill"
/>
【讨论】:
这不是创作者所要求的。这里的问题是选项卡永远无法滚动,并且您将摆脱屏幕上的空间。选项卡将开始缩小它的文本,最终它们会变成两行。【参考方案13】:if(tabLayout_chemistCategory.getTabCount()<4)
tabLayout_chemistCategory.setTabGravity(TabLayout.GRAVITY_FILL);
else
tabLayout_chemistCategory.setTabMode(TabLayout.MODE_SCROLLABLE);
【讨论】:
【参考方案14】: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
【讨论】:
【参考方案15】:当你在 tablayout 中添加标签时,在你的活动中添加这一行
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
【讨论】:
【参考方案16】: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
【讨论】:
以上是关于Android 支持设计 TabLayout: Gravity Center and Mode Scrollable的主要内容,如果未能解决你的问题,请参考以下文章
Android 支持设计 TabLayout: Gravity Center and Mode Scrollable