Android自定义View之Paint绘制文字和线

用继承View的方式来自定义View,我们就需要重写onDraw方法,也就是得咱自己来画图了。画图就得用到画笔和画布,也就是Paint和Canvas。我们先来了解下Paint。

Paint

Paint我们可以简单理解为画笔或是PS里的油漆桶,也就是实际上需要设置比如颜色、粗细、字体大小等属性的对象。我们在通过继承View来自定义View时,就是通过设置Paint的属性来控制我们画出来的View的一些特性。

Paint的一些常见API

Paint的set相关的API

1.设置文字的对齐方式:setTextAlign()

//设置Paint的文字对齐方式
textPaint = new Paint();
textPaint.setTextAlign(Paint.Align.RIGHT);
...

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    Log.e(TAG, "onDraw");
    
    //文字的起点为(getWidth()/2,getHeight()/2)
    canvas.drawText(text, getWidth()/2,getHeight()/2,textPaint);
}

对齐方式有左中右三种

//对齐方式有左中右三种
public enum Align {
    /**
     * The text is drawn to the right of the x,y origin
     */
    LEFT    (0),
    /**
     * The text is drawn centered horizontally on the x,y origin
     */
    CENTER  (1),
    /**
     * The text is drawn to the left of the x,y origin
     */
    RIGHT   (2);

    private Align(int nativeInt) {
        this.nativeInt = nativeInt;
    }
    final int nativeInt;
}

需要注意的是,这里的对齐方式指的是和绘制原点的对齐方式,也就是上面canvas.drawText方法中我们设置的绘制起点。比如我们设置的是右对齐,那就是文字的右边和绘制起点对齐,具体效果可以看图

设置了右对齐后

2.设置Paint的颜色、字体大小和字体

//这里设置Paint的颜色后,如果绘制的是字体那就是字体颜色,如果绘制的线条,那就是线条的颜色
textPaint.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));
textPaint.setTextSize(80f);
//设置字体加粗
textPaint.setTypeface(Typeface.DEFAULT_BOLD);

字体样式有很多,除了常见的加粗,还有斜体等。这些都在Typeface类中

public class Typeface {
    
    //字体样式,除了加粗,其他的好像看不出多大的区别
    public static final Typeface DEFAULT_BOLD;
    /** The NORMAL style of the default sans serif typeface. */
    public static final Typeface SANS_SERIF;
    /** The NORMAL style of the default serif typeface. */
    public static final Typeface SERIF;
    /** The NORMAL style of the default monospace typeface. */
    public static final Typeface MONOSPACE;
    
    // Style
    public static final int NORMAL = 0;
    //加粗
    public static final int BOLD = 1;
    //倾斜
    public static final int ITALIC = 2;
    //加粗并倾斜
    public static final int BOLD_ITALIC = 3;
}

设置加粗、倾斜

Typeface font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC);
textPaint.setTypeface(font);

Typeface类还提供了加载自定义字体的API

//从assets资源中加载
Typeface createFromAsset(AssetManager mgr, String path)
//从文件中加载字体
Typeface createFromFile(String path)
public static Typeface createFromFile(File path)

3.设置Paint的style

textPaint.setStyle(Paint.Style.FILL);

style有3种,分别为实心、空心和实心描边。

public enum Style {
    //实心
    FILL            (0),
    
    //空心
    STROKE          (1),
    
    //实心描边
    FILL_AND_STROKE (2);

    Style(int nativeInt) {
        this.nativeInt = nativeInt;
    }
    final int nativeInt;
}

测试发现对文字和线都会有影响,比如如果我们设置了文字的下划线,那FILL就是实线,而设置STROKE就会变成空心的一条线。如果我们是画一个矩形的话,那FILL就是实心的矩形,而STROKE就是一个矩形框,FILL_AND_STROKE效果大家可以试一下。文字的话STROKE就是文字的笔画中间是空的,Fill就是实心的。

下划线变成空心的了

4.设置缩放:setTextScaleX(float scaleX)。scaleX范围在0-1之间为缩小,大于1为放大

5.设置下划线和删除线有2种方式,一种是直接调用Paint的API,一种是直接设置Paint的Flag

1) 直接通过Paint的API设置文字的下划线和删除线

//设置下划线
textPaint.setUnderlineText(true);
//设置文字中间的删除线
textPaint.setStrikeThruText(true);

