1. 程式人生 > >自定義控制元件學習筆記(四)文字的繪製

自定義控制元件學習筆記(四)文字的繪製

1 Canvas 繪製文字的方式

Canvas 的文字繪製方法有三個:drawText() drawTextRun() 和 drawTextOnPath()。

1.1 drawText(String text, float x, float y, Paint paint)

text 是文字內容,x 和 y 是文字的座標。但需要注意:這個座標並不是文字的左上角,而是一個與左下角比較接近的位置。大概在這裡:
這裡寫圖片描述
而如果你像繪製其他內容一樣,在繪製文字的時候把座標填成 (0, 0),文字並不會顯示在 View 的左上角,而是會幾乎完全顯示在 View 的上方,到了 View 外部看不到的位置:

canvas.drawText(text, 0, 0, paint);  

這裡寫圖片描述
為什麼其它的 Canvas.drawXXX() 方法,都是以左上角作為基準點的,而 drawText() 卻是文字左下方?
drawText() 引數中的 y ,指的是文字的基線( baseline ) 的位置。
眾所周知,不同的語言和文字,每個字元的高度和上下位置都是不一樣的。要讓不同的文字並排顯示的時候整體看起來穩當,需要讓它們上下對齊。但這個對齊的方式,不能是簡單的「底部對齊」或「頂部對齊」或「中間對齊」,而應該是一種類似於「重心對齊」的方式。
而這個用來讓所有文字互相對齊的基準線,就是基線( baseline )。 drawText() 方法引數中的 y 值,就是指定的基線的位置。
從前面圖中的標記可以看出來,「Hello HenCoder」繪製出來之後的 x 點並不是字母 “H” 左邊的位置,而是比它的左邊再往左一點點。那麼這個「往左的一點點」是什麼呢?

它是字母 “H” 的左邊的空隙。絕大多數的字元,它們的寬度都是要略微大於實際顯示的寬度的。字元的左右兩邊會留出一部分空隙,用於文字之間的間隔,以及文字和邊框的間隔。

1.2 drawTextRun()

宣告:這個方法對中國人沒用。所以如果你有興趣,可以繼續看;而如果你想省時間,直接跳過這個方法看後面的就好了,沒有任何毒副作用。
它和 drawText() 一樣都是繪製文字,但加入了兩項額外的設定——上下文和文字方向——用於輔助一些文字結構比較特殊的語言的繪製。

額外設定一:上下文。

有些語言的文字,字元的形狀會互相之間影響:一個字你單獨寫是一個樣,和別的字放在一起寫又是另外一個樣。不過由於我們最熟悉的語言——漢語和英語——都沒有這種情況

額外設定二:文字方向。

除了上下文, drawTextRun() 還可以設定文字的方向,即文字是從左到右還是從右到左排列的。

drawTextRun(CharSequence text, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint paint)

引數:
text:要繪製的文字
start:從那個字開始繪製
end:繪製到哪個字結束
contextStart:上下文的起始位置。contextStart 需要小於等於 start
contextEnd:上下文的結束位置。contextEnd 需要大於等於 end
x:文字左邊的座標
y:文字的基線座標
isRtl:是否是 RTL(Right-To-Left,從右向左)

1.3 drawTextOnPath()

drawTextOnPath(String text, Path path, float hOffset, float vOffset, Paint paint)

引數裡,需要解釋的只有兩個: hOffset 和 vOffset。它們是文字相對於 Path 的水平偏移量和豎直偏移量,利用它們可以調整文字的位置。例如你設定 hOffset 為 5, vOffset 為 10,文字就會右移 5 畫素和下移 10 畫素。

canvas.drawPath(path, paint); // 把 Path 也繪製出來,理解起來更方便  
canvas.drawTextOnPath("Hello HeCoder", path, 0, 0, paint);  

這裡寫圖片描述
籲,拐角處的文字怎麼那麼難看?

所以記住一條原則: drawTextOnPath() 使用的 Path ,拐彎處全用圓角,別用尖角。

1.4 StaticLayout

額外講一個 StaticLayout。這個也是使用 Canvas 來進行文字的繪製,不過並不是使用 Canvas 的方法。

1.Canvas.drawText() 只能繪製單行的文字,而不能換行。它不能在 View 的邊緣自動折行,到了 View 的邊緣處,文字繼續向後繪製到看不見的地方,而不是自動換行。
2.Canvas.drawText() 不能在換行符 \n 處換行

  String text = "a\nbc\ndefghi\njklm\nnopqrst\nuvwx\nyz";
  canvas.drawText(text, 50, 100, paint);

