在 Android 设计支持库 TabLayout 中更改选项卡文本字体

139

我正在尝试使用Android设计库中的新TabLayout

我想要将选项卡文本更改为自定义字体。我尝试搜索与TabLayout相关的样式,但最终找到了这里的内容。

请指导我如何更改选项卡文本字体。


3
使用 Spannable String。 - NullByte
21个回答

174
如果你正在使用 TabLayout,并且想要更改字体,你需要向上一个解决方案中添加一个新的循环,像这样:
private void changeTabsFont() {
    ViewGroup vg = (ViewGroup) tabLayout.getChildAt(0);
        int tabsCount = vg.getChildCount();
        for (int j = 0; j < tabsCount; j++) {
            ViewGroup vgTab = (ViewGroup) vg.getChildAt(j);
            int tabChildsCount = vgTab.getChildCount();
            for (int i = 0; i < tabChildsCount; i++) {
                View tabViewChild = vgTab.getChildAt(i);
                if (tabViewChild instanceof TextView) {
                    ((TextView) tabViewChild).setTypeface(Font.getInstance().getTypeFace(), Typeface.NORMAL);
                }
        }
    }
} 

请参考如何在Sherlock中更改操作栏选项卡的字体样式

我没有使用M预览版。还有其他方法吗? - Dory
1
你不需要M预览版,它适用于使用TabLayout的所有人。 - Mario Velasco
然而,我在 android.view.View android.support.design.widget.TabLayout.getChildAt(int) 上遇到了 NullPointerException,你能帮我修复它吗?找不到我的代码中缺少什么。 - brettbrdls
1
setTypeFace 的第一个参数是 TypeFace,如果你找不到 Font 类(在我的环境中似乎不存在),可以使用它。 - Vahid Amiri

150

创建您自己的自定义样式,并将父样式作为parent="@android:style/TextAppearance.Widget.TabWidget"

在选项卡布局中使用此样式:app:tabTextAppearance="@style/tab_text"

示例: 样式:

<style name="tab_text" parent="@android:style/TextAppearance.Widget.TabWidget">
    <item name="android:fontFamily">@font/poppins_regular</item>
</style>

示例:选项卡布局组件:

<android.support.design.widget.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        android:minHeight="?attr/actionBarSize"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:tabTextAppearance="@style/tab_text" />

15
这是正确的,我在我的情况下使用了 parent="TextAppearance.Design.Tab" - Javatar
2
这比第一个答案要好得多,而且没有黑魔法(隐藏的API),这可能会在未来破坏某些东西。 - Sulfkain
2
只有在使用 fontFamily 时才能正常工作,使用 fontPath 时无法正常工作。 - Tram Nguyen
2
我在使用TextAppearance.Widget.TabWidget时,偶尔会出现奇怪的异常。@Javatar的答案为我解决了这个问题。 - funct7
2
我正在使用 com.google.android.material.tabs.TabLayout,因此我的样式的父级是 Widget.MaterialComponents.TabLayout,除此之外,相同的解决方案,非常好用,谢谢。 - ProjectDelta

49

praveen Sharma的回答很好。只是有一个小建议: 在需要TabLayout的任何地方,你可以简单地使用自己的CustomTabLayout而不是到处使用changeTabsFont()

import android.content.Context;
import android.graphics.Typeface;
import android.support.design.widget.TabLayout;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class CustomTabLayout extends TabLayout {
    private Typeface mTypeface;

    public CustomTabLayout(Context context) {
        super(context);
        init();
    }

    public CustomTabLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public CustomTabLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mTypeface = Typeface.createFromAsset(getContext().getAssets(), "fonts/Roboto-Regular.ttf");
    }

    @Override
    public void addTab(Tab tab) {
        super.addTab(tab);

        ViewGroup mainView = (ViewGroup) getChildAt(0);
        ViewGroup tabView = (ViewGroup) mainView.getChildAt(tab.getPosition());

        int tabChildCount = tabView.getChildCount();
        for (int i = 0; i < tabChildCount; i++) {
            View tabViewChild = tabView.getChildAt(i);
            if (tabViewChild instanceof TextView) {
                ((TextView) tabViewChild).setTypeface(mTypeface, Typeface.NORMAL);
            }
        }
    }

}

还有一件事情。

TabView 是一个包含 TextView (也可以选择性地包含 ImageView)的 LinearLayout。因此,你可以让代码更简单:

@Override
public void addTab(Tab tab) {
    super.addTab(tab);

    ViewGroup mainView = (ViewGroup) getChildAt(0);
    ViewGroup tabView = (ViewGroup) mainView.getChildAt(tab.getPosition());
    View tabViewChild = tabView.getChildAt(1);
    ((TextView) tabViewChild).setTypeface(mTypeface, Typeface.NORMAL);
}

