用繼承View的方式來自定義View,我們就需要重寫onDraw方法,也就是得咱自己來畫圖了。畫圖就得用到畫筆和畫布,也就是Paint和Canvas。我們先來了解下Paint。
Paint
Paint我們可以簡(jiǎn)單理解為畫筆或是PS里的油漆桶,也就是實(shí)際上需要設(shè)置比如顏色、粗細(xì)、字體大小等屬性的對(duì)象。我們?cè)谕ㄟ^繼承View來自定義View時(shí),就是通過設(shè)置Paint的屬性來控制我們畫出來的View的一些特性。
Paint的一些常見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ì)齊,具體效果可以看圖
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;
}
從圖中我們可以看出,文字繪制以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è)有效