在 JavaFX 中,如何计算文本所占像素的宽度
JavaFX 的恼人之处在于很多基本的操作都要自己亲力亲为。有些时候,我们希望 TextArea 能自动根据文本内容换行以及调整大小。换行是 TextArea 中已经有的功能,不过这也带来了新的问题。在 JavaFX 中,可以使用 Text 对象的方法 .getBoundsInLocal().getWidth()
测得文本的基本尺寸。不过,依然有很多要注意的问题。
【注意】
不同的符号的尺寸是不同的,不仅是中文汉字与英文字母,就连运算符、数字、英文字母之间的尺寸也不尽相同。不要自行编写自适应各种 Unicode 符号的算法。应该使用 JavaFX 内置的 API,如 Text 类的方法 Text对象.getBoundsInLocal().getWidth()
。
注意事项
主要的注意事项如下:
-
计算后最终的行宽值不会大于 TextArea 的行宽值。另外,当文本内容超过 TextArea 的最大宽度时,会触发换行。因此,还需要计算 TextArea 自动换行后的行数。
-
用户输入的原始内容中就有可能包含换行。对这种情况可以有不同的处理方案,但是需要考虑这个问题。如果不选择直接去除换行符,则需要先将用户输入分割成各个不含换行符的文本,然后分别统计这些文本的行数与最大行宽,最后加行数相加,并来取这些最大行宽中的最大值。
-
Windows 会将回车解释成
\n\r
,但 TextArea 清除文本中所有的\r
。换句话说,当在 Windows 输入回车时,实际上输入的是\n\r
。但当向 TextArea 输入\n\r
时,TextArea 会移除所有的\r
。从 TextArea 得到的字符串中不会包含任何\r
,TextArea 的换行符为\n
。 -
行数只能是整数。另外,如果使用整数除法,需要将结果加 1。因为不满 1 行也需要占用一行。
-
考虑用户的特殊输入:
-
输入为空串
-
输入的内容全是换行符
-
输入的内容存在连续的换行符
-
输入的开头是换行符
-
算法
实现的算法大致如下:
(假设:当用户输入内容包含换行符时,不管是否连续,也直接进行原始输出而不删除。当用户输入的内容为空时,不报错,也直接视文本长度为 0 来计算)
-
判断输入是否为 null 或空串,如果是,视文本行宽为 0,行数为 1。如果不是,进行下一步。
-
判断输入是否只有换行符,如果是,视文本行宽为 0,行数为换行符的个数。如果不是,进行下一步。
-
将输入按照换行符分割成各个不含换行符的文本,然后先计算单个文本尺寸,最后叠加。方法如下:
-
计算单个文本的尺寸。方法如下:
-
求文本的原始像素长度。
-
计算文本的行宽值:取像素长度与单行文本最大长度之间的最小值。
-
计算文本的行数:将像素长度除以单行文本最大长度,然后向上取整。
-
-
计算整个文本的行宽值:取各个文本行宽值的最大值。
-
计算整个文本的行数:将各个文本行数值累加。
-
-
计算文本框的宽度:将文本的行宽值与文本框左右内边距相加。
-
计算文本框的长度:将文本的行数乘以单行文本行高,然后加上文本框上下内边距。
代码
核心代码如下。
计算单行文本的像素宽度
public static double calculateTextPixelWidth(String text, Font font) {
Text theText = new Text(text);
theText.setFont(font);
return theText.getBoundsInLocal().getWidth();
}
计算文本框尺寸
/**
* @param originText 内文本的内容
* @param font 内文本的字体
* @param lineSeparator 换行符的定义
* @param originMaxWidth 内文本最大的行宽
* @param rowExtension 对话框横向两端与内文本的边距
* @param originSingleHeight 内文本一行的高度
* @param columnExtension 对话框纵向向两端与内文本的边距
* @return 计算出的对话框的宽度。其中,[0] 代表宽度,[1] 代表高度
*/
public static double[] calculateTextBoxSize(String originText, Font font, String lineSeparator,
double originMaxWidth, double rowExtension,
double originSingleHeight, double columnExtension) {
double maxRowLength = 0;
int formattedColumnNum = 0;
if (originText != null && !"".equals(originText)) {
var texts = originText.split(lineSeparator);
if (texts.length == 0) { // 如果文本中只有换行符
maxRowLength = 0;
formattedColumnNum = originText.length() + 1; // 注意要加 1
} else {
double singleRowLength = 0;
for (var text : texts) {
var singleOriginWidth = calculateTextPixelWidth(text, font);
singleRowLength = Math.min(singleOriginWidth, originMaxWidth); // 注意:这是求最小值
maxRowLength = Math.max(maxRowLength, singleRowLength); // 注意:这里求最大值
formattedColumnNum += (int) (singleOriginWidth / originMaxWidth) + 1; // 注意要加 1
}
}
}
double[] result = new double[2];
result[0] = maxRowLength + rowExtension * 2;
result[1] = formattedColumnNum * originSingleHeight + columnExtension * 2;
return result;
}
对于 TextArea,其换行符为 \n
,因此可以使用如下代码:
/**
* @param originText 内文本的内容
* @param font 内文本的字体
* @param originMaxWidth 内文本最大的行宽
* @param rowExtension 对话框横向两端与内文本的边距
* @param originSingleHeight 内文本一行的高度
* @param columnExtension 对话框纵向向两端与内文本的边距
* @return 计算出的对话框的宽度。其中,[0] 代表宽度,[1] 代表高度
*/
public static double[] calculateTextBoxSize(String originText, Font font,
double originMaxWidth, double rowExtension,
double originSingleHeight, double columnExtension) {
String lineSeparator = "\n"; // TextArea 中的换行符为 '\n'
return calculateTextBoxSize(originText, font, lineSeparator,
originMaxWidth, rowExtension, originSingleHeight, columnExtension);
}