TabLayout背景

基础知识

com.google.android.material.tabs.TabLayout
在材料主题下,默认是有个背景颜色的,用的colorSurface【默认白色】

实现如下效果

忘了那个帖子哪里看到的了,需求就是这个样子,弧线


image.png

image.png

image.png

原贴作者是自定义了一个View,我们这里偷懒了,直接继承TabLayout,只是在监听到tab切换的时候修改下背景即可,保留TabLayout的所有属性.
弧线就是个三阶贝塞尔曲线,中间两个黑点就是控制点,两个蓝点是起点和终点


image.png

分析下,有3种情况,第一个只有右半边,最后一个只有左半边,中间的两边都有.
在容器大小确定以后,这3种其实都可以画出来的,额,就是path可以计算出来了,当然了,中间那个位置还不确定,我们可以先默认中间的位置就在(0,0)的位置,后边根据实际需求移动一下就完事了
所以在onLayout里把这3种path都计算出来,后边用到就直接拿来用了

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if(tabCount==0){
            return
        }
        delet=height/4f//我这里用了高度的1/4,自己可以适当修改
        val y=height.toFloat()

        pathTotal.reset()//这就是整个容器的矩形path
        pathTotal.addRect(0f,0f,width.toFloat(),y,Path.Direction.CW)

        var normalX0=width*1f/tabCount;
        pathFirst.reset()
        pathFirst.moveTo(0f,0f)
        pathFirst.lineTo(normalX0-delet,0f)
        pathFirst.cubicTo(normalX0,0f,normalX0,height.toFloat(),normalX0+delet,height.toFloat())
        pathFirst.lineTo(0f,height.toFloat())
        pathFirst.close()

        val normalXN=width-normalX0

        pathLast.reset()
        pathLast.moveTo(normalXN+delet,0f)
        pathLast.cubicTo(normalXN,0f,normalXN,y,normalXN-delet,y)
        pathLast.lineTo(width.toFloat(),y)
        pathLast.lineTo(width.toFloat(),0f)
        pathLast.close()

        //path center,temp location at (0,0)
        pathCenter.reset()
        pathCenter.moveTo(delet,0f)
        pathCenter.cubicTo(0f,0f,0f,y,-delet,y)
        pathCenter.lineTo(normalX0+delet,y)
        pathCenter.cubicTo(normalX0,y,normalX0,0f,normalX0-delet,0f)
        pathCenter.close()

        updateBg()
    }

如果tab有4个5个,那么我们要画这么多吗?不用,我们只需要知道选中的那个path,然后用整个容器的矩形path 把选中的path去掉就ok了
pathSelect就是选中的tab背景path,
剩余部分的path就是利用Path.FillType.EVEN_ODD 从pathTotal里把上边的pathSelect抠掉就完事了.

    private fun checkSelectPath(){
        pathSelect.reset()
        when(index){
            0->{
                pathSelect.addPath(pathFirst)
            }
            tabCount-1->{
                pathSelect.addPath(pathLast)
            }
            else->{
                val matrix=Matrix()
                matrix.setTranslate(index*width.toFloat()/tabCount,0f)
                pathCenter.transform(matrix,pathSelect)//我们把那个pathCenter 平移一下赋值给pathSelect即可
            }
        }
        pathOther.reset()
        pathOther.addPath(pathTotal)
        pathOther.fillType=Path.FillType.EVEN_ODD
        pathOther.addPath(pathSelect)
    }

2个path都有了,设置背景即可
这里用的setBackgroud的原因是为了保留ripple效果,最早我是直接写在onDraw里的,可那样的话ripple效果就被挡住了,看不见了【完全没弄明白为啥挡住了,按理parent的onDraw是画在最底层的,咋能挡住里边child的ripple效果?】

    private fun updateBg(){
        if(height>0&&width>0&&tabCount>0){
            val bitmap=Bitmap.createBitmap(width,height,Bitmap.Config.RGB_565)
            val canvas=Canvas(bitmap)
            checkSelectPath()
            p.setColor(Color.parseColor("#FF5722"))
            canvas.drawPath(pathSelect,p)

            p.setColor(Color.parseColor("#E91E63"))
            canvas.drawPath(pathOther,p)

            background=BitmapDrawable(resources,bitmap)
        }
    }

