筆者最近在做一款彈幕控制元件,裡面涉及到繪製文字,以及文字邊框。而繪製文字邊框需要知道文字的左邊位置,上邊位置,以及文字的寬高。
通常來說,使用 Canvas 繪製文字,可以通過畫筆 Paint 來設定文字的大小。但是畫筆的大小與文字的寬高並無直接關係。
大家應該能說上幾種測量文字寬高的方法,如:
方案1. 通過 Paint 的 measureText 方法,可以測量文字的寬度
方案2. 通過獲取 Paint 的 FontMetrics, 根據 FontMetrics 的 leading, ascent, 和 descent可以獲取文字的高度。
方案3. 通過 Paint 的 getTextBounds 獲取文字的邊界矩形 Rect,根據 Rect 可以計算出文字的寬高。
方案4. 通過 Paint 獲取文字的 Path, 根據 Path 獲取文字的邊界矩形 Rect, 根據 Rect 可以計算出文字的寬高。
表面上看,我們有以上四種方案可以獲取文字的寬或高。但是不幸的,這四種方案裡,有些方法獲取到的數值不是真實的文字寬高。
我們通過以下測試程式碼,分別測試字母 "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);
}
複製程式碼
呼叫以下程式碼測試
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" 的顯示高度應該不一樣,而使用第二種 FontMetrics 方案計算出的兩種情況文字高度一樣,而且從程式碼的呼叫上 看,我們也是直接根據 Paint 獲取的 FontMetrics, 與文字內容無關。所以我們需要測量文字真實高度的話,需要排除第二種方案了。
我們準備一個自定義 View,在 onDraw 方法中使用 mPaint 繪製一個文字 "e", 然後截圖測量文字寬高,得出以下結果:
可以看到,文字的寬為 18, 高為 22。 可以得出以下結論:
方案1測量結果為近似值,存在一定誤差。
方案3測量結果準確。
方案4測量結果精度更高,數值基本與方案3一致。
再多說幾句。與測量文字高度類似,我們如何獲取文字的基線 baseline 位置。
一般的部落格上會告訴我們,如果需要計算文字的基線 baseline 位置,可以通過 FontMetrics 來計算。FontMetrics 基線上面的值為負數,基線下面的值為正數。baseline 計算公式為:
baseline = ascent + leading
如果你真的使用了這個公式就會發現坑。這個公式計算的基線位置實際上是預設字型的基線位置,與文字內容無關。我們可以看下面的例子:
在自定義 View 的 onDraw 方法中,繪製一個字元 "e", 繪製y座標為 baseline,所以文字應該會頂著 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);
}
複製程式碼
顯示結果為:
那問題來了,究竟怎麼計算才能計算出真實的文字的基線位置呢。
我們使用之前的方案3來試試。程式碼如下:
@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);
}
複製程式碼
看看效果, 已經能夠滿足我們的需求,左上都頂著 Activity 顯示了。
總結
精確測量文字寬高時,儘量不要使用 FontMetrics 去做。如果要求不精確,可以使用 Paint 的 measureText 方法計算文字寬度, 如果要求精確測量,可以使用 Paint 的 getTextBounds 方法 或者 getTextPath 方法,獲取文字的邊界框矩形 Rect, 所獲的 Rect 的寬高即為文字的寬高, Rect的 top 為文字上邊界距基線的距離, Rect 的 bottom 為文字下邊距距離基線的距離。
本文涉及的程式碼可以在我的 GitHub 專案 AndroidBlogDemo github.com/hust2010107… 。
微信公眾號
您可以關注我的微信公眾號: Android開發實驗室。 用心做,希望能得到您的關注與支援。