Java – 如何在矩形中以视觉方式居中特定字符串(而不仅仅是字体)

我试图在JPanel上以视觉方式居中任意用户提供的字符串。 我已经在SO上阅读了几十个其他类似的问题和答案,但没有发现任何直接解决我遇到的问题。

在下面的代码示例中,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); 

这适用于全部大写的字符串。 但是,当我开始使用带有下行字符的小写字母(如g,p,y)进行测试时,文本不再居中。 小写字母上的下划线(延伸到字体基线下方的部分)在JPanel上绘制得太低,使文本看起来居中。

那时我发现(感谢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); .... 

我用g,p和y等小写字母重的字符串测试了它,效果很好! 哇噢! 可是等等。 啊。 现在,当我尝试只使用大写字母时,JPanel上的文字太高,看起来居中。

那时我发现TextLayout.getDescent() (以及我为其他类找到的所有其他getDescent()方法)返回FONT的最大下降而不是特定字符串。 因此,我的大写字符串被提升以考虑甚至没有出现在该字符串中的下行字符。

我是什么做的? 如果我没有调整drawString()的y参数来考虑下行,那么带有下行的小写字符串在JPanel上看起来太低了。 如果我确实调整了drawString()的y参数来考虑下行器,那么不包含任何带有下行器的字符的字符串在视觉上太高了。 我似乎没有办法确定基线在GIVEN字符串的文本边界矩形中的位置。 因此,我无法确切地知道要传递给drawString()的y。

感谢您的任何帮助或建议。

虽然我使用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(); } } } 

好吧,经过一番忙碌…

基本上,文本渲染发生在基线处,这使得边界的y位置通常出现在此点之上,使得它看起来像是在y位置上方绘制的文本

为了解决这个问题,我们需要将字体的上升减去字体的下降到y位置…

例如…

 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(); 

…这就是我喜欢手工渲染文字的原因……

可运行的例子……

布局

 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(); } } } 

有关更多信息,请查看使用Text API …

更新

正如已经建议的那样,你可以使用GlyphVector ……

每个单词(猫和狗)分别计算以certificate差异

猫狗

 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(); } } } 

我认为这个答案是正确的方法,但是我在过去使用自定义字体并获得限制时遇到了问题。 在一个项目中,我不得不求助于实际获取字体轮廓并使用这些边界。 这种方法可能会占用大量内存,但它似乎是获取字体边界的必然方法。

 @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); } 

就像我之前说过的那样,尝试使用字体指标,但是如果其他所有方法都失败了,请尝试使用