博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从源码角度来理解TabLayout设置下划线宽度问题
阅读量:6116 次
发布时间:2019-06-21

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

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

使用

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();        }    }    //集合存储下当前的Tab    private 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 TabStrip        mTabStrip = 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方法,这个地方直接看注释吧,代码有点多

@Override        protected 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 pass                    return;                }                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 width                    for (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_FILL                    mTabGravity = GRAVITY_FILL;                    updateTabViews(false);                    remeasure = true;                }                                if (remeasure) {                    // Now re-measure after our changes                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);                }            }复制代码

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

然后大致来看看onLayout方法

@Override        protected 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 duration                mIndicatorAnimator.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 position                updateIndicatorPosition();            }        }复制代码

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

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

private void updateIndicatorPosition() {        //根据当前滑块的位置拿到当前TabView            final View selectedTitle = getChildAt(mSelectedPosition);            int left, right;            if (selectedTitle != null && selectedTitle.getWidth() > 0) {            //拿到TabView的左、右位置                left = selectedTitle.getLeft();                right = selectedTitle.getRight();                //这句就是在滑块滑动的时候,如果滑动超过了上一个或是下一个滑块一半的话,那就说明移动到了上一个或是下一个滑块,然后取出left和right                if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {                    // Draw the selection partway between the tabs                    View 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, invalidate                mIndicatorLeft = left;                mIndicatorRight = right;                ViewCompat.postInvalidateOnAnimation(this);            }        }复制代码

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


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

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

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

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

@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {     //一些TabLayout的addTab操作            tabLayout.post(new Runnable() {            @Override            public void run() {                setTabWidth();            }        });}public void setTabWidth(){ //拿到SlidingTabStrip的布局  LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);  //遍历SlidingTabStrip的所有TabView子view  for (int i = 0; i < mTabStrip.getChildCount(); i++) {        View tabView = mTabStrip.getChildAt(i);        LinearLayout.LayoutParams params = (LinearLayout.LayoutParams)tabView.getLayoutParams();        //给TabView设置leftMargin和rightMargin        params.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 layouts                if (mIconView == null) {                    ImageView iconView = (ImageView) LayoutInflater.from(getContext())                            .inflate(R.layout.design_layout_tab_icon, this, false);                    //将iconView添加到TabView                    addView(iconView, 0);                    mIconView = iconView;                }                if (mTextView == null) {                    TextView textView = (TextView) LayoutInflater.from(getContext())                            .inflate(R.layout.design_layout_tab_text, this, false);                    //将TextView添加到TabView                    addView(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 them                if (mCustomTextView != null || mCustomIconView != null) {                    updateTextAndIcon(mCustomTextView, mCustomIconView);                }        }复制代码

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

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

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

那么,来试试吧

@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {     //一些TabLayout的addTab操作            tabLayout.post(new Runnable() {            @Override            public void run() {                setTabWidth();            }        });}public void setTabWidth(){ //拿到SlidingTabStrip的布局  LinearLayout mTabStrip = (LinearLayout) tabLayout.getChildAt(0);  //遍历SlidingTabStrip的所有TabView子view  for (int i = 0; i < mTabStrip.getChildCount(); i++) {        View tabView = mTabStrip.getChildAt(i);        //通过反射拿到TabView的的mTextView        Field 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和rightMargin        params.leftMargin = dp2px(10);        params.rightMargin = dp2px(10);        tabView.setLayoutParams(params);        //触发绘制        tabView.invalidate();    }}复制代码

效果图

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

最后还有一点就是有的人这么使用会报错,是因为混淆产生的问题,反射mTextView的时候可能会出问题,可以在混淆配置里面设置下TabLayout不被混淆

-keep class android.support.design.widget.TabLayout{*;}

总结

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

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

转载地址:http://zlvka.baihongyu.com/

你可能感兴趣的文章
bash complete -C command
查看>>
解决zabbix 3.0中1151端口不能运行问题
查看>>
售前工程师的成长---一个老员工的经验之谈
查看>>
Get到的优秀博客网址
查看>>
dubbo
查看>>
【Git入门之四】操作项目
查看>>
老男孩教育每日一题-第107天-简述你对***的理解,常见的有哪几种?
查看>>
Python学习--time
查看>>
在OSCHINA上的第一篇博文,以后好好学习吧
查看>>
高利率时代的结局,任重道远,前途叵测
查看>>
Debian 6.05安装后乱码
查看>>
欢迎大家观看本人录制的51CTO精彩视频课程!
查看>>
IntelliJ IDEA中设置忽略@param注释中的参数与方法中的参数列表不一致的检查
查看>>
关于软件开发的一些感悟
查看>>
uva 10806
查看>>
纯CSS3绘制的黑色图标按钮组合
查看>>
Linux中环境变量文件及配置
查看>>
从0开始学Flutter
查看>>
mysql操作入门基础之对数据库和表的增删改查
查看>>
IIS负载均衡
查看>>