TabLayout的一些坑

修改下划线宽度的坑

效果如下:

效果

代码实现方式:

如果想要实现这种效果,最主要控制的就是下划线部分,现在网上有很多通过反射的方式来修改下划线宽度的文章,但这段代码如果实现我们想要的效果是不可能的,因为如果研究过源码就知道Indicator的宽度跟Tab的宽度一致的,不能让指示器的宽度小于Tab的宽度,所以我们只能另辟蹊径:通过CustomView自己绘制线,通过添加OnTabSelectedListener来展示隐藏下划线,让原生的Indicaqtor高度为0也不会影响我们的展示。

OK! Talk is cheap, show me the code.

先隐藏原来的下划线,让其不显示:

    <android.support.design.widget.TabLayout
        android:id="@+id/radio_playlist_tab_layout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/dp_36.7"
        android:background="@color/color_e8e8e8"
        app:tabBackground="@null"
        app:tabIndicatorHeight="0dp"
        app:tabMode="scrollable" />

其次设置CustomView效果:

CustomView

再手动控制切换时Tab的下划线:

mTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabReselected(tab: TabLayout.Tab?) {
                val view = tab?.customView?.findViewById<View>(R.id.tab_indicator)
                val textView = tab?.customView?.findViewById<TextView>(R.id.tab_tv_date)
                textView?.setTextColor(mTabSelectedColor)
                view?.visibility = View.VISIBLE
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {
                val view = tab?.customView?.findViewById<View>(R.id.tab_indicator)
                val textView = tab?.customView?.findViewById<TextView>(R.id.tab_tv_date)
                textView?.setTextColor(mTabUnSelectedColor)
                view?.visibility = View.INVISIBLE
            }

            override fun onTabSelected(tab: TabLayout.Tab?) {
                val view = tab?.customView?.findViewById<View>(R.id.tab_indicator)
                val textView = tab?.customView?.findViewById<TextView>(R.id.tab_tv_date)
                textView?.setTextColor(mTabSelectedColor)
                view?.visibility = View.VISIBLE
            }

        })
        

间距部分

只是这样的话,下划线的问题解决了。但对于我们的UI对于界面还原度要求较高,对于Tab之间的间距也有一些要求,所以也要处理,对于间距部分的处理可以按照之前的方式通过反射来完成。注意,这种方式因为需要计算TextView的文字宽度,所以要放到设置完所有的customView后调用。


    private fun customTabWidth(tabLayout: TabLayout) {
        try {
            //拿到tabLayout的mTabStrip属性
            val mTabStripField = tabLayout.javaClass.getDeclaredField("mTabStrip")
            mTabStripField.isAccessible = true

            val mTabStrip = mTabStripField.get(tabLayout) as LinearLayout

            val dp2 = DensityUtils.dp2px(context, 2f)
            val dp30 = DensityUtils.dp2px(context, 30f)


            for (i in 0 until mTabStrip.childCount) {
                // 此时获取到tabView其实是我们的CustomView
                val tabView = mTabStrip.getChildAt(i)
                // 找到我们的TextView
                val mTextView = tabView.findViewById<TextView>(R.id.tab_tv_date)

                // 测量出TextView文字的宽度
                var width = 0
                width = mTextView.width
                if (width == 0) {
                    mTextView.measure(0, 0)
                    width = mTextView.measuredWidth
                }

                // PDL = padding left    ---   PDR = padding right
                // |PDL30 CONTENT PDR30|  |PDL2 CONTENT PDR30| |PDL2 CONTENT PDR30| |PDL2 CONTENT PDR30|

                
                val params = tabView.layoutParams as LinearLayout.LayoutParams
                // 如果是第一个View,则View左侧还要30dp的空间需要padding
                // 有了TextView的宽度,我们可以根据自己想要的效果去设置Tab的宽度,Tab的实际间距其实是0,但我们可以
                // 通过改变Padding的方式做出Tab间距的效果
                // 对于为什么用**padding**而不是**margin**的原因,请向下看
                if (i == 0) {
                    params.width = width + dp30 + dp30
                    tabView.layoutParams = params
                    tabView.setPadding(dp30, 0, dp30, 0)
                } else {
                    params.width = width + dp2 + dp30
                    tabView.layoutParams = params
                    tabView.setPadding(dp2, 0, dp30, 0)
                }

                tabView.invalidate()
            }
        } catch (e: NoSuchFieldException) {
            e.printStackTrace()
        } catch (e: IllegalAccessException) {
            e.printStackTrace()
        }
    }

修改完下划线宽度后tab的滑动位置错乱的坑(TabView的 MarginLeft & MarginRigt 导致的问题)

开始做的时候看网上相关的文档然后做出的效果如下:

错乱

可以看到当滑过去以后,Tab又位移了一段距离,我想如果你的脑子没有被踢过的话,肯定都知道滑动完位移后的位置是正确的位置,那么为什么会出现这种情况已经如何解决??

看源码!

