Border with rounded corners & transparency

2019-01-01 10:50发布

The following screenshot shows a test of TextBubbleBorder1. 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 a Clip (representing the area outside the rounded corners) on the Graphics2D instance and calling clearRect(). That can be seen in Label 1.

Border Test

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);
    }
}
  1. While the TextBubbleBorder was devised for Internal padding for JTextArea with background Image (& ended up using a JLabel since the text area was a mess for the reasons mentioned above), by specifying a pointerSize of 0 we end up with a 'rounded rectangle' instead.

3条回答
看风景的人
2楼-- · 2019-01-01 11:38

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:

Code result

查看更多
琉璃瓶的回忆
3楼-- · 2019-01-01 11:46

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:

BorderTest with 0px speech pointer

BorderTest with 16px speech pointer

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);
    }
}
查看更多
看风景的人
4楼-- · 2019-01-01 11:54

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.

enter image description here

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
        }
    }
}
查看更多
登录 后发表回答