Java: Friendlier way to get an instance of FontMet

2019-01-25 21:17发布

问题:

Is there a friendlier way to get an instance of FontMetrics than

FontMetrics fm = Graphics.getFontMetrics(Font);

I hate this way because of the following example:

If you want to create in a game a menu and you want all the menuitems in the center of the screen you need fontmetrics. But, mostly, menuitems are clickable. So I create an array of Rectangles and all the rectangles fits around the items, so when the mouse is pressed, I can simply use

for (int i = 0; i < rects.length; i++)
if (rects[i].contains(mouseX, mouseY)) { ... }

But to create the rects I also need FontMetrics for their coordinates. So this mean that I have to construct all my rectangles in the paint-method of my menu.

So I want a way to get the FontMetrics so I can construct the Rectangles in a method called by the constructor.

回答1:

For me the easiest way was to:

Font font = new Font("Helvetica",Font.PLAIN,12);
Canvas c = new Canvas();
FontMetrics fm = c.getFontMetrics(font);

Benefits:

  1. If you call c.getGraphics() it will return null (thus there is no graphics object)
  2. This (canvas) will also work in headless mode.

Now you can easily get height and width...



回答2:

The really correct answer is to use Toolkit.

Font font = new Font("Courier New", Font.PLAIN, 14);
FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(font);


回答3:

Once the background component, i.e. whatever is behind your menu, has been rendered, it has a Graphics object that you can use to get the metrics for a given font, just once.

You certainly don't want to be doing this in the paint method, which should be as lightweight as possible. I'd hang this code on a listener that gets called when the component is first rendered. It can store the resulting FontMetrics object somewhere where you can later access it, either in a paint method for drawing those menu item boxes.

Rather than determining the measurements of your menu graphics at the last moment, i.e. when painting, it might be a good idea instead to create some components to represent your menu. You can place those components on the Glass Pane more info here so they'll float above everything else, and you'll have the added bonus that those components are all capable of accepting mouse clicks and firing listener events on them, and since they only capture events on their own geometry you don't even have to figure out which part of menu was hit by the click, if at all.

Another advantage of using components here is that you may entirely get around the requirement for fiddling with font metrics. There are ready-made menu items, or you could just use JLabels, and you can specify their alignment, you can use a LayoutManager to size the boxes to the width of the biggest label, and so forth.



回答4:

Assuming the menu text is fixed, you could pre-draw the text to a BufferedImage with alpha transparency and make your calculations then. Then, when you need the menu text, just draw the image.

You'll still have to do some offset calculations to centre the image (assuming the panel size can change), but these should be relatively lightweight.



回答5:

I think this is a good solution

private static HashMap<Font, FontMetrics> fontmetrics = new HashMap<Font, FontMetrics>();


public static FontMetrics getFontMetrics(Font font)
{
    if (fontmetrics.containsKey(font))
    {
        return fontmetrics.get(font);
    }
    FontMetrics fm = createFontMetrics(font);
    fontmetrics.put(font, fm);
    return fm;
}

private static FontMetrics createFontMetrics(Font font)
{
    BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
    Graphics g = bi.getGraphics();
    FontMetrics fm = g.getFontMetrics(font);
    g.dispose();
    bi = null;
    return fm;
}


回答6:

Adding to what Lonzak said, how about this:

public static FontMetrics getFontMetrics(Font font){
    GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice gd = ge.getDefaultScreenDevice();
    GraphicsConfiguration config = gd.getDefaultConfiguration();

    Canvas c = new Canvas(config);
    return c.getFontMetrics(font);
}

You could store the 'config' variable as a static variable so it is constructed once in some utility font class that contains other font related information for your game/development environment. I guess you could also do this with the canvas variable.