由于android自带的tablayout不能实现某些效果,所以我们简单自定义一个,能够满足大部分的使用场景
要点1:继承HorizontalScrollView,linearlayout中addview每个tab
要点2:viewpager的addOnPageChangeListener监听滚动设置指示器的位置,然后在ondraw方法中使用GradientDrawable设置bounds,draw(canvas)画指示器
要点3:滚动时自动使tab滚到中间,即x=tab.left-屏幕宽度/2+tab.width/2
要点4:为了保证指示器的y坐标一致,需要事先设置相对标题为选中加粗后的标题,使用Paint.getTextBounds()测量标题的宽高,计算指示器的marginTop
代码:
style文件:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CustomTabLayout">
<attr name="tab_width" format="dimension"/>
<attr name="tab_height" format="dimension"/>
<attr name="scrollable" format="boolean"/>
<attr name="even_screen" format="boolean"/>
<attr name="title_color" format="color"/>
<attr name="title_select_color" format="color"/>
<attr name="title_size" format="dimension"/>
<attr name="title_select_size" format="dimension"/>
<attr name="show_indicator" format="boolean"/>
<attr name="indicator_height" format="dimension"/>
<attr name="indicator_color" format="color"/>
<attr name="indicator_radius" format="dimension"/>
<attr name="indicator_marginTop" format="dimension"/>
<attr name="indicator_marginBottom" format="dimension"/>
<attr name="indicator_width_equal_title" format="boolean"/>
<attr name="tab_background_color" format="color"/>
<attr name="tab_select_background_color" format="color"/>
<attr name="indicator_width" format="dimension"/>
</declare-styleable>
</resources>
customtablayout.java
import android.content.Context
import android.graphics.*
import android.graphics.drawable.GradientDrawable
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.HorizontalScrollView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.core.view.get
import androidx.core.view.size
import androidx.viewpager.widget.ViewPager
import com.JTJK.jpl.R
import com.JTJK.jpl.util.*
class CustomTabLayout : HorizontalScrollView {
private lateinit var mTabsContainer:LinearLayout
private var mViewPager:ViewPager?=null
private var mTitles:MutableList<String> = mutableListOf()
private var mEventScreen=true
private var mTabWidth=0f
private var mTabHeight=0f
private var mCurrentPosition=0
private var mPositionOffset=0f
private var mTitleColor=Color.BLACK
private var mTitleSize=0f
private var mTitleSelectColor=Color.BLACK
private var mTitleSelectSize=0f
private var mShowIndicator=true
private var mIndicatorHeight=0f
private var mIndicatorRadius=0f
private var mIndicatorMarginTop=0f
private var mIndicatorMarginBottom=0f
private var mTabBackGroundColor=Color.WHITE
private var mTabSelectBackGroundColor=Color.WHITE
private var mTabsContainerHeight=0
private var mIndicatorDrawable=GradientDrawable()
private var mTabCount=0
private var mIndicatorWidthEqualTitle=true
private var mIndicatorBounds=Rect()
private var mIndicatorWidth=0f
private var mDefaultTextSize=0f
private var mDefaultIndicatorHeight=6f
private var mIndicatorColor=Color.BLUE
constructor(context: Context) : super(context) {
}
constructor(context: Context, attrs: AttributeSet):this(context,attrs,0){
}
constructor(context: Context, attrs: AttributeSet, defStyle:Int):this(context,attrs,defStyle,0){
}
constructor(context: Context, attrs: AttributeSet, defStyle:Int, defRes:Int):super(context,attrs,defStyle,defRes){
initView(context,attrs)
}
private fun initView(context:Context,attrs: AttributeSet){
setWillNotDraw(false)
val ta=context.obtainStyledAttributes(attrs, R.styleable.CustomTabLayout)
mEventScreen=ta.getBoolean(R.styleable.CustomTabLayout_even_screen,true)
mTabWidth=ta.getDimension(R.styleable.CustomTabLayout_tab_width,0f)
mTabHeight=ta.getDimension(R.styleable.CustomTabLayout_tab_height,0f)
mTitleColor=ta.getColor(R.styleable.CustomTabLayout_title_color,Color.BLACK)
mTitleSelectColor=ta.getColor(R.styleable.CustomTabLayout_title_select_color,Color.BLACK)
mTitleSize=ta.getDimension(R.styleable.CustomTabLayout_title_size,0f)
mTitleSelectSize=ta.getDimension(R.styleable.CustomTabLayout_title_select_size,0f)
mTabBackGroundColor=ta.getColor(R.styleable.CustomTabLayout_tab_background_color,Color.WHITE)
mTabSelectBackGroundColor=ta.getColor(R.styleable.CustomTabLayout_tab_select_background_color,Color.WHITE)
mShowIndicator=ta.getBoolean(R.styleable.CustomTabLayout_show_indicator,true)
mIndicatorHeight=ta.getDimension(R.styleable.CustomTabLayout_indicator_height,0f)
if(mShowIndicator && mIndicatorHeight==0f){
mIndicatorHeight=mDefaultIndicatorHeight.dp2px()
}
mIndicatorRadius=ta.getDimension(R.styleable.CustomTabLayout_indicator_radius,0f)
mIndicatorMarginTop=ta.getDimension(R.styleable.CustomTabLayout_indicator_marginTop,0f)
mIndicatorMarginBottom=ta.getDimension(R.styleable.CustomTabLayout_indicator_marginBottom,0f)
mIndicatorWidth=ta.getDimension(R.styleable.CustomTabLayout_indicator_width,0f)
mIndicatorWidthEqualTitle=ta.getBoolean(R.styleable.CustomTabLayout_indicator_width_equal_title,true)
if(mIndicatorWidth>0f){
mIndicatorWidthEqualTitle=false
}
mIndicatorColor=ta.getColor(R.styleable.CustomTabLayout_indicator_color,ColorUtil.getResuorceColor(context,R.color.indicator))
ta.recycle()
mTabsContainer=LinearLayout(context)
mTabsContainer.gravity=Gravity.CENTER
addView(mTabsContainer)
}
fun setViewPager(vp:ViewPager){
if(vp.adapter==null){
throw Throwable("viewpager adapter can not be null")
}
mTabCount = vp.adapter!!.count
if(mTabCount==0){
throw Throwable("viewpager adapter count can not be zero")
}
mViewPager=vp
mTitles.clear()
for(i in 0 until mTabCount){
addTab(i)
}
vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
override fun onPageScrollStateChanged(state: Int) {
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
mPositionOffset=positionOffset
if(positionOffset==0f){
mCurrentPosition=position
selectTab(mCurrentPosition)
scrollToMiddle()
}
calculateIndicatorBounds(position)
invalidate()
}
override fun onPageSelected(position: Int) {
//onPageSelected会在onPageScrolled的中途调用
if(mCurrentPosition!=position){
mCurrentPosition=position
}
}
})
//初始选中第一项
/*if(mShowIndicator){
Handler(Looper.myLooper()!!).post {
//selectTab(0)
//calculateIndicatorBounds()
}
}
*/
}
//滚动到中间
private fun scrollToMiddle(){
if(mTabsContainer.size-1<mCurrentPosition) return
mViewPager?.let {
val tabView= mTabsContainer[mCurrentPosition]
var x=tabView.left-ScreenUtil.getScreenWidth()/2+tabView.width/2
scrollTo(x,0)
}
}
private fun getTab(i:Int):View?{
if(mTabsContainer.childCount-1>=i){
return mTabsContainer[i]
}
return null
}
private fun addTab(i:Int){
val tabView=LayoutInflater.from(context).inflate(R.layout.item_custom_tab,mTabsContainer,false)
if(mTabWidth>0f){
tabView.layoutParams.width=mTabWidth.toInt()
mEventScreen=false
}else{
if(mEventScreen){
tabView.layoutParams.width= ScreenUtil.getScreenWidth()/mViewPager!!.adapter!!.count
}
}
if(mTabHeight>0){
tabView.layoutParams.height=mTabHeight.toInt()
}
mTabsContainerHeight=tabView.layoutParams.height
//tabView.setBackgroundColor(mTabBackGroundColor)
val tabTitle=tabView.findViewById<TextView>(R.id.tv_tab_title)
val title=mViewPager!!.adapter!!.getPageTitle(i).toString()
mTitles.add(title)
tabTitle.text=(title)
tabTitle.setTextColor(mTitleColor)
mDefaultTextSize=tabTitle.textSize
if(mTitleSize>0f)
tabTitle.textSize=mTitleSize.px2sp()
//setTitleMarginTop(tabTitle)
tabView.setOnClickListener{
if(mViewPager?.currentItem==i){
return@setOnClickListener
}
selectTab(i)
}
if(mTitleSize>0f){
tabTitle.textSize=mTitleSize.px2sp()
}
mTabsContainer.addView(tabView)
}
/* private fun setTitleMarginTop(tabTitle:TextView){
val bounds = getTextBounds(tabTitle) ?: return
//居中
val titleTop=(mTabsContainerHeight-(bounds.bottom-bounds.top+mIndicatorHeight)+mIndicatorMarginTop+mIndicatorMarginBottom)/2
(tabTitle.layoutParams as RelativeLayout.LayoutParams).topMargin=titleTop.toInt()
}*/
private fun getTextBounds(title:String,textSize:Float,isBold:Boolean):Rect?{
if(title.isNullOrEmpty()) return null
val titlePaint=Paint(Paint.ANTI_ALIAS_FLAG)
titlePaint.textSize=textSize
titlePaint.isFakeBoldText=isBold
val bounds=Rect()
titlePaint.getTextBounds(title,0,title.length,bounds)
return bounds
}
private fun selectTab(i:Int){
if(mTabsContainer.childCount-1<i) return
val tabView=mTabsContainer[i]
val tabTitle=tabView.findViewById<TextView>(R.id.tv_tab_title)
//val defaultTextSize=tabTitle.textSize
mViewPager?.currentItem=i
//选中样式
tabTitle.typeface=Typeface.defaultFromStyle(Typeface.BOLD)
tabTitle.setTextColor(mTitleSelectColor)
if(mTitleSelectSize>0f)
tabTitle.textSize=mTitleSelectSize.px2sp()
//未选中样式
for(j in 0 until mViewPager!!.adapter!!.count){
if(j!=i && mTabsContainer.childCount-1>=j){
val tv=mTabsContainer[j]
val tt=tv.findViewById<TextView>(R.id.tv_tab_title)
tt.typeface=Typeface.defaultFromStyle(Typeface.NORMAL)
tt.setTextColor(mTitleColor)
if(mTitleSize>0f){
tt.textSize=mTitleSize.px2sp()
}else{
tt.textSize=mDefaultTextSize.px2sp()
}
}
}
// calculateIndicatorBounds()
}
private fun calculateIndicatorBounds(position:Int){
if(!mShowIndicator || mTabCount==0) return
val tabView=mTabsContainer[position]
val tabTitle=tabView.findViewById<TextView>(R.id.tv_tab_title)
val bounds = getTextBounds(tabTitle.text.toString(),if(mTitleSelectSize>0f) mTitleSelectSize else mDefaultTextSize,true) ?: return
var left=0
var top=0
var right=0
var bottom=0
/*top=(tabTitle.bottom+mIndicatorMarginTop).toInt()
bottom=(top+mIndicatorHeight-mIndicatorMarginBottom).toInt()
if(mIndicatorWidthEqualTitle){
left=tabTitle.left
right=tabTitle.right
}else{
if(mIndicatorWidth>0f){
left=((tabView.layoutParams.width-mIndicatorWidth)/2).toInt()
right=left+mIndicatorWidth.toInt()
}
}
val offset=(tabView.layoutParams.width*mPositionOffset).toInt()
left+=tabView.left+offset
right+=tabView.left+offset*/
top=(tabView.height-(tabView.height-(bounds.bottom-bounds.top))/2+mIndicatorMarginTop).toInt()
bottom=(top+mIndicatorHeight-mIndicatorMarginBottom).toInt()
if(mIndicatorWidthEqualTitle){
left=(tabView.width-(bounds.right-bounds.left))/2
right=left+(bounds.right-bounds.left)
}else{
if(mIndicatorWidth>0f){
left=((tabView.layoutParams.width-mIndicatorWidth)/2).toInt()
right=left+mIndicatorWidth.toInt()
}
}
val offset=(tabView.layoutParams.width*mPositionOffset).toInt()
left+=tabView.left+offset
right+=tabView.left+offset
mIndicatorBounds.apply {
this.left=left
this.top=top
this.right=right
this.bottom=bottom
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if(canvas==null || mTabCount<=0 || !mShowIndicator) return
mIndicatorDrawable.bounds=mIndicatorBounds
mIndicatorDrawable.setColor(mIndicatorColor)
if(mIndicatorRadius>0f){
mIndicatorDrawable.cornerRadius=mIndicatorRadius
}
mIndicatorDrawable.draw(canvas)
}
}
效果:
1665486292888192