這裡寫圖片描述

在換行符 \n 的位置並沒有換行,而只是加了個空格
StaticLayout 並不是一個 View 或者 ViewGroup ,而是 android.text.Layout 的子類,它是純粹用來繪製文字的。 StaticLayout 支援換行,它既可以為文字設定寬度上限來讓文字自動換行,也會在 \n 處主動換行。

StaticLayout(CharSequence source, TextPaint paint, int width, Layout.Alignment align, float spacingmult, float spacingadd, boolean includepad)

其中引數裡:

width 是文字區域的寬度,文字到達這個寬度後就會自動換行;
align 是文字的對齊方向;
spacingmult 是行間距的倍數,通常情況下填 1 就好;
spacingadd 是行間距的額外增加值,通常情況下填 0 就好;
includeadd 是指是否在文字上下新增額外的空間,來避免某些過高的字元的繪製出現越界。
如果你需要進行多行文字的繪製,並且對文字的排列和樣式沒有太複雜的花式要求,那麼使用 StaticLayout 就好。

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();  

這裡寫圖片描述
上面程式碼中出現的 Canvas.save() Canvas.translate() Canvas.restore() 配合起來可以對繪製的內容進行移動。它們的具體用法我會在下期講,這期你就先依葫蘆畫瓢照搬著用吧。

2 Paint 對文字繪製的輔助

Paint 對文字繪製的輔助,有兩類方法:設定顯示效果的和測量文字尺寸的。

2.1 設定顯示效果類

2.1.1 setTextSize(float textSize)

設定文字大小。

paint.setTextSize(18);  
canvas.drawText(text, 100, 25, paint);  

2.1.2 setTypeface(Typeface typeface)

設定字型。

paint.setTypeface(Typeface.DEFAULT);  
canvas.drawText(text, 100, 150, paint);  
paint.setTypeface(Typeface.SERIF);  
canvas.drawText(text, 100, 300, paint);  
paint.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "Satisfy-Regular.ttf"));  
canvas.drawText(text, 100, 450, paint);  

這裡寫圖片描述

2.1.3 setFakeBoldText(boolean fakeBoldText)

是否使用偽粗體。

paint.setFakeBoldText(false);  
canvas.drawText(text, 100, 150, paint);  
paint.setFakeBoldText(true);  
canvas.drawText(text, 100, 230, paint);  

這裡寫圖片描述
之所以叫偽粗體( fake bold ),因為它並不是通過選用更高 weight 的字型讓文字變粗,而是通過程式在執行時把文字給「描粗」了。

2.1.4 setStrikeThruText(boolean strikeThruText)

是否加刪除線。

paint.setStrikeThruText(true);  
canvas.drawText(text, 100, 150, paint);  

這裡寫圖片描述

2.1.5 setUnderlineText(boolean underlineText)

是否設定下劃線。

paint.setUnderlineText(true);  
canvas.drawText(text, 100, 150, paint);  

這裡寫圖片描述

2.1.6 setTextSkewX(float skewX)

設定文字橫向錯切角度。其實就是文字傾斜度的啦。

paint.setTextSkewX(-0.5f);  
canvas.drawText(text, 100, 150, paint);  

這裡寫圖片描述

2.1.7 setTextScaleX(float scaleX)

設定文字橫向放縮。也就是文字變胖變瘦。

paint.setTextScaleX(1);  
canvas.drawText(text, 100, 150, paint);  
paint.setTextScaleX(0.8f);  
canvas.drawText(text, 100, 230, paint);  
paint.setTextScaleX(1.2f);  
canvas.drawText(text, 100, 310, paint);  

這裡寫圖片描述

2.1.8 setLetterSpacing(float letterSpacing)

設定字元間距。預設值是 0。

paint.setLetterSpacing(0.2f);  
canvas.drawText(text, 100, 150, paint);  

這裡寫圖片描述

2.1.9 setFontFeatureSettings(String settings)

用 CSS 的 font-feature-settings 的方式來設定文字。

paint.setFontFeatureSettings("smcp"); // 設定 "small caps"  
canvas.drawText("Hello HenCoder", 100, 150, paint);  

