用TextPaint來繪制文字


  TextPaint是paint的子類,用它可以很方便的進行文字的繪制,一般情況下遇到繪制文字的需求時,我們一般用TextPaint所提供的方法。開始學習如何繪制文字之前,我們必須要先了解下android中文字是怎么繪制到屏幕上的,文字的格式又是怎么樣的。

 

一、FontMetrics

1.1 理論知識

它是一個Paint的內部類,作用是“字體測量”。它里面呢就定義了top,ascent,descent,bottom,leading五個成員變量其他什么也沒有,和rect很相似。如果你不信,我們可以去看看源碼:

   /**
     * Class that describes the various metrics for a font at a given text size.
     * Remember, Y values increase going down, so those values will be positive,
     * and values that measure distances going up will be negative. This class
     * is returned by getFontMetrics().
     */
    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;
    }

為了很好的理解這5個變量的意義,我們用下面的圖示來進行說明。

  Baseline是基線,在Android中,文字的繪制都是從Baseline處開始的,Baseline往上至字符“最高處”的距離我們稱之為ascent(上坡度),Baseline往下至字符“最低處”的距離我們稱之為descent(下坡度);

  leading(行間距)則表示上一行字符的descent到該行字符的ascent之間的距離;

  top和bottom文檔描述地很模糊,其實這里我們可以借鑒一下TextView對文本的繪制,TextView在繪制文本的時候總會在文本的最外層留出一些內邊距,為什么要這樣做?因為TextView在繪制文本的時候考慮到了類似讀音符號,下圖中的A上面的符號就是一個拉丁文的類似讀音符號的東西:

top的意思其實就是除了Baseline到字符頂端的距離外還應該包含這些符號的高度,bottom的意思也是一樣。一般情況下我們極少使用到類似的符號所以往往會忽略掉這些符號的存在,但是Android依然會在繪制文本的時候在文本外層留出一定的邊距,這就是為什么top和bottom總會比ascent和descent大一點的原因。而在TextView中我們可以通過xml設置其屬性android:includeFontPadding="false"去掉一定的邊距值但是不能完全去掉。

 

1.2 代碼驗證

為了測試一下上述的理論是否正確,我們寫下了下面的代碼:

private static final String TEXT = "ap卡了ξτβбпшㄎㄊěǔぬも┰┠№@↓"; 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mTextPaint.setTextSize(50);  
        mTextPaint.setColor(Color.BLACK);  
        
        FontMetrics fontMetrics = mTextPaint.getFontMetrics();  
        Log.d("Aige", "ascent:" + fontMetrics.ascent);  
        Log.d("Aige", "top:" + fontMetrics.top);  
        Log.d("Aige", "leading:" + fontMetrics.leading);  
        Log.d("Aige", "descent:" + fontMetrics.descent);  
        Log.d("Aige", "bottom:" + fontMetrics.bottom);  
        
        mTextPaint.clearShadowLayer();
        canvas.drawText(TEXT, 0, Math.abs(fontMetrics.top), mTextPaint);
    }

結果:

打印的Log:

ascent:-46.38672
top:-52.807617
leading:0.0
descent:12.207031
bottom:13.549805

注:Baseline上方的值為負,下方的值為正

我們來分析一下這個結果:

因為基線上方為負,所以ascent和top的值都是負數,而且top要大於ascent,原因是要為符號留出位置。

因為只有一行文本所以leading恆為0。

基線下方為正,所以descent和bottom都是正的,bottom要略大於descent

在得到的結果中,我們發現文字是緊緊貼着屏幕頂端的,再看下我們的程序代碼:

canvas.drawText(TEXT, 0, Math.abs(fontMetrics.top), mTextPaint);

x坐標是0,y坐標是Math.abs(fontMetrics.top),因為android是從基線開始繪制的,所以我們為了讓字體頂端緊貼屏幕就必須讓它移下來一點,移動的距離是top的距離,也就是基線到文字對頂部的距離。有人可能會問,如果不設置呢?x,y坐標都是0,是什么效果呢?因為android會從基線開始繪制,所以如果不做處理,基線就是屏幕的頂部,因此會出現如下的效果:

最終,我們驗證了上面的理論是完全正確的。

 

1.3 fontMetrics中的變量和文字的size、typeface有關

從代碼中我們可以看到一個很特別的現象,在我們繪制文本之前我們便可以獲取文本的FontMetrics屬性值,也就是說我們FontMetrics的這些值跟我們要繪制什么文本是無關的,而僅與繪制文本Paint的size和typeface有關。當你改變了paint繪制文字的size或typeface時,FontMetrics中的top、bottom等值就會發生改變。如果我們僅僅更改了文字,這些值是不會發生任何改變的。

 

1.4 繪制居中屏幕的文字

我們知道了這些理論知識,也知道android是怎么繪制文字的,一會我們要做一個實際的例子來鞏固鞏固。首先,我們要先來擴展認識兩個方法:

float android.graphics.Paint.descent()
解釋:the distance below (positive) the baseline (descent) based on the current typeface and text size. 
一句話解釋:得到下坡度的值
 
float android.graphics.Paint.ascent()

解釋:the distance above (negative) the baseline (ascent) based on the current typeface and text size. 

一句話解釋:就是得到上坡度的值

實際代碼:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mTextPaint.setTextSize(50);
        mTextPaint.setColor(Color.BLACK);

        // 計算Baseline繪制的起點X軸坐標 ,計算方式:畫布寬度的一半 - 文字寬度的一半
        int baseX = (int) (canvas.getWidth() / 2 - mTextPaint.measureText(TEXT) / 2);

        // 計算Baseline繪制的Y坐標 ,計算方式:畫布高度的一半 - 文字總高度的一半
        int baseY = (int) ((canvas.getHeight() / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));

        // 居中畫一個文字
        canvas.drawText(TEXT, baseX, baseY, mTextPaint);

        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(2);
        // 為了便於理解我們在畫布中心處繪制一條中線
        canvas.drawLine(0, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, mPaint);
    }

我們計算了x坐標和y坐標。

x坐標的計算方法是(屏幕寬度-文字寬度)/2,如果文字寬度比屏幕寬度長得到的就是負數,如果文字寬度比屏幕寬度短,得到的就是正數,這個很容易理解;

y坐標的的計算方式是(屏幕高度-文字高度)/2,這里的文字高度用的是:descent+ascent(忽略了音標)。

結果:

 

二、TextPaint中的各種方法

float ascent()

顧名思義就是返回上坡度的值

 

float descent()

得到下坡度的值

 

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

public int breakText (char[] text, int index, int count, float maxWidth, float[] measuredWidth)

public int breakText (CharSequence text, int start, int end, boolean measureForwards, float maxWidth, float[] measuredWidth)

這個方法讓我們設置一個最大寬度,在不超過這個寬度的范圍內返回實際測量值否則停止測量。

text表示我們的字符串;

start表示從第幾個字符串開始測量;

end表示從測量到第幾個字符串為止;

measureForwards表示向前還是向后測量;

maxWidth表示一個給定的最大寬度在這個寬度內能測量出幾個字符;

measuredWidth為一個可選項,可以為空,不為空時返回真實的測量值

這些方法在一些結合文本處理的應用里比較常用,比如文本閱讀器的翻頁效果,我們需要在翻頁的時候動態折斷或生成一行字符串,這就派上用場了~

 

getFontMetrics()

得到一個FontMetrics對象。

 

getFontMetrics (Paint.FontMetrics metrics)

這個和我們之前用到的getFontMetrics()相比多了個參數,getFontMetrics()返回的是FontMetrics對象,而getFontMetrics(Paint.FontMetrics metrics)返回的是文本的行間距,如果metrics的值不為空則返回FontMetrics對象的值。

 

getFontMetricsInt()

該方法返回了一個FontMetricsInt對象,FontMetricsInt和FontMetrics是一樣的,只不過getFontMetricsInt()得到的對象中的參數都是int類型,而getFontMetrics()返回對象中的參數都是float。

 

getFontMetricsInt(Paint.FontMetricsInt fmi)

得到文字的間距,距離是int類型

 

getFontSpacing()

返回字符行間距

 

setUnderlineText(boolean underlineText)

設置文字的下划線

 

setTypeface(Typeface typeface)

設置字體類型,上面我們也使用過。

Android中字體有四種樣式:BOLD(加粗),BOLD_ITALIC(加粗並傾斜),ITALIC(傾斜),NORMAL(正常)

android為我們提供的字體有五種:DEFAULT,DEFAULT_BOLD,MONOSPACE,SANS_SERIF和SERIF,我們也可以用自己定義的字體:

          Paint p = new Paint();  
                String familyName = "宋體";  
                Typeface font = Typeface.create(familyName, Typeface.BOLD);  
                p.setColor(Color.RED);  
                p.setTypeface(font); 

 

setTextSkewX(float skewX)

設置文本在水平方向上的傾斜。這個傾斜值沒有具體的范圍,但是官方推崇的值為-0.25可以得到比較好的傾斜文本效果,值為負右傾值為正左傾,默認值為0。

setTextSize (float textSize)

設置文字的大小,但是要注意該值必需大於零。

 

setTextScaleX (float scaleX)

將文本沿X軸水平縮放,默認值為1,當值大於1會沿X軸水平放大文本,當值小於1會沿X軸水平縮放文本

// 設置畫筆文本傾斜  
textPaint.setTextScaleX(0.5F);           

// 設置畫筆文本傾斜  
textPaint.setTextScaleX(1.5F);  

注意:setTextScaleX不僅放大了文本寬度同時還拉伸了字符!這是亮點~

 

setTextLocale (Locale locale)

設置地理位置,這里如果你要使用,直接傳入Locale.getDefault()即可。

 

setTextAlign (Paint.Align align)

設置文本的對齊方式,可供選的方式有三種:CENTER,LEFT和RIGHT