因为是我们关联的ViewPager滑动导致的Tab位移,那么就从ViewPager的移动时开始找线索,通过setupWithViewPager()方法中可以看到,TabLayout代码中给ViewPager对象通过addOnPageChangeListener()方法添加了一个监听,那么我们看一下监听的回调好了!

 public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
        // 忽略无关代码

        // **主要看这里哦~!~!~ **
        @Override
        public void onPageScrolled(final int position, final float positionOffset,
                final int positionOffsetPixels) {
            final TabLayout tabLayout = mTabLayoutRef.get();
            if (tabLayout != null) {
                // Only update the text selection if we're not settling, or we are settling after
                // being dragged
                final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
                        mPreviousScrollState == SCROLL_STATE_DRAGGING;
                // Update the indicator if we're not settling after being idle. This is caused
                // from a setCurrentItem() call and will be handled by an animation from
                // onPageSelected() instead.
                final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
                        && mPreviousScrollState == SCROLL_STATE_IDLE);
                        // 这里调用了setScrollPosition去让TabLayout进行滑动,穿进去目标下标和偏移量
                tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
            }
        }

    }

再向下看一下setScrollPosition方法!

 void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
            boolean updateIndicatorPosition) {
        final int roundedPosition = Math.round(position + positionOffset);
        if (roundedPosition < 0 || roundedPosition >= mTabStrip.getChildCount()) {
            return;
        }

        // Set the indicator position, if enabled
        // 如果更新指示器的话那么会走进这里,我们指示器都隐藏掉了无需看这里
        if (updateIndicatorPosition) {
            mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
        }

        // Now update the scroll position, canceling any running animation
        if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
            mScrollAnimator.cancel();
        }
        // 这里调用了scrollTo方法去移动位置,那么我们猜测应该是这个X位移距离算错了,进去看一下
        scrollTo(calculateScrollXForTab(position, positionOffset), 0);

        
    }
    
    private int calculateScrollXForTab(int position, float positionOffset) {
        if (mMode == MODE_SCROLLABLE) {
    
            // 获取目标的View
            final View selectedChild = mTabStrip.getChildAt(position);
            final View nextChild = position + 1 < mTabStrip.getChildCount()
                    ? mTabStrip.getChildAt(position + 1)
                    : null;
            final int selectedWidth = selectedChild != null ? selectedChild.getWidth() : 0;
            final int nextWidth = nextChild != null ? nextChild.getWidth() : 0;

            // base scroll amount: places center of tab in center of parent
            // 注意这里的,**selectedChild.getLeft()**,getLeft()是计算相对于父布局的左边距,那么如果设置了margin的话,childView相当于父布局的位置就会变化了,那么我们为了避免这种情况可以使用padding来改变距离,避免使原来的逻辑收到干扰。一切真想大白了!
            int scrollBase = selectedChild.getLeft() + (selectedWidth / 2) - (getWidth() / 2);
            // offset amount: fraction of the distance between centers of tabs
            int scrollOffset = (int) ((selectedWidth + nextWidth) * 0.5f * positionOffset);

            return (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR)
                    ? scrollBase + scrollOffset
                    : scrollBase - scrollOffset;
        }
        return 0;
    }

TabLayout设置最后一个默认选中时位置错乱

因为的TabLayout在dialog中, 而我想让dialog展示之前就把数据设置完毕并且切换到默认的下标。

 fun showPos(index: Int) {
        mTabLayout.getTabAt(index)?.select()
        show()
    }

切换成功了,但是位置不对!

错乱

更操蛋的是将对话框dissmiss后再重新show的时候就正确了!!(这个是最气的

最后debug半天多终于搞定了!现在!让我们回归当出问题时的代码(案发现场


    private fun customTabWidth(tabLayout: TabLayout) {
        // 嫌疑人在这里
        //        ↓
        tabLayout.post {

            try {
                //拿到tabLayout的mTabStrip属性
                val mTabStripField = tabLayout.javaClass.getDeclaredField("mTabStrip")
                mTabStripField.isAccessible = true

                val mTabStrip = mTabStripField.get(tabLayout) as LinearLayout

                
                // balabala 修改Tab间距的代码

                    tabView.invalidate()
                }
            } catch (e: NoSuchFieldException) {
                e.printStackTrace()
            } catch (e: IllegalAccessException) {
                e.printStackTrace()
            }

        }
    }

没错就是这个tabLayout.post(Runnable runnable)啊!当我第一次运行的时候,TabLayout虽然已经创建但是并没有依附到任何Window中,导致runnable会被添加到运行队列中,然后等到这个View已经添加到Window时,再一起运行!那么会导致的现象就是,我第一次修改这个宽度的时候,其实并没有真的修改,真的修改是在对话框展示后,TabLayout依附到View了,那时才会运行!所以才会出现第一次的位置是错误的,第二次的位置是正确的情况!!

关于本人使用TabLayout时的坑就介绍到这里了!希望能够帮助的了大家!!

以上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容