美文网首页
文字的测量与绘制

文字的测量与绘制

作者: 王灵 | 来源:发表于2021-04-02 11:45 被阅读0次

首先本文要叙述的肯定不是在布局上放置一个TextView,然后绘制几个文字

一、明确目标

实现两个效果,如下图


文字居中.jpg 文字位置设置.jpg

二、实现效果一

想要让文字显示在我们希望的位置,我们就需要知道文字绘制的基准点以及基准点位置计算。

//绘制文字
        paint.apply {
            style = Paint.Style.FILL
            textSize = 20.sp
        }
        canvas.drawText("abab", width / 2f, height / 2f, paint)
Screenshot_20210402_102257_com.matthew.drawing_ed.jpg
似乎文字的基准点就是它的左下角了。那问题就简单了。
左右局中:paint.textAlign = Paint.Align.CENTER
上下局中:获取文字的高度,然后把基准点下移1/2

那如何获取文字的高度?

1、静态文字用Bounds

private val bounds = Rect()
//...
paint.getTextBounds("abab", 0, "abab".length, bounds)
canvas.drawText("abab", width / 2f, height / 2f - (bounds.top + bounds.bottom) / 2f, paint)
Screenshot_20210402_113525_com.matthew.drawing_ed.jpg

因为是测量的文字的顶和底,所以计算结果和文字的高有关。比如如果acg和abc。结果就不一样。所以不适合在文字变化的情况下使用

2、动态文字用FontMetrics

private val fontMetrics = Paint.FontMetrics()
//...
paint.getFontMetrics(fontMetrics)
canvas.drawText("abg", width / 2f, height / 2f - (fontMetrics.ascent + fontMetrics.descent) / 2f, paint)
Screenshot_20210402_114459_com.matthew.drawing_ed.jpg

和具体的文字无关,只和字体有关

还有别的属性(可以去探索了解下)

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;
...
}
public static class FontMetrics {
        /**
         * The maximum distance above the baseline for the tallest glyph in
         * the font at a given text size.
         */
        public float   top;
        /**
         * The recommended distance above the baseline for singled spaced text.
         */
        public float   ascent;
        /**
         * The recommended distance below the baseline for singled spaced text.
         */
        public float   descent;
        /**
         * The maximum distance below the baseline for the lowest glyph in
         * the font at a given text size.
         */
        public float   bottom;
        /**
         * The recommended additional space to add between lines of text.
         */
        public float   leading;
    }

效果1的代码

/**
 *Author:wangling
 *Email:wl_0420@163.com
 *Date:4/2/21 9:17 AM
 */
class SportView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private val CIRCLE_COLOR = Color.parseColor("#90A4AE")
    private val HIGHLIGHT_COLOR = Color.parseColor("#FF4081")
    private val RING_WIDTH = 20.dp
    private val RADIUS = 150.dp

    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        //设置字体
        typeface = ResourcesCompat.getFont(context, R.font.font)
    }

    private val bounds = Rect()
    private val fontMetrics = Paint.FontMetrics()

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

        //绘制环
        paint.apply {

            style = Paint.Style.STROKE
            color = CIRCLE_COLOR
            strokeWidth = RING_WIDTH
        }
        canvas.drawCircle(width / 2f, height / 2f, RADIUS, paint)

        // 绘制进度条
        paint.apply {
            color = HIGHLIGHT_COLOR
            strokeCap = Paint.Cap.ROUND
        }
        canvas.drawArc(
            width / 2f - RADIUS,
            height / 2f - RADIUS,
            width / 2f + RADIUS,
            height / 2f + RADIUS,
            -90f,
            225f,
            false,
            paint
        )

        //绘制文字
        paint.apply {
            textAlign = Paint.Align.CENTER
            style = Paint.Style.FILL
            textSize = 80.dp
        }
        paint.getTextBounds("abab", 0, "abab".length, bounds)
        canvas.drawText("abab", width / 2f, height / 2f - (bounds.top + bounds.bottom) / 2f, paint)

//        paint.getFontMetrics(fontMetrics)
//        canvas.drawText("abg", width / 2f, height / 2f - (fontMetrics.ascent + fontMetrics.descent) / 2f, paint)

        paint.strokeWidth = 1f
        //画两条细线,明确中心
        canvas.drawLine(0f, height / 2f, width.toFloat(), height / 2f, paint)
        canvas.drawLine(width / 2f, 0f, width / 2f, height.toFloat(), paint)

    }
}

三、实现效果二

首先我们做一个最简单的drawText

class MultilineTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private val text = "What is Lorem Ipsum?\n" +
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."

    private val fontMetrics = Paint.FontMetrics()
    private val paint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
        //设置字体
        typeface = ResourcesCompat.getFont(context, R.font.font)
        textSize = 20.dp
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        paint.getFontMetrics(fontMetrics)
        //fontMetrics.top 是负数
        canvas.drawText(text, 0f, -fontMetrics.top, paint)
    }
}

效果如图。只有一行,显示不下的被折断了


