Покраска компонента за пределами пользовательской границы

В этом пользовательском классе границы я определяю фигуру RoundRectangle2D. Этот объект используется для рисования границы. К сожалению, поскольку метод paint объекта JComponent вызывает paintComponent перед paintBorder, установка клипа Graphics в форму RoundRectangle2D не имеет никакого эффекта; даже если я выдам repaint. Поэтому компонент будет рисовать за пределами своей границы, что по понятным причинам нежелательно.

Итак, мне было интересно: как заставить компонент рисовать исключительно внутри пользовательской границы?

Один подход, который я рассматривал, заключался в получении объекта Border компонента в методе paintComponent. А затем привожу этот объект к соответствующему классу, в котором я определяю параметры, которые будут влиять на клип. Но это не было похоже на «звуковой» дизайн.


Изменить –

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.border.AbstractBorder;

class JRoundedCornerBorder extends AbstractBorder 
{   
    private static final long serialVersionUID = 7644739936531926341L;
    private static final int THICKNESS = 2;

    JRoundedCornerBorder()
    {
        super();
    }

    @Override
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) 
    {
        Graphics2D g2 = (Graphics2D)g.create();

        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        if(c.hasFocus())
        {
            g2.setColor(Color.BLUE);
        }
        else
        {
            g2.setColor(Color.BLACK);
        }
        g2.setStroke(new BasicStroke(THICKNESS, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
        g2.drawRoundRect(THICKNESS, THICKNESS, width - THICKNESS - 2, height - THICKNESS - 2, 20, 20);

        g2.dispose();
    }

    @Override
    public Insets getBorderInsets(Component c) 
    {
        return new Insets(THICKNESS, THICKNESS, THICKNESS, THICKNESS);
    }

    @Override
    public Insets getBorderInsets(Component c, Insets insets) 
    {
        insets.left = insets.top = insets.right = insets.bottom = THICKNESS;
        return insets;
    }

    public boolean isBorderOpaque() {
        return false;
    }

    public static void main(String[] args) 
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run() 
            {
                final JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new FlowLayout());

                // Add button with custom border
                final JButton button = new JButton("Hello");
                button.setBorder(new JRoundedCornerBorder());
                button.setBackground(Color.YELLOW);
                button.setPreferredSize(new Dimension(200, 200));
                frame.add(button);

                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

введите здесь описание изображения

Красные круги подчеркивают, где компонент выходит за пределы своей границы.


person mre    schedule 07.12.2011    source источник
comment
должно происходить автоматически, при условии, что ваша граница правильно сообщает о своих вставках (иначе: пространство, в которое она хочет закрасить) Лучше всего показать свой код :-)   -  person kleopatra    schedule 07.12.2011
comment
только что вспомнил (может быть связано или нет, трудно сказать, не видя кода): раньше у AbstractBorder была ошибка в getBorderInsets — убедитесь, что реализовали ее правильно.   -  person kleopatra    schedule 07.12.2011
comment
@kleopatra, я думаю, что правильно сообщаю о своих вставках ... пожалуйста, посмотрите мое редактирование!   -  person mre    schedule 07.12.2011


Ответы (3)


ааа ... наконец-то я это понял (по какой-то причине пропустил roundBorder, моя вина :-) Кнопка просто соответствует своему контракту: она заявляет, что она непрозрачна, поэтому должна заполнять всю свою область, включая область границы. Таким образом, установка клипа (что вы можете сделать в paintComponent) приведет к нарушению контракта:

 // DO NOT - a opaque component violates its contract, as it will not fill
 // its complete area  
        @Override
        protected void paintComponent(Graphics g) {
            if (getBorder() instanceof JRoundedCornerBorder) {
                Shape borderShape = ((JRoundedCornerBorder) getBorder()).
                    getBorderShape(getWidth(), getHeight());
                g.setClip(borderShape);
            }
            super.paintComponent(g);
        }

Некрасиво, но безопасно было бы сообщить о себе как о прозрачном и взять на себя рисование фона самостоятельно:

        @Override
        protected void paintComponent(Graphics g) {
            if (getBorder() instanceof JRoundedCornerBorder) {
                g.setColor(getBackground());
                Shape borderShape = ((JRoundedCornerBorder) getBorder())
                    .getBorderShape(getWidth(), getHeight());
                ((Graphics2D) g).fill(borderShape);
            }
            super.paintComponent(g);
        }

        @Override
        public boolean isContentAreaFilled() {
            if (getBorder() instanceof JRoundedCornerBorder) return false;
            return super.isContentAreaFilled();
        }

        // using
        button.setOpaque(false);
person kleopatra    schedule 07.12.2011

не ответ на ваш вопрос, просто еще одна идея, как это сделать

введите здесь описание изображения

из очень длинного кода

    import java.awt.*;
    import java.awt.geom.*;
    import javax.swing.*;
    import javax.swing.event.*;

    public class Panel2Test {

        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {

                @Override
                public void run() {
                    ShadeOptionsPanel shadeOptions = new ShadeOptionsPanel();
                    ShadowSelector shadowSelector = new ShadowSelector(shadeOptions);
                    //ComponentSource componentSource = new ComponentSource(shadeOptions);
                    JFrame f = new JFrame("Rounded Concept Demo with Shadows");
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.add(shadowSelector, "North");
                    f.add(shadeOptions);
                    //f.add(componentSource, "South");
                    f.setSize(300, 200);
                    f.setLocation(150, 150);
                    f.setVisible(true);
                }
            });
        }

        private Panel2Test() {
        }
    }

    class ShadeOptionsPanel extends JPanel {

        private static final long serialVersionUID = 1L;
        private final int PAD, DIA, BORDER;
        private Color colorIn, colorOut;
        private int xc, yc;
        private Ellipse2D eIn, eOut;
        private GradientPaint gradient;
        private CustomPaint customPaint;
        private Area arcBorder;
        private int width, height;
        private Point2D neOrigin, nwOrigin, swOrigin, seOrigin, neDiag, nwDiag, swDiag, seDiag;
        private final static int NORTHEAST = 0, NORTHWEST = 1, SOUTHWEST = 2, SOUTHEAST = 3;
        public int shadowVertex = 3;

        public ShadeOptionsPanel() {
            PAD = 25;
            DIA = 75;
            BORDER = 10;
            colorIn = Color.black;
            colorOut = getBackground();
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            width = getWidth();
            height = getHeight();
            g2.drawRoundRect(PAD, PAD, width - 2 * PAD, height - 2 * PAD, DIA, DIA);
            calculateArcOrigins();
            calculateCardinalDiagonals();
            drawVertexArc(g2, shadowVertex);
            switch (shadowVertex) {
                case NORTHEAST:
                    drawNorthSide(g2);
                    drawEastSide(g2);
                    xc = PAD + DIA / 2; // draw northwest arc
                    yc = PAD + DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(nwOrigin, nwDiag);
                    eOut = getOuterEllipse(nwOrigin, nwDiag);
                    arcBorder = getArcArea(eIn, eOut, 90.0);
                    g2.fill(arcBorder);
                    xc = width - PAD - DIA / 2; // draw southeast arc
                    yc = height - PAD - DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(seOrigin, seDiag);
                    eOut = getOuterEllipse(seOrigin, seDiag);
                    arcBorder = getArcArea(eIn, eOut, 270.0);
                    g2.fill(arcBorder);
                    break;
                case NORTHWEST:
                    drawNorthSide(g2);
                    drawWestSide(g2);
                    xc = width - PAD - DIA / 2;// draw northeast arc
                    yc = PAD + DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(neOrigin, neDiag);
                    eOut = getOuterEllipse(neOrigin, neDiag);
                    arcBorder = getArcArea(eIn, eOut, 0.0);
                    g2.fill(arcBorder);
                    xc = PAD + DIA / 2;// draw southwest arc
                    yc = height - PAD - DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(swOrigin, swDiag);
                    eOut = getOuterEllipse(swOrigin, swDiag);
                    arcBorder = getArcArea(eIn, eOut, 180.0);
                    g2.fill(arcBorder);
                    break;
                case SOUTHWEST:
                    drawWestSide(g2);
                    drawSouthSide(g2);
                    xc = PAD + DIA / 2; // draw northwest arc
                    yc = PAD + DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(nwOrigin, nwDiag);
                    eOut = getOuterEllipse(nwOrigin, nwDiag);
                    arcBorder = getArcArea(eIn, eOut, 90.0);
                    g2.fill(arcBorder);
                    xc = width - PAD - DIA / 2; // draw the southeast arc
                    yc = height - PAD - DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(seOrigin, seDiag);
                    eOut = getOuterEllipse(seOrigin, seDiag);
                    arcBorder = getArcArea(eIn, eOut, 270.0);
                    g2.fill(arcBorder);
                    break;
                case SOUTHEAST:
                    drawEastSide(g2);
                    drawSouthSide(g2);
                    xc = width - PAD - DIA / 2; // draw northeast arc
                    yc = PAD + DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(neOrigin, neDiag);
                    eOut = getOuterEllipse(neOrigin, neDiag);
                    arcBorder = getArcArea(eIn, eOut, 0.0);
                    g2.fill(arcBorder);
                    xc = PAD + DIA / 2;  // draw southwest arc
                    yc = height - PAD - DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = getInnerEllipse(swOrigin, swDiag);
                    eOut = getOuterEllipse(swOrigin, swDiag);
                    arcBorder = getArcArea(eIn, eOut, 180.0);
                    g2.fill(arcBorder);
            }
        }

        private Ellipse2D getInnerEllipse(Point2D center, Point2D corner) {
            return new Ellipse2D.Double(center.getX() - DIA / 2,
                    center.getY() - DIA / 2, DIA, DIA);
        }

        private Ellipse2D getOuterEllipse(Point2D center, Point2D corner) {
            int w = DIA, h = DIA;
            if (shadowVertex < 2) {
                if (center.getY() > corner.getY()) {
                    h += 2 * BORDER;
                } else {
                    w += 2 * BORDER;
                }
            } else if (center.getY() > corner.getY()) {
                w += 2 * BORDER;
            } else {
                h += 2 * BORDER;
            }
            return new Ellipse2D.Double(center.getX() - w / 2, center.getY() - h / 2, w, h);
        }

        private Area getArcArea(Ellipse2D e1, Ellipse2D e2, double start) {
            Arc2D arc1 = new Arc2D.Double(e1.getBounds2D(), start, 90.0, Arc2D.PIE);
            Arc2D arc2 = new Arc2D.Double(e2.getBounds2D(), start, 90.0, Arc2D.PIE);
            Area arc = new Area(arc2);
            arc.subtract(new Area(arc1));
            return arc;
        }

        private void drawNorthSide(Graphics2D g2) {
            gradient = new GradientPaint(width / 2, PAD - BORDER, colorOut,
                    width / 2, PAD, colorIn);
            g2.setPaint(gradient);
            g2.fill(new Rectangle2D.Double(PAD + DIA / 2, PAD - BORDER,
                    width - 2 * (PAD + DIA / 2) + 1, BORDER));
        }

        private void drawWestSide(Graphics2D g2) {
            gradient = new GradientPaint(PAD - BORDER, height / 2, colorOut,
                    PAD, height / 2, colorIn);
            g2.setPaint(gradient);
            g2.fill(new Rectangle2D.Double(PAD - BORDER, PAD + DIA / 2,
                    BORDER, height - 2 * (PAD + DIA / 2) + 1));
        }

        private void drawSouthSide(Graphics2D g2) {
            gradient = new GradientPaint(width / 2, height - PAD, colorIn,
                    width / 2, height - PAD + BORDER, colorOut);
            g2.setPaint(gradient);
            g2.fill(new Rectangle2D.Double(PAD + DIA / 2, height - PAD,
                    width - 2 * (PAD + DIA / 2) + 1, BORDER));
        }

        private void drawEastSide(Graphics2D g2) {
            gradient = new GradientPaint(width - PAD, height / 2, colorIn,
                    width - PAD + BORDER, height / 2, colorOut);
            g2.setPaint(gradient);
            g2.fill(new Rectangle2D.Double(width - PAD, PAD + DIA / 2,
                    BORDER, height - 2 * (PAD + DIA / 2) + 1));
        }

        /**
         * Draws the central, full-shaded arc (opposite of the unshaded arc).
         */
        private void drawVertexArc(Graphics2D g2, int index) {
            switch (index) {
                case NORTHEAST:
                    xc = width - PAD - DIA / 2;
                    yc = PAD + DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = new Ellipse2D.Double(width - PAD - DIA, PAD, DIA, DIA);
                    eOut = new Ellipse2D.Double(width - PAD - DIA - BORDER, PAD - BORDER,
                            DIA + 2 * BORDER, DIA + 2 * BORDER);
                    arcBorder = getArcArea(eIn, eOut, 0.0);
                    g2.fill(arcBorder);
                    break;
                case NORTHWEST:
                    xc = PAD + DIA / 2;
                    yc = PAD + DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = new Ellipse2D.Double(PAD, PAD, DIA, DIA);
                    eOut = new Ellipse2D.Double(PAD - BORDER, PAD - BORDER,
                            DIA + 2 * BORDER, DIA + 2 * BORDER);
                    arcBorder = getArcArea(eIn, eOut, 90.0);
                    g2.fill(arcBorder);
                    break;
                case SOUTHWEST:
                    xc = PAD + DIA / 2;
                    yc = height - PAD - DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = new Ellipse2D.Double(PAD, height - PAD - DIA, DIA, DIA);
                    eOut = new Ellipse2D.Double(PAD - BORDER, height - PAD - DIA - BORDER,
                            DIA + 2 * BORDER, DIA + 2 * BORDER);
                    arcBorder = getArcArea(eIn, eOut, 180.0);
                    g2.fill(arcBorder);
                    break;
                case SOUTHEAST:
                    xc = width - PAD - DIA / 2;
                    yc = height - PAD - DIA / 2;
                    customPaint = new CustomPaint(xc, yc, new Point2D.Double(0, DIA / 2),
                            DIA / 2, BORDER, colorIn, colorOut);
                    g2.setPaint(customPaint);
                    eIn = new Ellipse2D.Double(width - PAD - DIA, height - PAD - DIA, DIA, DIA);
                    eOut = new Ellipse2D.Double(width - PAD - DIA - BORDER,
                            height - PAD - DIA - BORDER, DIA + 2 * BORDER, DIA + 2 * BORDER);
                    arcBorder = getArcArea(eIn, eOut, 270.0);
                    g2.fill(arcBorder);
            }
        }

        private void calculateArcOrigins() {
            neOrigin = new Point2D.Double(width - PAD - DIA / 2, PAD + DIA / 2);
            nwOrigin = new Point2D.Double(PAD + DIA / 2, PAD + DIA / 2);
            swOrigin = new Point2D.Double(PAD + DIA / 2, height - PAD - DIA / 2);
            seOrigin = new Point2D.Double(width - PAD - DIA / 2, height - PAD - DIA / 2);
        }

        private void calculateCardinalDiagonals() {
            neDiag = new Point2D.Double(neOrigin.getX()
                    + DIA * Math.cos(Math.toRadians(45)) / 2,
                    neOrigin.getY() - DIA * Math.sin(Math.toRadians(45)) / 2);
            nwDiag = new Point2D.Double(nwOrigin.getX()
                    + DIA * Math.cos(Math.toRadians(135)) / 2,
                    nwOrigin.getY() - DIA * Math.sin(Math.toRadians(135)) / 2);
            swDiag = new Point2D.Double(swOrigin.getX()
                    + DIA * Math.cos(Math.toRadians(225)) / 2,
                    swOrigin.getY() - DIA * Math.sin(Math.toRadians(225)) / 2);
            seDiag = new Point2D.Double(seOrigin.getX()
                    + DIA * Math.cos(Math.toRadians(315)) / 2,
                    seOrigin.getY() - DIA * Math.sin(Math.toRadians(315)) / 2);
        }

        public Dimension getInnerSize() {
            return new Dimension((int) nwOrigin.distance(neOrigin),
                    (int) nwOrigin.distance(swOrigin));
        }
    }

    class ShadowSelector extends JPanel {

        private static final long serialVersionUID = 1L;
        private ShadeOptionsPanel soPanel;
        private String[] directions = {"northeast", "northwest", "southwest", "southeast"};

        public ShadowSelector(ShadeOptionsPanel sop) {
            soPanel = sop;

            final SpinnerListModel model = new SpinnerListModel(directions);
            model.setValue(directions[3]);
            JSpinner spinner = new JSpinner(model);
            spinner.setPreferredSize(new Dimension(90, spinner.getPreferredSize().height));
            spinner.addChangeListener(new ChangeListener() {

                @Override
                public void stateChanged(ChangeEvent e) {
                    String value = (String) model.getValue();
                    soPanel.shadowVertex = model.getList().indexOf(value);
                    soPanel.repaint();
                }
            });
            add(new JLabel("shadow vertex", JLabel.RIGHT));
            add(spinner);
        }
    }

