从源码角度来理解TabLayout设置下划线宽度问题

2024-08-24 07:48

本文主要是介绍从源码角度来理解TabLayout设置下划线宽度问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

看了下网上很多的文章来设置下划线宽度的问题,有点杂乱无章,有的博文直接贴代码,无法理解设置的过程和实际的意义,看来只能自己动手才能丰衣足食了。

使用

        viewPager = (ViewPager) findViewById(R.id.qbdd_viewpager);viewPager.setAdapter(new MyViewPagerAdapter(getSupportFragmentManager(), fragmentList));tabLayout = (TabLayout) findViewById(R.id.qbdd_tablayout);tabLayout.addTab(tabLayout.newTab());tabLayout.addTab(tabLayout.newTab());tabLayout.addTab(tabLayout.newTab());tabLayout.setupWithViewPager(viewPager);tabLayout.getTabAt(0).setText("全部");tabLayout.getTabAt(1).setText("待付款");tabLayout.getTabAt(2).setText("待评论");

分析

来看tabLayout.newTab()操作
  public Tab newTab() {Tab tab = sTabPool.acquire();if (tab == null) {tab = new Tab();}tab.mParent = this;tab.mView = createTabView(tab);return tab;}private TabView createTabView(@NonNull final Tab tab) {TabView tabView = mTabViewPool != null ? mTabViewPool.acquire() : null;if (tabView == null) {tabView = new TabView(getContext());}tabView.setTab(tab);tabView.setFocusable(true);tabView.setMinimumWidth(getTabMinWidth());return tabView;}   

这个步骤的操作不难,就是创建一个TabView,设置一些参数,然后将TabView设置到tab.mView,然后返回Tab。

然后来看tabLayout.addTab(tabLayout.newTab())操作
   //有很多的重载方法,我们直接看最后调用的方法public void addTab(@NonNull Tab tab, int position, boolean setSelected) {if (tab.mParent != this) {throw new IllegalArgumentException("Tab belongs to a different TabLayout.");}configureTab(tab, position);addTabView(tab);if (setSelected) {tab.select();}}//集合存储下当前的Tabprivate void configureTab(Tab tab, int position) {tab.setPosition(position);mTabs.add(position, tab);final int count = mTabs.size();for (int i = position + 1; i < count; i++) {mTabs.get(i).setPosition(i);}}//将Tab里面存储的TabView添加到mTabStrip布局里面private void addTabView(Tab tab) {final TabView tabView = tab.mView;mTabStrip.addView(tabView, tab.getPosition(), createLayoutParamsForTabs());}

这个部分也比较简单,就是将之前创建好的Tab里面的TabView,添加到mTabStrip里面去。那么mTabStrip是个什么东西呢?我们ctrl+F来看看他在TabLayout这个类里面干了啥

    private final SlidingTabStrip mTabStrip;public TabLayout(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);...// Add the TabStripmTabStrip = new SlidingTabStrip(context);super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));...}

在TabLayout的构造方法里面我们看到了他的应用,将mTabStrip直接add到TabLayout,也就是说SlidingTabStrip是TabLayout的子类,那么,刚刚添加进来的TabView是SlidingTabStrip的子类。

然后我们来看看SlidingTabStrip这个类是干什么的
  private class SlidingTabStrip extends LinearLayout {SlidingTabStrip(Context context) {super(context);setWillNotDraw(false);mSelectedIndicatorPaint = new Paint();}}

构造方法没找到设置方向的代码,说明当前LineaLayout默认是水平方向,和我们看到TabLayout水平排列的一样。