Screenshot_20210402_151655_com.matthew.drawing_ed.jpg

现在还是一筹莫展,不知道怎么进行吧!那给点提示
有两个非常关键的函数
a、获取给定宽度能绘制多少个字


    /**
     * Measure the text, stopping early if the measured width exceeds maxWidth.
     * Return the number of chars that were measured, and if measuredWidth is
     * not null, return in it the actual width measured.
     *
     * @param text  The text to measure. Cannot be null.(要测量的文本。不能为空。)
     * @param start The offset into text to begin measuring at(从第几个字开始)
     * @param end   The end of the text slice to measure.(文本片的结束位置。)
     * @param measureForwards If true, measure forwards, starting at start.
     *                        Otherwise, measure backwards, starting with end.(如果为真,则从start开始测量。)
     * @param maxWidth The maximum width to accumulate.(允许绘制的最大宽度)
     * @param measuredWidth Optional. If not null, returns the actual width
     *                     measured.(可选如果不是null,则返回实际的宽度

* 测量。)
     * @return The number of chars that were measured. Will always be <=
     *         abs(end - start).(返回所测量的字符数。将永远是<=abs(结束-开始))
     */
    public int breakText(CharSequence text, int start, int end,
                         boolean measureForwards,
                         float maxWidth, float[] measuredWidth) {...}

b、把指定的文字绘制在指定的位置

/**
     * Draw the text, with origin at (x,y), using the specified paint. The origin is interpreted
     * based on the Align setting in the paint.
     *
     * @param text The text to be drawn(要绘制的文本)
     * @param start The index of the first character in text to draw(开始绘制文本中第一个字符的索引)
     * @param end (end - 1) is the index of the last character in text to draw(要绘制文本中最后一个字符的索引)
     * @param x The x-coordinate of the origin of the text being drawn(绘制的文本原点的x坐标)
     * @param y The y-coordinate of the baseline of the text being drawn(绘制的文本原点的y坐标)
     * @param paint The paint used for the text (e.g. color, size, style)
     */
    public void drawText(@NonNull String text, int start, int end, float x, float y,
            @NonNull Paint paint) {...}

有了这两个函数,问题似乎就简单了

class MultilineTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    private val text = "What is Lorem Ipsum?\n" +
            "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
    private val IMAGE_SIZE = 150.dp
    private val fontMetrics = Paint.FontMetrics()
    private val paint = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
        //设置字体
        typeface = ResourcesCompat.getFont(context, R.font.font)
        textSize = 20.dp
    }

    var bitmap = getAvatar(IMAGE_SIZE)
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.drawBitmap(bitmap, width - IMAGE_SIZE, 50.dp, paint)

        paint.getFontMetrics(fontMetrics)
        //给定的宽度能显示多少文字
        val measuredWidth = floatArrayOf(0f)

        var oldCount = 0
        var count = 0
        var verticaloffset = -fontMetrics.top

        while (oldCount < text.length) {
            count =
                paint.breakText(
                    text,
                    oldCount,
                    text.length,
                    true,
                    width.toFloat() ,
                    measuredWidth
                )
            // 绘制文字
            canvas.drawText(
                text,
                oldCount,
                oldCount + count,
                0f,
                verticaloffset,
                paint
            )
            oldCount += count
            verticaloffset += paint.fontSpacing
        }


    }


    /**
     * 按照给出的宽度,对图片进行缩放
     */
    fun getAvatar(width: Float): Bitmap {
        val options = BitmapFactory.Options()
        options.inJustDecodeBounds = true//只读取图片,不加载到内存中
        BitmapFactory.decodeResource(resources, R.drawable.avatar_rengwuxian, options)
        options.inJustDecodeBounds = false//加载到内存中
        options.inDensity = options.outWidth//预期的宽度
        options.inTargetDensity = width.toInt()//实际需要展示的宽度

        return BitmapFactory.decodeResource(resources, R.drawable.avatar_rengwuxian, options)

    }
}
Screenshot_20210402_162516_com.matthew.drawing.jpg

剩下的就是在与图片接触的地方,不进行绘制。
1、判断某一行会不会与图片重叠
2、如果重叠,那就减少文字的最大绘制宽度

不多说,上代码

 /**
     * 如果与图片重叠了就需要减去图片的宽度。
     */
    fun avoidWidth(verticaloffset: Float): Float {
        var top = verticaloffset + fontMetrics.top
        var boolean = verticaloffset + fontMetrics.bottom
        if (boolean > 50.dp && top < IMAGE_SIZE + 50.dp) {
            return IMAGE_SIZE
        }
        return 0f
    }
...
count =
                paint.breakText(
                    text,
                    oldCount,
                    text.length,
                    true,
                   //计算绘制宽度的时候判断
                    width.toFloat() - avoidWidth(verticaloffset),
                    measuredWidth
                )
...
Screenshot_20210402_165141_com.matthew.drawing.jpg

相关文章

网友评论

      本文标题:文字的测量与绘制

      本文链接:https://www.haomeiwen.com/subject/ddsfkltx.html