欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android 绘制文本的一些方法

程序员文章站 2022-05-30 09:26:47
...

转载部分于

http://hencoder.com/ui-1-3/

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 绘制文本的一些方法

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 绘制文本的一些方法

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 绘制文本的一些方法

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

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

Android 绘制文本的一些方法

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

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

Android 绘制文本的一些方法

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 绘制文本的一些方法

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

设置绘制所使用的 Locale。

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

Android 绘制文本的一些方法

另外,由于 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 绘制文本的一些方法

FontMetircs getFontMetrics() 获取 Paint 的 FontMetrics。

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

Android 绘制文本的一些方法
Android 绘制文本的一些方法
Android 绘制文本的一些方法
Android 绘制文本的一些方法

参数里,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 绘制文本的一些方法

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

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

Android 绘制文本的一些方法

Android 绘制文本的一些方法

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 绘制文本的一些方法

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 绘制文本的一些方法

其实,说是测量光标位置的,本质上这也是一个测量文字宽度的方法。上面这个例子中,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 绘制文本的一些方法

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

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

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

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

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

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

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

Android 绘制文本的一些方法