• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 自定義控件學習筆記(四)文字的繪制

    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)。
    這里寫圖片描述
    以上這些內容,就是文字繪制的相關知識。它們有的常用,有的不常用,有的甚至可以說是在某些情況下沒用,不過你把它們全部搞懂了,在實際的開發中,就知道哪些事情可以做到,哪些事情做不到,以及應該怎么做了。

    版權聲明:本文為yy471101598原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/yy471101598/article/details/78584652

    智能推薦

    自定義控件零基礎教學(一)——自定義控件的繪制理念

    開篇之言:         雖急于工作,但亦不隨便。予我一滴雨水,還你一片大海。 一、基于現實             設想我們身前有一張書案,上面有N多大小一樣的白色紙張,還有一根筆桿和很多大小粗細各樣的筆頭,還有各種顏料。        ...

    Android自定義控件 - View的繪制

    概述 在現實生活中,如果我們要去畫一個圖形,必須先知道他的大小和位置,同樣,在Android中,在繪制一個View前,也必須要先去測量將要繪制的View的大小,這個測量過程在onMeasure()方法中進行。 MeasureSpec類 Android系統給我們提供了一個強大的類MeasureSpec,通過這個類,可以幫助我們測量測量View,MeasureSpec是一個32位的int值,其中高2位...

    波形自定義控件(四):原理解析之控件繪制與動畫效果

    上一篇講解了WaveLoadingView的測量步驟,本篇將講解它的繪制原理。 前期準備 在講解Android自定義控件繪制之前,我們首先需要知道動畫效果是怎么實現的。 下面這張圖小球從左沿著一條弧線的軌跡運動到右邊。 把它的動作分解一下。 我們稱每一張圖片為幀,小球運動的動畫效果其實就是幀1~幀5快速切換產生的。動作分解的越細,動畫的效果也越好,當然幀數變多了,動畫的體積也就跟著變大了。 onD...

    AndroidStudio學習(四):創建自定義控件

    教材:第一行代碼(第2版) 當系統自帶的控件不能滿足需求時, 我們也可以利用上面的繼承結構來創建自定義控件。 新建項目UICustomViews 為功能相同的控件(比如:back-返回功能)在每個活動中單獨編寫一次事件注冊代碼, 也會導致代碼重復, 所以可以使用自定義控件。...

    自定義控件:左側有文字,右側有文字的EditText

    自定義控件:左側有文字,右側有文字的EditText 控件功能詳解: 左側文字一般為固定內容,文字大小默認16sp 與輸入內容的間距為設置的leftTextPadding ,默認為26dp(字體默認source_han_sans_cn_regular.otf") 右側文字 一般顯示單位等,(字體默認roboto_medium.ttf),文字大小默認14sp 沒有輸入內容時,右側文字距光標...

    猜你喜歡

    筆記 Androd 自定義控件學習(六)

    說明:文章來自《Android群英傳》學習筆記 事件攔截機制分析 當Android系統捕獲到用戶的各種輸入事件后,如何準確地傳遞給真正需要這個事件的控件呢?Android給我們提供了一套完整的事件傳遞,處理機制,來幫助開發者完成準確的事件分配與處理。 關于觸摸事件,大家都很清楚了,就不做解釋。Android為觸摸事件封裝了一個類—MotionEvent,如果重寫 onTouchEven...

    筆記 Androd 自定義控件學習(一)

    View的測量 Android 系統在繪制View前,需要對View進行測量,即告訴系統該畫一個多大的View,這個過程在 onMeasure() 方法中進行。Android系統給我們提供了一個幫助我們測量View的類—-MeasureSpec類,MeasureSpec是一個32為的值,其中高2位為測量的模式,低30為為測量的大小。 測量的模式有三種: EXACTLY 精確模式,當我們...

    筆記 Androd 自定義控件學習(二)

    view的繪制 文章方法使用案例,參考博客:https://blog.csdn.net/whuhan2013/article/details/51404737 測量好一個View之后,我們就可以簡單的重寫onDraw()方法,并在Canvas 對象上來繪制所需要的圖形。 要想在Android 的界面中繪制相應的圖像,就必須在 Canvas 上進行繪制,Canvas就像是一個畫板,使用Paint就可...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    精品国产乱码久久久久久蜜桃不卡