重写JButton的paintComponent方法时,使用透明度却不能显示背景面板颜色问题。

4
这是我的目标。我扩展了JButton并覆盖了paintComponent方法,创建了我所需的圆角按钮效果和当鼠标滚过按钮时的颜色渐变效果。一切都很好。但是,JButton仍在绘制一个白色矩形区域,如图所示。
我希望 1) 去掉白色角落,2) 按钮的中心显示其后面的面板。我已经尝试过以下方法:
1- 绘制按钮时使用getParent().getBackground(),并首先绘制按钮。这对不透明面板非常有效。但是,我希望此按钮能在部分或完全透明的面板上工作。对于透明面板,它会绘制颜色,但在白色背景上隐藏面板后面的任何内容(如图像)。
2- 我尝试了许多组合,例如setOpaque(false)或setContentAreaFilled(false)。我尝试了在调用super.paintComponent(g)时和不调用它时使用它们。但是,这些都似乎不起作用。
3- 当我不使用g2.clearRect(0,0,width,height)方法(在绘制之前清除图形区域)时,按钮看起来正确。但由于图形对象从未被覆盖,淡入效果在按钮滚动一次后停止工作。
4- 我使用JLabel来设置文本,并尝试了设置它的opaque或仅不使用它,但问题仍然存在。因此,我认为这不是问题所在。
由于我只想为JButton而不是其他swing组件添加效果,因此我真的希望避免制作自己的ButtonUI。
谢谢,希望这很清楚。以下是我的按钮代码:
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;

/**
 * WButton.java
 * 
 * An extension of JButton but with custom graphics
 *
 */

public class WButton extends JButton{

  private Timer timer;
  private float[] background = {.3f,.6f,.8f,0f};
  private boolean fadeUp = true;
  private boolean fadeDown = false;
  private JLabel label;

  /**
   * Default Constructor
   */
  public WButton(){
    super();
    label = new JLabel();
    setupButton();
  }

  /**
   * Text constructor
   */
  public WButton(String text){
    super(text);
    label = new JLabel(text);
    setupButton();
  }

  /**
   * common setup functions
   */
  private void setupButton(){
    timer = new Timer(24,new TimerAction(this));
    label.setLabelFor(this);
    add(label);
  }

  /**
   * Set the background color
   */
  @Override
  public void setBackground(Color bg){
    background = bg.getRGBComponents(background);
    background[3] = 0f;
    super.setBackground(new Color(background[0],background[1],
                                  background[2],background[3]));
    repaint();
  }

  /**
   * get background
   */
  @Override
  public Color getBackground(){
    if(background!=null)
      return new Color(background[0],background[1],background[2],background[3]);

    return new Color(.5f,.5f,.5f);
  }

  /**
   * Set the font of the button
   */
  @Override
  public void setFont(Font font){
    super.setFont(font);
    if(label!=null)
      label.setFont(font);
  }

  /**
   * Override the set text method
   */
  @Override
  public void setText(String t){
    super.setText(t);
    if(label!=null)
      label.setText(t);
  }

  /**
   * Paint the button
   */
  @Override
  public void paintComponent(Graphics g){
    super.paintComponent(g);
    int width = getWidth();
    int height = getHeight();

    Graphics2D g2 = (Graphics2D)g;
    g2.clearRect(0,0,width,height);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);

