Java - 如何在一个矩形中直观地将特定字符串(不仅仅是字体)居中
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/23729944/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Java - How to visually center a specific string (not just a font) in a rectangle
提问by MikeW
I am trying to visually center an arbitrary user-supplied string on a JPanel. I have read dozens of other similar questions and answers here on SO but haven't found any that directly address the problem I am having.
我试图在 JPanel 上直观地将用户提供的任意字符串居中。我已经在 SO 上阅读了许多其他类似的问题和答案,但没有找到任何直接解决我遇到的问题的问题。
In the code sample below, getWidth() and getHeight() refer to the width and height of the JPanel on which I'm placing the text string. I have found that TextLayout.getBounds()does a very good job of telling me the size of a bounding rectangle that encloses the text. So, I figured that it would be relatively simple to center the text rectangle in the JPanel rectangle by calculating the x and y positions on the JPanel of the lower left corner of the text-bounding rectangle:
在下面的代码示例中,getWidth() 和 getHeight() 指的是我放置文本字符串的 JPanel 的宽度和高度。我发现TextLayout.getBounds()在告诉我包围文本的边界矩形的大小方面做得非常好。因此,我认为通过计算文本边界矩形左下角的 JPanel 上的 x 和 y 位置,将文本矩形居中放置在 JPanel 矩形中会相对简单:
FontRenderContext context = g2d.getFontRenderContext();
messageTextFont = new Font("Arial", Font.BOLD, fontSize);
TextLayout txt = new TextLayout(messageText, messageTextFont, context);
Rectangle2D bounds = txt.getBounds();
xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2));
g2d.setFont(messageTextFont);
g2d.setColor(rxColor);
g2d.drawString(messageText, xString, yString);
This worked perfectly for strings which were all uppercase. However, when I started testing with strings that contained lowercase letters with descenders (like g, p, y), the text was no longer centered. The descenders on the lower case letters (the parts that extend below the baseline of the font) were being drawn too low on the JPanel to have the text appear to be centered.
这对于全部大写的字符串非常有效。但是,当我开始测试包含带有降序的小写字母(如 g、p、y)的字符串时,文本不再居中。小写字母(延伸到字体基线以下的部分)在 JPanel 上绘制得太低,无法使文本看起来居中。
That's when I discovered (thanks to SO) that the y parameter passed to drawString()specifies the baselineof the drawn text, not the lower bound. Thus, again with the help of SO, I realized that I needed to adjust the placement of the text by the length of the descenders in my string:
那时我发现(感谢 SO)传递给drawString()的 y 参数指定了绘制文本的基线,而不是下限。因此,再次在 SO 的帮助下,我意识到我需要通过字符串中下行的长度来调整文本的位置:
....
TextLayout txt = new TextLayout(messageText, messageTextFont, context);
Rectangle2D bounds = txt.getBounds();
int descent = (int)txt.getDescent();
xString = (int)((getWidth() - (int)bounds.getWidth()) / 2 );
yString = (int)((getHeight()/2) + (int)(bounds.getHeight()/2) - descent);
....
I tested this with strings heavy in lowercase letters like g, p, and y and it worked great! WooHoo! But....wait. Ugh. Now when I try with only uppercase letters, the text is way too HIGH on the JPanel to look centered.
我用 g、p 和 y 等小写字母重的字符串对此进行了测试,效果很好!呜呼!可是等等。啊。现在,当我只尝试使用大写字母时,JPanel 上的文本太高而无法看起来居中。
That's when I discovered that TextLayout.getDescent()(and all the other getDescent() methods I have found for other classes) returns the maximum descent of the FONTnot of the specific string. Thus, my uppercase string was being raised up to account for descenders that didn't even occur in that string.
那时我发现TextLayout.getDescent()(以及我为其他类找到的所有其他 getDescent() 方法)返回FONT的最大下降,而不是特定字符串的最大下降。因此,我的大写字符串被提升以解释甚至没有出现在该字符串中的下行。
What am I to do? If I don't adjust the y parameter for drawString() to account for descenders then lowercase strings with descenders are visually too low on the JPanel. If I do adjust the y parameter for drawString() to account for the descenders then strings which do not contain any characters with descenders are visually too high on the JPanel. There doesn't seem to be any way for me to determine where the baseline is in the text-bounding rectangle for a GIVEN string. Thus, I can't figure out exactly what y to pass to drawString().
我是什么做的?如果我不调整 drawString() 的 y 参数来考虑降序,那么带有降序的小写字符串在 JPanel 上看起来太低了。如果我确实调整了 drawString() 的 y 参数以说明降序,那么不包含任何带有降序的字符的字符串在 JPanel 上看起来太高了。我似乎没有任何方法可以确定基线在 GIVEN 字符串的文本边界矩形中的位置。因此,我无法确切地弄清楚要传递给 drawString() 的 y 是什么。
Thanks for any help or suggestions.
感谢您的任何帮助或建议。
采纳答案by MadProgrammer
While I muck about with TextLayout
, you could just use the Graphics
context's FontMetrics
, for example...
虽然我对 with 不屑一顾TextLayout
,但您可以只使用Graphics
上下文的FontMetrics
,例如...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LayoutText {
public static void main(String[] args) {
new LayoutText();
}
public LayoutText() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String text;
public TestPane() {
text = "Along time ago, in a galaxy, far, far away";
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
Font font = new Font("Arial", Font.BOLD, 48);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int x = ((getWidth() - fm.stringWidth(text)) / 2);
int y = ((getHeight() - fm.getHeight()) / 2) + fm.getAscent();
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);
g2d.dispose();
}
}
}
Okay, after some fussing about...
好吧,经过一番大惊小怪……
Basically, text rendering occurs at the baseline, this makes the y
position of the bounds usually appear above this point, making it look like the text is been painted above the y
position
基本上,文本渲染发生在基线处,这使得y
边界的位置通常出现在该点上方,使文本看起来像是在该y
位置上方绘制的
To overcome this, we need to add the font's ascent minus the font's descent to the y
position...
为了克服这个问题,我们需要将字体的上升减去字体的下降添加到y
位置...
For example...
例如...
FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);
Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();
... This is why I love rendering text by hand ...
... 这就是我喜欢手工渲染文本的原因 ...
Runnable example...
可运行示例...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LayoutText {
public static void main(String[] args) {
new LayoutText();
}
public LayoutText() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String text;
public TestPane() {
text = "Along time ago, in a galaxy, far, far away";
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
FontRenderContext context = g2d.getFontRenderContext();
Font font = new Font("Arial", Font.BOLD, 48);
TextLayout txt = new TextLayout(text, font, context);
Rectangle2D bounds = txt.getBounds();
int x = (int) ((getWidth() - (int) bounds.getWidth()) / 2);
int y = (int) ((getHeight() - (bounds.getHeight() - txt.getDescent())) / 2);
y += txt.getAscent() - txt.getDescent();
g2d.setFont(font);
g2d.setColor(Color.BLACK);
g2d.drawString(text, x, y);
g2d.setColor(Color.BLUE);
g2d.translate(x, y);
g2d.draw(bounds);
g2d.dispose();
}
}
}
Take a look at Working with Text APIsfor more information...
查看使用文本 API以获取更多信息...
Updated
更新
As has already been suggested, you could use a GlyphVector
...
正如已经建议的那样,您可以使用GlyphVector
...
Each word (Cat
and Dog
) is calculated separatly to demonstrate the differences
每个单词 ( Cat
and Dog
) 单独计算以显示差异
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class LayoutText {
public static void main(String[] args) {
new LayoutText();
}
public LayoutText() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private String text;
public TestPane() {
text = "A long time ago, in a galaxy, far, far away";
}
@Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
g2d.drawLine(getWidth() / 2, 0, getWidth() / 2, getHeight());
g2d.drawLine(0, getHeight() / 2, getWidth(), getHeight() / 2);
Font font = new Font("Arial", Font.BOLD, 48);
g2d.setFont(font);
FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = font.createGlyphVector(frc, "Cat");
Rectangle2D box = gv.getVisualBounds();
int x = 0;
int y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
g2d.drawString("Cat", x, y);
x += box.getWidth();
gv = font.createGlyphVector(frc, "Dog");
box = gv.getVisualBounds();
y = (int)(((getHeight() - box.getHeight()) / 2d) + (-box.getY()));
g2d.drawString("Dog", x, y);
g2d.dispose();
}
}
}
回答by ug_
I think this answeris the correct way to do it however I have had problems in the past with custom fonts and getting their bounds. In one project I had to resort to actually getting the outline of the font and using those bounds. This method is likely more memory intensive however it seems to be a surefire way for getting font bounds.
我认为这个答案是正确的方法,但是我过去在自定义字体和获取它们的边界方面遇到了问题。在一个项目中,我不得不求助于实际获取字体的轮廓并使用这些边界。这种方法可能会占用更多内存,但它似乎是获取字体边界的可靠方法。
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Font font = new Font("Arial", Font.BOLD, 48);
String text = "Along time ago, in a galaxy, far, far away";
Shape outline = font.createGlyphVector(g.getFontMetrics().getFontRenderContext(), text).getOutline();
// the shape returned is located at the left side of the baseline, this means we need to re-align it to the top left corner. We also want to set it the the center of the screen while we are there
AffineTransform transform = AffineTransform.getTranslateInstance(
-outline.getBounds().getX() + getWidth()/2 - outline.getBounds().width / 2,
-outline.getBounds().getY() + getHeight()/2 - outline.getBounds().height / 2);
outline = transform.createTransformedShape(outline);
g2d.fill(outline);
}
Like I said before try to use the font metrics but if all else fails try this method out.
就像我之前说的,尝试使用字体度量,但如果所有其他方法都失败了,请尝试使用此方法。