监听tabListener

    override fun onTabSelected(tab: Tab?) {
        println("tab selected=======${tab?.position}")
        index=tab?.position?:0
        updateBg()
    }

最后附上完整的代码

    
package com.mitac.app2020.august.motion

import android.content.Context
import android.graphics.*
import android.graphics.drawable.BitmapDrawable
import android.util.AttributeSet
import com.google.android.material.tabs.TabLayout

class CustomBGTabLayout : TabLayout, TabLayout.OnTabSelectedListener {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    private val p = Paint()

    init {
        p.flags = Paint.ANTI_ALIAS_FLAG
        p.style = Paint.Style.FILL

    }

    private var index = 0

    var delet = 20f
    val pathTotal = Path()
    val pathFirst = Path()
    val pathLast = Path()
    val pathCenter = Path()
    val pathSelect = Path()
    val pathOther = Path()
    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
        if (tabCount == 0) {
            return
        }
        delet = height / 4f
        val y = height.toFloat()

        pathTotal.reset()
        pathTotal.addRect(0f, 0f, width.toFloat(), y, Path.Direction.CW)

        var normalX0 = width * 1f / tabCount;
        pathFirst.reset()
        pathFirst.moveTo(0f, 0f)
        pathFirst.lineTo(normalX0 - delet, 0f)
        pathFirst.cubicTo(
            normalX0,
            0f,
            normalX0,
            height.toFloat(),
            normalX0 + delet,
            height.toFloat()
        )
        pathFirst.lineTo(0f, height.toFloat())
        pathFirst.close()

        val normalXN = width - normalX0

        pathLast.reset()
        pathLast.moveTo(normalXN + delet, 0f)
        pathLast.cubicTo(normalXN, 0f, normalXN, y, normalXN - delet, y)
        pathLast.lineTo(width.toFloat(), y)
        pathLast.lineTo(width.toFloat(), 0f)
        pathLast.close()

        //path center,temp location at (0,0)
        pathCenter.reset()
        pathCenter.moveTo(delet, 0f)
        pathCenter.cubicTo(0f, 0f, 0f, y, -delet, y)
        pathCenter.lineTo(normalX0 + delet, y)
        pathCenter.cubicTo(normalX0, y, normalX0, 0f, normalX0 - delet, 0f)
        pathCenter.close()

        updateBg()
    }

    private fun checkSelectPath() {
        pathSelect.reset()
        when (index) {
            0 -> {
                pathSelect.addPath(pathFirst)
            }
            tabCount - 1 -> {
                pathSelect.addPath(pathLast)
            }
            else -> {
                val matrix = Matrix()
                matrix.setTranslate(index * width.toFloat() / tabCount, 0f)
                pathCenter.transform(matrix, pathSelect)
            }
        }
        pathOther.reset()
        pathOther.addPath(pathTotal)
        pathOther.fillType = Path.FillType.EVEN_ODD
        pathOther.addPath(pathSelect)
    }

    private fun updateBg() {
        if (height > 0 && width > 0 && tabCount > 0) {
            val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
            val canvas = Canvas(bitmap)
            checkSelectPath()
            p.setColor(Color.parseColor("#FF5722"))
            canvas.drawPath(pathSelect, p)

            p.setColor(Color.parseColor("#E91E63"))
            canvas.drawPath(pathOther, p)

            background = BitmapDrawable(resources, bitmap)
        }
    }

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        addOnTabSelectedListener(this)
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        removeOnTabSelectedListener(this)
    }

    override fun onTabReselected(tab: Tab?) {

    }

    override fun onTabUnselected(tab: Tab?) {

    }

    override fun onTabSelected(tab: Tab?) {
        println("tab selected=======${tab?.position}")
        index = tab?.position ?: 0
        updateBg()
    }
}

你可能感兴趣的:(TabLayout背景)