但我不建议这种方式。如果 TabLayout 的实现发生了更改,这段代码可能无法正常工作甚至崩溃。

另一种自定义 TabLayout 的方法是向其添加自定义视图。这里有一个很好的示例


如果您像这样设置选项卡:mViewPager.setAdapter(new YourPagerAdapter(getChildFragmentManager())); mTabLayout.setupWithViewPager(mViewPager));则addTab将不会被调用。 - Penzzz
2
@Penzzz 你说得太对了。在这种情况下,你应该将代码从addTab方法移到另一个位置,比如onMeasure方法。 - Andrei Aulaska
@AndreiAulaska 谢谢,你救了我的一天。你最后的链接帮了我很大的忙。 - Amol Dale
这段代码在最新版本中不能用了。@ejw的答案现在有效。需要添加到addTab(Tab tab,boolean setSelected)中。 - Sayem
3
这个很好用。注意:从支持库 25.x 开始,你需要重写 addTab(Tab tab, int position, boolean setSelected) 而不是 addTab(Tab tab) - Vicky Chijwani

41
在Java代码或XML中创建TextView,可以像这样:
<?xml version="1.0" encoding="utf-8"?>
<TextView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@android:id/text1"
    android:layout_width="match_parent"
    android:textSize="15sp"
    android:textColor="@color/tabs_default_color"
    android:gravity="center"
    android:layout_height="match_parent"
/>

请确保将此处的 ID 保持不变,因为如果使用自定义 TextView,则 TabLayout 将检查此 ID。

然后从代码中填充此布局,并在该 TextView 上设置自定义 Typeface,并将此自定义视图添加到选项卡中。

for (int i = 0; i < tabLayout.getTabCount(); i++) {
     //noinspection ConstantConditions
     TextView tv = (TextView)LayoutInflater.from(this).inflate(R.layout.custom_tab,null)
     tv.setTypeface(Typeface);       
     tabLayout.getTabAt(i).setCustomView(tv);
}

2
在这种情况下,我该如何设置 tabTextColortabSelectedTextColor 属性? - Alireza Noorali
我从普拉文·沙玛的答案中获得了解决方案。 - Alireza Noorali

31

要在运行Android 4.1(API级别16)及更高版本的设备上使用XML功能中的字体支持,请使用Support Library 26+。

  1. 右键单击res文件夹
  2. 新建 -> Android资源目录-> 选择字体 -> 确定
  3. myfont.ttf文件放入新创建的字体文件夹中

res/values/styles.xml中添加:

<style name="customfontstyle" parent="@android:style/TextAppearance.Small">
    <item name="android:fontFamily">@font/myfont</item>
</style>

在布局文件中添加 app:tabTextAppearance="@style/customfontstyle"

<android.support.design.widget.TabLayout
    android:id="@+id/tabs"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabGravity="fill"
    app:tabTextAppearance="@style/customfontstyle"
    app:tabMode="fixed" />
请查看[XML中的字体]。(https://developer.android.com/guide/topics/ui/look-and-feel/fonts-in-xml

它适用于Xamarin Forms和Xamarin Android。谢谢。 - Esmail Jamshidiasl
1
很棒的答案!不过应该扩展“TextAppearance.Design.Tab”。 - X.Y.
简单的答案比其他答案更好。注意:如果您想要全大写的文本,请使用parent="TextAppearance.Design.Tab",否则@android:style/TextAppearance.Small就可以了。 - DIRTY DAVE

21

如果您正在使用

com.google.android.material:material:1.2.0(最新版本)

<style name="MyCustomTabTextAppearance" parent="TextAppearance.Design.Tab">
        <item name="fontFamily">Your Font</item>
        <item name="android:fontFamily">Your Font</item>
        <item name="textAllCaps">false</item>
    </style>

<com.google.android.material.tabs.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabBackground="@color/colorPrimary"
        app:tabGravity="fill"
        app:tabIndicatorColor="@color/white"
        app:tabMode="fixed"
        app:tabTextAppearance="@style/MyCustomTabTextAppearance"
        app:tabTextColor="@android:color/white" />

16
以下方法将递归地更改整个ViewGroup中的字体。我选择这种方法是因为您不必关心TabLayout的内部结构。我使用Calligraphy库来设置字体。
void changeFontInViewGroup(ViewGroup viewGroup, String fontPath) {
    for (int i = 0; i < viewGroup.getChildCount(); i++) {
        View child = viewGroup.getChildAt(i);
        if (TextView.class.isAssignableFrom(child.getClass())) {
            CalligraphyUtils.applyFontToTextView(child.getContext(), (TextView) child, fontPath);
        } else if (ViewGroup.class.isAssignableFrom(child.getClass())) {
            changeFontInViewGroup((ViewGroup) viewGroup.getChildAt(i), fontPath);
        }
    }
}

1
问题在于,如果您将布局附加到Viewpager上,您将失去自定义字体。 - rupps

11

正如Andrei所回答的,您可以通过扩展TabLayout类来更改字体。而且就像Penzzz所说的那样,您无法在addTab方法中执行此操作。请按照以下方式重写onLayout方法:

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
    super.onLayout(changed, left, top, right, bottom);

    final ViewGroup tabStrip = (ViewGroup)getChildAt(0);
    final int tabCount = tabStrip.getChildCount();
    ViewGroup tabView;
    int tabChildCount;
    View tabViewChild;

    for(int i=0; i<tabCount; i++){
        tabView = (ViewGroup)tabStrip.getChildAt(i);
        tabChildCount = tabView.getChildCount();
        for(int j=0; j<tabChildCount; j++){
            tabViewChild = tabView.getChildAt(j);
            if(tabViewChild instanceof AppCompatTextView){
                if(fontFace == null){
                    fontFace = Typeface.createFromAsset(context.getAssets(), context.getString(R.string.IranSans));
                }
                ((TextView) tabViewChild).setTypeface(fontFace, Typeface.BOLD);
            }
        }
    }
}

