Android TabLayout indicator 指示器宽度修改 最新思路及实现 调用方便

这两天手头上的项目需要给Tablayout指示器设置一个固定的宽度,但谷歌并没有提供api,网上搜索的各种方式试了之后也没什么效果,而且调用比较麻烦,于是自己翻了源码,发现只要在tablayout中修改指示器左右的值就可以达到目的。源码如下:

        public void draw(Canvas canvas) {
            ....

            if (this.indicatorLeft >= 0 && this.indicatorRight > this.indicatorLeft) {
                Drawable selectedIndicator = DrawableCompat.wrap((Drawable)(TabLayout.this.tabSelectedIndicator != null ? TabLayout.this.tabSelectedIndicator : this.defaultSelectionIndicator));
                selectedIndicator.setBounds(this.indicatorLeft, indicatorTop, this.indicatorRight, indicatorBottom);
                if (this.selectedIndicatorPaint != null) {
                    if (VERSION.SDK_INT == 21) {
                        selectedIndicator.setColorFilter(this.selectedIndicatorPaint.getColor(), android.graphics.PorterDuff.Mode.SRC_IN);
                    } else {
                        DrawableCompat.setTint(selectedIndicator, this.selectedIndicatorPaint.getColor());
                    }
                }

                selectedIndicator.draw(canvas);
            }

            super.draw(canvas);
        }
    }

实现功能后的效果如下:

调用也非常方便,直接设置一个dp值:

setIndicatorWidth(70);

原理:

1、既然我们的目的是要在draw时修改指定的数值,那首先想到用反射修改相应的field值就好;

    private class SlidingTabIndicator extends LinearLayout {
        private int indicatorLeft = -1;
        private int indicatorRight = -1
}

2、找到SlidingTabIndicator对象,发现在Tablayout的构造函数中进行了初始化:

        this.slidingTabIndicator = new TabLayout.SlidingTabIndicator(context);
        super.addView(this.slidingTabIndicator, 0, new LayoutParams(-2, -1));
        TypedArray a = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.TabLayout, defStyleAttr, style.Widget_Design_TabLayout, new int[]{styleable.TabLayout_tabTextAppearance});
        this.slidingTabIndicator.setSelectedIndicatorHeight(a.getDimensionPixelSize(styleable.TabLayout_tabIndicatorHeight, -1));
        this.slidingTabIndicator.setSelectedIndicatorColor(a.getColor(styleable.TabLayout_tabIndicatorColor, 0));

3、反射拿到该对象及两个field,然后结合android view绘制的原理,想到使用OnPreDrawListener,并且为了避免多次调用重复添加listener和方便写修改数值的代码,写了一个内部类实现OnPreDrawListener,并且每次添加前删除已有的listener;

完整代码如下:


import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.google.android.material.tabs.TabLayout;

import java.lang.reflect.Field;

import androidx.annotation.NonNull;

/**
 * 描述:可设置tab indicator的宽度
 *
 * @author renpeng
 * @since 2019/10/10
 */
public class TabLayoutEx extends TabLayout {

    public TabLayoutEx(Context context) {
        this(context, null);
    }

    public TabLayoutEx(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    private void init(AttributeSet attrs) {
       
        setTabIndicatorFullWidth(false);
        setIndicatorWidth(70);
        
    }

    
    private class DefPreDrawListener implements ViewTreeObserver.OnPreDrawListener {

        private LinearLayout tabStrip = null;
        private int tabWidth;
        private Field fieldLeft;
        private Field fieldRight;

        public void setTabStrip(LinearLayout tabStrip, int width) {
            try {
                this.tabStrip = tabStrip;
                this.tabWidth = width;
                Class cls = tabStrip.getClass();
                fieldLeft = cls.getDeclaredField("indicatorLeft");
                fieldLeft.setAccessible(true);
                fieldRight = cls.getDeclaredField("indicatorRight");
                fieldRight.setAccessible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public boolean onPreDraw() {
            try {
                if (tabWidth > 0) {
                    int left = fieldLeft.getInt(this.tabStrip);
                    int right = fieldRight.getInt(this.tabStrip);
                    //根据目标宽度及现在的宽度调整为合适的left和right
                    int diff = right - left - tabWidth;
                    left = left + diff / 2;
                    right = right - diff / 2;
                    fieldLeft.setInt(this.tabStrip, left);
                    fieldRight.setInt(this.tabStrip, right);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
    }

    private DefPreDrawListener defPreDrawListener = new DefPreDrawListener();

    public void setIndicatorWidth(int widthDp) {
        Class tabLayout = TabLayout.class;
        Field tabStrip = null;
        try {
            tabStrip = tabLayout.getDeclaredField("slidingTabIndicator");
            tabStrip.setAccessible(true);
            LinearLayout tabIndicator = (LinearLayout) tabStrip.get(this);
            int width = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, widthDp, Resources.getSystem().getDisplayMetrics());
            //avoid add preDrawListener multi times
            tabIndicator.getViewTreeObserver().removeOnPreDrawListener(defPreDrawListener);
            tabIndicator.getViewTreeObserver().addOnPreDrawListener(defPreDrawListener);
            defPreDrawListener.setTabStrip(tabIndicator, width);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

其他:

Tablayout使用中几个要注意的点:

setSelectedTabIndicator可以自定义指示器样式,比如使用渐变等,但要注意setSelectedTabIndicatorColor的着色影响;
app:tabRippleColor="@null"可以去掉默认的点击波纹效果

你可能感兴趣的:(Android,开发及核心技术)