It is well known that TabLayout does not provide a fixed indicator width method, and there are already several solutions available online, such as reflection, custom TabViews, and importing other dependencies.

But my personal resistance to reflection, and feeling that custom TabViews and import dependencies were too heavy, led me to another approach: “Custom Drawable indicators.”

Quick use:

1, define methods/fixed the underline width * * * / fun TabLayout. SetSelectedTabIndicatorFixWidth (width: Int) {//TabLayout does not provide a fixed width method, and it has no way to calculate the width logic. Fortunately, the last width is drawn by drawable.setbounds (). this.setSelectedTabIndicator(object : DrawableWrapper(this.tabSelectedIndicator) { override fun setBounds(left: Int, top: Int, right: Int, bottom: Int) { var realLeft = left var realRight = right if (right - left ! = width) { val center = left + (right - left) / 2 realLeft = center - width / 2 realRight = center + width / 2 } Super.setbounds (realLeft, top, realRight, bottom)}} super.setbounds (realLeft, top, realRight, bottom)}} super.setBounds(realLeft, top, realRight, bottom)}} . 3, call tabLayout setSelectedTabIndicatorFixWidth (width) / / if you want to modify IndicatorDrawable is modified to call againCopy the code

Principle:

TabLayout of indicator width calculation in TabLayout. SlidingTabIndicator. SetIndicatorPosition () up and down, really don’t have anywhere to observe its calculation logic.

But no matter how to calculate it, and finally had to draw, see TabLayout. SlidingTabIndicator. The draw () the logic

if (this.indicatorLeft >= 0 && this.indicatorRight > this.indicatorLeft) { Drawable selectedIndicator = DrawableCompat.wrap((Drawable)(TabLayout.this.tabSelectedIndicator ! = null ? TabLayout.this.tabSelectedIndicator : this.defaultSelectionIndicator)); / / -- - calling setBounds () the position of the control indicator selectedIndicator. SetBounds (enclosing indicatorLeft, indicatorTop, enclosing 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);Copy the code

The TabLayout allows you to change the indicator Drawable, so I just override setBounds() to fix the width.

tabLayout.setSelectedTabIndicator(new ColorDrawable(Color.GREEN) {
            @Override
            public void setBounds(int left, int top, int right, int bottom) {
                int fixWidth = 30;
                if (right - left != fixWidth) {
                    int center = left + (right - left) / 2;
                    left = center - fixWidth / 2;
                    right = center + fixWidth / 2;
                }
                super.setBounds(left, top, right, bottom);
            }
        });
Copy the code

But I don’t want to write a bunch of code every time, and I want to accommodate more drawables. I want a class that looks like this:

class DrawableProxy(val indicatorDrawable:Drawable){ //.. All other methods call the indicatorDrawable method setBounds() // Just modify the setBounds() logic to a fixed width}Copy the code

So I went to Drawable to find a convenient subclass to use, and that led me to DrawableWrapper