這裡寫圖片描述
CSS 全稱是 Cascading Style Sheets ,是網頁開發用來設定頁面各種元素的樣式的。
大多數 Android 開發者都不瞭解這個 CSS 的 font-feature-settings 屬性,不過沒關係,這個屬性設定的都是文字的一些次要特性,所以不用著急瞭解這個方法。當然有興趣的話也可以看一看哈

2.1.10 setTextAlign(Paint.Align align)

設定文字的對齊方式。一共有三個值:LEFT CETNER 和 RIGHT。預設值為 LEFT。

paint.setTextAlign(Paint.Align.LEFT);  
canvas.drawText(text, 500, 150, paint);  
paint.setTextAlign(Paint.Align.CENTER);  
canvas.drawText(text, 500, 150 + textHeight, paint);  
paint.setTextAlign(Paint.Align.RIGHT);  
canvas.drawText(text, 500, 150 + textHeight * 2, paint);  

這裡寫圖片描述

2.1.11 setTextLocale(Locale locale) / setTextLocales(LocaleList locales)

設定繪製所使用的 Locale。

Locale 直譯是「地域」,其實就是你在系統裡設定的「語言」或「語言區域」(具體名稱取決於你用的是什麼手機),比如「簡體中文(中國)」「English (US)」「English (UK)」。有些同源的語言,在文化發展過程中對一些相同的字衍生出了不同的寫法(比如中國大陸和日本對於某些漢字的寫法就有細微差別。注意,不是繁體和簡體這種同音同義不同字,而真的是同樣的一個字有兩種寫法)。系統語言不同,同樣的一個字的顯示就有可能不同。你可以試一下把自己手機的語言改成日文,然後開啟微信看看聊天記錄,你會明顯發現文字的顯示發生了很多細微的變化,這就是由於系統的 Locale 改變所導致的。
Canvas 繪製的時候,預設使用的是系統設定裡的 Locale。而通過 Paint.setTextLocale(Locale locale) 就可以在不改變系統設定的情況下,直接修改繪製時的 Locale。

paint.setTextLocale(Locale.CHINA); // 簡體中文  
canvas.drawText(text, 150, 150, paint);  
paint.setTextLocale(Locale.TAIWAN); // 繁體中文  
canvas.drawText(text, 150, 150 + textHeight, paint);  
paint.setTextLocale(Locale.JAPAN); // 日語  
canvas.drawText(text, 150, 150 + textHeight * 2, paint);  

這裡寫圖片描述

2.1.12 setHinting(int mode)

設定是否啟用字型的 hinting (字型微調)。
現在的 Android 裝置大多數都是是用的向量字型。向量字型的原理是對每個字型給出一個字形的向量描述,然後使用這一個向量來對所有的尺寸的字型來生成對應的字形。由於不必為所有字號都設計它們的字型形狀,所以在字號較大的時候,向量字型也能夠保持字型的圓潤,這是向量字型的優勢。不過當文字的尺寸過小(比如高度小於 16 畫素),有些文字會由於失去過多細節而變得不太好看。 hinting 技術就是為了解決這種問題的:通過向字型中加入 hinting 資訊,讓向量字型在尺寸過小的時候得到針對性的修正,從而提高顯示效果。
這裡寫圖片描述
功能很強,效果很贊。不過在現在( 2017 年),手機螢幕的畫素密度已經非常高,幾乎不會再出現字型尺寸小到需要靠 hinting 來修正的情況,所以這個方法其實……沒啥用了。可以忽略。

2.1.13 setElegantTextHeight(boolean elegant)

宣告:這個方法對中國人沒用,不想看的話可以直接跳過,無毒副作用。
那麼,setElegantTextHeight() 的作用到這裡就很清晰了:

把「大高個」文字的高度恢復為原始高度;
增大每行文字的上下邊界,來容納被加高了的文字。

2.1.14 setSubpixelText(boolean subpixelText)

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

次畫素級抗鋸齒這個功能解釋起來很麻煩,簡單說就是根據程式所執行的裝置的螢幕型別,來進行鍼對性的次畫素級的抗鋸齒計算,從而達到更好的抗鋸齒效果。
不過,和前面講的字型 hinting 一樣,由於現在手機螢幕畫素密度已經很高,所以預設抗鋸齒效果就已經足夠好了,一般沒必要開啟次畫素級抗鋸齒,所以這個方法基本上沒有必要使用。

2.2 測量文字尺寸類