class CustomPaint implements Paint {

    Point2D originP, radiusP;
    int radius, border;
    Color colorIn, colorOut;

    public CustomPaint(int x, int y, Point2D radiusP,
            int radius, int border,
            Color colorIn, Color colorOut) {
        originP = new Point2D.Double(x, y);
        this.radiusP = radiusP;
        this.radius = radius;
        this.border = border;
        this.colorIn = colorIn;
        this.colorOut = colorOut;
    }

    @Override
    public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) {
        Point2D xformOrigin = xform.transform(originP, null), xformRadius = xform.deltaTransform(radiusP, null);
        return new CustomPaintContext(xformOrigin, xformRadius, radius, border, colorIn, colorOut);
    }

    @Override
    public int getTransparency() {
        int alphaIn = colorIn.getAlpha();
        int alphaOut = colorOut.getAlpha();
        return (((alphaIn & alphaOut) == 0xff) ? OPAQUE : TRANSLUCENT);
    }
}

class CustomPaintContext implements PaintContext {

    Point2D originP, radiusP;
    Color colorIn, colorOut;
    int radius, border;

    public CustomPaintContext(Point2D originP, Point2D radiusP, int radius, int border, Color colorIn, Color colorOut) {
        this.originP = originP;
        this.radiusP = radiusP;
        this.radius = radius;
        this.border = border;
        this.colorIn = colorIn;
        this.colorOut = colorOut;
    }

