Android自定義View之Paint繪制文字和線

用繼承View的方式來自定義View,我們就需要重寫onDraw方法,也就是得咱自己來畫圖了。畫圖就得用到畫筆和畫布,也就是Paint和Canvas。我們先來了解下Paint。

Paint

Paint我們可以簡(jiǎn)單理解為畫筆或是PS里的油漆桶,也就是實(shí)際上需要設(shè)置比如顏色、粗細(xì)、字體大小等屬性的對(duì)象。我們?cè)谕ㄟ^繼承View來自定義View時(shí),就是通過設(shè)置Paint的屬性來控制我們畫出來的View的一些特性。

Paint的一些常見API

Paint的set相關(guān)的API

1.設(shè)置文字的對(duì)齊方式:setTextAlign()

//設(shè)置Paint的文字對(duì)齊方式
textPaint = new Paint();
textPaint.setTextAlign(Paint.Align.RIGHT);
...

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

對(duì)齊方式有左中右三種

//對(duì)齊方式有左中右三種
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;
}

需要注意的是,這里的對(duì)齊方式指的是和繪制原點(diǎn)的對(duì)齊方式,也就是上面canvas.drawText方法中我們?cè)O(shè)置的繪制起點(diǎn)。比如我們?cè)O(shè)置的是右對(duì)齊,那就是文字的右邊和繪制起點(diǎn)對(duì)齊,具體效果可以看圖

設(shè)置了右對(duì)齊后

2.設(shè)置Paint的顏色、字體大小和字體

//這里設(shè)置Paint的顏色后,如果繪制的是字體那就是字體顏色,如果繪制的線條,那就是線條的顏色
textPaint.setColor(ContextCompat.getColor(getContext(), R.color.colorAccent));
textPaint.setTextSize(80f);
//設(shè)置字體加粗
textPaint.setTypeface(Typeface.DEFAULT_BOLD);

字體樣式有很多,除了常見的加粗,還有斜體等。這些都在Typeface類中

public class Typeface {
    
    //字體樣式,除了加粗,其他的好像看不出多大的區(qū)別
    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;
}

設(shè)置加粗、傾斜

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.設(shè)置Paint的style

textPaint.setStyle(Paint.Style.FILL);

style有3種,分別為實(shí)心、空心和實(shí)心描邊。

public enum Style {
    //實(shí)心
    FILL            (0),
    
    //空心
    STROKE          (1),
    
    //實(shí)心描邊
    FILL_AND_STROKE (2);

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

測(cè)試發(fā)現(xiàn)對(duì)文字和線都會(huì)有影響,比如如果我們?cè)O(shè)置了文字的下劃線,那FILL就是實(shí)線,而設(shè)置STROKE就會(huì)變成空心的一條線。如果我們是畫一個(gè)矩形的話,那FILL就是實(shí)心的矩形,而STROKE就是一個(gè)矩形框,F(xiàn)ILL_AND_STROKE效果大家可以試一下。文字的話STROKE就是文字的筆畫中間是空的,F(xiàn)ill就是實(shí)心的。

下劃線變成空心的了

4.設(shè)置縮放:setTextScaleX(float scaleX)。scaleX范圍在0-1之間為縮小,大于1為放大

5.設(shè)置下劃線和刪除線有2種方式,一種是直接調(diào)用Paint的API,一種是直接設(shè)置Paint的Flag

1) 直接通過Paint的API設(shè)置文字的下劃線和刪除線

//設(shè)置下劃線
textPaint.setUnderlineText(true);
//設(shè)置文字中間的刪除線
textPaint.setStrikeThruText(true);

2) 用設(shè)置Paint的Flag的方式設(shè)置文字的下劃線和刪除線

設(shè)置下劃線
textPaint.setFlags(Paint.UNDERLINE_TEXT_FLAG);
//設(shè)置文字中間的刪除線
textPaint.setFlags(Paint.STRIKE_THRU_TEXT_FLAG);

需要注意的是Paint的Flag相互之間是沖突的,如果設(shè)置了多個(gè)Flag,那只有最后一個(gè)Flag會(huì)生效。像上面的例子中,通過Flag來設(shè)置文字的下劃線和刪除線,最后只有刪除線會(huì)生效。而通過Paint的API就不會(huì)有這個(gè)問題,刪除線和下劃線都會(huì)同時(shí)生效。