不論是文字,還是圖形或 Bitmap,只有知道了尺寸,才能更好地確定應該擺放的位置。由於文字的繪製和圖形或 Bitmap 的繪製比起來,尺寸的計算複雜得多,所以它有一整套的方法來計算文字尺寸。

2.2.1 float getFontSpacing()

獲取推薦的行距。

即推薦的兩行文字的 baseline 的距離。這個值是系統根據文字的字型和字號自動計算的。它的作用是當你要手動繪製多行文字(而不是使用 StaticLayout)的時候,可以在換行的時候給 y 座標加上這個值來下移文字。

canvas.drawText(texts[0], 100, 150, paint);  
canvas.drawText(texts[1], 100, 150 + paint.getFontSpacing, paint);  
canvas.drawText(texts[2], 100, 150 + paint.getFontSpacing * 2, paint);  

這裡寫圖片描述

2.2.2 FontMetircs getFontMetrics()

獲取 Paint 的 FontMetrics。

FontMetrics 是個相對專業的工具類,它提供了幾個文字排印方面的數值:ascent, descent, top, bottom, leading。
這裡寫圖片描述
如圖,圖中有兩行文字,每一行都有 5 條線:top, ascent, baseline, descent, bottom。(leading 並沒有畫出來,因為畫不出來,下面會給出解釋)
baseline: 上圖中黑色的線。前面已經講過了,它的作用是作為文字顯示的基準線。
ascent / descent: 上圖中綠色和橙色的線,它們的作用是限制普通字元的頂部和底部範圍。 普通的字元,上不會高過 ascent ,下不會低過 descent 。具體到 Android 的繪製中, ascent 的值是圖中綠線和 baseline 的相對位移,它的值為負(因為它在 baseline 的上方); descent 的值是圖中橙線和 baseline 相對位移,值為正(因為它在 baseline 的下方)。
top / bottom: 上圖中藍色和紅色的線,它們的作用是限制所有字形( glyph )的頂部和底部範圍。 除了普通字元,有些字形的顯示範圍是會超過 ascent 和 descent 的,而 top 和 bottom 則限制的是所有字形的顯示範圍,包括這些特殊字形。例如上圖的第二行文字裡,就有兩個泰文的字形分別超過了 ascent 和 descent 的限制,但它們都在 top 和 bottom 兩條線的範圍內。具體到 Android 的繪製中, top 的值是圖中藍線和 baseline 的相對位移,它的值為負(因為它在 baseline 的上方); bottom 的值是圖中紅線和 baseline 相對位移,值為正(因為它在 baseline 的下方)。
leading: 這個詞在上圖中沒有標記出來,因為它並不是指的某條線和 baseline 的相對位移。 leading 指的是行的額外間距,即對於上下相鄰的兩行,上行的 bottom 線和下行的 top 線的距離,也就是上圖中第一行的紅線和第二行的藍線的距離(對,就是那個小細縫)。

FontMetrics 提供的就是 Paint 根據當前字型和字號,得出的這些值的推薦值。它把這些值以變數的形式儲存,供開發者需要時使用。
另外,ascent 和 descent 這兩個值還可以通過 Paint.ascent() 和 Paint.descent() 來快捷獲取。

FontMetrics 和 getFontSpacing():

從定義可以看出,上圖中兩行文字的 font spacing (即相鄰兩行的 baseline 的距離) 可以通過 bottom - top + leading (top 的值為負,前面剛說過,記得吧?)來計算得出。

但你真的執行一下會發現, bottom - top + leading 的結果是要大於 getFontSpacing() 的返回值的。

兩個方法計算得出的 font spacing 竟然不一樣?

這並不是 bug,而是因為 getFontSpacing() 的結果並不是通過 FontMetrics 的標準值計算出來的,而是另外計算出來的一個值,它能夠做到在兩行文字不顯得擁擠的前提下縮短行距,以此來得到更好的顯示效果。所以如果你要對文字手動換行繪製,多數時候應該選取 getFontSpacing() 來得到行距,不但使用更簡單,顯示效果也會更好。

getFontMetrics() 的返回值是 FontMetrics 型別。它還有一個過載方法 getFontMetrics(FontMetrics fontMetrics) ,計算結果會直接填進傳入的 FontMetrics 物件,而不是重新建立一個物件。這種用法在需要頻繁獲取 FontMetrics 的時候效能會好些。

另外,這兩個方法還有一對同樣結構的對應的方法 getFontMetricsInt() 和 getFontMetricsInt(FontMetricsInt fontMetrics) ,用於獲取 FontMetricsInt 型別的結果。

