android时间textView不等距导致宽度跳来跳去自定义View解决

android textView显示时间的时候,经常遇到由于每个字符不等距导致宽度跳来跳去。原因是,大部分字体0~9的数字的宽度是不等距的,因此当显示时间数字的时候,由于组合不同,就会导致宽度不同,就会跳来跳去。

因此解决办法有2个:
第一,使用monospace的字体库。系统有自带的,或者让你们公司给一套monospace的字体库集成使用。
第二,就是今天我要说的自定义view来解决了。

解决的思路如下:

  1. 统计0~9 10个数字的最大宽度,所有的数字字符就这10个,以最大宽度为准;
  2. 其他字符比如/, :, -以它原本宽度为准就好;

最后效果,能够支持wrap_content,效果还是不错的。
当然,这里是抛砖引玉,功能是比较有限的,主要是思路和基本功能已经实现。
比如这里我的实现,
是没有写atrrs来支持自定义颜色,字体大小等;可以自行实现;
是不建议修改出现文字大小不一样的情况的,可以简单的继承来实现多段颜色。

open class MonospaceTimeView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
    fun convertDpToPixel(dp: Float, context: Context): Float {
        val metrics = context.resources.displayMetrics
        val px = dp * (metrics.densityDpi / 160f)
        return px
    }

    open val mColor = Color.BLACK

    //字体大小,其实就是整个View的高度;这是一个随意的值,必须通过initParamOnCreate设置。
    open val mTextSize = convertDpToPixel(16f, context)

    protected lateinit var mPaint:Paint

    var mTextChars:CharArray? = null
        private set

    private var spaceWidth = Int.MAX_VALUE
    private var eachNumWidth = 0
    private val normalCharMap = mutableMapOf<Char, Int>()

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        initPaint()
        eachWidthAndHeight()
    }

	/** 可以继承它;然后给super().initPaint()继续追加自己想要的属性,比如setTypeface
	*/
    protected open fun initPaint() {
        mPaint = Paint().apply {
            setColor(mColor) // 设置画笔颜色
            textSize = mTextSize
            isAntiAlias = true // 抗锯齿
            textAlign = Paint.Align.CENTER
        }
    }

    open fun setTimeString(str:String) {
        mTextChars = str.toCharArray()
        invalidate()
    }

    /**
     * 显示之前,一定要优先调用一下,确定每个字符的宽度。
     */
    private fun eachWidthAndHeight() {
        val rect = Rect()
        var maxWidth = -1
        val chars = listOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
        var minWidth = Int.MAX_VALUE
        for (ch in chars) {
            mPaint.getTextBounds("" + ch, 0, 1, rect)
            val width = rect.left + rect.right
            maxWidth = max(width, maxWidth)
            minWidth = min(minWidth, width)
        }

        val otherChars = listOf(':', '/', '-')
        for (ch in otherChars) {
            mPaint.getTextBounds("" + ch, 0, 1, rect)
            val width = rect.left + rect.right
            minWidth = min(minWidth, width)
            normalCharMap[ch] = width
        }

        //由于空格不能单独统计,所以以最小的一半为准
        eachNumWidth = maxWidth
        this.spaceWidth = minWidth / 2
    }

    private val canvasRect = Rect()

	/**
	可以继承给特定的字段特定的paint,这样就能改变颜色。
	*/
    open fun getOnDrawPaintByTextIndex(index:Int, total:Int) : Paint{
        return mPaint
    }

    private fun charToWidth(ch:Char) : Int{
        if (ch in '0'..'9') {
            return eachNumWidth
        } else if (ch == ' ') {
            return spaceWidth
        } else if (normalCharMap.containsKey(ch)) {
            return normalCharMap[ch]!!
        } else {
            return eachNumWidth
        }
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        if (mTextChars == null) {
            return
        }

        canvas.getClipBounds(canvasRect)
        val cHeight: Int = canvasRect.height()
        mPaint.getTextBounds("0", 0, 1, canvasRect) //取任意字符的高度即可
        val baseLine: Float = cHeight / 2f + canvasRect.height() / 2f - canvasRect.bottom

        var startX = 0f
        val size = mTextChars?.size ?: 0
        mTextChars?.forEachIndexed { index, ch ->
            val w = charToWidth(ch)
            canvas.drawText("" + ch, startX + (w.toFloat() / 2), baseLine, getOnDrawPaintByTextIndex(index, size))
            startX += w
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)

        val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSpecSize = MeasureSpec.getSize(heightMeasureSpec)

        if (isInEditMode) {
            if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(100, (mTextSize + 0.5).toInt())
            } else if(widthSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(100, heightSpecSize)
            } else if (heightSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(widthSpecSize, (mTextSize + 0.5).toInt())
            }
            return
        }

        var wid = 0f
        mTextChars?.forEach { ch->
            wid += charToWidth(ch)
        }

        if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension((wid  + 0.5).toInt(), (mTextSize + 0.5).toInt())
        } else if(widthSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension((wid  + 0.5).toInt(), heightSpecSize)
        } else if (heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSpecSize, (mTextSize + 0.5).toInt())
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值