1. 程式人生 > >自定義View——Paint 之 文字繪製

自定義View——Paint 之 文字繪製

前言

自定義View——Paint畫筆 中,效果方法提到了設定文字相關的方法,這裡是對其的補充。 關於文字的API,具體如圖所示:

圖片描述

使用

對於文字相關的API,有很多,使用也很簡單,這裡簡單羅列一下:

setTextSize(float textSize)	                                     設定文字大小
setUnderlineText(boolean underlineText)	                         設定下劃線
setStrikeThruText(boolean strikeThruText)	                     設定刪除線
setFakeBoldText(boolean fakeBoldText)	                         設定文字粗體
setTextSkewX(float skewX)	                                     設定文字水平的傾斜度,值可為負數(向右斜)、正數(向左斜)
setTextScaleX(float scaleX)	                                     設定水平方向縮放
setLetterSpacing(float letterSpacing)                            設定文字間隔
setShadowLayer(float radius, float dx, float dy, int shadowColor)設定陰影
setTypeface(Typeface typeface)	                                 設定文字的字型

以上這些方法,使用很簡單,這裡不詳細介紹了,這裡給個效果圖:

圖片描述

setTypeface(Typeface typeface)

用於設定字型,其實 Typeface 包括字型與樣式。可以使用系統自帶的樣式,也可以自定義。

獲取Typeface 物件的方式:

Typeface defaultFromStyle(int style)                    //建立預設字型(宋體)
Typeface create(Typeface family, int style)             //通過其它Typeface變數來構建文字樣式(常用)
Typeface create(String familyName, int style)           //直接通過指定字型名來載入系統中自帶的文字樣式
Typeface createFromAsset(AssetManager mgr, String path) //通過從Asset中獲取外部字型來顯示字型樣式
Typeface createFromFile(String path)                    //直接從路徑建立
Typeface createFromFile(File path)                      //從外部路徑來建立字型樣式

引數: family:Typeface 型別,指定該類已定義的字型,有:

Typeface.DEFAULT          //常規字型型別
Typeface.DEFAULT_BOLD     //黑體字型型別
Typeface.MONOSPACE        //等寬字型型別
Typeface.SANS_SERIF       //sans serif字型型別
Typeface.SERIF            //serif字型型別

familyName:指定系統字型型別,如:“宋體”、"微軟雅黑"等

style:指的是樣式,常用的字型樣式有:

Typeface.BOLD        //粗體
Typeface.BOLD_ITALIC //粗斜體
Typeface.ITALIC      //斜體
Typeface.NORMAL      //常規

使用:

1.系統字型

mTestPaint.setTextSize(40);

//直接設定Typeface物件
mTestPaint.setTypeface(Typeface.MONOSPACE);
canvas.drawText("字型monospace,樣式預設", 50, 100, mTestPaint);


//指定樣式
Typeface typeface0 = Typeface.defaultFromStyle(Typeface.ITALIC);
mTestPaint.setTypeface(typeface0);
canvas.drawText("字型預設,樣式斜體", 50, 150, mTestPaint);

//指定字型、樣式
Typeface typeface1 = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD_ITALIC);
mTestPaint.setTypeface(typeface1);
canvas.drawText("字型sans serif,樣式粗體+斜體", 50, 200, mTestPaint);

//指定字型、樣式
Typeface typeface2 = Typeface.create("微軟雅黑", Typeface.BOLD);
mTestPaint.setTypeface(typeface2);
canvas.drawText("字型微軟雅黑,樣式粗體", 50, 250, mTestPaint);

2.自定義字型 一般使用createFromAsset()方法,後面2種就不介紹了。 首先,在main目錄下新建asset資料夾,然後在asset目錄下新建font資料夾,最後將格式為.ttf的字型放置該目錄,就可以開始使用了。

//該字型對中文沒有效果
Typeface typeface3 = Typeface.createFromAsset(getContext().getAssets(),"fonts/samplefont.ttf");
mTestPaint.setTypeface(typeface3);
canvas.drawText("Custom Font", 50, 300, mTestPaint);

效果圖:

字型顯示

setTextAlign(Paint.Align align)

設定文字對齊方式,這個方法,看起來貌似也是很簡單。但要是實際用起來,可能有點懵了,看程式碼:

