Android advanced advanced - Canvas draw text drawText detailed

opening

The last article introduced the basic operations of Canvas, such as drawing circles, rectangles, ellipses, arcs, etc. In addition to these operations, Canvas also has two more heavyweight drawing capabilities, Text (text) and Path (path) . Today, let's take a look at Text. draw

Let's take a look at the settings that Paint provides for Text:

Paint related settings for Text

  • General settings

    paint.setStrokeWidth(5): Set the brush width
    paint.setAntiAlias(true): Set whether to use the anti-aliasing function, if it is used, it will cause the drawing speed to slow down
    paint.setStyle(Paint.Style.FILL): Set the drawing style, for setting Both text and geometry are valid, and there are three possible values: 1. Paint.Style.FILL: fill inside 2, Paint.Style.FILL_AND_STROKE: fill inside and stroke 3, Paint.Style.STROKE: stroke only
    paint.setTextAlign (Align.CENTER): Set the text alignment
    paint.setTextSize(12): Set the text size

  • style settings

    paint.setFakeBoldText(true): set whether it is a bold text
    paint.setUnderlineText(true): set the underline
    paint.setTextSkewX((float) -0.25): set the horizontal slope of the font, ordinary italics is -0.25
    paint.setStrikeThruText(true ): set with strikethrough effect

  • other settings

    paint.setTextScaleX(2): Set the horizontal stretch, the height will not change

Drawing text with Canvas

  • 1. Normal horizontal drawing

void drawText (String text, float x, float y, Paint paint)
void drawText (CharSequence text, int start, int end, float x, float y, Paint paint)
void drawText (String text, int start, int end, float x, float y, Paint paint)
void drawText (char[] text, int index, int count, float x, float y, Paint paint)

Description:
- The first constructor is the simplest constructor
- The third and fourth constructors: implement interception of part of the font drawing
- The second constructor is the most powerful, because the incoming font can be CharSequence type, so you can Realize the drawing of extended text with pictures, and can also intercept a part of the drawing

These functions are relatively simple and will not be demonstrated in detail.

  • 2. Specify the position of each text

void drawPosText (char[] text, int index, int count, float[] pos, Paint paint)
void drawPosText (String text, float[] pos, Paint paint)

Parameters:
- char[] text: the text array to be drawn
- int index: the index of the first text to be drawn
- int count: the number of text to be drawn, used to calculate the position of the last text, starting from the first Counted from the beginning of the drawn text
- float[] pos: the position of each font, in groups of two

onDraw method:

    private void init() {
        //初始化画笔
        paint = new Paint();
        paint.setTextSize(50);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPosText("巴扎黑", new float[]{100, 100, 100, 200, 100, 300}, paint);
    }

image.png

  • 3. Draw along the path

    void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint)
    void drawTextOnPath (char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)


parameter:
  • Path path: the drawing path of the text
  • char[] text: array of text to draw
  • int index: the index of the first text to draw
  • int count: The number of text to be drawn, used to calculate the position of the last text, starting from the first drawn text
  • float hOffset: the horizontal offset distance from the start point of the path
  • float vOffset: vertical offset from the center point of the path

onDraw method:

    private void init() {
        //初始化画笔
        paint = new Paint();
        paint.setTextSize(50);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        path = new Path();
        //设置路径,以圆作为我们文本显示的路线
        path.addCircle(300, 300, 200, Path.Direction.CW);  //路径的绘制方式 CW 表示正序绘制,CCW表示倒序绘制

        path1 = new Path();
        path1.addCircle(800, 300, 200, Path.Direction.CW);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //绘制出路径原型,方便后面比较
        canvas.drawPath(path, paint);
        canvas.drawPath(path1, paint);
        //把文字绘制在要显示的路径上,默认不偏移
        canvas.drawTextOnPath("搞笑我们是认真的!!!", path, 0, 0, paint);
        //把文字绘制在要显示的路径上,路径起始点偏移150,中心垂直点偏移 50
        canvas.drawTextOnPath("搞笑我们是认真的!!!", path1, 150, 50, paint);
    }

renderings
image.png

It can be seen that the displayed positions of the two texts are a bit different, which is caused by the offset we set. The starting position of the second text is offset by 150 pixels from the first text. The second text is is outside the path, and the first text is inside the path, which is caused by the vertical offset of the center we set

drawText drawing skills

We all know that to draw text, we only need to call the drawText method of Canvas to draw the text we want at any position, but do you know what conditions the drawText method draws text based on? Let's look at an example:

onDraw method:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //标准线,先绘制一条线出来,等会你就会发现一个非常不可思议的事情
       canvas.drawLine(100,100,1000,100,paint);

       canvas.drawText("gaoxiaowomenshirenzhende....”,200,100,paint);

    }

