TabLayoutは出会った穴と方案を使う


下線の幅を変更するピット
効果は次のとおりです.
コード実装方法:
このような効果を実現するには、下線部分を主に制御しています.現在、ネット上には反射によって下線幅を修正する文章がたくさんありますが、このコードは、ソースコードを検討するとIndicatorの幅がTabの幅と一致していることがわかり、インジケータの幅がTabの幅より小さくなることはできません.だから私たちは別の道を切り開くしかありません:CustomViewを通じて自分で線を描いて、OnTabSelectedListenerを追加することによって下線を隠すことを展示して、原生のIndicaqtorの高さを0にしても私たちの展示に影響しません.
OK! Talk is cheap, show me the code.
下線を表示しないように隠します.
    

次に、CustomViewエフェクトを設定します.
手動制御切替時のTabの下線:
mTabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabReselected(tab: TabLayout.Tab?) {
                val view = tab?.customView?.findViewById(R.id.tab_indicator)
                val textView = tab?.customView?.findViewById(R.id.tab_tv_date)
                textView?.setTextColor(mTabSelectedColor)
                view?.visibility = View.VISIBLE
            }

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

            override fun onTabSelected(tab: TabLayout.Tab?) {
                val view = tab?.customView?.findViewById(R.id.tab_indicator)
                val textView = tab?.customView?.findViewById(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(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してからショーを再開した時が正解!(これが一番腹立たしい
最後の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()
            }

        }
    }

そうだなpost(Runnable runnable)ですね!私が初めて実行したとき、TabLayoutはすでに作成されていましたが、Windowに依存していませんでした.runnableが実行キューに追加され、このViewがWindowに追加されるまで待ってから、一緒に実行します.では、私が初めてこの幅を修正したとき、実際には本当に修正していませんでした.本当に修正したのはダイアログボックスに表示された後、TabLayoutがViewに添付され、その時になってから実行されます.だから初めての位置が間違っていて、2回目の位置が正しい場合があります!!
本人がTabLayoutを使っていたときのピットについて紹介します!助けて欲しいみんな!
以上です.