• 转载部分于
  • Canvas 绘制文字的方式
  • drawText 普通文本绘制
  • 构造方法
  • 事例代码
  • drawTextOnPath 根据Path绘制文字
  • 构造方法
  • 事例代码
  • StaticLayout 多行文本绘制
  • 构造方法
  • 事例代码
  • 效果图
  • Paint 对文字绘制的辅助
  • setTextSizefloat textSize 设置文字大小
  • setTypefaceTypeface typeface 设置字体
  • setFakeBoldTextboolean fakeBoldText 是否使用伪粗体
  • setStrikeThruTextboolean strikeThruText 是否加删除线
  • setUnderlineTextboolean underlineText 是否加下划线
  • setTextSkewXfloat skewX 设置文字横向错切角度其实就是文字倾斜度的啦
  • setTextScaleXfloat scaleX 设置文字横向放缩也就是文字变胖变瘦
  • setLetterSpacingfloat letterSpacing 设置字符间距默认值是 0
  • setFontFeatureSettingsString settings 用 CSS 的 font-feature-settings 的方式来设置文字
  • setTextAlignPaintAlign align 设置文字的对齐方式
  • setTextLocaleLocale locale setTextLocalesLocaleList locales 地区设置
  • float getFontSpacing 获取推荐的行距
  • FontMetircs getFontMetrics 获取 Paint 的 FontMetrics
  • float measureTextString text 测量文字的宽度并返回
  • getTextWidthsString text float widths 获取字符串中每个字符的宽度并把结果填入参数 widths
  • int breakText 测量文字宽度的临近超限的位置截断文字
  • 光标相关
  • getRunAdvance 计算出某个字符处光标的 x 坐标
  • getOffsetForAdvance 即第几个字符最接近这个坐标
  • hasGlyphString string 字符串中是否是一个单独的字形


Canvas 绘制文字的方式

drawText() 普通文本绘制

构造方法

/**
     * 绘制普通的文本
     * @param text 文字内容
     * @param x 基线的 x 起点坐标
     * @param y 基线的 y 起点坐标
     * @param paint The paint used for the text (e.g. color, size, style)
     */
    public void drawText(String text, float x, float y, Paint paint) {
    }

事例代码

String text = "Hello  word...";
    canvas.drawText(text,200,100,mPaint);

drawTextOnPath() 根据Path绘制文字

构造方法

/**
     * 根据Path路径  绘制文字
     * @param text 文本内容
     * @param path 路径
     * @param hOffset 相对Path的 水平偏移
     * @param vOffset 相对Path的 垂直偏移
     * @param paint 画笔
     */
    public void drawTextOnPath(@NonNull String text, @NonNull Path path, float hOffset,
            float vOffset, @NonNull Paint paint) {
        super.drawTextOnPath(text, path, hOffset, vOffset, paint);
    }

事例代码

/**  初始化画笔 */
    private void initPaint(){
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(Color.BLACK);
        mPaint.setTextSize(50);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        String text = "Hello  word...";
        //根据path绘制
        Path path = new Path();
        path.lineTo(100,100);
        path.lineTo(200,0);
        //先绘制一条线,可以跟清晰的查看效果
        canvas.drawPath(path,mPaint);
        //绘制文本
        canvas.drawTextOnPath(text,path,0,0,mPaint);
    }

StaticLayout 多行文本绘制

构造方法

/**
* 根据Path路径  绘制文字
* @param source      文本内容
* @param TextPaint   画笔
* @param width       是文字区域的宽度,文字到达这个宽度后就会自动换行; 
* @param align       是文字的对齐方向; 
* @param spacingmult 是行间距的倍数,通常情况下填 1 就好;
* @param spacingadd  是行间距的额外增加值,通常情况下填 0 就好; 
* @param includeadd  是指是否在文字上下添加额外的空间,来避免某些过高的字符的绘制出现越界。 
*/
public StaticLayout(CharSequence source, TextPaint paint,
                  int width,
                  Alignment align, float spacingmult, float spacingadd,
                  boolean includepad) {
  this(source, 0, source.length(), paint, width, align,
       spacingmult, spacingadd, includepad);
}

