1.Canvas 繪制文字方式
Canvas 繪制文字的方式:drawText() drawTextRun() drawTextOnPath()
1.1 drawText(String text,float x,float y,Paint paint)
注:如果你從(0,0)點(diǎn)開(kāi)始繪制Text,文字不會(huì)顯示在View左上角,會(huì)顯示在View的左上方。

這張圖,電線上的小鳥(niǎo),這里,電線就類似于文字的基線。

盜圖:Hencoder
canvas.drawText()中,參數(shù)y,是指的文字的基線(baseLine)。參數(shù)x,也不是文字最左面的點(diǎn),x的位置是文字起點(diǎn)靠左一點(diǎn)(這里是因?yàn)閷?duì)于絕大多數(shù)字符,他們的寬度都要略微大于實(shí)際顯示的寬度,字符的左右兩邊都會(huì)留出一部分間隙,用于文字間的間隔,因此我們?cè)O(shè)定繪制文字起點(diǎn)的時(shí)候 ,會(huì)發(fā)現(xiàn)實(shí)際文字繪制時(shí)候會(huì)靠右一點(diǎn))。
1.2 drawTextRun() (這個(gè)在中國(guó)應(yīng)該用不到)
類 似于drawText() 但是增加了兩個(gè)設(shè)置----上下文---文字方向
drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)
參數(shù):
text:要繪制的文字
start:從那個(gè)字開(kāi)始繪制
end:繪制到哪個(gè)字結(jié)束
contextStart:上下文的起始位置。contextStart 需要小于等于 start
contextEnd:上下文的結(jié)束位置。contextEnd 需要大于等于 end
x:文字左邊的坐標(biāo)
y:文字的基線坐標(biāo)
isRtl:是否是 RTL(Right-To-Left,從右向左)
1.3 drawTextOnPath()
沿著一條Path來(lái)繪制文字。
例:

paint.setTextSize(60);
paint.setColor(Color.RED);
mRectF = new RectF(100,100,800,500);
mPath.addOval(mRectF,Path.Direction.CW);
canvas.drawTextOnPath(text,mPath,1,0,paint);
drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)
參數(shù)
hOffset:文字相對(duì)于 Path 的水平偏移量
vOffset:文字相對(duì)于 Path 的豎直偏移量
利用它們可以調(diào)整文字的位置。例如你設(shè)置 hOffset 為 5, vOffset 為 10,文字就會(huì)右移 5 像素和下移 10 像素。
1.4 staticLayout
staticLayout 是用canvas來(lái)繪制,但是不是canvas的方法。staticLayout一般用于繪制多行textView。如果你需要進(jìn)行多行文字的繪制,并且對(duì)文字的排列和樣式?jīng)]有太復(fù)雜的花式要求,那么使用staticLayout最好。
StaticLayout 并不是一個(gè) View 或者 ViewGroup ,而是 android.text.Layout 的子類,它是純粹用來(lái)繪制文字的。 StaticLayout 支持換行,它既可以為文字設(shè)置寬度上限來(lái)讓文字自動(dòng)換行,也會(huì)在 \n 處主動(dòng)換行。
例:

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();
StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)
參數(shù):
width 是文字區(qū)域的寬度,文字到達(dá)這個(gè)寬度后就會(huì)自動(dòng)換行;
align 是文字的對(duì)齊方向;
spacingmult 是行間距的倍數(shù),通常情況下填 1 就好;
spacingadd 是行間距的額外增加值,通常情況下填 0 就好;
includeadd 是指是否在文字上下添加額外的空間,來(lái)避免某些過(guò)高的字符的繪制出現(xiàn)越界。
2.Paint 對(duì)文字繪制的輔助
2.1 設(shè)置顯示效果類
2.1.1 setTextSize(float textSize)
設(shè)置文字大小
2.1.2 setTypeface(Typeface typeface)
設(shè)置字體,字體可以是系統(tǒng)所有的字體,也可以是自己的字體。官方文檔
這里的Typeface跟font是一個(gè)意思,都表示字體,但是,typerface指的是某套字體(font family),而font值指的是typeface具體的某個(gè)weight和size的分支。
2.1.3 setFakeBoldText(boolean fakeBoldText)
是否使用偽粗體。

這種粗體叫做偽粗體,因?yàn)樗皇窃O(shè)置更高的weight的字體讓文字變粗,他是通過(guò)程序在運(yùn)行時(shí)候?qū)⒆煮w描粗。
2.1.4 setStrikeThurText(boolean strikeThruText)
是否添加刪除線

2.1.5 setUnderlineText(boolean underlineText)
是否添加下劃線

#######2.1.6 setTextSkewX(float skewX)
設(shè)置文字的錯(cuò)切角度,通俗的說(shuō),就是設(shè)置文字傾斜

