Android支持设计TabLayout:重心和模式可滚动
Android Support Design TabLayout: Gravity Center and Mode Scrollable
我正在尝试在我的项目中使用新的 Design TabLayout。我希望布局适应每个屏幕尺寸和方向,但它可以在一个方向上正确看到。
我正在处理重力和模式,将我的 tabLayout 设置为:
tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
所以我希望如果没有空间,tabLayout 是可滚动的,但如果有空间,它会居中。
来自指南:
public static final int GRAVITY_CENTER Gravity used to lay out the
tabs in the center of the TabLayout.
public static final int GRAVITY_FILL Gravity used to fill the
TabLayout as much as possible. This option only takes effect when used
with MODE_FIXED.
public static final int MODE_FIXED Fixed tabs display all tabs
concurrently and are best used with content that benefits from quick
pivots between tabs. The maximum number of tabs is limited by the
view’s width. Fixed tabs have equal width, based on the widest tab
label.
public static final int MODE_SCROLLABLE Scrollable tabs display a
subset of tabs at any given moment, and can contain longer tab labels
and a larger number of tabs. They are best used for browsing contexts
in touch interfaces when users don’t need to directly compare the tab
labels.
所以 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" />
因为我没有找到为什么会发生这种行为,所以我使用了以下代码:
float myTabLayoutSize = 360;
if (DeviceInfo.getWidthDP(this) >= myTabLayoutSize ){
tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}
基本上,我必须手动计算 tabLayout 的宽度,然后根据 tabLayout 是否适合设备设置选项卡模式。
我手动获取布局大小的原因是因为在 Scrollable 模式下并非所有选项卡都具有相同的宽度,这可能会导致某些名称使用 2 行,就像我在示例中遇到的那样。
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
将匹配屏幕宽度并激活滚动。
Automatically switch TabLayout.MODE_FIXED and
TabLayout.MODE_SCROLLABLE depends on total tab width.
这是我用来在SCROLLABLE
和FIXED
+FILL
之间自动切换的解决方案。这是@Fighter42解决方案的完整代码:
(如果您使用了 Google 的选项卡式 activity 模板,下面的代码显示了修改的位置)
@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 解决方案。
我在可运行部分对 做了一些小改动:
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);
}
}
};
这是唯一对我有用的代码:
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"/>
让事情变得简单只需添加 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" />
您只需将以下内容添加到您的 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"
/>
我创建了一个 AdaptiveTabLayout class 来实现这一点。这是我发现真正解决问题、回答问题和 avoid/workaround 其他答案没有的问题的唯一方法。
备注:
- 处理 phone/tablet 布局。
- 处理足够的情况
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 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;
}
}
使用示例:
<?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.Whosebug.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>
Problems/Ask求助:
- 你会看到这个:
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 child 措施,我正在做一些转换和假设(比如 child 是一个包含其他视图的 LinearLayout....)
这可能会导致进一步的设计支持库更新出现问题。更好的approach/suggestions?
if(tabLayout_chemistCategory.getTabCount()<4)
{
tabLayout_chemistCategory.setTabGravity(TabLayout.GRAVITY_FILL);
}else
{
tabLayout_chemistCategory.setTabMode(TabLayout.MODE_SCROLLABLE);
}
<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"/>
非常简单的示例,它总是有效。
/**
* 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);
});
}
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
}
}
}
当您在 tablayout 中添加选项卡时,将此行添加到您的活动中
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
我认为更好的方法是设置 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"
/>
太棒了!它的工作原理与基本组件的工作原理完全相同。
在我的例子中,选项卡可以根据过滤器的变化动态演变,所以我做了一些小的调整以允许使用 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
}
}
我正在尝试在我的项目中使用新的 Design TabLayout。我希望布局适应每个屏幕尺寸和方向,但它可以在一个方向上正确看到。
我正在处理重力和模式,将我的 tabLayout 设置为:
tabLayout.setTabGravity(TabLayout.GRAVITY_CENTER);
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
所以我希望如果没有空间,tabLayout 是可滚动的,但如果有空间,它会居中。
来自指南:
public static final int GRAVITY_CENTER Gravity used to lay out the tabs in the center of the TabLayout.
public static final int GRAVITY_FILL Gravity used to fill the TabLayout as much as possible. This option only takes effect when used with MODE_FIXED.
public static final int MODE_FIXED Fixed tabs display all tabs concurrently and are best used with content that benefits from quick pivots between tabs. The maximum number of tabs is limited by the view’s width. Fixed tabs have equal width, based on the widest tab label.
public static final int MODE_SCROLLABLE Scrollable tabs display a subset of tabs at any given moment, and can contain longer tab labels and a larger number of tabs. They are best used for browsing contexts in touch interfaces when users don’t need to directly compare the tab labels.
所以 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" />
因为我没有找到为什么会发生这种行为,所以我使用了以下代码:
float myTabLayoutSize = 360;
if (DeviceInfo.getWidthDP(this) >= myTabLayoutSize ){
tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}
基本上,我必须手动计算 tabLayout 的宽度,然后根据 tabLayout 是否适合设备设置选项卡模式。
我手动获取布局大小的原因是因为在 Scrollable 模式下并非所有选项卡都具有相同的宽度,这可能会导致某些名称使用 2 行,就像我在示例中遇到的那样。
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
将匹配屏幕宽度并激活滚动。
Automatically switch TabLayout.MODE_FIXED and TabLayout.MODE_SCROLLABLE depends on total tab width.
这是我用来在SCROLLABLE
和FIXED
+FILL
之间自动切换的解决方案。这是@Fighter42解决方案的完整代码:
(如果您使用了 Google 的选项卡式 activity 模板,下面的代码显示了修改的位置)
@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 解决方案。
我在可运行部分对
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);
}
}
};
这是唯一对我有用的代码:
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"/>
让事情变得简单只需添加 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" />
您只需将以下内容添加到您的 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"
/>
我创建了一个 AdaptiveTabLayout class 来实现这一点。这是我发现真正解决问题、回答问题和 avoid/workaround 其他答案没有的问题的唯一方法。
备注:
- 处理 phone/tablet 布局。
- 处理足够的情况
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 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;
}
}
使用示例:
<?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.Whosebug.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>
Problems/Ask求助:
- 你会看到这个:
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 child 措施,我正在做一些转换和假设(比如 child 是一个包含其他视图的 LinearLayout....) 这可能会导致进一步的设计支持库更新出现问题。更好的approach/suggestions?
if(tabLayout_chemistCategory.getTabCount()<4)
{
tabLayout_chemistCategory.setTabGravity(TabLayout.GRAVITY_FILL);
}else
{
tabLayout_chemistCategory.setTabMode(TabLayout.MODE_SCROLLABLE);
}
<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"/>
非常简单的示例,它总是有效。
/**
* 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);
});
}
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
}
}
}
当您在 tablayout 中添加选项卡时,将此行添加到您的活动中
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
我认为更好的方法是设置 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"
/>
在我的例子中,选项卡可以根据过滤器的变化动态演变,所以我做了一些小的调整以允许使用 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
}
}