基礎知識
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()
}
}