Android 自定义View----文字绘制(文字居中自动换行)

c

使用canvas绘制文字非常简单,但文字绘制根据baseLine无法剧中,网上说法很多,有点麻烦,这里用到一个非常简单的办法,先来看下文字的绘制参考线

图片直接百度复制的,不是自己画的,有时间自己画一个;

文字上下居中其实很简单

        // 单行绘制 (先计算出基线和到文字中间的距离,mPaint不是TextPaint)
        float offset = Math.abs(mPaint.ascent() + mPaint.descent()) / 2;
        canvas.drawText(
                singleTxt,
                getWidth() / 2,
                getHeight() / 2 + offset,
                mPaint  
        );

4多行绘制有两种办法: 

先来看下StaticLayout

        // 多行绘制
        StaticLayout staticLayout = new StaticLayout(
                txtStr,             // 需要绘制的文本
                mTextPaint,         // 画笔对象(这里需要TextPaint)
                getWidth() / 2,         // layout的宽度,文本超出宽度时自动换行
                Layout.Alignment.ALIGN_NORMAL,  // layout的对其方式
                1f,         // 文字行间距,根据文字大小比例计算 0.8f  1.2f
                0,          // 文字行间距,填写具体值
                false       // 纵向是否加额外高度

        );
        // 默认从0,0开始绘制,如果需要调整位置,可通过移动Canvas的方式解决
        canvas.save();
        canvas.translate(getWidth()/4,0);
        staticLayout.draw(canvas);
        canvas.restore();

StaticLayout很好用,系统的TextView里面也用到了,但是如果有自己的需求,随意在任何位置换行就无法实现了。

比如有这样的需求,如何绘制?这里可使用breakText()进行随意折行

class ImageBeginTextView constructor(context: Context) : View(context) {

    private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val txtStr = "人之初,性本善。性相近,习相远。苟不教,性乃迁。教之道,贵以专。" +
            "昔孟母,择邻处。子不学,断机杼。窦燕山,有义方。教五子,名俱扬。养不教,父之过。"

    // 图片左右padding值
    private var mPadding = UnitUtil.dp2px(5)
    // 文字大小
    private var txtSize = UnitUtil.sp2px(16)
    // 图片宽度(绘制文字的时候使用)
    private var imgWidth = UnitUtil.dp2px(20)

    // 声明一个长度为1的Float数组
    private val measuredWidth = FloatArray(1)

    private var fontMetrics: Paint.FontMetrics? = null

    constructor(context: Context, attrs: AttributeSet?) : this(context) {
        initXmlAttrs(context, attrs)
        initPaint()
    }

    private fun initPaint() {
        mPaint.textSize = txtSize
        fontMetrics = mPaint.fontMetrics
        mPaint.strokeWidth = 5f
        mPaint.color = Color.RED
    }

    private fun initXmlAttrs(context: Context, attrs: AttributeSet?) {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageBeginTextView)
                ?: return
        // xml中配置的dp,这里获取的数据是转换后的像素,所以不用再次转换,可直接使用 (后面默认值是像素)
        mPadding = typedArray.getDimension(R.styleable.ImageBeginTextView_image_padding, 20f)
        txtSize = typedArray.getDimension(R.styleable.ImageBeginTextView_text_size, 12f)
        typedArray.recycle()
    }

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

        // 绘制图片
        canvas?.drawBitmap(
                // 根据单行文字高度等比例缩放图片
                getAvatar(mPaint.fontSpacing.toInt(), R.drawable.flag),
                mPadding, 0f,
                mPaint
        )

        // 基线Y轴
        var verticalOffset = abs(fontMetrics?.top ?: mPaint.ascent())

        var start = 0
        while (start < txtStr.length) {

            // 首行需要减掉图片宽度和开始padding,图片宽度,图片右边padding,文字右边padding;
            val maxWidth = if (start == 0)
                width - mPadding - imgWidth - mPadding - mPadding
            else
                width - mPadding * 2

            // 返回一个长度,本行绘制了多少个字
            val breakTextCount = mPaint.breakText(
                    txtStr,
                    true,     // 是否正向绘制
                    maxWidth,                   // 最大绘制宽度
                    measuredWidth               // 绘制本行文字的宽度存在这个数组中
            )

            /*
            绘制文字
            text:  需要绘制的文本
            start:  从哪个位置开始绘制
            end:    绘制到哪里,最多绘制到最后一位
            x,y:    如果是第一行,从图片后边开始绘制
            paint:  画笔
             */
            canvas?.drawText(
                    txtStr,
                    start,
                    if (start + breakTextCount > txtStr.length) txtStr.length else start + breakTextCount,   //
                    if (start == 0) mPadding + imgWidth + mPadding else mPadding, verticalOffset,
                    mPaint
            )
            start += breakTextCount
            verticalOffset += mPaint.fontSpacing
        }

    }

    private fun getAvatar(height: Int, img: Int): Bitmap {
        val options = BitmapFactory.Options()
        // inJustDecodeBounds为true,不返回bitmap,只返回这个bitmap的尺寸
        options.inJustDecodeBounds = true
        // 从资源中读取(比较浪费资源,所以上面设置为true,只获取图片宽高)
        BitmapFactory.decodeResource(resources, img, options)

        // 根据缩放比例重新计算宽高
        options.inDensity = options.outHeight
        options.inTargetDensity = height

        imgWidth = options.outWidth.toFloat() * height / options.outHeight

        // 再设置为false,最后要返回bitmap
        options.inJustDecodeBounds = false

        return BitmapFactory.decodeResource(resources, img, options)
    }

}

上面只是简单的设置了两个自定义属性,如果有需要,可随意增加

在styles.xml中添加

    <declare-styleable name="ImageBeginTextView">
        <!--图片左右padding,也是文字左右两边的padding-->
        <attr name="image_padding" format="dimension" />
        <!--文字大小-->
        <attr name="text_size" format="dimension" />
    </declare-styleable>

在布局文件中可直接设置参数:

    <com.example.hencoderplus.view.ImageBeginTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:image_padding="5dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:text_size="30sp" />

   

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值