Effect picture:

image.png

You will find a very interesting thing, that is, we clearly set the position of the text we want to draw to be (200, 100), and the Y coordinate of the position of the line we draw is at the position of 100, why the text we draw The first letter "g" will appear below the standard line?

Why does this happen? The following is a brief introduction

Quads and Baselines

Remember the four-line grid book we used to write Pinyin when we were young? Take you to recall memories of childhood, when we all knew to write in the four-line grid

image.png

So here comes the problem. In fact, Canvas also has rules when calling the drawText method to draw text. This rule is the baseline.

image.png

That is to say, when Canvas calls drawText to draw text, it determines the position of the Text to be drawn according to the position of the baseline. In order to draw the Text to the correct position, the position of the baseline must be known.

canvas.drawText() with baseline

Let's revisit the canvas.drawText() function

/** 
* text:要绘制的文字 
* x:绘制原点x坐标 
* y:绘制原点y坐标 
* paint:用来做画的画笔 
*/  
public void drawText(String text, float x, float y, Paint paint)  

The above function is the most commonly used method for drawing text. We have misunderstood the (x, y) passed in before, thinking that (x, y) is the coordinate of the upper left corner of the text we want to draw. The y in the (x, y) entered is actually the position of the baseline in the above picture, and of course x cannot be the x you imagined. Do you think x represents the position where the text starts to be drawn? Sao Nian, you are still too young, take a look

paint.setTextAlign(Paint.Align.XXX);

We know that this function is used to set the alignment of the text. It has three values, left alignment (Panit.Align.LEFT), center alignment (Paint.Align.CENTER) and right alignment (Paint.Align.RIGHT) , let's try it out and see the results:

        @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //标准线,以位置为 (5500) 和 (5501500)绘制一条标准线,X 轴距离坐标轴原点距离为 550
        canvas.drawLine(550,0,550,1500,paint);

        //左对齐
        paint.setTextAlign(Paint.Align.LEFT);
        //基线位置
        canvas.drawLine(0,200,1500,200,paint);
        canvas.drawText("搞笑我们是认真的",550,200,paint);

        //居中对齐
        paint.setTextAlign(Paint.Align.CENTER);
        //基线位置
        canvas.drawLine(0,300,1500,300,paint);
        canvas.drawText("搞笑我们是认真的",550,300,paint);

        //右对齐
        paint.setTextAlign(Paint.Align.RIGHT);
        //基线位置
        canvas.drawText("搞笑我们是认真的",550,400,paint);
        canvas.drawLine(0,400,1500,400,paint);

    }

Effect picture:
image.png

Above, we first drew a vertical standard line, the distance between the X coordinate and the origin is 550,
and then started to draw the text with this standard line and the set alignment (funny, we are serious), and found the position difference displayed by different alignment methods big, why is that? In fact, when we pass in (x, y), x refers only to a relative distance, not the coordinates of the X-axis of the text to be drawn

  • When left aligned:
    When the alignment is left-aligned, x refers to the X-axis coordinates of the text to be drawn, and (x, y) is the starting position of the text to be drawn
  • Center alignment:
    When the alignment is center alignment, x refers to a relative distance, which is the distance from the origin (0, 0), and the text to be drawn will be displayed in the center based on this distance standard
  • When right-aligned: the
    same principle as above

drawText's four-line grid and FontMetrics