2.2.3 getTextBounds(String text, int start, int end, Rect bounds)

獲取文字的顯示範圍。
引數裡,text 是要測量的文字,start 和 end 分別是文字的起始和結束位置,bounds 是儲存文字顯示範圍的物件,方法在測算完成之後會把結果寫進 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); 

這裡寫圖片描述
它有一個過載方法 getTextBounds(char[] text, int index, int count, Rect bounds),用法非常相似,不再介紹。

2.2.4 float measureText(String text)

測量文字的寬度並返回。

canvas.drawText(text, offsetX, offsetY, paint);  
float textWidth = paint.measureText(text);  
canvas.drawLine(offsetX, offsetY, offsetX + textWidth, offsetY, paint);  

這裡寫圖片描述
如果你用程式碼分別使用 getTextBounds() 和 measureText() 來測量文字的寬度,你會發現 measureText() 測出來的寬度總是比 getTextBounds() 大一點點。這是因為這兩個方法其實測量的是兩個不一樣的東西。
getTextBounds: 它測量的是文字的顯示範圍(關鍵詞:顯示)。形象點來說,你這段文字外放置一個可變的矩形,然後把矩形儘可能地縮小,一直小到這個矩形恰好緊緊包裹住文字,那麼這個矩形的範圍,就是這段文字的 bounds。

measureText(): 它測量的是文字繪製時所佔用的寬度(關鍵詞:佔用)。前面已經講過,一個文字在介面中,往往需要佔用比他的實際顯示寬度更多一點的寬度,以此來讓文字和文字之間保留一些間距,不會顯得過於擁擠。上面的這幅圖,我並沒有設定 setLetterSpacing() ,這裡的 letter spacing 是預設值 0,但你可以看到,圖中每兩個字母之間都是有空隙的。另外,下方那條用於表示文字寬度的橫線,在左邊超出了第一個字母 H 一段距離的,在右邊也超出了最後一個字母 r(雖然右邊這裡用肉眼不太容易分辨),而就是兩邊的這兩個「超出」,導致了 measureText() 比 getTextBounds() 測量出的寬度要大一些。

在實際的開發中,測量寬度要用 measureText() 還是 getTextBounds() ,需要根據情況而定。不過你只要掌握了上面我所說的它們的本質,在選擇的時候就不會為難和疑惑了。

measureText(String text) 也有幾個過載方法,用法和它大同小異,不再介紹。

2.2.5 getTextWidths(String text, float[] widths)

獲取字串中每個字元的寬度,並把結果填入引數 widths。
這相當於 measureText() 的一個快捷方法,它的計算等價於對字串中的每個字元分別呼叫 measureText() ,並把它們的計算結果分別填入 widths 的不同元素。

getTextWidths() 同樣也有好幾個變種,使用大同小異,不再介紹。

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

這個方法也是用來測量文字寬度的。但和 measureText() 的區別是, breakText() 是在給出寬度上限的前提下測量文字的寬度。如果文字的寬度超出了上限,那麼在臨近超限的位置截斷文字。

int measuredCount;  
float[] measuredWidth = {0};

// 寬度上限 300 (不夠用,截斷)
measuredCount = paint.breakText(text, 0, text.length(), true, 300, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150, paint);

// 寬度上限 400 (不夠用,截斷)
measuredCount = paint.breakText(text, 0, text.length(), true, 400, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150 + fontSpacing, paint);

// 寬度上限 500 (夠用)
measuredCount = paint.breakText(text, 0, text.length(), true, 500, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150 + fontSpacing * 2, paint);

// 寬度上限 600 (夠用)
measuredCount = paint.breakText(text, 0, text.length(), true, 600, measuredWidth);  
canvas.drawText(text, 0, measuredCount, 150, 150 + fontSpacing * 3, paint);

這裡寫圖片描述
breakText() 的返回值是擷取的文字個數(如果寬度沒有超限,則是文字的總個數)。引數中, text 是要測量的文字;measureForwards 表示文字的測量方向,true 表示由左往右測量;maxWidth 是給出的寬度上限;measuredWidth 是用於接受資料,而不是用於提供資料的:方法測量完成後會把擷取的文字寬度(如果寬度沒有超限,則為文字總寬度)賦值給 measuredWidth[0]。
這個方法可以用於多行文字的折行計算。

breakText() 也有幾個過載方法,使用大同小異,不再介紹。

