Android 手把手进阶自定义View(二)- 文字

一、基础准备


这一篇我们来学习一下绘制文字相关,先看一下 API 介绍 自定义 View 1-3 drawText() 文字的绘制

 

二、圆环进度条,文字横向、纵向居中


如上图所示,上图的绘制可以分为三步:底圆环、进度条、中间文字。

圆环的绘制可以直接把 Paint 的边加粗然后绘制一个圆框即可即可:

Paint.setStyle(Paint.Style.STROKE)
Paint.setStrokeWidth()

进度条的的绘制可以使用圆弧,并且需要设置两个端点的形状:

setStrokeCap(Paint.Cap cap)

对应平头、圆头、方头

接下来是中间的文字,绘制文字的api是:

drawText(String text, float x, float y, Paint paint)

方法的参数很简单:text 是文字内容,x 和 y 是文字的坐标。但需要注意:这个坐标并不是文字的左上角,而是一个与左下角比较接近的位置。大概在这里:

所以需要涉及到文字的横向和纵向居中。

横向居中

setTextAlign(Paint.Align align) 
设置文字的对齐方式。一共有三个值:LEFT、CETNER 和 RIGHT。默认值为 LEFT。分别对应下面的效果:

纵向居中:

纵向居中复杂一些,一种是 getTextBounds 获取文字区域占的矩形 Rect,另一种是 getFontMetrics() 获取文字线(有5条),如下图:

 两种方式的代码实现如下:

        //文字纵向居中
        //方式1:getTextBounds,缺点是文字动态变化时,文字会跳动
        mPaint.getTextBounds(CENTER_TEXT,0,CENTER_TEXT.length,mCenterTextBound)
        var offset = (mCenterTextBound.bottom + mCenterTextBound.top)/2
        canvas.drawText(CENTER_TEXT,width/2f,height/2f - offset,mPaint)
        //方式2
        var offset = (mFontMetrics.ascent + mFontMetrics.descent)/2
        canvas.drawText(CENTER_TEXT,width/2f,height/2f - offset,mPaint)

完整代码如下:

class SportsView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    val RADIUS = Utils.dp2px(150)
    val RING_WIDTH = Utils.dp2px(20)
    val CIRCLE_COLOR = Color.parseColor("#9C9C9C")
    val HIGHT_LIGHT_COLOE = Color.parseColor("#D81B60")
    val CENTER_TEXT = "top1"

    var mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    var mCenterTextBound:Rect = Rect()
    var mFontMetrics : Paint.FontMetrics

    init {
        mPaint.strokeWidth = RING_WIDTH
        mPaint.textSize = Utils.dp2px(100)
        //文字横向居中
        mPaint.textAlign = Paint.Align.CENTER
        mFontMetrics = mPaint.fontMetrics
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        //圆环
        mPaint.style = Paint.Style.STROKE
        mPaint.color = CIRCLE_COLOR
        canvas.drawCircle(width / 2f, height / 2f, RADIUS, mPaint)
        //圆弧
        mPaint.color = HIGHT_LIGHT_COLOE
        mPaint.strokeCap = Paint.Cap.ROUND
        canvas.drawArc(
            width / 2f - RADIUS, height / 2f - RADIUS, width / 2f + RADIUS, height / 2f + RADIUS,
            -90f, 225f, false, mPaint
        )
        mPaint.style = Paint.Style.FILL

        //文字纵向居中
        //方式1:getTextBounds,缺点是文字动态变化时,文字会跳动
//        mPaint.getTextBounds(CENTER_TEXT,0,CENTER_TEXT.length,mCenterTextBound)
//        var offset = (mCenterTextBound.bottom + mCenterTextBound.top)/2
//        canvas.drawText(CENTER_TEXT,width/2f,height/2f - offset,mPaint)
        //方式2
        var offset = (mFontMetrics.ascent + mFontMetrics.descent)/2
        canvas.drawText(CENTER_TEXT,width/2f,height/2f - offset,mPaint)
    }
}

 

三、文字换行


3.1 自动换行

在 Android 中,Canvas.drawText(String) 超过一行时是不会自动换行的,想要换行可以使用 StaticLayout:

StaticLayout 的构造方法是 :

StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)

其中参数里:

  • width 是文字区域的宽度,文字到达这个宽度后就会自动换行;
  • align 是文字的对齐方向;
  • spacingmult 是行间距的倍数,通常情况下填 1 就好;
  • spacingadd 是行间距的额外增加值,通常情况下填 0 就好;
  • includepad 是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。

如果你需要进行多行文字的绘制,并且对文字的排列和样式没有太复杂的花式要求,那么使用 StaticLayout 就好。

class ImageTextView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    val TEXT = "广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android" +
            "广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android" +
            "广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android"

    lateinit var mStaticLayout: StaticLayout
    var mPaint = TextPaint()

    init {
        mPaint.textSize = Utils.dp2px(16)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mStaticLayout = StaticLayout(TEXT, mPaint, width, Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false)
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        mStaticLayout.draw(canvas)
    }

}

效果如下:

3.2 自定义换行

如果想要自己控制换行,就需要用到下面这个方法了:

breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
这个方法也是用来测量文字宽度的。但和 measureText() 的区别是, breakText() 是在给出宽度上限的前提下测量文字的宽度。如果文字的宽度超出了上限,那么在临近超限的位置截断文字。

比如我们想实现下面这个效果:

假如我们直接用这个方法:

canvas.drawText(TEXT,  0f, 50f, mPaint)

会出现下面的效果,文字绘制到屏幕外了:

完整的代码应该这么写:

class ImageTextView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {

    val BITMAP_WIDTH = Utils.dp2px(100)
    val TEXT = "广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android123" +
            "广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android123" +
            "广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android123" +
            "广东省广州市深圳市 Android123广东省广州市深圳市 Android123广东省广州市深圳市 Android123"

    var mPaint = TextPaint()
    var mBitmap: Bitmap
    var cutWidth = FloatArray(1)

    init {
        mPaint.textSize = Utils.dp2px(16)
        mBitmap = Utils.decodeBitmap(resources, R.drawable.avatar, BITMAP_WIDTH.toInt())
    }

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

        canvas.drawBitmap(mBitmap, width - BITMAP_WIDTH, 100f, mPaint)
        //第一行
        var index = mPaint.breakText(TEXT, true, width.toFloat(), cutWidth)
        canvas.drawText(TEXT, 0, index, 0f, 50f, mPaint)
        //第二行
        var oldIndex = index
        index = mPaint.breakText(TEXT, index, TEXT.length, true, width.toFloat(), cutWidth)
        canvas.drawText(TEXT, oldIndex, oldIndex + index, 0f, 50f + mPaint.fontSpacing, mPaint)
        //第三行
        oldIndex = index + oldIndex
        index = mPaint.breakText(TEXT, index, TEXT.length, true, width - BITMAP_WIDTH, cutWidth)
        canvas.drawText(TEXT, oldIndex, oldIndex + index, 0f, 50f + mPaint.fontSpacing * 2, mPaint)
    }

}

当然,这里只是一个演示,实际项目中要复杂的多,需要自动去判断当前行的文字是否与图片覆盖来达到自动换行,这里就不再细说了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值