2) 用设置Paint的Flag的方式设置文字的下划线和删除线

设置下划线
textPaint.setFlags(Paint.UNDERLINE_TEXT_FLAG);
//设置文字中间的删除线
textPaint.setFlags(Paint.STRIKE_THRU_TEXT_FLAG);

需要注意的是Paint的Flag相互之间是冲突的,如果设置了多个Flag,那只有最后一个Flag会生效。像上面的例子中,通过Flag来设置文字的下划线和删除线,最后只有删除线会生效。而通过Paint的API就不会有这个问题,删除线和下划线都会同时生效。

6.设置Paint的抗锯齿Flag。一般我们都要设置这个抗锯齿效果,否则画出来的线和文字会坑坑洼洼的,设置了以后就会很平滑。

//通过设置Flag来应用抗锯齿效果
textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);

//通过Paint的API来设置抗锯齿效果
textPaint.setAntiAlias(true);

这里也就引申出一个问题:那就是我们一搬都会设置Paint的Flag为抗锯齿效果,所以如果我们要设置文字的下划线和删除线,就只能通过Paint的API来设置了。

文字居中的问题

不知道大家发现没有,上面图片中的文字尽管绘制的时候设置的位置是 (getWidth()/2,getHeight()/2) 但实际效果却并没有居中

(1)水平方向居中问题

  • 水平方向没有居中是因为文字本身有宽度,所以要先获取文字的宽度
float textWidth = textPaint2.measureText(text);
  • 然后重新计算绘制的水平坐标
canvas.drawText(text, (getWidth()-textWidth)/2,getHeight()/2,textPaint2);

(2)竖直方向居中问题

文字的绘制规则跟Paint的一个内部类有关:FontMetrics

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;
}
FontMetrics参数值含义

从图中我们可以看出,文字绘制以baseline为标准,我们将baseline设置为getHeight()/2后,文字势必会往上偏,所以我们要想让文字在竖直方向上居中,baseline需要往下一点。

Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float y = getHeight()/2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent)/2;
canvas.drawText(text, (getWidth()-textWidth)/2,y,textPaint);

需要注意的是Paint的TextAlign属性需要去掉

这样子文字就居中了

文字终于居中啦

用Paint绘制线

PathEffect setPathEffect(PathEffect effect)

1)通过setPathEffect方法可以设置路径效果,一共可以设置7种路径效果,比较常见的有虚线和拐角圆滑效果

//CornerPathEffect用于增加点与点之间的圆弧效果,CornerPathEffect中的参数表示圆弧效果的半径
brokenLinePaint.setPathEffect(new CornerPathEffect(5));
//DashPathEffect用于绘制虚线,第一个参数表示线段各个点之间的偏移,第二个参数表示绘制时数组的偏移量
brokenLinePaint.setPathEffect(new DashPathEffect(new float[]{5f,5f,5f,5f}, 1f));

需要注意的是Paint的PathEffect也只能设置一个,设置多个时只有最后一个设置有效

2)Paint绘制折线需要用到一个Path类,用于存放折线的各个点

1)初始化

Path mPath = new Path();

2)设置折线的起点

//2个参数分别为起点的X坐标和Y坐标
mPath.moveTo(mPaddingLeft, mHeight-mPaddingBottom);

3)添加折线上的其他点

 mPath.lineTo(pointX, pointy);

4)绘制

canvas.drawPath(mPath, brokenLinePaint);

总结

  • Paint是绘制View必须要掌握的,我们只需要掌握Paint的常见的API就行了,包括绘制文字的和绘制线的。
  • Paint绘制文字时需要注意文字居中的问题,水平居中需要考虑文字的宽度,竖直居中需要考虑文字绘制的baseline,这就需要注意理解FontMetrics,如果不理解的话直接记结论也行
Paint.FontMetrics fontMetrics = textPaint.getFontMetrics();
float y = getHeight()/2 + (Math.abs(fontMetrics.ascent) - fontMetrics.descent)/2;
canvas.drawText(text, (getWidth()-textWidth)/2,y,textPaint);
  • Paint的Flag只能设置一个,设置多个时也只有最后一个有效。
  • Paint的PathEffect和Flag也一样,只能设置一个,设置多个时只有最后一个有效
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容