从源码角度来理解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

相关文章

好题——hdu2522(小数问题:求1/n的第一个循环节)

好喜欢这题,第一次做小数问题,一开始真心没思路,然后参考了网上的一些资料。 知识点***********************************无限不循环小数即无理数,不能写作两整数之比*****************************(一开始没想到,小学没学好) 此题1/n肯定是一个有限循环小数,了解这些后就能做此题了。 按照除法的机制,用一个函数表示出来就可以了,代码如下

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

购买磨轮平衡机时应该注意什么问题和技巧

在购买磨轮平衡机时,您应该注意以下几个关键点: 平衡精度 平衡精度是衡量平衡机性能的核心指标,直接影响到不平衡量的检测与校准的准确性,从而决定磨轮的振动和噪声水平。高精度的平衡机能显著减少振动和噪声,提高磨削加工的精度。 转速范围 宽广的转速范围意味着平衡机能够处理更多种类的磨轮,适应不同的工作条件和规格要求。 振动监测能力 振动监测能力是评估平衡机性能的重要因素。通过传感器实时监

缓存雪崩问题

缓存雪崩是缓存中大量key失效后当高并发到来时导致大量请求到数据库,瞬间耗尽数据库资源,导致数据库无法使用。 解决方案: 1、使用锁进行控制 2、对同一类型信息的key设置不同的过期时间 3、缓存预热 1. 什么是缓存雪崩 缓存雪崩是指在短时间内,大量缓存数据同时失效,导致所有请求直接涌向数据库,瞬间增加数据库的负载压力,可能导致数据库性能下降甚至崩溃。这种情况往往发生在缓存中大量 k

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL