JButton 的图标在悬停时变得像素化

JButton's icon becomes pixelated on hovering

我在制作 Java Swing 应用程序时遇到了问题。我想制作一个带有图标的按钮。 为此,我首先制作一个重新缩放的 ImageIcon:

studentIcon = new ImageIcon(new ImageIcon( 
"D:\Programming\Java\ELearningDesktop\src\com\core\student.png")
                .getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH));

然后我制作一个按钮并在上面设置图标:

JButton studentButton = new JButton();
studentButton.setIcon(studentIcon);
studentButton.setFocusable(false);

它工作正常,但出于某种原因,每次我将鼠标悬停在按钮上时,按钮上的图标都会像素化。悬停后它永远不会变得平滑,除非我重新缩放 JFrame,因此它可能会在某处调用 repaint() 并重新绘制它。

我使用下载的外观,但如果我使用默认外观,问题仍然存在。以相同的方式使用 ImageIcon,但不重新缩放也无济于事 - 像素化仍然出现。 解决方案是什么?

节目起点

public class Starter {
    public static void main(String[] args) {
        FlatLightLaf.install();
        EventQueue.invokeLater(()->{
            AuthFrame frame = new AuthFrame();
            frame.setVisible(true);
        });
    }
}

AuthFrame

public class AuthFrame extends JFrame {

    private JPanel mainPanel;
    private LoginPanel loginPanel;
    private ImageIcon studentIcon;
    private ImageIcon teacherIcon;

    public AuthFrame() {
        setLayout(new GridBagLayout());
        ImageIcon imageIcon = new ImageIcon(new ImageIcon(
                "D:\Programming\Java\ELearningDesktop\src\com\core\tileBackground.jpg")
                .getImage().getScaledInstance(321, 333, Image.SCALE_SMOOTH));
        mainPanel = new BackgroundPanel(imageIcon.getImage(), BackgroundPanel.TILED,
                0f, 0.5f);

        add(mainPanel, new GBC(0, 0).setFill(BOTH).setWeights(1, 1));

        mainPanel.setLayout(new GridBagLayout());

        studentIcon = new ImageIcon(new ImageIcon(
                "D:\Programming\Java\ELearningDesktop\src\com\core\student.png")
                .getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH));
        teacherIcon = new ImageIcon(new ImageIcon(
                "D:\Programming\Java\ELearningDesktop\src\com\core\teacher.png")
                .getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH));

        loginPanel = new LoginPanel();
        mainPanel.add(loginPanel);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(FRAME_WIDTH, FRAME_HEIGHT);
        setExtendedState(Frame.MAXIMIZED_BOTH);
    }

LoginPanel - 在 AuthFrame

中使用的私有内部 class
 private class LoginPanel extends JPanel {
        private JTextField usernameField;
        private JPasswordField passwordField;
        private JLabel errorLabel;
        private JButton studentButton;
        private JButton teacherButton;
        private JLabel titleLabel;
        private boolean forStudent = true;

        public LoginPanel() {
            setLayout(new GridBagLayout());
            setBackground(new Color(255, 255, 255, 181));

            studentButton = new JButton();
            studentButton.setIcon(studentIcon);
            studentButton.setFocusable(false);
            //add(studentButton, new GBC(0, 0).setAnchor(GBC.WEST).setInsets(10));

            teacherButton = new JButton(teacherIcon);
            teacherButton.setFocusable(false);
            add(teacherButton, new GBC(0, 0).setAnchor(GBC.WEST).setInsets(10));

            titleLabel = new JLabel("<html>Signing in as <b>student</b></html>");
            Utils.deriveFontForTo(titleLabel, 24f);
            add(titleLabel, new GBC(1, 0).setAnchor(GBC.EAST).setInsets(10));
            titleLabel.setVerticalAlignment(JLabel.BOTTOM);

            JLabel usernameLabel = new JLabel("Username");
            Utils.deriveFontForTo(usernameLabel, 24f);
            add(usernameLabel, new GBC(0, 1).setAnchor(GBC.WEST).setInsets(10));
            usernameLabel.setHorizontalAlignment(LEFT);

            JLabel passwordLabel = new JLabel("Password");
            Utils.deriveFontForTo(passwordLabel, 24f);
            add(passwordLabel, new GBC(0, 2).setAnchor(GBC.WEST).setInsets(10));

            usernameField = new JTextField(15);
            Utils.deriveFontForTo(usernameField, 24f);
            add(usernameField, new GBC(1, 1).setInsets(10));

            passwordField = new JPasswordField(15);
            Utils.deriveFontForTo(passwordField, 24f);
            add(passwordField, new GBC(1, 2).setInsets(10));

            errorLabel = new JLabel();
            Utils.deriveFontForTo(errorLabel, 16f);
            errorLabel.setForeground(Color.RED);
            add(errorLabel, new GBC(1, 3).setAnchor(GBC.WEST).setInsets(2));
            errorLabel.setHorizontalAlignment(LEFT);

            JButton loginButton = new JButton("Log in");
            loginButton.setFocusable(false);
            Utils.deriveFontForTo(loginButton, 24f);
            add(loginButton, new GBC(1, 4, 1, 1)
                    .setFill(GridBagConstraints.HORIZONTAL).setInsets(10));

         
            JButton registerButton = new JButton("Sign Up");
            loginButton.setFocusable(false);
            Utils.deriveFontForTo(registerButton, 24f);
            add(registerButton, new GBC(1, 5, 1, 1)
                    .setInsets(10));
        }
    }