2.1.7 setTextScaleX(float scaleX)
設(shè)置文字橫向縮放(就是文字變胖變瘦)。

paint.setTextScaleX(0.5f);
canvas.drawText(text, 50, 100, paint);
paint.setTextScaleX(1.0f);
canvas.drawText(text, 50, 150, paint);
paint.setTextScaleX(1.5f);
canvas.drawText(text, 50, 200, paint);
2.1.8 setLetterSpacing(float letterSpacing)
設(shè)置字符間距。默認(rèn)為0。
注:setLetterSpacing為字符間距,setTextScaleX為文字橫向?qū)挾取?/p>
2.1.9 setFontFeatureSettrings(String settings)
用CSS的font-feature-settings 的方式來(lái)設(shè)置文字。
2.1.10 setTextAlign(Paint.Align align)
設(shè)置文字對(duì)其方式(LEFFT,CETNER,RIGHT 左中右,默認(rèn)為左 既LEFT)。

2.1.11setTextLocale(Locale locale)/setTextLocales(LocaleList locales)
設(shè)置繪制所使用的Locale,就是設(shè)置不同地域的語(yǔ)言(是漢語(yǔ)還是英語(yǔ))
2.1.12 setHinting(int mode)
設(shè)置是否啟用字體的hinting(字體微調(diào))
現(xiàn)在的 Android 設(shè)備大多數(shù)都是是用的矢量字體。矢量字體的原理是對(duì)每個(gè)字體給出一個(gè)字形的矢量描述,然后使用這一個(gè)矢量來(lái)對(duì)所有的尺寸的字體來(lái)生成對(duì)應(yīng)的字形。由于不必為所有字號(hào)都設(shè)計(jì)它們的字體形狀,所以在字號(hào)較大的時(shí)候,矢量字體也能夠保持字體的圓潤(rùn),這是矢量字體的優(yōu)勢(shì)。不過(guò)當(dāng)文字的尺寸過(guò)小(比如高度小于 16 像素),有些文字會(huì)由于失去過(guò)多細(xì)節(jié)而變得不太好看。 hinting 技術(shù)就是為了解決這種問(wèn)題的:通過(guò)向字體中加入 hinting 信息,讓矢量字體在尺寸過(guò)小的時(shí)候得到針對(duì)性的修正,從而提高顯示效果。效果圖盜一張維基百科的:

對(duì)于現(xiàn)在的手機(jī)(屏幕密度非常高),幾乎不會(huì)出現(xiàn)字體尺寸小道需要修改hinting來(lái)修正的情況,所以這個(gè)方法,現(xiàn)在幾乎不會(huì)用到。
2.1.13 setElegantTextHeight(boolean elegant)
設(shè)置是否開(kāi)啟文字的elegant height。
這個(gè)方法適用于有較高文字的語(yǔ)言。
把「大高個(gè)」文字的高度恢復(fù)為原始高度;
增大每行文字的上下邊界,來(lái)容納被加高了的文字。
中文不需要!?。?/p>
2.1.14 setSubpixelText(boolean subpixeText)
是否開(kāi)啟像素級(jí)抗鋸齒
詳細(xì)介紹
2.1.15 setLinearText(boolean linearText)
2.2 測(cè)量文字尺寸類
文字也有他自己的尺寸。
########2.2.1 float getFontsPACING()
獲取推薦的行距
即推薦的兩行文字的 baseline 的距離。這個(gè)值是系統(tǒng)根據(jù)文字的字體和字號(hào)自動(dòng)計(jì)算的。它的作用是當(dāng)你要手動(dòng)繪制多行文字(而不是使用 StaticLayout)的時(shí)候,可以在換行的時(shí)候給 y 坐標(biāo)加上這個(gè)值來(lái)下移文字。
2.2.2 FontMetrics getFontMetrice()
獲取Paint的FontMetrics
盜圖:henCoder