    @Override
    public void dispose() {
    }

    @Override
    public ColorModel getColorModel() {
        return ColorModel.getRGBdefault();
    }

    @Override
    public Raster getRaster(int x, int y, int w, int h) {
        WritableRaster raster = getColorModel().createCompatibleWritableRaster(w, h);
        int[] data = new int[w * h * 4];
        for (int j = 0; j < h; j++) {
            for (int i = 0; i < w; i++) {
                double distance = originP.distance(x + i, y + j);
                double r = radiusP.distance(radius, radius);
                double ratio = distance - r < 0 ? 0.0 : (distance - r) / border;
                if (ratio > 1.0) {
                    ratio = 1.0;
                }
                int base = (j * w + i) * 4;
                data[base + 0] = (int) (colorIn.getRed() + ratio * (colorOut.getRed() - colorIn.getRed()));
                data[base + 1] = (int) (colorIn.getGreen() + ratio * (colorOut.getGreen() - colorIn.getGreen()));
                data[base + 2] = (int) (colorIn.getBlue() + ratio * (colorOut.getBlue() - colorIn.getBlue()));
                data[base + 3] = (int) (colorIn.getAlpha() + ratio * (colorOut.getAlpha() - colorIn.getAlpha()));
            }
        }
        raster.setPixels(0, 0, w, h, data);
        return raster;
    }
}
person mKorbel    schedule 07.12.2011
comment
@Thomas в основном (за исключением меня) все из Top10 in All Time очень хороши в алгебре :-) - person mKorbel; 24.02.2012
comment
@Thomas, я нашел этот код интересным, поэтому немного погуглил и нашел это, pastemine.com/97q0 . Там вы можете найти реализацию CustomPaint. В любом случае, это еще один нерабочий пример, поэтому я исправлю код - person albfan; 20.02.2013

Как нарисовать границу? Вы реализовали интерфейс Border? Есть 3 способа разместить там всю логику границ

    void paintBorder(Component c, Graphics g, int x, int y, int width, int height);
    Insets getBorderInsets(Component c);
    boolean isBorderOpaque();
person StanislavL    schedule 07.12.2011
comment
Я создал подкласс AbstractBorder. Когда я реализовал логику в paintBorder, части компонента были выведены за границы границ, чего я не хочу. Итак, я подумал, что установка клипа ранее на эти границы предотвратит это, и я не знал, как это реализовать. - person mre; 07.12.2011