当 tabMode 设置为 'scrollable' 时,TabLayout 不填充宽度
TabLayout not filling width when tabMode set to 'scrollable'
我已将 TabLayout
(来自支持库 v22.2.1)添加到我的片段中:
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/MyColorAccentTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"/>
问题是当 Fragment 的方向是横向时(在初始创建片段之前或之后),TabLayout
与 Fragment
的宽度不匹配(是的父其宽度也设置为 match_parent
)。
当屏幕宽度较小时(即无法同时显示所有标签):
当屏幕宽度足够大以显示所有选项卡时(请参阅右侧的空白 space):
如果我将 tabMode
更改为固定,宽度会被填满但制表符太小。有没有合适的解决办法?
我想这是实现你想要的最简单的方法。
public class CustomTabLayout extends TabLayout {
public CustomTabLayout(Context context) {
super(context);
}
public CustomTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
try {
if (getTabCount() == 0)
return;
Field field = TabLayout.class.getDeclaredField("mTabMinWidth");
field.setAccessible(true);
field.set(this, (int) (getMeasuredWidth() / (float) getTabCount()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
尝试进行此更改
<android.support.design.widget.TabLayout
android:id="@+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill" />
我不关心选项卡填充宽度,但我关心背景颜色没有扩展到整个宽度。所以我想到了这个解决方案,我在它后面放了一个 FrameLayout,背景颜色与选项卡相同。
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/MyColor">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/MyCustomTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"/>
</FrameLayout>
而不是创建自定义 TabLayout 和修改或创建更多布局,这些布局仅作为背景的 TabLayout 包装器。试试这个,
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/MyColorAccentTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- Instead of setting app:tabBackground -->
android:background="@color/colorAccent"
app:tabGravity="fill"
app:tabMode="scrollable"/>
这会将背景设置在 tabLayout 后面,而不是在每个选项卡后面设置背景。
@dtx12 如果任何选项卡标题大于 (measuredWidth/tabCount),则答案无效。
我的 TabLayout
子类适用于这种情况(在 Kotlin 中)。我希望这会对某人有所帮助。
class FullWidthTabLayout : 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 onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
if (changed) {
val widths = mutableListOf<Int>()
for (i in 0..this.tabCount - 1) {
widths.add(this.getTabAt(i)!!.customView!!.width)
}
if (widths.sum() < this.measuredWidth) {
var equalPart: Long = this.measuredWidth.toLong() / this.tabCount.toLong()
var biggerWidths = widths.filter { it > equalPart }
var smallerWidths = widths.filter { it <= equalPart }
var rest: Long = this.measuredWidth.toLong() - biggerWidths.sum()
while (biggerWidths.size != 0) {
equalPart = rest / smallerWidths.size
biggerWidths = smallerWidths.filter { it >= equalPart }
smallerWidths = smallerWidths.filter { it < equalPart }
rest -= biggerWidths.sum()
}
val minWidth = (rest / smallerWidths.size) + 10 //dont know why there is small gap on the end, thats why +10
for (i in 0..this.tabCount - 1) {
this.getTabAt(i)!!.customView!!.minimumWidth = minWidth.toInt()
}
}
}
}
}
请用这个肯定能解决这个问题
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed" />
请检查这个我认为它有效
public class MainActivity 扩展 AppCompatActivity {
private TextView mTxv_Home, mTxv_News, mTxv_Announcement;
private View mView_Home, mView_News, mView_Announcements;
private HorizontalScrollView hsv;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTxv_Home = (TextView) findViewById(R.id.txv_home);
mTxv_News = (TextView) findViewById(R.id.txv_news);
mTxv_Announcement = (TextView) findViewById(R.id.txv_announcements);
mView_Home = (View) findViewById(R.id.view_home);
mView_News = (View) findViewById(R.id.view_news);
mView_Announcements = (View) findViewById(R.id.view_announcements);
hsv = (HorizontalScrollView) findViewById(R.id.hsv);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int wt = displaymetrics.widthPixels/3;
mTxv_Home.setWidth(wt);
mTxv_News.setWidth(wt);
// mTxv_Announcement.setWidth(wt);
mTxv_Home.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mTxv_Home.setTextColor(Color.parseColor("#3F51B5"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_LEFT);
}
});
viewPager.setCurrentItem(0);
}
});
mTxv_News.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#3F51B5"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
int centerX = hsv.getChildAt(0).getWidth()/2;
hsv.scrollTo(centerX, 0);
}
});
viewPager.setCurrentItem(1);
}
});
mTxv_Announcement.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#3F51B5"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#3F51B5"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
});
viewPager.setCurrentItem(2);
}
});
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position == 0) {
mTxv_Home.setTextColor(Color.parseColor("#3F51B5"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_LEFT);
}
});
} else if (position == 1) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#3F51B5"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
int centerX = hsv.getChildAt(0).getWidth()/2;
hsv.scrollTo(centerX, 0);
}
});
} else if (position == 2) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#3F51B5"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#3F51B5"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
});
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new HomeFragment(), "Home");
adapter.addFragment(new NewsFragment(), "News");
adapter.addFragment(new AnnouncementsFragment(), "Announcements");
viewPager.setAdapter(adapter);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
}
<HorizontalScrollView
android:id="@+id/hsv"
android:layout_width="fill_parent"
android:layout_height="56dp"
android:layout_weight="0"
android:fillViewport="true"
android:measureAllChildren="false"
android:scrollbars="none" >
<LinearLayout
android:id="@+id/innerLay"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txv_home"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Home"
android:singleLine="true"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:gravity="center"
android:textSize="15sp"/>
<View
android:id="@+id/view_home"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@color/colorPrimary"
/>
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:visibility="gone"
android:background="#e8e8e8"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txv_news"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="News"
android:singleLine="true"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:gravity="center"
android:textSize="15sp"/>
<View
android:id="@+id/view_news"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#e8e8e8"/>
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:visibility="gone"
android:background="#e8e8e8"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txv_announcements"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:singleLine="true"
android:text="Announcements"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:gravity="center"
android:textSize="15sp"/>
<View
android:id="@+id/view_announcements"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e8e8e8"/>
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/hsv" />
androidx 版本:
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" >
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed"
/>
</androidx.viewpager.widget.ViewPager>
***** 以下是旧答案 *****
试试这个,它是设置 tabMaxWidth="0dp"
、tabGravity="fill"
和 tabMode="fixed"
的解决方法。
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed"/>
</android.support.v4.view.ViewPager>
10 英寸平板电脑上的屏幕截图:
当你想显示标签时,你应该设置 app:tabMode="fixed"。
**<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/toolbar"
android:background="#353535"
app:tabMode="fixed"
android:minHeight="?attr/actionBarSize"
app:tabIndicatorColor="@color/red"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />**
您可以在 tablayout
中使用 app:tabMaxWidth="0dp"
我也很挣扎,上面的大多数选项都不适合我,所以我根据 DTX12 的答案创建了一个版本。
我有一个布尔值来检查是否有平板电脑,并检查横向和纵向。然后根据选项卡的数量设置布局。
您可能需要创建方法 public 并在旋转/配置更改时调用它。
在 CustomTabLayout 代码中,我替换了 mintabswidth 方法
private void initTabMinWidth()
{
boolean isTablet = getContext().getResources().getBoolean(R.bool.device_is_tablet);
boolean isLandscape = (wh[WIDTH_INDEX] > wh[HEIGHT_INDEX]) ? true : false;
if (isTablet)
{
if (isLandscape)
{
if (this.getTabCount() > 8)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
} else
{
if (this.getTabCount() > 6)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
}
} else
{
if (isLandscape)
{
if (this.getTabCount() > 6)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
} else
{
if (this.getTabCount() > 4)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
}
}
}
在花了 2 天时间找出问题后,现在终于对我有用了。请查看@Tyler V 的回答 check here
来自泰勒的回答 'It was key to set the minimum width before calling super.onMeasure().' 非常重要
此解决方案的工作原理是让 android 创建选项卡,然后计算它们的总宽度。然后检查选项卡是否适合屏幕宽度,如果适合则将 tabMode 设置为 "fixed",这会缩放所有选项卡以适合屏幕宽度。
选项卡布局 xml,父级无关紧要:
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"/>
然后在您的 activity onCreate 或片段 onCreateView 中:
var totalWidth = 0
var maxWidth = 0
for (i in 0 until tabLayout.tabCount) {
val tabWidth = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(i)!!.width
totalWidth += tabWidth
maxWidth = max(maxWidth, tabWidth)
}
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
if (totalWidth < screenWidth&& screenWidth/ tabLayout.tabCount >= maxWidth) {
tabLayout.tabMode = TabLayout.MODE_FIXED
}
如果您将 TabLayout 与 Viewpager 一起使用,则必须设置 layoutChangeListener,因为选项卡在开始时并未膨胀。
在您的 activity onCreate 或 onCreateView 片段中:
tabLayout.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
var totalWidth = 0
var maxWidth = 0
for (i in 0 until tabLayout.tabCount) {
val tabWidth = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(i)!!.width
totalWidth += tabWidth
maxWidth = max(maxWidth, tabWidth)
}
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
if (totalWidth < screenWidth && screenWidth / tabLayout.tabCount >= maxWidth) {
tabLayout.tabMode = TabLayout.MODE_FIXED
}
}
注意:如果您希望所有选项卡在 tabMode 为 "fixed" 时具有相同的文本大小,则必须从 styles.xml 手动设置文本大小。
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:tabMode="scrollable"/>
这里有一个更简单的方法来确保制表符的宽度等于制表符的字符串长度
<androidx.viewpager.widget.ViewPager
android:id="@+id/vpTabs"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabMode="scrollable"/>
</androidx.viewpager.widget.ViewPager>
使用了以下代码:-
app:tabMaxWidth="0dp"
app:tabMode="scrollable"
这是我将 tabMode 设置为的解决方案:
app:tabMode="scrollable"
class MyTabLayout(
context: Context,
attrs: AttributeSet?
) : TabLayout(context, attrs) {
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
) {
val equalTabWidth= (MeasureSpec.getSize(widthMeasureSpec) / tabCount.toFloat()).toInt()
for (index in 0..tabCount) {
val tab = getTabAt(index)
val tabMeasuredWidth = tab?.view?.measuredWidth ?: equalTabWidth
if (tabMeasuredWidth < equalTabWidth) {
tab?.view?.minimumWidth = equalTabWidth
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
几乎所有的答案我都试过了,最后发现这个很管用。
public void dynamicSetTabLayoutMode(TabLayout tabLayout) {
int tabWidth = calculateTabWidth(tabLayout);
int screenWidth = getApplicationContext().getResources().getDisplayMetrics().widthPixels;
if (tabWidth <= screenWidth) {
tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}
}
private int calculateTabWidth(TabLayout tabLayout) {
int tabWidth = 0;
for (int i = 0; i < tabLayout.getChildCount(); i++) {
final View view = tabLayout.getChildAt(i);
view.measure(0, 0);
tabWidth += view.getMeasuredWidth();
}
return tabWidth;
}
这是我的 2021 Kotlin 解决方案。
它实现了以下我无法从其他答案中做到的:
- 如果标签不多,它们会展开以填满整个宽度。
- 如果有很多选项卡,它们是可滚动的,所以它们不会被压扁。
- 即使 tabLayout 不是屏幕的全宽也能正常工作。
为了实现这一点,我创建了一个 TabLayout
的子类,它覆盖了 onMeasure
class ScalableTabLayout : 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 onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val tabLayout = getChildAt(0) as ViewGroup
val childCount = tabLayout.childCount
if (childCount > 0) {
val widthPixels = MeasureSpec.getSize(widthMeasureSpec)
val tabMinWidth = widthPixels / childCount
var remainderPixels = widthPixels % childCount
tabLayout.forEachChild {
if (remainderPixels > 0) {
it.minimumWidth = tabMinWidth + 1
remainderPixels--
} else {
it.minimumWidth = tabMinWidth
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
然后在我的布局文件中使用它:
<com.package.name.ScalableTabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabMode="scrollable" />
tabMaxWidth="0dp"
和 tabMode="scrollable"
都是必需的
如果您想将其用作具有自定义选项卡宽度的可滚动选项卡,此方法将起作用
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="48dp"
app:tabMaxWidth="48dp"
app:tabGravity="fill"
app:tabMode="scrollable"/>
我已将 TabLayout
(来自支持库 v22.2.1)添加到我的片段中:
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/MyColorAccentTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"/>
问题是当 Fragment 的方向是横向时(在初始创建片段之前或之后),TabLayout
与 Fragment
的宽度不匹配(是的父其宽度也设置为 match_parent
)。
当屏幕宽度较小时(即无法同时显示所有标签):
当屏幕宽度足够大以显示所有选项卡时(请参阅右侧的空白 space):
如果我将 tabMode
更改为固定,宽度会被填满但制表符太小。有没有合适的解决办法?
我想这是实现你想要的最简单的方法。
public class CustomTabLayout extends TabLayout {
public CustomTabLayout(Context context) {
super(context);
}
public CustomTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
try {
if (getTabCount() == 0)
return;
Field field = TabLayout.class.getDeclaredField("mTabMinWidth");
field.setAccessible(true);
field.set(this, (int) (getMeasuredWidth() / (float) getTabCount()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
尝试进行此更改
<android.support.design.widget.TabLayout
android:id="@+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="fixed"
app:tabGravity="fill" />
我不关心选项卡填充宽度,但我关心背景颜色没有扩展到整个宽度。所以我想到了这个解决方案,我在它后面放了一个 FrameLayout,背景颜色与选项卡相同。
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/MyColor">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/MyCustomTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"/>
</FrameLayout>
而不是创建自定义 TabLayout 和修改或创建更多布局,这些布局仅作为背景的 TabLayout 包装器。试试这个,
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
style="@style/MyColorAccentTabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
<!-- Instead of setting app:tabBackground -->
android:background="@color/colorAccent"
app:tabGravity="fill"
app:tabMode="scrollable"/>
这会将背景设置在 tabLayout 后面,而不是在每个选项卡后面设置背景。
@dtx12 如果任何选项卡标题大于 (measuredWidth/tabCount),则答案无效。
我的 TabLayout
子类适用于这种情况(在 Kotlin 中)。我希望这会对某人有所帮助。
class FullWidthTabLayout : 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 onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
if (changed) {
val widths = mutableListOf<Int>()
for (i in 0..this.tabCount - 1) {
widths.add(this.getTabAt(i)!!.customView!!.width)
}
if (widths.sum() < this.measuredWidth) {
var equalPart: Long = this.measuredWidth.toLong() / this.tabCount.toLong()
var biggerWidths = widths.filter { it > equalPart }
var smallerWidths = widths.filter { it <= equalPart }
var rest: Long = this.measuredWidth.toLong() - biggerWidths.sum()
while (biggerWidths.size != 0) {
equalPart = rest / smallerWidths.size
biggerWidths = smallerWidths.filter { it >= equalPart }
smallerWidths = smallerWidths.filter { it < equalPart }
rest -= biggerWidths.sum()
}
val minWidth = (rest / smallerWidths.size) + 10 //dont know why there is small gap on the end, thats why +10
for (i in 0..this.tabCount - 1) {
this.getTabAt(i)!!.customView!!.minimumWidth = minWidth.toInt()
}
}
}
}
}
请用这个肯定能解决这个问题
<android.support.design.widget.TabLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed" />
请检查这个我认为它有效
public class MainActivity 扩展 AppCompatActivity {
private TextView mTxv_Home, mTxv_News, mTxv_Announcement;
private View mView_Home, mView_News, mView_Announcements;
private HorizontalScrollView hsv;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTxv_Home = (TextView) findViewById(R.id.txv_home);
mTxv_News = (TextView) findViewById(R.id.txv_news);
mTxv_Announcement = (TextView) findViewById(R.id.txv_announcements);
mView_Home = (View) findViewById(R.id.view_home);
mView_News = (View) findViewById(R.id.view_news);
mView_Announcements = (View) findViewById(R.id.view_announcements);
hsv = (HorizontalScrollView) findViewById(R.id.hsv);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
DisplayMetrics displaymetrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int wt = displaymetrics.widthPixels/3;
mTxv_Home.setWidth(wt);
mTxv_News.setWidth(wt);
// mTxv_Announcement.setWidth(wt);
mTxv_Home.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mTxv_Home.setTextColor(Color.parseColor("#3F51B5"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_LEFT);
}
});
viewPager.setCurrentItem(0);
}
});
mTxv_News.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#3F51B5"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
int centerX = hsv.getChildAt(0).getWidth()/2;
hsv.scrollTo(centerX, 0);
}
});
viewPager.setCurrentItem(1);
}
});
mTxv_Announcement.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#3F51B5"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#3F51B5"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
});
viewPager.setCurrentItem(2);
}
});
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
@Override
public void onPageSelected(int position) {
if (position == 0) {
mTxv_Home.setTextColor(Color.parseColor("#3F51B5"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_LEFT);
}
});
} else if (position == 1) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#3F51B5"));
mTxv_Announcement.setTextColor(Color.parseColor("#808080"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#3F51B5"));
mView_Announcements.setBackgroundColor(Color.parseColor("#E8E8E8"));
hsv.post(new Runnable() {
public void run() {
int centerX = hsv.getChildAt(0).getWidth()/2;
hsv.scrollTo(centerX, 0);
}
});
} else if (position == 2) {
mTxv_Home.setTextColor(Color.parseColor("#808080"));
mTxv_News.setTextColor(Color.parseColor("#808080"));
mTxv_Announcement.setTextColor(Color.parseColor("#3F51B5"));
mView_Home.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_News.setBackgroundColor(Color.parseColor("#E8E8E8"));
mView_Announcements.setBackgroundColor(Color.parseColor("#3F51B5"));
hsv.post(new Runnable() {
public void run() {
hsv.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
});
}
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
}
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new HomeFragment(), "Home");
adapter.addFragment(new NewsFragment(), "News");
adapter.addFragment(new AnnouncementsFragment(), "Announcements");
viewPager.setAdapter(adapter);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
}
<HorizontalScrollView
android:id="@+id/hsv"
android:layout_width="fill_parent"
android:layout_height="56dp"
android:layout_weight="0"
android:fillViewport="true"
android:measureAllChildren="false"
android:scrollbars="none" >
<LinearLayout
android:id="@+id/innerLay"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txv_home"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="Home"
android:singleLine="true"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:gravity="center"
android:textSize="15sp"/>
<View
android:id="@+id/view_home"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="@color/colorPrimary"
/>
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:visibility="gone"
android:background="#e8e8e8"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txv_news"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:text="News"
android:singleLine="true"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:gravity="center"
android:textSize="15sp"/>
<View
android:id="@+id/view_news"
android:layout_width="match_parent"
android:layout_height="3dp"
android:background="#e8e8e8"/>
</LinearLayout>
<View
android:layout_width="1dp"
android:layout_height="match_parent"
android:visibility="gone"
android:background="#e8e8e8"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/txv_announcements"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_weight="1"
android:singleLine="true"
android:text="Announcements"
android:paddingLeft="35dp"
android:paddingRight="35dp"
android:gravity="center"
android:textSize="15sp"/>
<View
android:id="@+id/view_announcements"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#e8e8e8"/>
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/hsv" />
androidx 版本:
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" >
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed"
/>
</androidx.viewpager.widget.ViewPager>
***** 以下是旧答案 *****
试试这个,它是设置 tabMaxWidth="0dp"
、tabGravity="fill"
和 tabMode="fixed"
的解决方法。
<android.support.v4.view.ViewPager
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabGravity="fill"
app:tabMode="fixed"/>
</android.support.v4.view.ViewPager>
10 英寸平板电脑上的屏幕截图:
当你想显示标签时,你应该设置 app:tabMode="fixed"。
**<android.support.design.widget.TabLayout
android:id="@+id/tab_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/toolbar"
android:background="#353535"
app:tabMode="fixed"
android:minHeight="?attr/actionBarSize"
app:tabIndicatorColor="@color/red"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />**
您可以在 tablayout
中使用 app:tabMaxWidth="0dp"我也很挣扎,上面的大多数选项都不适合我,所以我根据 DTX12 的答案创建了一个版本。
我有一个布尔值来检查是否有平板电脑,并检查横向和纵向。然后根据选项卡的数量设置布局。
您可能需要创建方法 public 并在旋转/配置更改时调用它。
在 CustomTabLayout 代码中,我替换了 mintabswidth 方法
private void initTabMinWidth()
{
boolean isTablet = getContext().getResources().getBoolean(R.bool.device_is_tablet);
boolean isLandscape = (wh[WIDTH_INDEX] > wh[HEIGHT_INDEX]) ? true : false;
if (isTablet)
{
if (isLandscape)
{
if (this.getTabCount() > 8)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
} else
{
if (this.getTabCount() > 6)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
}
} else
{
if (isLandscape)
{
if (this.getTabCount() > 6)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
} else
{
if (this.getTabCount() > 4)
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_SCROLLABLE);
} else
{
this.setTabGravity(TabLayout.GRAVITY_FILL);
this.setTabMode(TabLayout.MODE_FIXED);
}
}
}
}
在花了 2 天时间找出问题后,现在终于对我有用了。请查看@Tyler V 的回答 check here 来自泰勒的回答 'It was key to set the minimum width before calling super.onMeasure().' 非常重要
此解决方案的工作原理是让 android 创建选项卡,然后计算它们的总宽度。然后检查选项卡是否适合屏幕宽度,如果适合则将 tabMode 设置为 "fixed",这会缩放所有选项卡以适合屏幕宽度。
选项卡布局 xml,父级无关紧要:
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMode="scrollable"/>
然后在您的 activity onCreate 或片段 onCreateView 中:
var totalWidth = 0
var maxWidth = 0
for (i in 0 until tabLayout.tabCount) {
val tabWidth = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(i)!!.width
totalWidth += tabWidth
maxWidth = max(maxWidth, tabWidth)
}
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
if (totalWidth < screenWidth&& screenWidth/ tabLayout.tabCount >= maxWidth) {
tabLayout.tabMode = TabLayout.MODE_FIXED
}
如果您将 TabLayout 与 Viewpager 一起使用,则必须设置 layoutChangeListener,因为选项卡在开始时并未膨胀。
在您的 activity onCreate 或 onCreateView 片段中:
tabLayout.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
var totalWidth = 0
var maxWidth = 0
for (i in 0 until tabLayout.tabCount) {
val tabWidth = (tabLayout.getChildAt(0) as ViewGroup).getChildAt(i)!!.width
totalWidth += tabWidth
maxWidth = max(maxWidth, tabWidth)
}
val screenWidth = Resources.getSystem().displayMetrics.widthPixels
if (totalWidth < screenWidth && screenWidth / tabLayout.tabCount >= maxWidth) {
tabLayout.tabMode = TabLayout.MODE_FIXED
}
}
注意:如果您希望所有选项卡在 tabMode 为 "fixed" 时具有相同的文本大小,则必须从 styles.xml 手动设置文本大小。
<android.support.design.widget.TabLayout
android:id="@+id/tabs"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:tabMode="scrollable"/>
这里有一个更简单的方法来确保制表符的宽度等于制表符的字符串长度
<androidx.viewpager.widget.ViewPager
android:id="@+id/vpTabs"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabMode="scrollable"/>
</androidx.viewpager.widget.ViewPager>
使用了以下代码:-
app:tabMaxWidth="0dp"
app:tabMode="scrollable"
这是我将 tabMode 设置为的解决方案: app:tabMode="scrollable"
class MyTabLayout(
context: Context,
attrs: AttributeSet?
) : TabLayout(context, attrs) {
override fun onMeasure(
widthMeasureSpec: Int,
heightMeasureSpec: Int
) {
val equalTabWidth= (MeasureSpec.getSize(widthMeasureSpec) / tabCount.toFloat()).toInt()
for (index in 0..tabCount) {
val tab = getTabAt(index)
val tabMeasuredWidth = tab?.view?.measuredWidth ?: equalTabWidth
if (tabMeasuredWidth < equalTabWidth) {
tab?.view?.minimumWidth = equalTabWidth
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
几乎所有的答案我都试过了,最后发现这个很管用。
public void dynamicSetTabLayoutMode(TabLayout tabLayout) {
int tabWidth = calculateTabWidth(tabLayout);
int screenWidth = getApplicationContext().getResources().getDisplayMetrics().widthPixels;
if (tabWidth <= screenWidth) {
tabLayout.setTabMode(TabLayout.MODE_FIXED);
} else {
tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE);
}
}
private int calculateTabWidth(TabLayout tabLayout) {
int tabWidth = 0;
for (int i = 0; i < tabLayout.getChildCount(); i++) {
final View view = tabLayout.getChildAt(i);
view.measure(0, 0);
tabWidth += view.getMeasuredWidth();
}
return tabWidth;
}
这是我的 2021 Kotlin 解决方案。 它实现了以下我无法从其他答案中做到的:
- 如果标签不多,它们会展开以填满整个宽度。
- 如果有很多选项卡,它们是可滚动的,所以它们不会被压扁。
- 即使 tabLayout 不是屏幕的全宽也能正常工作。
为了实现这一点,我创建了一个 TabLayout
的子类,它覆盖了 onMeasure
class ScalableTabLayout : 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 onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val tabLayout = getChildAt(0) as ViewGroup
val childCount = tabLayout.childCount
if (childCount > 0) {
val widthPixels = MeasureSpec.getSize(widthMeasureSpec)
val tabMinWidth = widthPixels / childCount
var remainderPixels = widthPixels % childCount
tabLayout.forEachChild {
if (remainderPixels > 0) {
it.minimumWidth = tabMinWidth + 1
remainderPixels--
} else {
it.minimumWidth = tabMinWidth
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
}
然后在我的布局文件中使用它:
<com.package.name.ScalableTabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabMaxWidth="0dp"
app:tabMode="scrollable" />
tabMaxWidth="0dp"
和 tabMode="scrollable"
都是必需的
如果您想将其用作具有自定义选项卡宽度的可滚动选项卡,此方法将起作用
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="48dp"
app:tabMaxWidth="48dp"
app:tabGravity="fill"
app:tabMode="scrollable"/>