如圖,圖中有兩行文字,每一行都有 5 條線:top, ascent, baseline, descent, bottom。
? baseline: 上圖中黑色的線。它的作用是作為文字顯示的基準(zhǔn)線。
? ascent / descent: 上圖中綠色和橙色的線,它們的作用是限制普通字符的頂部和底部范圍。
普通的字符,上不會(huì)高過(guò) ascent ,下不會(huì)低過(guò) descent ,例如上圖中大部分的字形都顯示在 ascent 和 descent 兩條線的范圍內(nèi)。具體到 Android 的繪制中, ascent 的值是圖中綠線和 baseline 的相對(duì)位移,它的值為負(fù)(因?yàn)樗?baseline 的上方); descent 的值是圖中橙線和 baseline 相對(duì)位移,值為正(因?yàn)樗?baseline 的下方)。
? top / bottom: 上圖中藍(lán)色和紅色的線,它們的作用是限制所有字形( glyph )的頂部和底部范圍。
除了普通字符,有些字形的顯示范圍是會(huì)超過(guò) ascent 和 descent 的,而 top 和 bottom 則限制的是所有字形的顯示范圍,包括這些特殊字形。例如上圖的第二行文字里,就有兩個(gè)泰文的字形分別超過(guò)了 ascent 和 descent 的限制,但它們都在 top 和 bottom 兩條線的范圍內(nèi)。具體到 Android 的繪制中, top 的值是圖中藍(lán)線和 baseline 的相對(duì)位移,它的值為負(fù)(因?yàn)樗?baseline 的上方); bottom 的值是圖中紅線和 baseline 相對(duì)位移,值為正(因?yàn)樗?baseline 的下方)。
? leading: 這個(gè)詞在上圖中沒(méi)有標(biāo)記出來(lái),因?yàn)樗⒉皇侵傅哪硹l線和 baseline 的相對(duì)位移。 leading 指的是行的額外間距,即對(duì)于上下相鄰的兩行,上行的 bottom 線和下行的 top 線的距離,也就是上圖中第一行的紅線和第二行的藍(lán)線的距離(對(duì),就是那個(gè)小細(xì)縫)。
leading 這個(gè)詞的本意其實(shí)并不是行的額外間距,而是行距,即兩個(gè)相鄰行的 baseline 之間的距離。不過(guò)對(duì)于很多非專業(yè)領(lǐng)域,leading 的意思被改變了,被大家當(dāng)做行的額外間距來(lái)用;而 Android 里的 leading ,同樣也是行的額外間距的意思。leading 在這里應(yīng)該讀作 "ledding" 而不是 "leeding" 。
FontMetrics 提供的就是 Paint 根據(jù)當(dāng)前字體和字號(hào),得出的這些值的推薦值。它把這些值以變量的形式存儲(chǔ),供開(kāi)發(fā)者需要時(shí)使用。
FontMetrics.ascent:float 類型。
FontMetrics.descent:float 類型。
FontMetrics.top:float 類型。
FontMetrics.bottom:float 類型。
FontMetrics.leading:float 類型。
另外,ascent 和 descent 這兩個(gè)值還可以通過(guò) Paint.ascent() 和 Paint.descent() 來(lái)快捷獲取。
FontMetrics 和 getFontSpacing();
從定義可以看出,上圖中兩行文字的 font spacing (即相鄰兩行的 baseline 的距離) 可以通過(guò) bottom - top + leading (top 的值為負(fù),前面剛說(shuō)過(guò),記得吧?)來(lái)計(jì)算得出。
實(shí)際 bottom - top + leading 的結(jié)果是要大于 getFontSpacing() 的返回值的。
兩個(gè)方法計(jì)算得出的 font spacing 竟然不一樣?
這并不是 bug,而是因?yàn)?getFontSpacing() 的結(jié)果并不是通過(guò) FontMetrics 的標(biāo)準(zhǔn)值計(jì)算出來(lái)的,而是另外計(jì)算出來(lái)的一個(gè)值,它能夠做到在兩行文字不顯得擁擠的前提下縮短行距,以此來(lái)得到更好的顯示效果。所以如果你要對(duì)文字手動(dòng)換行繪制,多數(shù)時(shí)候應(yīng)該選取 getFontSpacing() 來(lái)得到行距,不但使用更簡(jiǎn)單,顯示效果也會(huì)更好。
getFontMetrics() 的返回值是 FontMetrics 類型。它還有一個(gè)重載方法 getFontMetrics(FontMetrics fontMetrics) ,計(jì)算結(jié)果會(huì)直接填進(jìn)傳入的 FontMetrics 對(duì)象,而不是重新創(chuàng)建一個(gè)對(duì)象。這種用法在需要頻繁獲取 FontMetrics 的時(shí)候性能會(huì)好些。
另外,這兩個(gè)方法還有一對(duì)同樣結(jié)構(gòu)的對(duì)應(yīng)的方法 getFontMetricsInt() 和 getFontMetricsInt(FontMetricsInt fontMetrics) ,用于獲取 FontMetricsInt 類型的結(jié)果。
推薦:計(jì)算基線
2.2.3 getTextBounds(String text,int start,int end, Rect bounds);
獲取文字的顯示范圍,text為要測(cè)量的文字,start為文字起始的位置,end為文字介素的位子,bounds存儲(chǔ)文字顯示范圍的對(duì)象,在測(cè)量結(jié)束以后會(huì)將結(jié)果寫(xiě)入bounds.
(他還有一個(gè)重載方法 getTextBounds(char[] text, int index, int count, Rect bounds) 用法相似)
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);