事例代码

String text1 = "Lorem Ipsum is simply dummy text of the printing and typesetting industry.";  
StaticLayout staticLayout1 = new StaticLayout(text1, paint, 600,  
        Layout.Alignment.ALIGN_NORMAL, 1, 0, true);
String text2 = "a\nbc\ndefghi\njklm\nnopqrst\nuvwx\nyz";  
StaticLayout staticLayout2 = new StaticLayout(text2, paint, 600,  
        Layout.Alignment.ALIGN_NORMAL, 1, 0, true);

...

canvas.save();  
canvas.translate(50, 100);  
staticLayout1.draw(canvas);  
canvas.translate(0, 200);  
staticLayout2.draw(canvas);  
canvas.restore();

效果图

android 文字显示不好 画板 安卓绘制文字_ci

Paint 对文字绘制的辅助

setTextSize(float textSize) 设置文字大小

paint.setTextSize(18);  
canvas.drawText(text, 100, 25, paint);  
paint.setTextSize(36);  
canvas.drawText(text, 100, 70, paint);  
paint.setTextSize(60);  
canvas.drawText(text, 100, 145, paint);  
paint.setTextSize(84);  
canvas.drawText(text, 100, 240, paint);

setTypeface(Typeface typeface) 设置字体。

paint.setTypeface(Typeface.DEFAULT);  
canvas.drawText(text, 100, 150, paint);  
paint.setTypeface(Typeface.SERIF);  
canvas.drawText(text, 100, 300, paint);  
paint.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "Satisfy-Regular.ttf"));  
canvas.drawText(text, 100, 450, paint);

setFakeBoldText(boolean fakeBoldText) 是否使用伪粗体。

String text = "a\nbc\ndefghi\njklm\nnopqrst\nuvwx\nyz";
  ...
  canvas.drawText(text, 50, 100, paint);

setStrikeThruText(boolean strikeThruText) 是否加删除线。

paint.setStrikeThruText(true);  
canvas.drawText(text, 100, 150, paint);

setUnderlineText(boolean underlineText) 是否加下划线。

paint.setUnderlineText(true);  
canvas.drawText(text, 100, 150, paint);

setTextSkewX(float skewX) 设置文字横向错切角度。其实就是文字倾斜度的啦。

paint.setTextSkewX(-0.5f);  
canvas.drawText(text, 100, 150, paint);

android 文字显示不好 画板 安卓绘制文字_android_02

setTextScaleX(float scaleX) 设置文字横向放缩。也就是文字变胖变瘦。

paint.setTextScaleX(1);  
canvas.drawText(text, 100, 150, paint);  
paint.setTextScaleX(0.8f);  
canvas.drawText(text, 100, 230, paint);  
paint.setTextScaleX(1.2f);  
canvas.drawText(text, 100, 310, paint);

android 文字显示不好 画板 安卓绘制文字_绘制文本_03

setLetterSpacing(float letterSpacing) 设置字符间距。默认值是 0。

paint.setLetterSpacing(0.2f);  
canvas.drawText(text, 100, 150, paint);

android 文字显示不好 画板 安卓绘制文字_android 文字显示不好 画板_04

setFontFeatureSettings(String settings) 用 CSS 的 font-feature-settings 的方式来设置文字。

paint.setFontFeatureSettings("smcp"); // 设置 "small caps"  
canvas.drawText("Hello HenCoder", 100, 150, paint);

android 文字显示不好 画板 安卓绘制文字_绘制文本_05

setTextAlign(Paint.Align align) 设置文字的对齐方式。

一共有三个值:LEFT CETNER 和 RIGHT。默认值为 LEFT。

paint.setTextAlign(Paint.Align.LEFT);  
canvas.drawText(text, 500, 150, paint);  
paint.setTextAlign(Paint.Align.CENTER);  
canvas.drawText(text, 500, 150 + textHeight, paint);  
paint.setTextAlign(Paint.Align.RIGHT);  
canvas.drawText(text, 500, 150 + textHeight * 2, paint);

android 文字显示不好 画板 安卓绘制文字_ci_06

