仿IOS标题切换控件

  • 再次更新
    使用中发现因为每个矩形都是左右两个小的拼成的,有时候中间会出现重叠的现象,也就是1px宽,考虑是使用RectF的原因,把RectF修改为Rect

  • 更新
    圆角矩形边框效果不好,像是被切了一般,原因是画笔有一定宽度,画到外面去了,就还剩一半,修改了一下
//画圆角矩形边框
        mPaint.reset()
        mPaint.color = textColor
        mPaint.style = Paint.Style.STROKE
        mPaint.strokeWidth = Utils.dp2px(context, 1f)
        mPaint.isAntiAlias = true
        rectF.left = rectF.left + mPaint.strokeWidth / 2
        rectF.top = rectF.top + mPaint.strokeWidth / 2
        rectF.right = rectF.right - mPaint.strokeWidth / 2
        rectF.bottom = rectF.bottom - mPaint.strokeWidth / 2
        canvas.drawRoundRect(rectF, corner, corner, mPaint)

修改之后的效果.png

每次都设计成ios原生控件的效果,android开发真是难。之前写过一次这个效果,在布局文件里写的,真的很麻烦。这次又有这个需求,写一个自定义view,以后再用就方便多了,记录一下。


ios.jpg

自定义view代码

class IOSTab(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : View(context, attrs, defStyleAttr) {

    private var mTabs: Array<String> = arrayOf("")
    private var selectedPosition = 0
    private var mWidth = 0
    private var mHeight = 0
    private var corner = Utils.dp2px(context, 5f)
    private val mPaint = Paint()

    //字号
    private var textSize = Utils.dp2px(context,15)
    //主色调
    private var mainColor = context.resources.getColor(R.color.colorPrimary)
    //字的颜色
    private var textColor = context.resources.getColor(R.color.white)
    //选中的颜色
    private var selectColor = Color.parseColor("#ccffffff")

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
    constructor(context: Context) : this(context, null)

    init {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.IOSTab)
        mainColor = typedArray.getColor(R.styleable.IOSTab_tabMainColor,context.resources.getColor(R.color.colorPrimary))
        textColor = typedArray.getColor(R.styleable.IOSTab_tabTextColor,context.resources.getColor(R.color.white))
        selectColor = typedArray.getColor(R.styleable.IOSTab_tabSelectColor,Color.parseColor("#ccffffff"))
        textSize = typedArray.getDimension(R.styleable.IOSTab_tabTextSize,Utils.dp2px(context,15))
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = MeasureSpec.getSize(widthMeasureSpec)
        mHeight = MeasureSpec.getSize(heightMeasureSpec)
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        val rectF = RectF(0f, 0f, mWidth.toFloat(), mHeight.toFloat())
        //画背景
        mPaint.isAntiAlias = true
        mPaint.color = mainColor
        mPaint.style = Paint.Style.FILL
        canvas!!.drawRoundRect(rectF, corner, corner, mPaint)
        //画圆角矩形边框
        mPaint.reset()
        mPaint.color = textColor
        mPaint.style = Paint.Style.STROKE
        mPaint.strokeWidth = Utils.dp2px(context, 1f)
        mPaint.isAntiAlias = true
        canvas!!.drawRoundRect(rectF, corner, corner, mPaint)

        //画每一个字
        mPaint.reset()
        mPaint.color = textColor
        mPaint.isAntiAlias = true
        mPaint.textSize = textSize

        val count = mTabs.size
        if (count == 0) {
            return
        } else {
            //画字
            for (i in mTabs.indices){
                drawText(i,canvas,mPaint)
            }
        }
        //每个item的宽度
        val itemWidth = mWidth / mTabs.size .toFloat()
        //画分隔线
        mPaint.strokeWidth = Utils.dp2px(context,0.5f)
        mTabs.indices
                .filter { it != 0 }
                .forEach {
                    canvas.drawLine(it * itemWidth,0f, it * itemWidth, mHeight.toFloat(),mPaint)
                }


        mPaint.reset()
        mPaint.color = selectColor
//        mPaint.alpha = 0xcc
        mPaint.style = Paint.Style.FILL

        //画选中状态
        if (count == 0 || count == 1){
            return
        }else{
            if (selectedPosition == 0){
                //第一个被选中,把左半边画上圆角
                canvas.save()
                //只画左半边
                canvas.clipRect(RectF(0f,0f,itemWidth /2,mHeight.toFloat()))
                //圆角矩形
                val rectF = RectF(0f,0f,itemWidth,mHeight.toFloat())
                canvas.drawRoundRect(rectF, corner, corner, mPaint)
                canvas.restore()

                canvas.drawRect(RectF(itemWidth /2,0f,itemWidth, mHeight.toFloat()),mPaint)
            }else if (selectedPosition == count -1){
                //最后一个被选中,右半边画上圆角
                canvas.save()
                //只画右半边
                canvas.clipRect(RectF((selectedPosition + 0.5f)* itemWidth,0f,count * itemWidth,mHeight.toFloat()))
                //圆角矩形
                val rectF = RectF(selectedPosition* itemWidth,0f,count* itemWidth,mHeight.toFloat())
                canvas.drawRoundRect(rectF, corner, corner, mPaint)
                canvas.restore()
                canvas.drawRect(RectF(selectedPosition* itemWidth,0f,(selectedPosition + 0.5f)* itemWidth, mHeight.toFloat()),mPaint)
            }else{
                //选中中间的
                val rect = Rect((itemWidth * selectedPosition).toInt()
                        , 0
                        , (itemWidth*(selectedPosition+1)).toInt()
                        , mHeight)
                canvas.drawRect(rect,mPaint)
            }

        }

        //画选中的条目的文字
        mPaint.reset()
        mPaint.color = mainColor
        mPaint.isAntiAlias = true
        mPaint.textSize = textSize
        drawText(selectedPosition,canvas,mPaint)

    }

    private fun drawText(position: Int, canvas: Canvas, paint: Paint) {
        val text = mTabs[position]
        val textWidth = paint.measureText(text)
        val itemWidth = mWidth / mTabs.size
        //X起始坐标,X轴居中
        val startX = (itemWidth - textWidth) / 2 + itemWidth * position
        //Y起始坐标,Y轴居中
        val startY = mHeight / 2 + (Math.abs(mPaint.fontMetrics.ascent) - mPaint.fontMetrics.descent) / 2

        canvas.drawText(text, startX, startY, paint)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        if (event!!.action == MotionEvent.ACTION_DOWN){
            if (mTabs.isNotEmpty()){
                selectedPosition = (event.x / (mWidth / mTabs.size)).toInt()
                invalidate()
                if (listener != null)
                    listener!!.onChange(selectedPosition)
            }
        }
        return super.onTouchEvent(event)
    }

    private var  listener: IOSTab.OnSelectedItemChange? = null

    //设置切换监听
    fun setOnSelectedItemChange(listener: OnSelectedItemChange){
        this.listener = listener
    }

    fun setTabs(tabs: Array<String>) {
        mTabs = tabs
        invalidate()
    }

    interface OnSelectedItemChange {
        fun onChange(select: Int)
    }
}

attrs文件中添加

<declare-styleable name="IOSTab">
        <!-- 主色调,未被选中的条目背景色,选中条目的文字颜色-->
        <attr name="tabMainColor" format="color"/>
        <!-- 未被选中的条目的文字颜色,外围圆角边框颜色-->
        <attr name="tabTextColor" format="color"/>
        <!-- 被选中条目的背景色 -->
        <attr name="tabSelectColor" format="color"/>
        <!-- 文字大小 -->
        <attr name="tabTextSize" format="dimension"/>
    </declare-styleable>

使用

在布局文件中

<com.test.www.test.view.IOSTab
            android:id="@+id/ios_tab"
            android:layout_width="200dp"
            android:layout_height="29dp"
            android:layout_gravity="center"
            app:tabSelectColor="#ccffffff"
            />

在activity或者fragment中

val tabs = arrayOf("标题一","标题二","标题三")
ios_tab.setTabs(tabs)
ios_tab.setOnSelectedItemChange(object : IOSTab.OnSelectedItemChange{
            override fun onChange(select: Int) {
                //在这里处理切换
            }
        })

效果


效果.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容