2.2.4 float measure(String text)
測(cè)量文字寬度并返回
注:measure(String text) 測(cè)量后的寬度比getTextBounds()測(cè)量后的值大一點(diǎn)
? getTextBounds: 它測(cè)量的是文字的顯示范圍(關(guān)鍵詞:顯示)。形象點(diǎn)來(lái)說(shuō),你這段文字外放置一個(gè)可變的矩形,然后把矩形盡可能地縮小,一直小到這個(gè)矩形恰好緊緊包裹住文字,那么這個(gè)矩形的范圍,就是這段文字的 bounds。
? measureText(): 它測(cè)量的是文字繪制時(shí)所占用的寬度(關(guān)鍵詞:占用)。前面已經(jīng)講過(guò),一個(gè)文字在界面中,往往需要占用比他的實(shí)際顯示寬度更多一點(diǎn)的寬度,以此來(lái)讓文字和文字之間保留一些間距,不會(huì)顯得過(guò)于擁擠。上面的這幅圖,我并沒(méi)有設(shè)置 setLetterSpacing() ,這里的 letter spacing 是默認(rèn)值 0,但你可以看到,圖中每?jī)蓚€(gè)字母之間都是有空隙的。另外,下方那條用于表示文字寬度的橫線,在左邊超出了第一個(gè)字母 H 一段距離的,在右邊也超出了最后一個(gè)字母 r(雖然右邊這里用肉眼不太容易分辨),而就是兩邊的這兩個(gè)「超出」,導(dǎo)致了 measureText() 比 getTextBounds() 測(cè)量出的寬度要大一些。
#######2.2.5 getTextWidths(String text,float[] widths)
獲取字符串中每個(gè)字符的寬度,并把結(jié)果填入?yún)?shù) widths。
這相當(dāng)于 measureText() 的一個(gè)快捷方法,它的計(jì)算等價(jià)于對(duì)字符串中的每個(gè)字符分別調(diào)用 measureText() ,并把它們的計(jì)算結(jié)果分別填入 widths 的不同元素。
2.2.6 int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)
這個(gè)方法也是用來(lái)測(cè)量文字寬度的。但和 measureText() 的區(qū)別是, breakText() 是在給出寬度上限的前提下測(cè)量文字的寬度。如果文字的寬度超出了上限,那么在臨近超限的位置截?cái)辔淖帧?br> 注:是在給出的寬度范圍下測(cè)量文字寬度,如果文字寬度超出上限,在臨近超限位置截?cái)辔淖帧?br> breakText的返回值是截取的文字個(gè)數(shù)。常用語(yǔ)多行文字的折行計(jì)算。
2.2.7 光標(biāo)
2.2.7.1 getRunAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)
對(duì)于一段文字,計(jì)算出某個(gè)字符處光標(biāo)的 x 坐標(biāo)。 start end 是文字的起始和結(jié)束坐標(biāo);contextStart contextEnd 是上下文的起始和結(jié)束坐標(biāo);isRtl 是文字的方向;offset 是字?jǐn)?shù)的偏移,即計(jì)算第幾個(gè)字符處的光標(biāo)。
例:
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);

其實(shí),說(shuō)是測(cè)量光標(biāo)位置的,本質(zhì)上這也是一個(gè)測(cè)量文字寬度的方法。上面這個(gè)例子中,start 和 contextStart 都是 0, end contextEnd 和 offset 都等于 text.length()。在這種情況下,它是等價(jià)于 measureText(text) 的,即完整測(cè)量一段文字的寬度。而對(duì)于更復(fù)雜的需求,getRunAdvance() 能做的事就比 measureText() 多了。
例:
// 包含特殊符號(hào)的繪制(如 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);
...

如上圖,???? 雖然占了 4 個(gè)字符(\uD83C\uDDE8\uD83C\uDDF3),但當(dāng) offset 是表情中間處時(shí), getRunAdvance() 得出的結(jié)果并不會(huì)在表情的中間處。
2.2.7.2 getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)
給出一個(gè)位置的像素值,計(jì)算出文字中最接近這個(gè)位置的字符偏移量(即第幾個(gè)字符最接近這個(gè)坐標(biāo))。
參數(shù):
text 是要測(cè)量的文字;
start end 是文字的起始和結(jié)束坐標(biāo);
contextStart contextEnd 是上下文的起始和結(jié)束坐標(biāo);
isRtl 是文字方向;
advance 是給出的位置的像素值。填入?yún)?shù),對(duì)應(yīng)的字符偏移量將作為返回值返回。
getOffsetForAdvance() 配合上 getRunAdvance() 一起使用,就可以實(shí)現(xiàn)「獲取用戶點(diǎn)擊處的文字坐標(biāo)」的需求。
#######2.2.8 hasGlyph(String string)
檢查指定的字符串中是否是一個(gè)單獨(dú)的字形 (glyph)。最簡(jiǎn)單的情況是,string 只有一個(gè)字母(比如 a)。