setTextLocale(Locale locale) / setTextLocales(LocaleList locales) 地区设置

设置绘制所使用的 Locale。

Locale 直译是「地域」,其实就是你在系统里设置的「语言」或「语言区域」(具体名称取决于你用的是什么手机),比如「简体中文(中国)」「English (US)」「English (UK)」。有些同源的语言,在文化发展过程中对一些相同的字衍生出了不同的写法(比如中国大陆和日本对于某些汉字的写法就有细微差别。注意,不是繁体和简体这种同音同义不同字,而真的是同样的一个字有两种写法)。系统语言不同,同样的一个字的显示就有可能不同。你可以试一下把自己手机的语言改成日文,然后打开微信看看聊天记录,你会明显发现文字的显示发生了很多细微的变化,这就是由于系统的 Locale 改变所导致的。

android 文字显示不好 画板 安卓绘制文字_绘制文本_07

另外,由于 Android 7.0 ( API v24) 加入了多语言区域的支持,所以在 API v24 以及更高版本上,还可以使用 setTextLocales(LocaleList locales) 来为绘制设置多个语言区域。

float getFontSpacing() 获取推荐的行距。

即推荐的两行文字的 baseline 的距离。这个值是系统根据文字的字体和字号自动计算的。它的作用是当你要手动绘制多行文字(而不是使用 StaticLayout)的时候,可以在换行的时候给 y 坐标加上这个值来下移文字。

canvas.drawText(texts[0], 100, 150, paint);  
canvas.drawText(texts[1], 100, 150 + paint.getFontSpacing, paint);  
canvas.drawText(texts[2], 100, 150 + paint.getFontSpacing * 2, paint);

android 文字显示不好 画板 安卓绘制文字_android 文字显示不好 画板_08

FontMetircs getFontMetrics() 获取 Paint 的 FontMetrics。

FontMetrics 是个相对专业的工具类,它提供了几个文字排印方面的数值:ascent, descent, top, bottom, leading。

android 文字显示不好 画板 安卓绘制文字_ci_09


android 文字显示不好 画板 安卓绘制文字_绘制文本_10


android 文字显示不好 画板 安卓绘制文字_android 文字显示不好 画板_11


android 文字显示不好 画板 安卓绘制文字_android 文字显示不好 画板_12

参数里,text 是要测量的文字,start 和 end 分别是文字的起始和结束位置,bounds 是存储文字显示范围的对象,方法在测算完成之后会把结果写进 bounds。

“` java 
 paint.setStyle(Paint.Style.FILL); 
 canvas.drawText(text, offsetX, offsetY, paint);paint.getTextBounds(text, 0, text.length(), bounds); 
 bounds.left += offsetX; 
 bounds.top += offsetY; 
 bounds.right += offsetX; 
 bounds.bottom += offsetY; 
 paint.setStyle(Paint.Style.STROKE); 
 canvas.drawRect(bounds, paint); 
 “`

android 文字显示不好 画板 安卓绘制文字_canvas_13

float measureText(String text) 测量文字的宽度并返回。

canvas.drawText(text, offsetX, offsetY, paint);  
float textWidth = paint.measureText(text);  
canvas.drawLine(offsetX, offsetY, offsetX + textWidth, offsetY, paint);

android 文字显示不好 画板 安卓绘制文字_ci_14

android 文字显示不好 画板 安卓绘制文字_android_15

getTextWidths(String text, float[] widths) 获取字符串中每个字符的宽度,并把结果填入参数 widths。

获取字符串中每个字符的宽度,并把结果填入参数 widths。

这相当于 measureText() 的一个快捷方法,它的计算等价于对字符串中的每个字符分别调用 measureText() ,并把它们的计算结果分别填入 widths 的不同元素。

getTextWidths() 同样也有好几个变种,使用大同小异,不再介绍。

int breakText(…) 测量文字宽度的,临近超限的位置截断文字

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

int measuredCount;  
float[] measuredWidth = {0};

// 宽度上限 300 (不够用,截断)
measuredCount = paint.breakText(text, 0, text.length(), true, 300, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150, paint);

