TabLayout 之改变 Indicator 的宽度

前言

默认情况下,TabLayout 中 Indicator 的宽度和该 Tab 的宽度相等。但是有时候我们需要自定义 Indicator 的宽度,所以本文将介绍改变 Indicator 的宽度的几种方法。

一、反射实现

第一种方法是通过反射的方式改变 Indicator 的宽度。

TabLayout 只提供了 app:tabIndicatorHeight 属性来设置 Indicator 的宽度,但没有提供设置宽度的 API。所以只能自己去修改了,先看下源码是怎么实现 Indicator 的。

源码分析

在 TabLayout 中,定义了一个 SlidingTabStrip 对象:

1
private final SlidingTabStrip mTabStrip;

SlidingTabStrip 继承自 LinearLayout,负责处理 Tab 滑动的相关操作。在它的 draw 方法中,有绘制 Indicator 相关的代码:

1
2
3
4
5
6
7
8
9
@Override
public void draw(Canvas canvas) {
    super.draw(canvas);

    if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
        canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
                mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
    }
}

其中 mIndicatorLeft 和 mIndicatorRight 决定 Indicator 的宽度,宽度为 mIndicatorRight - mIndicatorLeft。

而 mIndicatorLeft 和 mIndicatorRight 最终是在 SlidingTabStrip 的 updateIndicatorPosition() 方法设置的:

1
2
3
4
5
6
7
8
private void updateIndicatorPosition() {
    final View selectedTitle = getChildAt(mSelectedPosition);   // 当前 Tab
    int left, right;

    // 最终 left 和 right 会赋给 mIndicatorLeft 和 mIndicatorRight
    left = selectedTitle.getLeft();
    right = selectedTitle.getRight();
}

可以看到,最终 Indicator 的宽度就是 SlidingTabStrip 中子 View 的宽度。

也就是说,想要改变 Indicator 的宽度,只需设置 SlidingTabStrip 中子 View 的 宽度即可。

代码实现

  1. 得到 SlidingTabStrip 对象,这里不需要反射,因为 SlidingTabStrip 对象是 TabLayout 的第一个子 View,通过 tabLayout.getChildAt(0) 即可得到 SlidingTabStrip 对象。
  2. 设置 SlidingTabStrip 中相应子 View 的宽度。这里设置的宽度不能小于 TextView 的宽度,不然文字会显示不全。这时需要通过反射来得到 TextView,因为 TextView 是 TabView 的子 View,而 TabView 是 TabLayout 中的非公有内部类,外部无法直接访问 TabView。

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
 * 通过反射的方式改变 Indicator 的宽度
 *
 * @param tabLayout 要改变的 TabLayout
 * @param width     Indicator 的宽度
 * @param margin    Tab 之间的 margin
 */
public static void setIndicatorWidth(final TabLayout tabLayout, final int width, final int margin) {
    tabLayout.post(new Runnable() {
        @Override
        public void run() {
            try {
                LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);
                for (int i = 0; i < mTabStrip.getChildCount(); i++) {
                    // 得到当前子 View(TabView)
                    View child = mTabStrip.getChildAt(i);

                    // 得到 TextView 的宽度
                    Field textViewField = child.getClass().getDeclaredField("textView");
                    textViewField.setAccessible(true);
                    TextView mTextView = (TextView) textViewField.get(child);
                    child.setPadding(0,0,0,0);
                    int textViewWidth = mTextView.getWidth();
                    if (textViewWidth == 0) {
                        mTextView.measure(0,0);
                        textViewWidth = mTextView.getMeasuredWidth();
                    }

                    // 设置该子 View 的相关参数
                    LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
                    lp.width = textViewWidth > width ? textViewWidth : width;
                    lp.leftMargin = margin;
                    lp.rightMargin = margin;
                    child.setLayoutParams(lp);
                    child.invalidate();
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        }
    });
}

这种方式存在的不足:

  1. 如果升级 sdk 后,sdk 中获取反射的变量名发生改变,代码也要跟着变,不然会失效。
  2. 这种方式最小只能把 Indicator 的宽度设置成 TextView 的宽度,如果宽度更小的话,TextView 的文本就会被压缩。

二、SDK 28+ 属性配置

如果你使用的 SDK 版本在 28 以上,并且需要将 Indicator 的宽度设置成和文字宽度一样,那么只需在 xml 中给 TabLayout 加上 app:tabIndicatorFullWidth="false" 即可:

1
2
3
4
5
<android.support.design.widget.TabLayout
    android:id="@+id/tab_layout"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabIndicatorFullWidth="false"/>

这样做虽然比反射实现简单,但还是无法实现设置 Indicator 的宽度比文字宽度短的效果。

三、使用 layer-list

Indicator 允许我们设置 drawable 来自定义样式,通过 layer-list 可以在上层设置一个固定宽度的 shape:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <item android:gravity="center">
        <shape>
            <size android:width="20dp"
                android:height="4dp"/>
            <solid android:color="@android:color/holo_red_light"/>
        </shape>
    </item>
</layer-list>

然后在 xml 中给 TabLayout 设置:

1
2
3
4
5
<android.support.design.widget.TabLayout
    android:id="@+id/tl_style_one_tab"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:tabIndicator="@drawable/tab_indicator"/>

通过这种方式,就可以设置一个比 TextView 文本宽度更短的 Indicator 了。

这种方式的缺点是各 Indicator 是宽度是一致的,并且在移动时 Indicator 的长度不会改变,不能实现移动时 Indicator 的长度由短变长再变短的动画效果。

四、第三方库

推荐一个很棒的第三方库:MagicIndicator。除了可以改变 Indicator 的宽度外,里面还有很多炫酷的 Indicator 动画。

参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现在 TabLayout 点击背景时取消选中当前选项卡,可以在 TabLayout 的 OnTabSelectedListener 中监听 Tab 的选中状态,当选中状态发生变化时,根据需要执行相应的操作。以下是一些实现方法: 1. 在 TabLayout 的 OnTabSelectedListener 中使用 setSelectedTabIndicatorColor 方法,将选中状态下的指示器颜色设置为透明,实现取消选中当前选项卡的效果。 ``` tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { // do something when tab selected } @Override public void onTabUnselected(TabLayout.Tab tab) { // do something when tab unselected } @Override public void onTabReselected(TabLayout.Tab tab) { // do something when tab reselected } }); // set transparent color for selected tab indicator tabLayout.setSelectedTabIndicatorColor(Color.TRANSPARENT); ``` 2. 在 TabLayout 的 OnTabSelectedListener 中手动设置选中状态,实现取消选中当前选项卡的效果。 ``` tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() { @Override public void onTabSelected(TabLayout.Tab tab) { // do something when tab selected } @Override public void onTabUnselected(TabLayout.Tab tab) { // manually unselect tab when background clicked tabLayout.getTabAt(tab.getPosition()).select(); } @Override public void onTabReselected(TabLayout.Tab tab) { // do something when tab reselected } }); ``` 注意,第二种方法会在点击任何地方取消选中当前选项卡,而不仅仅是点击 TabLayout 的背景。如果您只想在点击 TabLayout 的背景时取消选中当前选项卡,可以使用以下方法: ``` tabLayout.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { // unselect tab when background clicked tabLayout.getTabAt(tabLayout.getSelectedTabPosition()).select(); } return false; } }); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值