自定義控件學習筆記(四)文字的繪制
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)。
以上這些內容,就是文字繪制的相關知識。它們有的常用,有的不常用,有的甚至可以說是在某些情況下沒用,不過你把它們全部搞懂了,在實際的開發中,就知道哪些事情可以做到,哪些事情做不到,以及應該怎么做了。
智能推薦
自定義控件零基礎教學(一)——自定義控件的繪制理念
開篇之言: 雖急于工作,但亦不隨便。予我一滴雨水,還你一片大海。 一、基于現實 設想我們身前有一張書案,上面有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就可...
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...