6.設(shè)置Paint的抗鋸齒Flag。一般我們都要設(shè)置這個(gè)抗鋸齒效果,否則畫出來的線和文字會(huì)坑坑洼洼的,設(shè)置了以后就會(huì)很平滑。

//通過設(shè)置Flag來應(yīng)用抗鋸齒效果
textPaint.setFlags(Paint.ANTI_ALIAS_FLAG);

//通過Paint的API來設(shè)置抗鋸齒效果
textPaint.setAntiAlias(true);

這里也就引申出一個(gè)問題:那就是我們一搬都會(huì)設(shè)置Paint的Flag為抗鋸齒效果,所以如果我們要設(shè)置文字的下劃線和刪除線,就只能通過Paint的API來設(shè)置了。

文字居中的問題

不知道大家發(fā)現(xiàn)沒有,上面圖片中的文字盡管繪制的時(shí)候設(shè)置的位置是 (getWidth()/2,getHeight()/2) 但實(shí)際效果卻并沒有居中

(1)水平方向居中問題

  • 水平方向沒有居中是因?yàn)槲淖直旧碛袑挾?,所以要先獲取文字的寬度
float textWidth = textPaint2.measureText(text);
  • 然后重新計(jì)算繪制的水平坐標(biāo)
canvas.drawText(text, (getWidth()-textWidth)/2,getHeight()/2,textPaint2);

(2)豎直方向居中問題

文字的繪制規(guī)則跟Paint的一個(gè)內(nèi)部類有關(guān):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參數(shù)值含義

從圖中我們可以看出,文字繪制以baseline為標(biāo)準(zhǔn),我們將baseline設(shè)置為getHeight()/2后,文字勢(shì)必會(huì)往上偏,所以我們要想讓文字在豎直方向上居中,baseline需要往下一點(diǎn)。

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方法可以設(shè)置路徑效果,一共可以設(shè)置7種路徑效果,比較常見的有虛線和拐角圓滑效果

//CornerPathEffect用于增加點(diǎn)與點(diǎn)之間的圓弧效果,CornerPathEffect中的參數(shù)表示圓弧效果的半徑
brokenLinePaint.setPathEffect(new CornerPathEffect(5));
//DashPathEffect用于繪制虛線,第一個(gè)參數(shù)表示線段各個(gè)點(diǎn)之間的偏移,第二個(gè)參數(shù)表示繪制時(shí)數(shù)組的偏移量
brokenLinePaint.setPathEffect(new DashPathEffect(new float[]{5f,5f,5f,5f}, 1f));

需要注意的是Paint的PathEffect也只能設(shè)置一個(gè),設(shè)置多個(gè)時(shí)只有最后一個(gè)設(shè)置有效

2)Paint繪制折線需要用到一個(gè)Path類,用于存放折線的各個(gè)點(diǎn)

1)初始化

Path mPath = new Path();

2)設(shè)置折線的起點(diǎn)

//2個(gè)參數(shù)分別為起點(diǎn)的X坐標(biāo)和Y坐標(biāo)
mPath.moveTo(mPaddingLeft, mHeight-mPaddingBottom);

3)添加折線上的其他點(diǎn)

 mPath.lineTo(pointX, pointy);

4)繪制

canvas.drawPath(mPath, brokenLinePaint);

總結(jié)

  • Paint是繪制View必須要掌握的,我們只需要掌握Paint的常見的API就行了,包括繪制文字的和繪制線的。
  • Paint繪制文字時(shí)需要注意文字居中的問題,水平居中需要考慮文字的寬度,豎直居中需要考慮文字繪制的baseline,這就需要注意理解FontMetrics,如果不理解的話直接記結(jié)論也行
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只能設(shè)置一個(gè),設(shè)置多個(gè)時(shí)也只有最后一個(gè)有效。
  • Paint的PathEffect和Flag也一樣,只能設(shè)置一個(gè),設(shè)置多個(gè)時(shí)只有最后一個(gè)有效
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容