博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android开发之无侵入式修改TabLayout tabIndicator宽度
阅读量:7105 次
发布时间:2019-06-28

本文共 6663 字,大约阅读时间需要 22 分钟。

在之前的文章中我已经大篇幅介绍过如何使用TabLayout这个控件,今天我们来玩点它的高级用法。通过大量阅读TabLayout的源码,我梳理并摸索出了一条修改tab indicator高级手段。在需要本文之前需要掌握以下知识点:

  • 具有阅读源码的能力
  • 自定义控件基础
  • java反射原理
  • 设计模式

首先我们来搞清楚一个问题,那就是TabLayout是如何实现indicator的?要搞清楚这个问题,我们需要进入到TabLayout的源代码。

注意:我用的design support包版本是27.0.0,由于design support 28.0.0修改了TabLayout部分源码,增加了新功能,看到的源码可能跟我的不一样。

进入TabLayout源码的世界

TabLayout继承结构图:

TabLayout继承自HorizontalScrollView,HorizontalScrollView是一个可以横向滚动的控件。

indicator是怎么实现的?

按照我最初的猜想,我以为indicator是一个View什么的,给他设置宽度、高度及颜色就可以显示在文字下方。然而,看了源码后才知道其实并不是这样的。 首先来看看TabLayout是怎么添加Tab的,我们从构造方法开始阅读,放出源码:

可以看到TabLayout内部添加了一个叫SlidingTabStrip的内部类作为容器,它是继承LinearLayout,下面是它的定义:
我可以告诉大家indicator是在这个类的draw方法中画的!!看:
在这个方法中它画了一个矩形,根据mIndicatorLeft和mIndicatorRight的值来决定indicator的显示位置。我们的突破点就是从这里开始。我的想法是通过反射来动态修改这两个成员变量的值,从而达到修改indicator的显示宽度。

我的思路

通过阅读TabLayout的源码得知indicator的宽度是由SlidingTabStrip这个内部类中的两个成员变量来决定的,如下:

所以我们只要通过动态修改这两个变量的值,就可以达到修改indicator的宽度目的。

实操指北

首先我们来拿到这两个成员变量的值。下面是它们的定义:

一看是private修饰的,二话不说,上反射先拿到再说:

try {            Field field = TabLayout.class.getDeclaredField("mTabStrip");            Log.d(TAG, "mTabStrip field = " + field);            field.setAccessible(true);            Object tabStrip = field.get(tabLayout);            if (tabStrip != null) {                Field leftField = tabStrip.getClass()                        .getDeclaredField("mIndicatorLeft");                Log.d(TAG, "mIndicatorLeft field = " + leftField);                leftField.setAccessible(true);                Log.d(TAG, "mIndicatorLeft value = " + leftField.get(tabStrip));                Log.d(TAG, "----------------------------------------------------");                Field rightField = tabStrip.getClass()                        .getDeclaredField("mIndicatorRight");                Log.d(TAG, "mIndicatorRight field = " + rightField);                rightField.setAccessible(true);                Log.d(TAG, "mIndicatorRight value = " + rightField.get(tabStrip));            }        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }复制代码

先来看看实际需要的效果:

我们希望indicator由我们自己指定宽度,而不是系统默认的样式。下一步是根据计算来修改这两个成员变量的值,如下:

