Code
class BadgeLayerUI extends LayerUI {
private static final int BADGE_SIZE = 17;
private static final Point OFFSET = new Point(6, 2);
private final Rectangle viewRect = new Rectangle();
private final Rectangle iconRect = new Rectangle();
private final Rectangle textRect = new Rectangle();
@Override public void paint(Graphics g, JComponent c) {
super.paint(g, c);
if (c instanceof JLayer) {
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
iconRect.setBounds(0, 0, 0, 0);
textRect.setBounds(0, 0, 0, 0);
BadgeLabel label = (BadgeLabel) ((JLayer<?>) c).getView();
SwingUtilities.calculateInnerArea(label, viewRect);
SwingUtilities.layoutCompoundLabel(
label,
label.getFontMetrics(label.getFont()),
label.getText(),
label.getIcon(),
label.getVerticalAlignment(),
label.getHorizontalAlignment(),
label.getVerticalTextPosition(),
label.getHorizontalTextPosition(),
viewRect,
iconRect,
textRect,
label.getIconTextGap()
);
int x = iconRect.x + iconRect.width - BADGE_SIZE + OFFSET.x;
int y = iconRect.y + iconRect.height - BADGE_SIZE + OFFSET.y;
g2.translate(x, y);
Icon badge = new BadgeIcon(label.getCounter(), Color.WHITE, new Color(0xAA_32_16_16, true));
badge.paintIcon(label, g2, 0, 0);
g2.dispose();
}
}
}
class BadgeIcon implements Icon {
private final Color badgeBgc;
private final Color badgeFgc;
private final int value;
protected BadgeIcon(int value, Color fgc, Color bgc) {
this.value = value;
this.badgeFgc = fgc;
this.badgeBgc = bgc;
}
@Override public void paintIcon(Component c, Graphics g, int x, int y) {
if (value <= 0) {
return;
}
int w = getIconWidth();
int h = getIconHeight();
Graphics2D g2 = (Graphics2D) g.create();
g2.translate(x, y);
RoundRectangle2D badge = new RoundRectangle2D.Double(0, 0, w, h, 6, 6);
g2.setPaint(badgeBgc);
g2.fill(badge);
g2.setPaint(badgeBgc.darker());
g2.draw(badge);
g2.setPaint(badgeFgc);
FontRenderContext frc = g2.getFontRenderContext();
// Java 12:
// NumberFormat fmt = NumberFormat.getCompactNumberInstance(
// Locale.US, NumberFormat.Style.SHORT);
// String txt = fmt.format(value);
String txt = value > 999 ? "1K" : Objects.toString(value);
AffineTransform at = txt.length() < 3 ? null : AffineTransform.getScaleInstance(.66, 1d);
Shape shape = new TextLayout(txt, g2.getFont(), frc).getOutline(at);
Rectangle2D b = shape.getBounds();
Point2D p = new Point2D.Double(
b.getX() + b.getWidth() / 2d, b.getY() + b.getHeight() / 2d);
AffineTransform toCenterAT = AffineTransform.getTranslateInstance(
w / 2d - p.getX(), h / 2d - p.getY());
g2.fill(toCenterAT.createTransformedShape(shape));
g2.dispose();
}
@Override public int getIconWidth() {
return 17;
}
@Override public int getIconHeight() {
return 17;
}
}
Explanation
- Set
JLabel
to JLayer
to display Badge near the specified corner of the Icon
area inside the JLabel
body
Icon
area inside JLabel
can be retrieved with SwingUtilities.layoutCompoundLabel(...)
method
- Show
6px
and Badge offset in x axis and 2px
offset in y axis, so need to set more margin in JLabel
- If you also want to display text in
JLabel
, the text may overlap with Badge if IconTextGap
is not set considering the above offset
Icon
s for Badge created with a fixed size of 17x17
using RoundRectangle2D
or Ellipse2D
- Suppress Badge if value is
0
- Set to
1K
for all numbers with more than 4
digits
- If the number to be displayed is
3
digits, apply a transformation of 66%
to set the length.
References