android自定义TabLayout

由于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

猜你喜欢

转载自blog.csdn.net/a1663049254/article/details/127269667