try {            Field field = TabLayout.class.getDeclaredField("mTabStrip");            field.setAccessible(true);            Object tabStrip = field.get(tabLayout);            if (tabStrip != null) {                Field leftField = tabStrip.getClass()                        .getDeclaredField("mIndicatorLeft");                leftField.setAccessible(true);                int leftValue = (int) leftField.get(tabStrip);                Log.d(TAG, "mIndicatorLeft field before update value = " + leftValue);                Field rightField = tabStrip.getClass()                        .getDeclaredField("mIndicatorRight");                rightField.setAccessible(true);                int rightValue = (int) rightField.get(tabStrip);                Log.d(TAG, "mIndicatorRight field before update value = " + rightValue);                // indicator实际宽度                int realWidth = rightValue - leftValue;                int currentSelectedTabPosition = tabLayout.getSelectedTabPosition();                Log.d(TAG, "TabLayout tab indicator real width = " + realWidth);                Log.d(TAG, "TabLayout tab indicator show width = " + builder.getIndicatorWidth());                if (width > 0) {                    int indicatorLeft = leftValue + (realWidth - width) / 2;                    leftField.set(tabStrip, indicatorLeft);                    Log.d(TAG, "currentSelectedTab = " + currentSelectedTabPosition                            + ",mIndicatorLeft field after update value = " + indicatorLeft);                    int indicatorRight = indicatorLeft + width;                    rightField.set(tabStrip, indicatorRight);                    Log.d(TAG, "currentSelectedTab = " + currentSelectedTabPosition                            + ",mIndicatorRight field after update value = " + indicatorRight);                } else {                    // 设置indicator高度为0,即不显示                    tabLayout.setSelectedTabIndicatorHeight(0);                }                // 刷新UI                ViewCompat.postInvalidateOnAnimation((LinearLayout) tabStrip);            }        } catch (NoSuchFieldException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }复制代码

这行代码是我参考的它源码里的写法:

// 刷新UI    ViewCompat.postInvalidateOnAnimation((LinearLayout) tabStrip);复制代码

实现重点

上面只是找到了修改indicator绘制宽度突破点,并没有解决实际问题。通过测试发现,切换tab中和切换tab后indicator的宽度会恢复到系统默认效果。经过测试和调试系统源码,通过监听tab切换回调来动态修改indicator的宽度,以达到我们想要的效果,如下:

切换后解决方案:

tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {            @Override            public void onTabSelected(TabLayout.Tab tab) {                Log.d(TAG, "OnTabSelectedListener -> onTabSelected.");                // 调试源码得知,TabLayout$SlidingTabStrip的draw方法会调用两次,需要延时获取,否则返回的不是最后修改的值                tabLayout.postDelayed(new Runnable() {                    @Override                    public void run() {                        getDeclaredFieldValue(tabLayout);                        setDeclaredFieldValue(tabLayout, 30);                    }                }, 420);            }            @Override            public void onTabUnselected(TabLayout.Tab tab) {                Log.d(TAG, "OnTabSelectedListener -> onTabUnselected.");            }            @Override            public void onTabReselected(TabLayout.Tab tab) {                Log.d(TAG, "OnTabSelectedListener -> onTabReselected.");                tabLayout.postDelayed(new Runnable() {                    @Override                    public void run() {                        setDeclaredFieldValue(tabLayout, 30);                    }                }, 420);            }        });复制代码

注意:这里的延时不宜过长或过短,250-450毫秒左右,时间间隔太短可能无效果,太长界面看起来像停顿。

如何食用

用法跟正常TabLayout是一样,不需要增加额外属性。

xml布局:

复制代码

Java代码:

private TabLayout tabLayout;        @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        tabLayout = findViewById(R.id.TabLayout);        setTabLayout();    }        private void setTabLayout() {        new TabLayoutIndicatorHelper.Builder(tabLayout)                .setIndicatorColor(R.color.colorPrimary)                .setIndicatorHeight(10) // 10dp                .setIndicatorWidth(30) // 30dp                .build();    }复制代码

更优雅的食用

为了简化调用和适应不同项目而不用拷贝来拷贝去的,我们需要用一种设计模式来简化食用流程。它就是Builder设计模式。

终极方案

别费劲了,用第三方库!

转载于:https://juejin.im/post/5c9c80276fb9a070f77d2163

你可能感兴趣的文章
测试职业规划和发展----从零开始到合格的测试工程师
查看>>
UIImageView上添加UIButton,button的点击事件无法响应
查看>>
网络编程学习——Unix域协议
查看>>
队列的使用
查看>>
Java Annotation入门
查看>>
java--序列化及其算法透析
查看>>
GenMyModel:拥有代码生成功能的法国创新型UML工具来袭
查看>>
nexus 手册
查看>>
【hadoop】15.HDFS-其他功能
查看>>
照葫芦画瓢-reading files(读文件)
查看>>
HTTP协议详解(转)
查看>>
Android零基础入门第54节:视图切换组件ViewSwitcher
查看>>
083-使用shell和expect一键批量分发SSH密钥脚本
查看>>
线上采购注意了
查看>>
压缩与打包
查看>>
0037-如何在Windows Server2008搭建DNS服务并配置泛域名解析
查看>>
.net core入门之HelloWorld
查看>>
OSChina 周六乱弹 ——给媳妇打电话,一个男人的声音……
查看>>
OSChina 周一乱弹 —— 鱼生不值得
查看>>
jodd mvc框架 madvoc 教程(12):madvoc 杂项
查看>>