TabLayout背景 基礎知識 實現如下效果

基礎知識

com.google.android.material.tabs.TabLayout
在材料主題下,默認是有個背景顏色的,用的colorSurface【默認白色】

實現如下效果

忘了那個帖子哪裏看到的了,需求就是這個樣子,弧線




原貼作者是自定義了一個View,我們這裏偷懶了,直接繼承TabLayout,只是在監聽到tab切換的時候修改下背景即可,保留TabLayout的所有屬性.
弧線就是個三階貝塞爾曲線,中間兩個黑點就是控制點,兩個藍點是起點和終點


分析下,有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()
    }

最後附上完整的代碼

    <com.mitac.app2020.august.motion.CustomBGTabLayout
        android:id="@+id/custom"
        android:layout_marginTop="20dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/toolbar"
        android:layout_width="match_parent"
        app:tabGravity="fill"
        app:tabMode="fixed"
        app:tabTextColor="#fff"
        app:tabSelectedTextColor="@color/colorPrimary"
        app:tabMaxWidth="2000dp"
        android:layout_height="70dp"/>
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()
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章