Earlier we mentioned that the drawing of Text is based on **baseline**. In fact, when the system draws Text, there are other lines, and the baseline is only a standard line used to draw Text![image.png] (https://upload-images.jianshu.io/upload_images/11455341-8d2059619f878345.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) As can be seen from the above figure, in addition to the baseline, there are The other four lines are top, bottom, ascent, descent, and their meanings are:
  • ascent: suggested by the system, when drawing a single character, the highest height of the character should be on the line
  • Descent: The system suggested that when drawing a single character, the minimum height of the character should be on the line
  • top: the line where the highest height can be drawn
  • bottom: the line where the lowest height can be drawn

It may be difficult to understand what these values ​​mean from the literal meaning. Don’t worry, let’s take an example to analyze. Let’s take a look at the display of the TV. Students who have used video processing tools (such as premiere, AE, You should all know that when making a video, there will be a safe area in the video display position, as shown below:

image.png

The black part represents the TV screen, and the red box represents the safe area box.
What is this safe area box for? This safety frame is the display area recommended to us by the system. Although we can display images in every area of ​​the TV screen, due to different standards, the screen size of each country is not necessarily the same as our screen size. When When inconsistencies are encountered, they are cropped. However, the display area recommended by the system to us can be completely displayed regardless of the format, so when we make a video, try to put the image to be displayed in the display area recommended by the system.

Similarly, when we are drawing text, ascent is the recommended maximum height for drawing text, which means that when drawing text, try to draw text below this maximum height, and descent is the recommended minimum height line for drawing text, which also means that it is in When drawing text, try to draw text above this descent line. The top line represents the highest height line that the text can draw, and the bottom line represents the lowest height line that the text can draw. Ascent and descent are the recommended drawing heights of the system, while top and bottom are the physical screen heights. The difference between them is the same as the safety frame and screen of the video processing we mentioned above.

FontMetrics

The five lines of top, bottom, ascent, descent, and baseline have been introduced above, so what is the relationship between these five lines? And how are the positions of these five lines calculated?

Android provides us with a class: FontMetrics, which has four member variables:

  • FontMetrics.ascent
  • FontMetrics.descent
  • FontMetrics.top
  • FontMetrics.bottom

The relationship between them is as follows:

  • ascent = Y coordinate of ascent line - Y coordinate of baseline line
  • descent = Y coordinate of the descent line - Y coordinate of the baseline line
  • top = Y coordinate of the top line - Y coordinate of the baseline line
  • bottom = Y coordinate of bottom line - Y coordinate of baseline line

Let's take a look at the picture analysis to see if this is the case:

image.png

From this figure, we first illustrate two issues, and then discuss the above formula:

  • Both the X-axis and the Y-axis have positive and negative directions, the X-axis is the positive direction to the right, and the Y-axis is the positive direction to the down, so the further down, the larger the Y-axis coordinate is.
  • Don't confuse the ascent, descent, top, bottom in FontMetrics with the real ascent, descent, top, bottom lines, these lines are real, and the ascent, descent, top, bottom in FontMetrics The values ​​of the bottom variables are used to calculate the positions of several lines. Let's use these variables to calculate the positions where these lines should be and draw them:

In fact, the ascent, descent, top, and bottom obtained by the above formulas are the positions of the baseline to each line, but for top and ascent, the baseline line is located below these two lines, so the Y coordinate of the baseline line must be greater than the top. and the Y coordinate of the ascent line, so the values ​​of ascent and top must be negative, and the bottom and descent lines are both below the baseline line, so they are both positive. Next, we use the above formula to find the value of each line. Y coordinate, and then draw each line through the Y coordinate

  • ascent Y coordinate = baseline Y coordinate + FontMetrics.ascent
  • descent Y coordinate = baseline Y coordinate + FontMetrics.descent
  • top Y coordinate = baseline Y coordinate + FontMetrics.top
  • bottom Y coordinate = baseline Y coordinate + FontMetrics.bottom

Obtaining the FontMetrics object

        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float ascent = fontMetrics.ascent;
        float descent = fontMetrics.descent;
        float top = fontMetrics.top;
        float bottom = fontMetrics.bottom;

As you can see from here, the corresponding FontMetrics object is obtained through paint.getFontMetrics(). There is another FontMetrics class called FontMetricsInt which has exactly the same meaning as FontMetrics, except that it gets a different type. The values ​​of the four member variables in FontMetricInt are of type Int, and the values ​​of the four member variables obtained by FontMetrics are of type float.

Draw the position of each line

The effect diagram is as follows:

image.png

onDraw method:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int baselineX = 10;      //基线的 X 轴
        int baselineY = 300;    //基线的 Y 轴

        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        //获取各个线距离 baseline 线的距离
        float ascent = fontMetrics.ascent;
        float descent = fontMetrics.descent;
        float top = fontMetrics.top;
        float bottom = fontMetrics.bottom;

        //绘制文本
        canvas.drawText("搞笑我们是认真的!!!", baselineX, baselineY, paint);

        paint.setStrokeWidth(3);

        //绘制基线
        paint.setColor(Color.RED);
        canvas.drawLine(baselineX, baselineY, 1000, baselineY, paint);

        //绘制 ascent 线
        paint.setColor(Color.BLUE);
        canvas.drawLine(baselineX, baselineY + ascent, 1000, baselineY + ascent, paint);

        //绘制 descent 线
        paint.setColor(Color.BLACK);
        canvas.drawLine(baselineX, baselineY + descent, 1000, baselineY + descent, paint);

        //绘制 top 线
        paint.setColor(Color.GREEN);
        canvas.drawLine(baselineX, baselineY + top, 1000, baselineY + top, paint);

        //绘制 bottom 线
        paint.setColor(Color.YELLOW);
        canvas.drawLine(baselineX, baselineY + bottom, 1000, baselineY + bottom, paint);
    }

The above code has been commented very clearly, so it will not be introduced separately.

Get the width, height and minimum rectangle of the drawn text

Here, we'll figure out how to get or draw the height and width of the area the string occupies, and the smallest rectangle that just wraps the string. Let's first look at the following example image:

image.png

From this picture, the green box at the bottom of the text is the size occupied by the drawn string, and the width and height we require are also the width and height of the green box.

It can also be seen from the figure that the width and height of the red box tightly wrap the string, so the red box is the minimum rectangle we require, which is the smallest rectangle that can wrap the string.

String height and width

  • 1. Height The height occupied by the
    string is easy to obtain, directly use FontMetrics.bottom - FontMetrics.top (because FontMetrics.top is a negative value) is the height occupied by the string:

  • 2. The width is very easy to get, you can get it directly by using the following function:

float width = paint.measureText(String text);

The usage example is as follows:

        paint = new Paint();
        paint.setTextSize(100);     //单位为 sp
        float width = paint.measureText("搞笑我们是认真的”);
  • Minimum rectangle
    To obtain the minimum rectangle, it is also obtained through the system function, and the function is defined as follows:
/** 
 * 获取指定字符串所对应的最小矩形,以(0,0)点所在位置为基线 
 * @param text  要测量最小矩形的字符串 
 * @param start 要测量起始字符在字符串中的索引 
 * @param end   所要测量的字符的长度 
 * @param bounds 接收测量结果 
 */  
public void getTextBounds(String text, int start, int end, Rect bounds);  

Code example:

        paint.setTextSize(100);     //单位为 sp
        String text = "搞笑我们是认真的”;
        Rect rect = new Rect();
        paint.getTextBounds(text, 0, text.length(), rect);

Let's take a look at the output:

image.png

You can see that the left, top, right, and bottom of this rectangle are 3, -82, 792, and 11, respectively. You may wonder why top is negative? In fact, we can also conclude from the code that we did not pass the baseline position to the getTextBounds() method, then it uses (0, 0) as the baseline to get the smallest rectangle! So the position of this smallest rectangle is the result of taking (0, 0) as the baseline.

Now that we have got the left, top, right, and bottom of the smallest rectangle, and know that the smallest rectangle is drawn with (0, 0) as the baseline, then we want to draw this rectangle at the position displayed by Text, so it is very simple , just add the distance of the baseline and it's OK. Let's try it:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int baselineX = 100;      //基线的 X 轴
        int baselineY = 300;    //基线的 Y 轴

        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        //获取各个线距离 baseline 线的距离
        float ascent = fontMetrics.ascent;
        float descent = fontMetrics.descent;
        float top = fontMetrics.top;
        float bottom = fontMetrics.bottom;

        float height = bottom - top;

        String text = "搞笑我们是认真的”;
        Rect rect = new Rect();
        paint.getTextBounds(text, 0, text.length(), rect);

        paint.setColor(Color.YELLOW);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(rect.left +baselineX, baselineY + rect.top, rect.right+baselineX, rect.bottom + baselineY, paint);

        //绘制文本
        paint.setColor(Color.RED);
        canvas.drawText(text, baselineX, baselineY, paint);
    }

Effect picture:

image.png

The complete code is as follows:

    private void init() {
        //初始化画笔
        paint = new Paint();
        paint.setTextSize(100);
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        paint.setTextSize(100);     //单位为 sp

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int baselineX = 100;      //基线的 X 轴
        int baselineY = 300;    //基线的 Y 轴

        String text = "搞笑我们是认真的";

        //获取当前线到baseline线的距离
        Paint.FontMetrics fontMetrics = paint.getFontMetrics();
        float top = fontMetrics.top;    //为负值
        float bottom = fontMetrics.bottom;
        //获取字符串所占高度
        float height = fontMetrics.bottom - fontMetrics.top;

        //获取字符串所占宽度
        float width = paint.measureText(text);

        //绘制字符串所占区域
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(baselineX, baselineY + top, width + baselineX, bottom + baselineY, paint);

        //获取最小矩形  默认是以(00)为基线获取,所以要想把最小矩形绘制到正确位置,需要 + baseline Y 
        Rect rect = new Rect();
        paint.getTextBounds(text, 0, text.length(), rect);

        paint.setColor(Color.YELLOW);
        paint.setStyle(Paint.Style.FILL);
        int left = rect.left + baselineX;
        top = baselineY + rect.top;
        int right = rect.right + baselineX;
        bottom = rect.bottom + baselineY;
        //绘制最小矩形
        canvas.drawRect(left, top, right, bottom, paint);

        //绘制文本
        paint.setColor(Color.RED);
        paint.setStyle(Paint.Style.STROKE);
        canvas.drawText(text, baselineX, baselineY, paint);
    }

I'm done, I'm finally done, I'll start the Path drawing later, I'm here for the Bezier curve...  

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324678322&siteId=291194637