Border with rounded corners & transparency
Solution 1
N.B. There is a clipping bug in this code, which is fixed in the accepted answer to paintComponent() is drawing on other components. This should only be considered as a solution if the 'clipping bug fix' is incorporated.
// Paint the BG color of the parent, everywhere outside the clip
// of the text bubble.
See this point in the code for the source that shows correctly as:
import java.awt.*;
import java.awt.image.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.border.*;
public class BorderTest {
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
JPanel gui = new JPanel(new GridLayout(2,0,5,5));
gui.setBorder(new EmptyBorder(10,10,10,10));
gui.setBackground(Color.RED);
AbstractBorder brdrLeft = new TextBubbleBorder(Color.BLACK,2,16,16);
AbstractBorder brdrRight = new TextBubbleBorder(Color.BLACK,2,16,16,false);
JLabel l1 = new JLabel("Label 1");
l1.setBorder(brdrRight);
gui.add(l1);
JLabel l2 = new JLabel("Label 2");
l2.setBorder(brdrLeft);
l2.setBackground(Color.YELLOW);
l2.setOpaque(true);
gui.add(l2);
JPanel p1 = new JPanel();
p1.add(new JLabel("Panel 1"));
p1.setBorder(brdrRight);
p1.setOpaque(false);
gui.add(p1);
JPanel p2 = new JPanel();
p2.add(new JLabel("Panel 2"));
p2.setBorder(brdrLeft);
gui.add(p2);
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
class TextBubbleBorder extends AbstractBorder {
private Color color;
private int thickness = 4;
private int radii = 8;
private int pointerSize = 7;
private Insets insets = null;
private BasicStroke stroke = null;
private int strokePad;
private int pointerPad = 4;
private boolean left = true;
RenderingHints hints;
TextBubbleBorder(
Color color) {
this(color, 4, 8, 7);
}
TextBubbleBorder(
Color color, int thickness, int radii, int pointerSize) {
this.thickness = thickness;
this.radii = radii;
this.pointerSize = pointerSize;
this.color = color;
stroke = new BasicStroke(thickness);
strokePad = thickness / 2;
hints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
int pad = radii + strokePad;
int bottomPad = pad + pointerSize + strokePad;
insets = new Insets(pad, pad, bottomPad, pad);
}
TextBubbleBorder(
Color color, int thickness, int radii, int pointerSize, boolean left) {
this(color, thickness, radii, pointerSize);
this.left = left;
}
@Override
public Insets getBorderInsets(Component c) {
return insets;
}
@Override
public Insets getBorderInsets(Component c, Insets insets) {
return getBorderInsets(c);
}
@Override
public void paintBorder(
Component c,
Graphics g,
int x, int y,
int width, int height) {
Graphics2D g2 = (Graphics2D) g;
int bottomLineY = height - thickness - pointerSize;
RoundRectangle2D.Double bubble = new RoundRectangle2D.Double(
0 + strokePad,
0 + strokePad,
width - thickness,
bottomLineY,
radii,
radii);
Polygon pointer = new Polygon();
if (left) {
// left point
pointer.addPoint(
strokePad + radii + pointerPad,
bottomLineY);
// right point
pointer.addPoint(
strokePad + radii + pointerPad + pointerSize,
bottomLineY);
// bottom point
pointer.addPoint(
strokePad + radii + pointerPad + (pointerSize / 2),
height - strokePad);
} else {
// left point
pointer.addPoint(
width - (strokePad + radii + pointerPad),
bottomLineY);
// right point
pointer.addPoint(
width - (strokePad + radii + pointerPad + pointerSize),
bottomLineY);
// bottom point
pointer.addPoint(
width - (strokePad + radii + pointerPad + (pointerSize / 2)),
height - strokePad);
}
Area area = new Area(bubble);
area.add(new Area(pointer));
g2.setRenderingHints(hints);
// Paint the BG color of the parent, everywhere outside the clip
// of the text bubble.
Component parent = c.getParent();
if (parent!=null) {
Color bg = parent.getBackground();
Rectangle rect = new Rectangle(0,0,width, height);
Area borderRegion = new Area(rect);
borderRegion.subtract(area);
g2.setClip(borderRegion);
g2.setColor(bg);
g2.fillRect(0, 0, width, height);
g2.setClip(null);
}
g2.setColor(color);
g2.setStroke(stroke);
g2.draw(area);
}
}
Solution 2
Try this:
JPanel p = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension arcs = new Dimension(15,15); //Border corners arcs {width,height}, change this to whatever you want
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//Draws the rounded panel with borders.
graphics.setColor(getBackground());
graphics.fillRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint background
graphics.setColor(getForeground());
graphics.drawRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint border
}
};
With my test:
JFrame f = new JFrame();
f.setLayout(null);
f.setDefaultCloseOperation(3);
f.setSize(500, 500);
JPanel p = new JPanel() {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension arcs = new Dimension(15,15);
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//Draws the rounded opaque panel with borders.
graphics.setColor(getBackground());
graphics.fillRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint background
graphics.setColor(getForeground());
graphics.drawRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height);//paint border
}
};
p.setBounds(10,10,100,30);
p.setOpaque(false);
f.getContentPane().setBackground(Color.red);
f.add(p);
f.show();
the result is:
Solution 3
Thanks @BackSlash, nice and simple. I expanded upon this so it's more reusable. This also allows setting a background color in the constructor. And I show how you can make a circular panel for fun.
import java.awt.*;
import javax.swing.*;
public class RoundedPanelExample extends JFrame
{
public RoundedPanelExample()
{
setDefaultCloseOperation(EXIT_ON_CLOSE);
setTitle("Rounded Panel Example");
setResizable(true);
setDefaultLookAndFeelDecorated(true);
setSize(500, 500);
Container pane = getContentPane();
pane.setLayout(null);
pane.setBackground(Color.LIGHT_GRAY);
JPanel p1 = new RoundedPanel(10, Color.CYAN);
p1.setBounds(10,10,100,60);
p1.setOpaque(false);
pane.add(p1);
JPanel p2 = new RoundedPanel(15, Color.RED);
p2.setBounds(150,10,50,50);
p2.setOpaque(false);
pane.add(p2);
JPanel p3 = new RoundedPanel(30);
p3.setBounds(230,10,100,150);
p3.setOpaque(false);
pane.add(p3);
JPanel p4 = new RoundedPanel(20);
p4.setBounds(10,200,100,100);
p4.setBackground(Color.GREEN);
p4.setOpaque(false);
pane.add(p4);
JPanel p5 = new RoundedPanel(200);
p5.setBounds(150,200,200,200);
p5.setBackground(Color.BLUE);
p5.setOpaque(false);
pane.add(p5);
}
public static void main(String[] args)
{
RoundedPanelExample gui = new RoundedPanelExample();
gui.setVisible(true);
}
class RoundedPanel extends JPanel
{
private Color backgroundColor;
private int cornerRadius = 15;
public RoundedPanel(LayoutManager layout, int radius) {
super(layout);
cornerRadius = radius;
}
public RoundedPanel(LayoutManager layout, int radius, Color bgColor) {
super(layout);
cornerRadius = radius;
backgroundColor = bgColor;
}
public RoundedPanel(int radius) {
super();
cornerRadius = radius;
}
public RoundedPanel(int radius, Color bgColor) {
super();
cornerRadius = radius;
backgroundColor = bgColor;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension arcs = new Dimension(cornerRadius, cornerRadius);
int width = getWidth();
int height = getHeight();
Graphics2D graphics = (Graphics2D) g;
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//Draws the rounded panel with borders.
if (backgroundColor != null) {
graphics.setColor(backgroundColor);
} else {
graphics.setColor(getBackground());
}
graphics.fillRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height); //paint background
graphics.setColor(getForeground());
graphics.drawRoundRect(0, 0, width-1, height-1, arcs.width, arcs.height); //paint border
}
}
}
Andrew Thompson
Java desktop app. enthusiast (for the x-platform, rich client experience). No strong opinions on technologies, platforms etc. beyond that. Author of the SSCCE and wrote the initial draft of the MCVE at SO. Also seen at DrewTubeish. Some of the tools formerly available at my pscode.org domain can be downloaded from my Google Share Drive. At StackExchange: Completed the Java Web Start, JNLP, Applet & Java Sound tag information pages. Top user in same tags. Asked the How to create screenshots? FAQ & wrote the Why CS teachers should stop teaching Java applets blog post. One of the most prolific editors of questions & answers. Most active on StackOverflow but also seen at other places around StackExchange such as Space Exploration, Sci-Fi & Fantasy & Movies & TV.
Updated on May 24, 2021Comments
-
Andrew Thompson almost 3 years
The following screenshot shows a test of
TextBubbleBorder
1. I would like to make the corners of the component that are outside the rectangle to be entirely transparent & show whatever component is beneath it. I found a way to restrict the BG color of a label to 'inside the border' by setting aClip
(representing the area outside the rounded corners) on theGraphics2D
instance and callingclearRect()
. That can be seen inLabel 1
.However you can see the downside of this approach when there is a red BG (or any non-standard color) on the parent panel. The corners default to the default panel color (easiest to see in
Panel 2
).Ultimately I would like this to work for a non-standard color in the parent container, but it was partly inspired by What do I need to do to replicate this component with gradient paint?
Does anybody know a way to get those corners transparent?
import java.awt.*; import java.awt.geom.*; import javax.swing.*; import javax.swing.border.*; public class BorderTest { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { JPanel gui = new JPanel(new GridLayout(1,0,5,5)); gui.setBorder(new EmptyBorder(10,10,10,10)); gui.setBackground(Color.RED); AbstractBorder brdr = new TextBubbleBorder(Color.BLACK,2,16,0); JLabel l1 = new JLabel("Label 1"); l1.setBorder(brdr); gui.add(l1); JLabel l2 = new JLabel("Label 2"); l2.setBorder(brdr); l2.setBackground(Color.YELLOW); l2.setOpaque(true); gui.add(l2); JPanel p1 = new JPanel(); p1.add(new JLabel("Panel 1")); p1.setBorder(brdr); p1.setOpaque(false); gui.add(p1); JPanel p2 = new JPanel(); p2.add(new JLabel("Panel 2")); p2.setBorder(brdr); gui.add(p2); JOptionPane.showMessageDialog(null, gui); } }; // Swing GUIs should be created and updated on the EDT // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html SwingUtilities.invokeLater(r); } } class TextBubbleBorder extends AbstractBorder { private Color color; private int thickness = 4; private int radii = 8; private int pointerSize = 7; private Insets insets = null; private BasicStroke stroke = null; private int strokePad; private int pointerPad = 4; RenderingHints hints; TextBubbleBorder( Color color) { new TextBubbleBorder(color, 4, 8, 7); } TextBubbleBorder( Color color, int thickness, int radii, int pointerSize) { this.thickness = thickness; this.radii = radii; this.pointerSize = pointerSize; this.color = color; stroke = new BasicStroke(thickness); strokePad = thickness / 2; hints = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); int pad = radii + strokePad; int bottomPad = pad + pointerSize + strokePad; insets = new Insets(pad, pad, bottomPad, pad); } @Override public Insets getBorderInsets(Component c) { return insets; } @Override public Insets getBorderInsets(Component c, Insets insets) { return getBorderInsets(c); } @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height) { Graphics2D g2 = (Graphics2D) g; int bottomLineY = height - thickness - pointerSize; RoundRectangle2D.Double bubble = new RoundRectangle2D.Double( 0 + strokePad, 0 + strokePad, width - thickness, bottomLineY, radii, radii); Polygon pointer = new Polygon(); // left point pointer.addPoint( strokePad + radii + pointerPad, bottomLineY); // right point pointer.addPoint( strokePad + radii + pointerPad + pointerSize, bottomLineY); // bottom point pointer.addPoint( strokePad + radii + pointerPad + (pointerSize / 2), height - strokePad); Area area = new Area(bubble); area.add(new Area(pointer)); g2.setRenderingHints(hints); Area spareSpace = new Area(new Rectangle(0, 0, width, height)); spareSpace.subtract(area); g2.setClip(spareSpace); g2.clearRect(0, 0, width, height); g2.setClip(null); g2.setColor(color); g2.setStroke(stroke); g2.draw(area); } }
- While the
TextBubbleBorder
was devised for Internal padding for JTextArea with background Image (& ended up using aJLabel
since the text area was a mess for the reasons mentioned above), by specifying apointerSize
of 0 we end up with a 'rounded rectangle' instead.
- While the