GBC - 方便 class 使用 GridBagLayout

package com.core.helpers.graphics;

import java.awt.*;

public class GBC extends GridBagConstraints {


    public GBC(int gridX, int gridY){
        super.gridx = gridX;
        super.gridy = gridY;
    }

    public GBC(int gridX, int gridY, int gridWidth, int gridHeight){
        super.gridx = gridX;
        super.gridy = gridY;
        super.gridwidth = gridWidth;
        super.gridheight = gridHeight;
    }

    public GBC setAnchor(int anchor){
        super.anchor = anchor;
        return this;
    }

    public GBC setWeights(double weightX, double weightY){
        super.weightx = weightX;
        super.weighty = weightY;
        return this;
    }

    public GBC setFill(int fill){
        super.fill = fill;
        return this;
    }

    public GBC setInsets(int k){
        this.insets = new Insets(k,k,k,k);
        return this;
    }

}

谢谢

截图:

首先-两个按钮是平滑的

第二 - 悬停按钮变得像素化

第三个 - 调整框架大小后按钮再次变得平滑

编辑:

看来我的问题与 windows 缩放有关,如此处标记的答案 建议。使用这个 link 中的标记答案帮助了我。这是:

蛮力法:

每次按钮发生变化时都重新绘制按钮面板,使它看起来一直很流畅。这是代码片段:

teacherButton.getModel().addChangeListener(new ChangeListener() {
     @Override
     public void stateChanged(ChangeEvent e) {
         mainPanel.repaint();
     }
});

studentButton.getModel().addChangeListener(new ChangeListener() {
     @Override
     public void stateChanged(ChangeEvent e) {
         mainPanel.repaint();
     }
});

剩下的唯一小问题是现在悬停和按钮对悬停的响应之间存在微小差距(稍微改变颜色以表示它已悬停)。

it never becomes smooth unless I rescale the JFrame, so that it probably calls repaint() somewhere and it repaints it.

setBackground(new Color(255, 255, 255, 181));

我猜上面的内容与问题有关。 Swing 不支持透明背景。

Swing 期望组件是不透明的,在这种情况下,组件负责绘制其不透明背景。

或者,组件可以是非不透明的,在这种情况下,首先绘制父组件以确保绘制不透明的背景。

如果你有透明度,那么你需要使组件不透明并覆盖 paintComponent 方法并自己绘制背景。

基本代码为:

JPanel panel = new JPanel()
{
    protected void paintComponent(Graphics g)
    {
        g.setColor( getBackground() );
        g.fillRect(0, 0, getWidth(), getHeight());
        super.paintComponent(g);
    }
};
panel.setOpaque(false); // background of parent will be painted first
panel.setBackground( new Color(255, 0, 0, 20) );
frame.add(panel);

有关详细信息和可重复使用的解决方案,请参阅 Backgrounds With Transparency,这样您就不需要每次都使用自定义绘画。