然后看看onMeasure方法,这个地方直接看注释吧,代码有点多

  @Overrideprotected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);...//这种情况只有在设置TabLayout的tabMode为fixed的时候触发if (mMode == MODE_FIXED && mTabGravity == GRAVITY_CENTER) {final int count = getChildCount();// First we'll find the widest tab//for循环遍历所有子TabView的宽度,以最宽的TabView的宽度来为每个子TabView的宽度int largestTabWidth = 0;for (int i = 0, z = count; i < z; i++) {View child = getChildAt(i);if (child.getVisibility() == VISIBLE) {largestTabWidth = Math.max(largestTabWidth, child.getMeasuredWidth());}}if (largestTabWidth <= 0) {// If we don't have a largest child yet, skip until the next measure passreturn;}final int gutter = dpToPx(FIXED_WRAP_GUTTER_MIN);boolean remeasure = false;//如果所有子TabView的宽度之和还没SlidingTabStrip宽度减去一个偏移值宽的话,那么就给所有的子TabView重新设置宽度为最大宽度的TabView,//然后设置weight权重为0,那么所有的子view就会按比例均分SlidingTabStrip的宽度if (largestTabWidth * count <= getMeasuredWidth() - gutter * 2) {// If the tabs fit within our width minus gutters, we will set all tabs to have// the same widthfor (int i = 0; i < count; i++) {final LinearLayout.LayoutParams lp =(LayoutParams) getChildAt(i).getLayoutParams();if (lp.width != largestTabWidth || lp.weight != 0) {lp.width = largestTabWidth;lp.weight = 0;remeasure = true;}}} else {// If the tabs will wrap to be larger than the width minus gutters, we need// to switch to GRAVITY_FILLmTabGravity = GRAVITY_FILL;updateTabViews(false);remeasure = true;}if (remeasure) {// Now re-measure after our changessuper.onMeasure(widthMeasureSpec, heightMeasureSpec);}}

主要就是计算下子view的宽度,然后设置子view的宽度