2.2.7 游標相關

對於 EditText 以及類似的場景,會需要繪製游標。游標的計算很麻煩,不過 API 23 引入了兩個新的方法,有了這兩個方法後,計算游標就方便了很多。

2.2.7.1 getRunAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset)

對於一段文字,計算出某個字元處游標的 x 座標。 start end 是文字的起始和結束座標;contextStart contextEnd 是上下文的起始和結束座標;isRtl 是文字的方向;offset 是字數的偏移,即計算第幾個字元處的游標。

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);  

這裡寫圖片描述
其實,說是測量游標位置的,本質上這也是一個測量文字寬度的方法。上面這個例子中,start 和 contextStart 都是 0, end contextEnd 和 offset 都等於 text.length()。在這種情況下,它是等價於 measureText(text) 的,即完整測量一段文字的寬度。而對於更復雜的需求,getRunAdvance() 能做的事就比 measureText() 多了。

// 包含特殊符號的繪製(如 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 個字元(\uD83C\uDDE8\uD83C\uDDF3),但當 offset 是表情中間處時, getRunAdvance() 得出的結果並不會在表情的中間處。為什麼?因為這是用來計算游標的方法啊,游標當然不能出現在符號中間啦。

2.2.7.2 getOffsetForAdvance(CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, float advance)

給出一個位置的畫素值,計算出文字中最接近這個位置的字元偏移量(即第幾個字元最接近這個座標)。
方法的引數很簡單: text 是要測量的文字;start end 是文字的起始和結束座標;contextStart contextEnd 是上下文的起始和結束座標;isRtl 是文字方向;advance 是給出的位置的畫素值。填入引數,對應的字元偏移量將作為返回值返回。
getOffsetForAdvance() 配合上 getRunAdvance() 一起使用,就可以實現「獲取使用者點選處的文字座標」的需求。

2.2.8 hasGlyph(String string)

檢查指定的字串中是否是一個單獨的字形 (glyph)。最簡單的情況是,string 只有一個字母(比如 a)。
這裡寫圖片描述
以上這些內容,就是文字繪製的相關知識。它們有的常用,有的不常用,有的甚至可以說是在某些情況下沒用,不過你把它們全部搞懂了,在實際的開發中,就知道哪些事情可以做到,哪些事情做不到,以及應該怎麼做了。

相關推薦

定義控制元件學習筆記文字繪製