我們的文本大小是通過size和typeface確定的(其實還有其他的因素但這里影響不大忽略),一旦baseline確定,對不對齊好像不相干吧。但是,你要知道一點,文本的繪制是從baseline開始沒錯,但是是從哪邊開始繪制的呢?左端還是右端呢?而這個Align就是為我們定義在baseline繪制文本究竟該從何處開始,上面我們在進行對文本的水平居中時是用Canvas寬度的一半減去文本寬度的一半:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        mTextPaint.setTextSize(50);
        mTextPaint.setColor(Color.BLACK);
        // 計算Baseline繪制的起點X軸坐標 ,計算方式:畫布寬度的一半 - 文字寬度的一半
        int baseX = (int) (canvas.getWidth() / 2 - mTextPaint.measureText(TEXT) / 2);

        // 計算Baseline繪制的Y坐標 ,計算方式:畫布高度的一半 - 文字總高度的一半
        int baseY = (int) ((canvas.getHeight() / 2) - ((mTextPaint.descent() + mTextPaint.ascent()) / 2));

        // 居中畫一個文字
        canvas.drawText(TEXT, baseX, baseY, mTextPaint);

        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(2);
        // 為了便於理解我們在畫布中心處繪制一條中線
        canvas.drawLine(0, canvas.getHeight() / 2, canvas.getWidth(), canvas.getHeight() / 2, mPaint);
    }

實際上我們大可不必這樣計算,我們只需設置Paint的文本對齊方式為CENTER,drawText的時候起點x = canvas.getWidth() / 2即可。產生的效果是,文字先算好一個基准線,從這個基准線的中點開始向左右開始繪制文字,最終自然就變成了居中顯示了。如果你設定了RIGHT,那么從baseline的右邊的頂點開始,文字開始慢慢繪制。

textPaint.setTextAlign(Align.CENTER);  
canvas.drawText(TEXT, canvas.getWidth() / 2, baseY, textPaint);  

 

當我們將文本對齊方式設置為CENTER后就相當於告訴Android我們這個文本繪制的時候從文本的中點開始向兩端繪制;如果設置為LEFT則從文本的左端開始往右繪制;如果為RIGHT則從文本的右端開始往左繪制:

 

setSubpixelText (boolean subpixelText)

設置是否打開文本的亞像素顯示,什么叫亞像素顯示呢?你可以理解為對文本顯示的一種優化技術,如果大家用的是Win7+系統可以在控制面板中找到一個叫ClearType的設置,該設置可以讓你的文本更好地顯示在屏幕上就是基於亞像素顯示技術。

 

setStrikeThruText (boolean strikeThruText)

文本刪除線

 

setLinearText (boolean linearText)

設置是否打開線性文本標識,這玩意對大多數人來說都很奇怪不知道這玩意什么意思。想要明白這東西你要先知道文本在Android中是如何進行存儲和計算的。在Android中文本的繪制需要使用一個bitmap作為單個字符的緩存,既然是緩存必定要使用一定的空間,我們可以通過setLinearText (true)告訴Android我們不需要這樣的文本緩存。

 

setFakeBoldText (boolean fakeBoldText)

設置文本仿粗體

 

measureText (String text)

measureText (CharSequence text, int start, int end)

measureText (String text, int start, int end)

measureText (char[] text, int index, int count)

測量文本寬度,上面我們已經使用過了,這四個方法都是一樣的只是參數稍有不同罷了。

 

 

三、Typeface中的方法

defaultFromStyle(int style)

最簡單的,簡而言之就是把上面所說的四種Style封裝成Typeface。傳入的參數是:BOLD(加粗),BOLD_ITALIC(加粗並傾斜),ITALIC(傾斜),NORMAL(正常)

mTextPaint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));

 

create(String familyName, int style)
create(Typeface family, int style)
textPaint.setTypeface(Typeface.create("SERIF", Typeface.NORMAL));  
textPaint.setTypeface(Typeface.create(Typeface.SERIF, Typeface.NORMAL));  

這兩個方法執行的效果完全一樣。

 

createFromAsset(AssetManager mgr, String path)
createFromFile(String path)
createFromFile(File path)

這三者也是一樣的,它們都允許我們使用自己的字體比如我們從asset目錄讀取一個字體文件。下面是一個簡單的例子:

// 獲取字體並設置畫筆字體  
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "kt.ttf");  
textPaint.setTypeface(typeface);

 

3.2 擴展到TextView

說到文本大家第一時間想到的應該是TextView,其實在TextView里我們依然可以找到上面很多方法的影子,比如我們可以從TextView中獲取到TextPaint:

TextPaint paint = mTextView.getPaint();  

當然也可以設置TextView的字體等等:

Typeface typeface = Typeface.createFromAsset(getAssets(), "kt.ttf");  
mTextView.setTypeface(typeface);  

 

 

說明:本文大部分內容來自:http://blog.csdn.net/aigestudio/article/details/41447349,我對原文進行了少量修改,記錄在此。

From AigeStudio(http://blog.csdn.net/aigestudio)Power by Aige  尊重原作者,感謝作者的分享!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM