Android精確測(cè)量文本寬高及基線位置的方法
前言
筆者最近在做一款彈幕控件,里面涉及到繪制文本,以及文本邊框。而繪制文本邊框需要知道文本的左邊位置,上邊位置,以及文本的寬高。
通常來(lái)說(shuō),使用 Canvas 繪制文本,可以通過(guò)畫筆 Paint 來(lái)設(shè)置文字的大小。但是畫筆的大小與文字的寬高并無(wú)直接關(guān)系。
大家應(yīng)該能說(shuō)上幾種測(cè)量文字寬高的方法,如:
方案1. 通過(guò) Paint 的 measureText 方法,可以測(cè)量文字的寬度
方案2. 通過(guò)獲取 Paint 的 FontMetrics, 根據(jù) FontMetrics 的 leading, ascent, 和 descent可以獲取文字的高度。
方案3. 通過(guò) Paint 的 getTextBounds 獲取文本的邊界矩形 Rect,根據(jù) Rect 可以計(jì)算出文字的寬高。
方案4. 通過(guò) Paint 獲取文字的 Path, 根據(jù) Path 獲取文本的邊界矩形 Rect, 根據(jù) Rect 可以計(jì)算出文字的寬高。
表面上看,我們有以上四種方案可以獲取文字的寬或高。但是不幸的,這四種方案里,有些方法獲取到的數(shù)值不是真實(shí)的文字寬高。
我們通過(guò)以下測(cè)試代碼,分別測(cè)試字母 "e" 和 "j"。
private void measureText(String str) { if (str == null) { return; } float width1 = mPaint.measureText(str); Log.i("lxc", "width1 ---> " + width1); Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); float height1 = Math.abs(fontMetrics.leading + fontMetrics.ascent) + fontMetrics.descent; Log.i("lxc", "height1 ---> " + height1); Rect rect = new Rect(); mPaint.getTextBounds(str, 0, str.length(), rect); float width2 = rect.width(); float height2 = rect.height(); Log.i("lxc", "width2 ---> " + width2); Log.i("lxc", "height2 ---> " + height2); Path textPath = new Path(); mPaint.getTextPath(str, 0, str.length(), 0.0f, 0.0f, textPath); RectF boundsPath = new RectF(); textPath.computeBounds(boundsPath, true); float width3 = boundsPath.width(); float height3 = boundsPath.height(); Log.i("lxc", "width3 ---> " + width3); Log.i("lxc", "height3 ---> " + height3); }
調(diào)用以下代碼測(cè)試
measureText("e"); Log.i("lxc", " <----分割線----> "); measureText("j");
日志輸出如下:
08-13 22:50:20.777 4977-4977/com.orzangleli.textbounddemo I/lxc: width1 ---> 21.0
08-13 22:50:20.777 4977-4977/com.orzangleli.textbounddemo I/lxc: height1 ---> 46.875
08-13 22:50:20.777 4977-4977/com.orzangleli.textbounddemo I/lxc: width2 ---> 18.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height2 ---> 22.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width3 ---> 17.929688
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height3 ---> 21.914062
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: <----分割線---->
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width1 ---> 10.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height1 ---> 46.875
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width2 ---> 8.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height2 ---> 37.0
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: width3 ---> 8.046875
08-13 22:50:20.778 4977-4977/com.orzangleli.textbounddemo I/lxc: height3 ---> 37.36328
首先,我們可以確定字母 "e" 和 "j" 的顯示高度應(yīng)該不一樣,而使用第二種 FontMetrics 方案計(jì)算出的兩種情況文字高度一樣,而且從代碼的調(diào)用上看,我們也是直接根據(jù) Paint 獲取的 FontMetrics, 與文字內(nèi)容無(wú)關(guān)。所以我們需要測(cè)量文字真實(shí)高度的話,需要排除第二種方案了。
我們準(zhǔn)備一個(gè)自定義 View,在 onDraw 方法中使用 mPaint 繪制一個(gè)文本 "e", 然后截圖測(cè)量文本寬高,得出以下結(jié)果:
可以看到,文本的寬為 18, 高為 22。 可以得出以下結(jié)論:
方案1測(cè)量結(jié)果為近似值,存在一定誤差。
方案3測(cè)量結(jié)果準(zhǔn)確。
方案4測(cè)量結(jié)果精度更高,數(shù)值基本與方案3一致。
再多說(shuō)幾句。與測(cè)量文字高度類似,我們?nèi)绾潍@取文字的基線 baseline 位置。
一般的博客上會(huì)告訴我們,如果需要計(jì)算文字的基線 baseline 位置,可以通過(guò) FontMetrics 來(lái)計(jì)算。FontMetrics 基線上面的值為負(fù)數(shù),基線下面的值為正數(shù)。baseline 計(jì)算公式為:
baseline = ascent + leading
如果你真的使用了這個(gè)公式就會(huì)發(fā)現(xiàn)坑。這個(gè)公式計(jì)算的基線位置實(shí)際上是默認(rèn)字體的基線位置,與文字內(nèi)容無(wú)關(guān)。我們可以看下面的例子:
在自定義 View 的 onDraw 方法中,繪制一個(gè)字符 "e", 繪制y坐標(biāo)為 baseline,所以文字應(yīng)該會(huì)頂著 Activity 的邊界。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint.FontMetrics fontMetrics = mPaint.getFontMetrics(); float baseline = Math.abs(fontMetrics.leading + fontMetrics.ascent); canvas.drawText("e", 0, baseline, mPaint); }
顯示結(jié)果為:
那問(wèn)題來(lái)了,究竟怎么計(jì)算才能計(jì)算出真實(shí)的文本的基線位置呢。
我們使用之前的方案3來(lái)試試。代碼如下:
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); String str = "e"; Rect rect = new Rect(); mPaint.getTextBounds(str, 0, str.length(), rect); float baseline = Math.abs(rect.top); canvas.drawText(str, 0, baseline, mPaint); }
看看效果, 已經(jīng)能夠滿足我們的需求,左上都頂著 Activity 顯示了。
總結(jié)
精確測(cè)量文本寬高時(shí),盡量不要使用 FontMetrics 去做。如果要求不精確,可以使用 Paint 的 measureText 方法計(jì)算文本寬度,如果要求精確測(cè)量,可以使用 Paint 的 getTextBounds 方法 或者 getTextPath 方法,獲取文本的邊界框矩形 Rect, 所獲的Rect 的寬高即為文本的寬高, Rect的 top 為文本上邊界距基線的距離, Rect 的 bottom 為文本下邊距距離基線的距離。
本文涉及的代碼可以在我的 GitHub 項(xiàng)目 AndroidBlogDemo github.com/hust2010107 (本地下載)… 。
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Android移動(dòng)應(yīng)用開發(fā)指南之六種布局詳解
Android應(yīng)用界面要美觀好看,就需要運(yùn)用到一定的布局技術(shù),Android布局是不可忽視的,是android應(yīng)用界面開發(fā)的重要一環(huán),這篇文章主要給大家介紹了關(guān)于Android移動(dòng)應(yīng)用開發(fā)指南之六種布局的相關(guān)資料,需要的朋友可以參考下2022-09-09Android中使用二級(jí)緩存、異步加載批量加載圖片完整案例
這篇文章主要介紹了Android中使用二級(jí)緩存、異步加載批量加載圖片完整案例,本文講解了實(shí)現(xiàn)的過(guò)程以及核心代碼展示,并給出了完整項(xiàng)目源碼,需要的朋友可以參考下2015-06-06Android studio制作簡(jiǎn)易計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了Android studio制作簡(jiǎn)易計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Android自定義View 實(shí)現(xiàn)水波紋動(dòng)畫引導(dǎo)效果
在android程序開發(fā)中,我們經(jīng)常簡(jiǎn)單通過(guò)自定義view實(shí)現(xiàn)水波紋動(dòng)畫引導(dǎo)功能,下面通過(guò)本文給大家分享實(shí)現(xiàn)代碼,需要的朋友參考下2017-01-01Android實(shí)現(xiàn)分享長(zhǎng)圖并且添加全圖水印
這篇文章主要介紹了Android實(shí)現(xiàn)分享長(zhǎng)圖并且添加全圖水印的相關(guān)資料,需要的朋友可以參考下2017-03-03Android使用分類型RecyclerView仿各大商城首頁(yè)
這篇文章主要為大家詳細(xì)介紹了Android使用分類型的RecyclerView仿各大商城首頁(yè),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android直播系統(tǒng)平臺(tái)搭建之圖片實(shí)現(xiàn)陰影效果的方法小結(jié)
這篇文章主要介紹了Android直播系統(tǒng)平臺(tái)搭建, 圖片實(shí)現(xiàn)陰影效果的若干種方法,本文給大家?guī)?lái)三種方法,每種方法通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-08-08Android Java調(diào)用自己C++類庫(kù)的實(shí)例講解
今天小編就為大家分享一篇關(guān)于Android Java調(diào)用自己C++類庫(kù)的實(shí)例講解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02