基础知识
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()
}
最后附上完整的代码
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()
}
}