1 Canvas 繪製文字的方式 Canvas 的文字繪製方法有三個:drawText() drawTextRun() 和 drawTextOnPath()。 1.1 drawText(String text, float x, float y, Paint

十七c#Winform定義控制元件-樹表格treeGrid

前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制元件,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr

定義控制元件讓TextViewRadiobutton、Button的drawableLeft和drawableRight與文字一起居中顯示

TextView的drawableLeft、drawableRight和drawableTop是一個常用、好用的屬性,可以在文字的上下左右放置一個圖片,而不使用更加複雜佈局就能達到,我也常常喜歡用RadioButton的這幾個屬性實現很多效果,但是苦於不支援讓drawbl

Android定義控制元件開發系列——仿支付寶六位支付密碼輸入頁面

        在移動互聯領域,有那麼幾家龍頭一直是我等學習和追求的目標,比如支付寶、微信、餓了麼、酷狗音樂等等,大神舉不勝舉,他們設計的介面、互動方式已經培養了中國(有可能會是世界)民眾的操作習慣:舉個小例子,對話方塊“確定”按鈕的左右位置就很有學問,如果大家都是左邊取消

swift 定義控制元件在StoryBoardxib裡使用的屬性

有時候我們在StoryBoard裡用拖拽方法建立屬性的時候,總會有一些常用的屬性沒有提供視覺化操作,所以我們必須在連線類中用程式碼去實現,雖然也比較簡單,但是這樣重複的操作大大的增加了開發時間,如果能在拖拽的介面就能直接除錯相關屬性,就像一個UIView直接視覺化設定背景顏

五十八c#Winform定義控制元件-管道閥門工業

前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制元件,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_contr

八十三c#Winform定義控制元件-導航選單擴充套件

前提 入行已經7,8年了,一直想做一套漂亮點的自定義控制元件,於是就有了本系列文章。 GitHub:https://github.com/kwwwvagaa/NetWinformControl 碼雲:https://gitee.com/kwwwvagaa/net_winform_custom_control.

學習安卓小碼哥定義控制元件筆記(三)

package com.example.wtz.viewpagerdemo; import android.graphics.Color; import android.support.annotation.NonNull; import android.support.v4.view

學習安卓小碼哥定義控制元件筆記(六)

<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="ScrollingView"> <attr name="scrolli

R語言學習筆記流程函式及定義函式

if(FALSE){條件執行} if(FALSE){if-else結構,多重判斷} if(FALSE){對score進行等級判定} score = 65 if(score >= 90){ pr

android 定義ListView實現下拉重新整理、分頁載入、點選事件——定義控制元件學習

package com.example.administrator.customerpulldownrefreshandpageload; import android.content.Context; import android.os.Handler; import android.os.Message

ExtJS學習筆記使用樹控制元件TreeNode ,TreeLoader

  在ExtJS中,不管是葉子節點還是非葉子節點,都統一用TreeNode表表示樹的節點。在ExtJS中,有兩種型別的樹節點。一種節點是普通的簡單樹 節點,由Ext.tree.TreeNode定義,另外一種是需要非同步載入子節點資訊的樹節點,該類由Ext.tree.Asyn

閱讀徐宜生《Android群英傳》的筆記——第3章 Android控制元件架構與定義控制元件詳解3.6-3.8

3.6 自定義 View 在自定義 View 時,我們通常會去重寫 onDraw() 方法來繪製 View 的顯示內容。如果該 View 還需要使用 wrap_content 屬性,那麼還必須重寫 onMeasure() 方法。另外,通過自定義 attr

定義控制元件之 PasswordEditText密碼輸入框

前兩天在掘金上看到了一個驗證碼輸入框,然後自己實現了一下,以前都是繼承的 View,這次繼承了 ViewGroup,也算是嘗試了一點不同的東西。先看看最終效果: 事實上就是用將輸入的密碼用幾個文字框來顯示而已,要打造這樣一個東西我剛開始也是一頭霧水,不急,直接寫不會,我們可以採取曲線救

定義控制元件之 Gamepad 遊戲手柄

這段時間自己在復刻一個小時候玩過的小遊戲——魔塔,在人物操控的時候剛開始用的感覺 low low 的上下左右四個方向鍵,後來受王者農藥啟發,決定採用現在很多遊戲中的那種遊戲手柄,網上也有例子,不過最近自己對自定義控制元件很感興趣,決定自己擼一個,最後實現的效果是這樣的: 看到這樣

定義控制元件之 SubmitBotton 提交按鈕

在 Android 中我覺得除了實現很多功能性很強的需求之外,最吸引我的就是各種炫酷的自定義控制元件,但是自定義控制元件這個東西沒有辦法用一種固定的模式來講解,因為自定義控制元件都是根據需求來定製的。同時這也說明只要程式猿牛逼,就沒有實現不了的功能。 之前有看到一個效果: Android

定義控制元件學習繪製刻度盤

以前面試的時候面試官問過我會不會寫標尺工具,我沒做過呀,然後胡亂的說什麼畫布,ondraw繪製。。然後就沒有然後了--!,現在想想真的有點囧。所以今天我試了下自己畫刻度盤,不是很難,只有方法對了,輕輕鬆鬆。。大神勿噴,這是菜鳥的日常(高手退散退散。。巴拉巴拉能量**>_<**)

React 學習筆記 父子元件

super Es6中的super可以用在類的繼承中,super關鍵字,它指代父類的例項(即父類的this物件) 子類必須在constructor方法中呼叫super方法,否則新建例項時會報錯。 這是因為子類沒有自己的this物件,而是繼承父類的this物件,然後對其進行加工。 不呼叫

Go語言學習筆記 流程控制

程式設計語言的流程控制語句,用於設定計算執行的次序,建立程式的邏輯結構。可以說,流程控制語句是整個程式的骨架。從根本上講,流程控制只是為了控制程式語句的執行順序,一般需要與各種條件配合,因此,在各種流程中,會加入條件判斷語句。流程控制語句一般起以下3個作用:   選擇,即根據

WPF 定義控制元件的坑蠢的:定義控制元件內容不顯示

原文: WPF 自定義控制元件的坑(蠢的:自定義控制元件內容不顯示) 自定義控制元件不顯示內容 由於工作需要在寫WPF,其中想要實現一些自己的控制元件所以直接自定義了控制元件博主是繼承了ContenControl的控制元件開始寫的但是發現不管設定Content屬性為任何都是不顯示