必须重写 onLayout 方法,因为当你使用 setupWithViewPager 方法将 TabLayout 与 ViewPager 绑定时,必须使用 setText 方法或在 PagerAdapter 中设置选项卡文本,此时会调用父 ViewGroup(TabLayout)的 onLayout 方法,这是设置字体的位置。(更改 TextView 文本会导致其父级调用 onLayout 方法 - tabView 有两个子元素,一个是 ImageView,另一个是 TextView)

另一种解决方案:

首先,这些代码行:

    if(fontFace == null){
        fontFace = Typeface.createFromAsset(context.getAssets(), context.getString(R.string.IranSans));
    }
在上面的解决方案中,应该写在两个循环之外。
但是,对于API >= 16的更好的解决方案是使用android:fontFamily:
创建一个名为font的Android资源目录,并将所需的字体复制到该目录中。
然后使用这些样式:
<style name="tabLayoutTitles">
    <item name="android:textColor">@color/white</item>
    <item name="android:textSize">@dimen/appFirstFontSize</item>
    <item name="android:fontFamily">@font/vazir_bold</item>
</style>

<style name="defaultTabLayout">
    <item name="android:layout_width">match_parent</item>
    <item name="android:layout_height">@dimen/defaultTabLayoutHeight</item>
    <item name="android:gravity">right</item>
    <item name="tabTextAppearance">@style/tabLayoutTitles</item>
    <item name="tabSelectedTextColor">@color/white</item>
    <item name="tabIndicatorColor">@color/white</item>
    <item name="tabIndicatorHeight">@dimen/accomTabIndicatorHeight</item>
    <item name="tabMode">fixed</item>
    <item name="tabGravity">fill</item>
    <item name="tabBackground">@drawable/rectangle_white_ripple</item>
    <item name="android:background">@color/colorPrimary</item>
</style>

这是一个糟糕的性能修复,每次布局更改(如标签切换或甚至在标签下滚动列表)时都会调用 onLayout(),在许多标签的嵌套 TabLayout 应用程序中使用嵌套的for循环将导致卡顿。 - Amr Barakat
2
根据此链接:https://developer.android.com/reference/android/view/View.html#onLayout(boolean, int, int, int, int),@Amr Barakat所说的并不正确。我也进行了测试。我在onLayout方法中设置了断点,发现它在创建选项卡时被调用,而不是在切换选项卡或滚动列表时被调用。 - Arash
1
对我来说,在选项卡切换时onLayout()会被多次调用(不太确定为什么),但是为了解决这个问题,我只在布尔值changedtrue时设置字体。这样做可以防止多次设置字体。 - Robert
伟大的解决方案,无需编写代码,仅需XML属性。 - careful7j

10

如果要使用 23.2.0 版本的设计支持库中的 setupWithViewPager 方法,请将代码从 addTab(Tab tab) 改为 addTab(Tab tab, boolean setSelected)。


8

你可以使用这个,这对我很有效。

 private void changeTabsFont() {
    ViewGroup vg = (ViewGroup) tabLayout.getChildAt(0);
    int tabsCount = vg.getChildCount();
    for (int j = 0; j < tabsCount; j++) {
        ViewGroup vgTab = (ViewGroup) vg.getChildAt(j);
        int tabChildsCount = vgTab.getChildCount();
        for (int i = 0; i < tabChildsCount; i++) {
            View tabViewChild = vgTab.getChildAt(i);
            if (tabViewChild instanceof TextView) {
                AssetManager mgr = getActivity().getAssets();
                Typeface tf = Typeface.createFromAsset(mgr, "fonts/Roboto-Regular.ttf");//Font file in /assets
                ((TextView) tabViewChild).setTypeface(tf);
            }
        }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接