简单的可以设定宽度的自定义TabLayout指示器

       借鉴TabLayout自带的指示器的移动逻辑,一个可以设定指示器长度的指示器

简单的可以设定宽度的自定义TabLayout指示器_第1张图片
效果.png

       TabLayout继承自HorizontalScrollView,他有一个直接的子view,是一个LinearLayout(TabLayout的内部类SlidingTabStrip)。加入tab的操作是直接添加TabView(或者是自定义的布局)为LinearLayout的子view。如果是scrollable的模式的时候,每次选中tab之后,预期是将此tab移动到中间,然后滚动TabLayout;如果是fixed模式,不需要滚动TabLayout。接下来获取tab的当前的左右位置就可以绘制指示器了。
       我的想法是,利用TabLayout的自带的指示器的移动逻辑,只需改变长度就可以了。在自定义的IndicatorView中,添加TabLaoyut切换监听,切换的时候计算tab的实际滚动距离,然后获取tab的左右位置,根据指示器的长度计算指示器的left,绘制指示器就可以了。

具体实现:
class IndicatorView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {
    constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context?) : this(context, null)

    private var bindTabLayout: TabLayout? = null
    var indicatorWidth = 0f
    var indicatorheight = -1f
    private var indicatorLeft = 0f
    private var lastIndicatorLeft = -1f
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val rectF = RectF()
    private var lastSelectedPosition = 0

    private val animator = ValueAnimator.ofFloat()

    fun setIndicatorColor(color: Int) {
        paint.color = color
    }

    fun setupWithTabLayout(tabLayout: TabLayout) {
        bindTabLayout = tabLayout
        tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabReselected(tab: TabLayout.Tab?) {
            }

            override fun onTabUnselected(tab: TabLayout.Tab?) {
                lastSelectedPosition = tab!!.position
            }

            override fun onTabSelected(tab: TabLayout.Tab?) {
                calculate()
            }
        })

        tabLayout.setOnTouchListener { v, event ->
            if (event.actionMasked == MotionEvent.ACTION_UP){
                UIHandler.postDelayed({
                    calculateScroll()
                },300)
            }
            return@setOnTouchListener false
        }

        tabLayout.post {
            calculate()
        }
    }
    // 计算指示器的目标位置
    private fun calculate(){
        val selectedTabPosition = bindTabLayout?.selectedTabPosition ?: -1
        if (selectedTabPosition >= 0) {
            // mTabStrip是TabLayout的子view,他是TabView的直接父View,
            val mTabStrip = bindTabLayout?.getChildAt(0)
            if (mTabStrip is ViewGroup?) {
                mTabStrip?.let {

                    // 计算tab要滚动多少
                    var targetScrollX = calculateScrollXForTab(selectedTabPosition,0f)
                    // tab的父view的长度
                    val tabStripWidth = it.measuredWidth
                    // 计算滚动的实际距离,滚动不能越界
                    if (targetScrollX + bindTabLayout!!.measuredWidth > tabStripWidth){
                        // 滚动到最右边,滚动最大值就是 tab的父view的长度 - tabLayout的长度
                        targetScrollX = tabStripWidth - bindTabLayout!!.measuredWidth
                    }else if (targetScrollX < 0){
                        // 滚动到最左边,滚动最小值为0,不能为负数
                        targetScrollX = 0
                    }
                    val tabView = mTabStrip.getChildAt(selectedTabPosition)
                    tabView?.let {
                        indicatorLeft = it.left + (it.measuredWidth - indicatorWidth) / 2 - (targetScrollX )
                        if (lastIndicatorLeft == -1f){
                            lastIndicatorLeft = indicatorLeft
                            postInvalidate()
                        }else{
                            animator()
                        }
                    }
                }
            }
        }
    }

    private fun calculateScroll(){
        val selectedTabPosition = bindTabLayout?.selectedTabPosition ?: -1
        if (selectedTabPosition >= 0) {
            // mTabStrip是TabLayout的子view,他是TabView的直接父View,
            val mTabStrip = bindTabLayout?.getChildAt(0)
            val currentScrollX = bindTabLayout?.scrollX?:0
            if (mTabStrip is ViewGroup?) {
                mTabStrip?.let {

                    val tabView = mTabStrip.getChildAt(selectedTabPosition)
                    tabView?.let {
                        indicatorLeft = it.left - currentScrollX + (it.measuredWidth - indicatorWidth) / 2
                        animator()
                    }
                }
            }
        }
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (indicatorheight == -1f) indicatorheight = measuredHeight.toFloat()
    }
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        rectF.set(indicatorLeft, measuredHeight - indicatorheight, indicatorLeft + indicatorWidth, measuredHeight.toFloat())
        canvas?.drawRoundRect(rectF, indicatorheight / 2, indicatorheight / 2, paint)
    }

    private fun animator() {
        if (animator.isRunning){
            animator.cancel()
        }
        animator.setFloatValues(lastIndicatorLeft, indicatorLeft)
        animator.duration = 300L
        animator.addUpdateListener { animation ->
            indicatorLeft = animation?.animatedValue as Float
            lastIndicatorLeft = indicatorLeft
            postInvalidate()
        }
        animator.start()
    }

    /**
     * 计算TabLayout要滚动多少,预期值,不是实际距离。
     * 此方法是从TabLayout类中复制过来的。选中指定position的tab,预期是,把此tab移动到中间位置
     * 此方法就是计算这个预期值的
     */
    private fun calculateScrollXForTab(position: Int, positionOffset: Float): Int {
        if (bindTabLayout?.tabMode == TabLayout.MODE_SCROLLABLE) {
            bindTabLayout?.let {
                val mTabStrip = it.getChildAt(0) as ViewGroup
                val selectedChild = mTabStrip.getChildAt(position)
                val nextChild = if (position + 1 < mTabStrip.getChildCount())
                    mTabStrip.getChildAt(position + 1)
                else
                    null
                val selectedWidth = selectedChild?.width ?:0
                val nextWidth = nextChild?.width ?:0

                // base scroll amount: places center of tab in center of parent
                val scrollBase = selectedChild!!.getLeft() + selectedWidth / 2 - it.width / 2
                // offset amount: fraction of the distance between centers of tabs
                val scrollOffset = ((selectedWidth + nextWidth).toFloat() * 0.5f * positionOffset).toInt()

                return if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR)
                    scrollBase + scrollOffset
                else
                    scrollBase - scrollOffset
            }

        }
        return 0
    }

}

使用

在布局中


        
        
    

在activity或者fragment中

// 指示器宽度
indicatorView.indicatorWidth = Utils.dp2px(this, 20f)
// 指示器颜色
indicatorView.setIndicatorColor(resources.getColor(R.color.green))
// 指示器绑定TabLayout
indicatorView.setupWithTabLayout(tabLayout_lesson_analysis)

你可能感兴趣的:(简单的可以设定宽度的自定义TabLayout指示器)