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"可以去掉默认的点击波纹效果
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要在 Android 应用程序中实现手势缩放图片,您可以使用 Android 框架提供的 GestureDetector 和 ScaleGestureDetector 类。下面是一些实现步骤: 1. 在布局文件中添加一个 ImageView,用于显示图片。 2. 在 Activity 中获取 ImageView 的引用,并创建 GestureDetector 和 ScaleGestureDetector 对象。 3. 在 ImageView 上注册触摸事件监听器,并在监听器中处理手势事件。具体来说,您需要实现以下方法: a. onTouchEvent(MotionEvent event):在此方法中,您需要将 MotionEvent 对象传递给 GestureDetector 和 ScaleGestureDetector 对象,并根据手势类型调用相应的方法。 b. onDown(MotionEvent event):在此方法中,您需要返回 true,以指示您要处理后续的事件。 c. onScroll(MotionEvent event1, MotionEvent event2, float distanceX, float distanceY):在此方法中,您需要计算手势滑动的距离,并将其应用于 ImageView 的 Matrix 变换矩阵中,以实现平移效果。 d. onScale(ScaleGestureDetector detector):在此方法中,您需要获取手势缩放的比例因子,并将其应用于 ImageView 的 Matrix 变换矩阵中,以实现缩放效果。 e. onScaleBegin(ScaleGestureDetector detector):在此方法中,您需要返回 true,以指示您要处理后续的事件。 f. onScaleEnd(ScaleGestureDetector detector):在此方法中,您可以执行任何必要的清理操作。 4. 最后,您需要在 Activity 的 onCreate() 方法中启用 ImageView 的手势支持。具体来说,您需要调用以下方法: a. setClickable(true):启用 ImageView 的点击事件。 b. setScaleType(ImageView.ScaleType.MATRIX):设置 ImageView 的缩放类型为 Matrix。 c. setOnTouchListener(listener):为 ImageView 注册触摸事件监听器。 以上是手势缩放图片的基本实现步骤,您可以根据自己的需求进行相应的修改和扩展。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值