    //Check Button Model state
    if(model.isPressed())
      paintPressedButton(g2,width,height);
    else{
      if(model.isRollover()){
        if(fadeUp){
          fadeUp = false;
          timer.start();
        }
      }
      else{
        if(fadeDown){
          fadeDown = false;
          timer.start();
        }
      }
      g2.setPaint(new Color(background[0],background[1],background[2],background[3]));
      g2.fillRoundRect(0,0,width-1,height-1,height,height);
    }
  }

  /**
   * Draw a pressed button
   */
  private void paintPressedButton(Graphics2D g2,int width,int height){

    float[] temp = new float[4];
    for(int i=0;i<background.length;i++)
      temp[i] = background[i]-.4f < 0f ? 0f : background[i]-.4f;

    g2.setPaint(new Color(temp[0],temp[1],temp[2],temp[3]));
    g2.fillRoundRect(0,0,width-1,height-1,height,height);

  }

  /**
   * paint the border
   */
  public void paintBorder(Graphics g){

    int width = getWidth();
    int height = getHeight();
    g.setColor(Color.BLACK);
    g.drawRoundRect(0,0,width-1,height-1,height,height);
  }

  /**
   * Inner action listener class
   */
  private class TimerAction implements ActionListener{

    private float alphaInc = .2f;
    WButton button;

    public TimerAction(WButton b){
      button = b;
    }

    public void actionPerformed(ActionEvent e){
      if(model.isRollover()){
        background[3] += alphaInc;
        if(background[3] > 1.0f){
          timer.stop();
          background[3] = 1.0f;
          fadeDown = true;
        }
      }
      else{
        background[3] -= alphaInc;
        if(background[3] < 0f){
          timer.stop();
          background[3] = 0f;
          fadeUp = true;
        }
      }
      button.repaint();
    }
  }
}

编辑 1

aly建议的方法让我更接近目标,但仍未达成。与其使用g2.clearRect(),我按照建议用透明颜色涂抹了物体。这样就去掉了白色方框,但出现了不同的颜色。经过调查,发现它是父面板的颜色,但没有透明度。以下为示例图片(面板透明度为70%)。第一张图片是程序启动时的情况。第二张图片是鼠标悬停后的情况。

pic3 enter image description here


2个回答

7
你可以不使用clearRect(),而是用完全透明的颜色清除背景。
g2.setColor(new Color(0,0,0,0));
g2.drawRect(0,0,width,height);

您仍然需要在JButton上调用setOpaque(false),这样当您悬停在它上面时,它不会使用蓝色的悬停颜色作为背景。

编辑:看到您刚才发布的内容后,我认为问题在于主框架没有重绘。

尝试:

SwingUtilities.getWindowAncestor(this).repaint();    

在绘制方法中重新绘制框架,可能会解决问题。

那让我接近了目标,并适用于非透明面板。请参见上面帖子的编辑,以了解它如何在透明面板中运作。 - greekbeard
就是这样!看来我得更加仔细地研究我的Swing工具。谢谢! - greekbeard

0
问题在于,Swing 在 JButton 上使用 paintImmediately(x, y, width, height) 来重新绘制更改的区域。如果您查看该方法的内部实现,它会遍历父层次结构(如果父组件不是不透明的),直到找到一个不透明的组件,然后调用其 repaint 方法。正如您所注意到的那样,这种方法不考虑背景颜色中的 alpha 组件,因为这些组件是不透明的(isOpaque = true)。
为了解决这个问题,您可以重写 JButton 中的 paintImmediately() 方法,如下所示:
@Override
public void paintImmediately(int x, int y, int w, int h) {
    super.paintImmediately(x, y, w, h);

    Component component = this;

    boolean repaint = false;
    while (!component.isOpaque() || (component.getBackground() != null && component.getBackground().getAlpha() < 255)) {
        if (component.getBackground() != null && component.getBackground().getAlpha() < 255) {
            repaint = true;
        }

        if (component.getParent() == null) {
            break;
        }

        component = component.getParent();

        if (!(component instanceof JComponent)) {
            break;
        }
    }

    // There is no need to repaint if no component with an alpha component in
    // background is found and no parent component is found (implied by component != this)
    // since super.paintImmediately() should have handled the general case.
    if (repaint && component != this) {
        component.repaint();
    }
}

该方法将检查顶层组件的父级,如果其不是不透明的或具有透明背景,则重新绘制它。从性能角度来看,这比当前接受的答案要好得多(每次悬停/按下按钮时都会重新绘制整个窗口)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接