private void testTextAlign(Canvas canvas){
canvas.translate(getWidth()/2, 200);//先將畫布平移,方便觀察

mTestPaint.reset();
mTestPaint.setTextSize(60);
mTestPaint.setStrokeWidth(8);

mTestPaint.setTextAlign(Paint.Align.LEFT);
canvas.drawText("左對齊的文字", 0, 0, mTestPaint);

mTestPaint.setTextAlign(Paint.Align.CENTER);
canvas.drawText("居中對齊的文字", 0, 100, mTestPaint);

mTestPaint.setTextAlign(Paint.Align.RIGHT);
canvas.drawText("右對齊的文字", 0, 200, mTestPaint);
字型顯示
這樣的效果,跟我們平時佈局不一樣,好像反了。 是的,這裡的相對位置不是指文字繪製的起點,而是指的繪製文字所在矩形的相對位置(圖中的紅框)。注意這一點就可以了。

setFontFeatureSettings(String settings)

使用CSS的格式設定字型,具體可以參考CSS文件

setTextLocale(Locale locale)

通過地域設定字型

setTextLocales(LocaleList locales)

在API24新增的,android7.0可以設定多語言列表,通過locales的順序設定字型

不常用:(建議看看就好,沒必要研究,信我!)

setHinting(int mode)

設定是否啟用字型的 hinting (字型微調)

setSubpixelText(boolean subpixelText)

是否開啟次畫素級的抗鋸齒( sub-pixel anti-aliasing )

setLinearText(boolean linearText)

設定設定是否開啟線性文字標識 以上三個方法,主要是針對以前裝置低畫素、低記憶體,低效能,目前來說,已經用不到,也很少用。

setElegantTextHeight(boolean elegant)

設定優雅(原始)高度 。預設時,字型是壓縮之後才顯示的,如果想顯示原始的高度,就可以設定為true。不過,該方法對於中文沒什麼用,一般用於特殊的字元

以上方法,繪製文字應該沒什麼大問題。 但,還有一些誤區,我們需要注意下。(也是通過這次總結才知道) 先來看一個demo:

mTestPaint.setTextSize(75);
canvas.drawText("原點為(50,50)的文字", 50, 50, mTestPaint);

mTestPaint.setTextSize(40);
canvas.drawText("在螢幕中央的文字", getWidth()/2, getHeight()/2, mTestPaint);

最後效果圖:

字型顯示

這個效果圖,應該會疑惑: 問題1:

canvas.drawText("原點為(50,50)的文字", 50, 50, mTestPaint);

傳進去的原點座標(50,50), 為什麼字型會顯示在上面,被擋住?(紅點是座標)

問題2:

canvas.drawText("在螢幕中央的文字", getWidth()/2, getHeight()/2, mTestPaint);

傳進去的原點座標是螢幕中心點,而且文字對齊方式也是居中,但最終垂直方向還是沒有居中。

這裡引出2個知識點: 文字座標具體的是什麼?(基線) 如何讓文字居中顯示?(FontMetrics測量類)

小時候,寫字母,必須在四線格規定的格子寫。文字的繪製,也有他的規則,其中一個規則就是基線。如圖:

字型顯示

會發現,基線的位置(紅色),和我們剛才繪製的紅點座標(50,50)位置,是一致。 是的。一般而言,(x,y)所代表的位置是所畫圖形對應的矩形的左上角點。但在drawText()中是非常例外的,y所代表的是基線的位置!

所以,這裡知道文字座標的意義,那對於第一個問題,就很好解決了,只要把y的座標改變一下就可以了。(後面學習FontMetrics,也可以讓字型準確在螢幕左上角顯示)

對於第2個問題,也是改變y的座標,但需要居中顯示,這個y值應該是確定的才對。現在就需要看看繪製文字其他規則:

這裡借鑑引入一張圖片: 字型顯示

  • baseLine:繪製文字的開始的基線
  • ascent: 系統建議繪製單個字元時,字元應當的最高高度所線上,特殊字元除外
  • descent:系統建議繪製單個字元時,字元應當的最低高度所線上,特殊字元除外
  • top: 可繪製的最高高度所線上,對於特殊字元會超出ascent高度
  • bottom:可繪製的最低高度所線上,對於特殊字元會超出descent高度

baseline是開始的基線,把它作為參照線,那麼根據android的xy軸方向,baseline上方的值為負,下方的值為正。那這些線是怎麼計算出來的呢?Android提供了FontMetrics。

FontMetrics

FontMetrics是字型測量類,根據字型、字號測量以上建議值。它包含了這些測量引數:

FontMetrics.ascent:float 型別 FontMetrics.descent:float 型別 FontMetrics.top:float 型別 FontMetrics.bottom:float 型別 FontMetrics.leading:float 型別

這裡稍微提下:leading指的是相鄰兩行的額外間隔,即上行的bottom線,與下行的top之間的空隙。如果只有一行,那麼該值就是0;

獲取FontMetrics物件:

Paint.FontMetrics fontMetrics = mTestPaint.getFontMetrics();//得到測量引數是float型別
Paint.FontMetrics fontMetrics = mTestPaint.getFontMetricsInt();//得到測量引數是int型別
getFontMetrics(FontMetrics fontMetrics) //計算結果會直接填進傳入的 FontMetrics 物件,而不是重新建立一個物件。這種用法在需要頻繁獲取 FontMetrics 的時候效能會好些。

有了這個測量類,那我們就可以解決第2個問題:

首先分析:

在開始的時候,我們傳入的座標是螢幕的中點:

canvas.drawText("在螢幕中央的文字", getWidth()/2, getHeight()/2, mTestPaint);

通過問題1,我們知道y的座標值(getHeight()/2)是基線的位置,那肯定是不會居中顯示了,需要改變y的座標。通過原先的圖片可以看出,字型需要往下移,才能居中,如示意圖。那麼移多少呢?

字型顯示

在示意圖中,左邊是原先圖片,右邊是改變後居中的圖片。這裡注意左右2邊,文字框中的紅線,該線是繪製文字區域的中心線,即((FontMetrics.bottom - FontMetrics.top)/2)。從圖中可以看出,只要將文字的中心線顯示在螢幕的中心(即如果y值是中心線的值,那麼就居中)那麼字型就是居中的。怎麼計算?借鑑一張圖:

字型顯示

B = baseLine + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom,其中baseLine = getHeight()/2

最終:

canvas.drawText("在螢幕中央的文字", getWidth()/2, getHeight()/2 + (FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom, mTestPaint);

上面也說過,只有特殊字元,才會顯示在top、bottom位置,平時我們只考慮ascent、descent的位置,所以也可以是:

canvas.drawText("在螢幕中央的文字", getWidth()/2, getHeight()/2 + (fontMetrics1.descent) + (fontMetrics1.ascent)/2 - fontMetrics1.bottom, mTestPaint);

最終看解決的效果圖:

嘎嘎,已經居中了。所以,很多時候,一直在除錯文字居中問題,怎麼調多不對,那是腦子多沒有這種概念…

但是,說了一大堆,還有那麼多引數要理解,相信很多人還是很拒絕使用這種方式。那就在提供一種更簡單的,通過Paint的getTextBound()方法。

void getTextBounds(String text, int start, int end, Rect bounds) 獲取文字顯示範圍

引數: text:需要測量的字串 start:字串開始的索引 end:字串結束的索引 bounds:返回的測量結果,即僅包裹文字的最小區域

具體是哪個位置呢?如圖紅色顯示區域:

那麼,有了這樣的一個區域,我們就不用管前面那些複雜的引數了。(一般網上的處理方式也是這種),具體實現如下:

/**
* 對於第2個問題的另一種解決
*/
mTestPaint.setTextSize(60);
String text = "在螢幕中央的文字";
Rect textRect = new Rect();
mTestPaint.getTextBounds(text, 0, text.length(), textRect);
canvas.drawText(text, getWidth()/2 - textRect.width()/2, getHeight()/2 + textRect.height()/2, mTestPaint);

對於文字的測量,還有很多方法:

float measureText(String text) 獲取文字 佔用 的寬度(包括文字間隙)

getTextWidths(String text, float[] widths) 獲取每個字元的寬度,將值儲存在widths

int breakText(String text, boolean measureForwards, float maxWidth, float[] measuredWidth)

引數:
text:測量為文字
measureForwards:測量文字的方向
maxWidth:測量區域最大的寬度,超過該區域的文字,將不會測量
measuredWidth:儲存測量的寬度值

float getFontSpacing() 獲取推薦的行距,即相鄰2行baseline的距離

結束

好了,關於文字的介紹就到這裡了。api方法都比較簡單,這裡主要是要了解,文字顯示位置與基線的關係,後面如果遇到文字不居中的問題,能想到這種方式處理即可。

主要參考:

歡迎點贊