// 宽度上限 400 (不够用,截断)
measuredCount = paint.breakText(text, 0, text.length(), true, 400, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150 + fontSpacing, paint);

// 宽度上限 500 (够用)
measuredCount = paint.breakText(text, 0, text.length(), true, 500, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150 + fontSpacing * 2, paint);

// 宽度上限 600 (够用)
measuredCount = paint.breakText(text, 0, text.length(), true, 600, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150 + fontSpacing * 3, paint);

android 文字显示不好 画板 安卓绘制文字_android_16

breakText() 的返回值是截取的文字个数(如果宽度没有超限,则是文字的总个数)。参数中, text 是要测量的文字;measureForwards 表示文字的测量方向,true 表示由左往右测量;maxWidth 是给出的宽度上限;measuredWidth 是用于接受数据,而不是用于提供数据的:方法测量完成后会把截取的文字宽度(如果宽度没有超限,则为文字总宽度)赋值给 measuredWidth[0]。

这个方法可以用于多行文字的折行计算。

breakText() 也有几个重载方法,使用大同小异,不再介绍。

光标相关

getRunAdvance(….) 计算出某个字符处光标的 x 坐标

对于一段文字,计算出某个字符处光标的 x 坐标。 start end 是文字的起始和结束坐标;contextStart contextEnd 是上下文的起始和结束坐标;isRtl 是文字的方向;offset 是字数的偏移,即计算第几个字符处的光标。

int length = text.length();  
float advance = paint.getRunAdvance(text, 0, length, 0, length, false, length);  
canvas.drawText(text, offsetX, offsetY, paint);  
canvas.drawLine(offsetX + advance, offsetY - 50, offsetX + advance, offsetY + 10, paint);

android 文字显示不好 画板 安卓绘制文字_绘制文本_17

其实,说是测量光标位置的,本质上这也是一个测量文字宽度的方法。上面这个例子中,start 和 contextStart 都是 0, end contextEnd 和 offset 都等于 text.length()。在这种情况下,它是等价于 measureText(text) 的,即完整测量一段文字的宽度。而对于更复杂的需求,getRunAdvance() 能做的事就比 measureText() 多了。

// 包含特殊符号的绘制(如 emoji 表情)
String text = "Hello HenCoder \uD83C\uDDE8\uD83C\uDDF3" // "Hello HenCoder ����"

...

float advance1 = paint.getRunAdvance(text, 0, length, 0, length, false, length);  
float advance2 = paint.getRunAdvance(text, 0, length, 0, length, false, length - 1);  
float advance3 = paint.getRunAdvance(text, 0, length, 0, length, false, length - 2);  
float advance4 = paint.getRunAdvance(text, 0, length, 0, length, false, length - 3);  
float advance5 = paint.getRunAdvance(text, 0, length, 0, length, false, length - 4);  
float advance6 = paint.getRunAdvance(text, 0, length, 0, length, false, length - 5);

...

android 文字显示不好 画板 安卓绘制文字_android_18

如上图,���� 虽然占了 4 个字符(\uD83C\uDDE8\uD83C\uDDF3),但当 offset 是表情中间处时, getRunAdvance() 得出的结果并不会在表情的中间处。为什么?因为这是用来计算光标的方法啊,光标当然不能出现在符号中间啦。

getOffsetForAdvance(…) 即第几个字符最接近这个坐标

给出一个位置的像素值,计算出文字中最接近这个位置的字符偏移量(即第几个字符最接近这个坐标)。

方法的参数很简单: text 是要测量的文字;start end 是文字的起始和结束坐标;contextStart contextEnd 是上下文的起始和结束坐标;isRtl 是文字方向;advance 是给出的位置的像素值。填入参数,对应的字符偏移量将作为返回值返回。

getOffsetForAdvance() 配合上 getRunAdvance() 一起使用,就可以实现「获取用户点击处的文字坐标」的需求。

hasGlyph(String string) 字符串中是否是一个单独的字形

检查指定的字符串中是否是一个单独的字形 (glyph)。最简单的情况是,string 只有一个字母(比如 a)。

android 文字显示不好 画板 安卓绘制文字_canvas_19