然后大致来看看onLayout方法

 @Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {// If we're currently running an animation, lets cancel it and start a// new animation with the remaining durationmIndicatorAnimator.cancel();final long duration = mIndicatorAnimator.getDuration();//动画移动指示器在哪个position位置上面,此处就不深入了,主要就是一个ValueAnimatorCompat动画animateIndicatorToPosition(mSelectedPosition,Math.round((1f - mIndicatorAnimator.getAnimatedFraction()) * duration));} else {// If we've been layed out, update the indicator positionupdateIndicatorPosition();}}

主要就是判断动画有么有被初始化并且是否在做动画,如果是的话,给TabLayout滑块做动画,滑动到指定位置的position上面,如果没有,则来设置这个滑块。此处,我们来到了我们想要设置的关键点,滑块的宽度。

我们来看updateIndicatorPosition方法,看注释吧

        private void updateIndicatorPosition() {//根据当前滑块的位置拿到当前TabViewfinal View selectedTitle = getChildAt(mSelectedPosition);int left, right;if (selectedTitle != null && selectedTitle.getWidth() > 0) {//拿到TabView的左、右位置left = selectedTitle.getLeft();right = selectedTitle.getRight();//这句就是在滑块滑动的时候,如果滑动超过了上一个或是下一个滑块一半的话,那就说明移动到了上一个或是下一个滑块,然后取出left和rightif (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {// Draw the selection partway between the tabsView nextTitle = getChildAt(mSelectedPosition + 1);left = (int) (mSelectionOffset * nextTitle.getLeft() +(1.0f - mSelectionOffset) * left);right = (int) (mSelectionOffset * nextTitle.getRight() +(1.0f - mSelectionOffset) * right);}} else {left = right = -1;}//设置滑块的位置setIndicatorPosition(left, right);}void setIndicatorPosition(int left, int right) {if (left != mIndicatorLeft || right != mIndicatorRight) {// If the indicator's left/right has changed, invalidatemIndicatorLeft = left;mIndicatorRight = right;ViewCompat.postInvalidateOnAnimation(this);}}

从代码的整体来看,设置滑块的宽度是根据子TabView的宽度来设置的,也就是说,TabView的宽度是多少,那么滑块的宽度就是多少。


停下来想想,按照上面的分析,我们先画个布局层图

最外面是TabLayout,子view是SlidingTabStrip,所有addTab创建的tab都是SlidingTabStrip的子类,并且通过onMeasure重新给Tabview设置了宽度和权重,致使每个tabView都是占满布局。

因为滑块的宽度跟TabView的宽度有关,那么我们重新给TabView设置LayoutParams,设置marginLeft和marginRight,这样,会压缩TabView的宽度,致使滑块的宽度也跟着变化,如下思路图所示

朝上的花括号是margin,黄色的是滑块,这样不就可以控制滑块的宽度了嘛,那么,我们来试试

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {//一些TabLayout的addTab操作tabLayout.post(new Runnable() {@Overridepublic void run() {setTabWidth();}});
}
public void setTabWidth(){//拿到SlidingTabStrip的布局LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);//遍历SlidingTabStrip的所有TabView子viewfor (int i = 0; i < mTabStrip.getChildCount(); i++) {View tabView = mTabStrip.getChildAt(i);LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)tabView.getLayoutParams();//给TabView设置leftMargin和rightMarginparams.leftMargin = dp2px(10);params.rightMargin = dp2px(10);tabView.setLayoutParams(params);//触发绘制tabView.invalidate();}
}

这个地方需要在Tablayout设置完成后操作,并且必须等所有绘制操作结束,使用tabLayout.post拿到属性参数,然后设置下margin,搞定,来看看效果图

看起来还行,仔细看的时候会发现,当TabView的文字有2个的时候TextSize特别大,有三个的时候TextSize比较小,这种情况只适合所有TabView的文字长度都是一样的情况。我们来想想,为啥会导致这种情况呢,在我们设置TabView的宽度的时候,并没有考虑到TabView子view的wrap情况,万一TabView的子view宽度有的大,有的小,我只考虑到给每个TabView设置平均的宽度的话,考虑的不是特别周全,所以,我们先来看看TabView里面有啥,然后重新整理思路。

来看看TabView是个啥玩意
    class TabView extends LinearLayout implements OnLongClickListener {private Tab mTab;private TextView mTextView;private ImageView mIconView;private View mCustomView;private TextView mCustomTextView;private ImageView mCustomIconView;private int mDefaultMaxLines = 2;public TabView(Context context) {super(context);if (mTabBackgroundResId != 0) {ViewCompat.setBackground(this, AppCompatResources.getDrawable(context, mTabBackgroundResId));}ViewCompat.setPaddingRelative(this, mTabPaddingStart, mTabPaddingTop,mTabPaddingEnd, mTabPaddingBottom);setGravity(Gravity.CENTER);setOrientation(VERTICAL);setClickable(true);ViewCompat.setPointerIcon(this,PointerIconCompat.getSystemIcon(getContext(), PointerIconCompat.TYPE_HAND));}}

是一个垂直方向的LineaLayout,然后我们回到最上面,看一下TabView的创建。调用了tabView.setTab(tab)方法

        void setTab(@Nullable final Tab tab) {if (tab != mTab) {mTab = tab;update();}}final void update() {...if (mCustomView == null) {// If there isn't a custom view, we'll us our own in-built layoutsif (mIconView == null) {ImageView iconView = (ImageView) LayoutInflater.from(getContext()).inflate(R.layout.design_layout_tab_icon, this, false);//将iconView添加到TabViewaddView(iconView, 0);mIconView = iconView;}if (mTextView == null) {TextView textView = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.design_layout_tab_text, this, false);//将TextView添加到TabViewaddView(textView);mTextView = textView;mDefaultMaxLines = TextViewCompat.getMaxLines(mTextView);}TextViewCompat.setTextAppearance(mTextView, mTabTextAppearance);if (mTabTextColors != null) {mTextView.setTextColor(mTabTextColors);}updateTextAndIcon(mTextView, mIconView);} else {// Else, we'll see if there is a TextView or ImageView present and update themif (mCustomTextView != null || mCustomIconView != null) {updateTextAndIcon(mCustomTextView, mCustomIconView);}}

这地方就是初始化mTextView和mIconView,就是我们常见的TabLayout设置带有icon的tab,然后添加到TabView里面。

TabView的子view没多少,总共就5个,自定义的view目前可以不用关心,我们先来关心关心mTextView,因为我们刚刚是因为他出了问题。

我们之前设置的是TabView的宽度,导致了TextView显示被压缩,那么,现在我们换个思路,我们去拿TextView的宽度,然后设置到TabView的宽上面,使TabView的宽度来适应TextView的宽度,不采用之前平均分配宽度的方式。

那么,来试试吧

 @Override
protected void onCreate(@Nullable Bundle savedInstanceState) {//一些TabLayout的addTab操作tabLayout.post(new Runnable() {@Overridepublic void run() {setTabWidth();}});
}
public void setTabWidth(){//拿到SlidingTabStrip的布局LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);//遍历SlidingTabStrip的所有TabView子viewfor (int i = 0; i < mTabStrip.getChildCount(); i++) {View tabView = mTabStrip.getChildAt(i);//通过反射拿到TabView的的mTextViewField mTextViewField = tabView.getClass().getDeclaredField("mTextView");mTextViewField.setAccessible(true);//拿到TextView的宽度TextView mTextView = (TextView) mTextViewField.get(tabView);int txWidth = mTextView.getMeasuredWidth();LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)tabView.getLayoutParams();//给TabView设置宽度params.width = txWidth;//还可以使用tabView.setMinimumWidth(txWidth);给TabView设置最小宽度为textView的宽度//给TabView设置leftMargin和rightMarginparams.leftMargin = dp2px(10);params.rightMargin = dp2px(10);tabView.setLayoutParams(params);//触发绘制tabView.invalidate();}
}

效果图

嗯,很完美,这里地方主要就是通过mTextView的宽度来控制TabView的宽度,这里有两种方式,一种是直接给TabView设置宽度,还有一种是给TabView设置最小宽度为mTextView的宽度,总之就是要保证mTextView的文字正常显示。

总结

继续看源码,看别人的设计思路

推荐一本最近在看的书《自控力》,人要做到“慎独”、严于律己,慎其独也者,言舍夫五而慎其心之谓也。独然后一,一也者,夫五为一也,然后得之。

这篇关于从源码角度来理解TabLayout设置下划线宽度问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1101876

相关文章

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

CSS去除a标签的下划线的几种方法

《CSS去除a标签的下划线的几种方法》本文给大家分享在CSS中,去除a标签(超链接)的下划线的几种方法,本文给大家介绍的非常详细,感兴趣的朋友一起看看吧... 在 css 中,去除a标签(超链接)的下划线主要有以下几种方法:使用text-decoration属性通用选择器设置:使用a标签选择器,将tex

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

SpringBoot启动报错的11个高频问题排查与解决终极指南

《SpringBoot启动报错的11个高频问题排查与解决终极指南》这篇文章主要为大家详细介绍了SpringBoot启动报错的11个高频问题的排查与解决,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一... 目录1. 依赖冲突:NoSuchMethodError 的终极解法2. Bean注入失败:No qu

PyCharm如何设置新建文件默认为LF换行符

《PyCharm如何设置新建文件默认为LF换行符》:本文主要介绍PyCharm如何设置新建文件默认为LF换行符问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录PyCharm设置新建文件默认为LF换行符设置换行符修改换行符总结PyCharm设置新建文件默认为LF

MySQL新增字段后Java实体未更新的潜在问题与解决方案

《MySQL新增字段后Java实体未更新的潜在问题与解决方案》在Java+MySQL的开发中,我们通常使用ORM框架来映射数据库表与Java对象,但有时候,数据库表结构变更(如新增字段)后,开发人员可... 目录引言1. 问题背景:数据库与 Java 实体不同步1.1 常见场景1.2 示例代码2. 不同操作

如何解决mysql出现Incorrect string value for column ‘表项‘ at row 1错误问题

《如何解决mysql出现Incorrectstringvalueforcolumn‘表项‘atrow1错误问题》:本文主要介绍如何解决mysql出现Incorrectstringv... 目录mysql出现Incorrect string value for column ‘表项‘ at row 1错误报错

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

如何解决Spring MVC中响应乱码问题

《如何解决SpringMVC中响应乱码问题》:本文主要介绍如何解决SpringMVC中响应乱码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Spring MVC最新响应中乱码解决方式以前的解决办法这是比较通用的一种方法总结Spring MVC最新响应中乱码解