JavaFX - 如何创建带有图像的自定义按钮

23

我想创建一个自定义按钮,有两个状态按下或未按下,就像一个切换按钮。我有两张图片来实现这个效果(按下和未按下),那么我该如何创建按钮并使用我的图片显示它?该按钮必须采用图像的尺寸。
我没有使用FXML。谢谢你的帮助。

4个回答

52
有几种不同的方法可以实现这个,我将概述我的最爱。此问题是关于切换行为的,所以答案侧重于它。如果您不需要切换行为,那么只需使用标准按钮Button,可以像此答案中提到的ToggleButton一样进行样式设置。
另请参阅: 使用ToggleButton并对其应用自定义样式。我建议这样做是因为您需要的控件“类似于切换按钮”,但只是看起来不同于默认切换按钮样式。我首选的方法是在CSS中为按钮定义一个图形。
.toggle-button {
  -fx-graphic: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Pizza-icon.png');
}

.toggle-button:selected {
  -fx-graphic: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Piece-of-cake-icon.png');
}

或者使用附带的 CSS 定义背景图片。

// file imagetogglebutton.css deployed in the same package as ToggleButtonImage.class
.toggle-button {
  -fx-background-image: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Pizza-icon.png');
  -fx-background-repeat: no-repeat;
  -fx-background-position: center;
}

.toggle-button:selected {
  -fx-background-image: url('http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Piece-of-cake-icon.png');
}

我更喜欢使用-fx-graphic规范而不是-fx-background-*规范,因为样式化背景图像的规则很棘手,并且设置背景不会自动调整按钮大小,而设置图形会。

以下是一些示例代码:

注意:此示例中使用的StackPaneBuilder已被弃用并从JavaFX中删除,请用new StackPane()替换该代码,并调用结果堆栈窗格上的方法来设置堆栈窗格属性,而不是使用生成器。

import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.StackPaneBuilder;
import javafx.stage.Stage;

public class ToggleButtonImage extends Application {
  public static void main(String[] args) throws Exception { launch(args); }
  @Override public void start(final Stage stage) throws Exception {
    final ToggleButton toggle = new ToggleButton();
    toggle.getStylesheets().add(this.getClass().getResource(
      "imagetogglebutton.css"
    ).toExternalForm());
    toggle.setMinSize(148, 148); toggle.setMaxSize(148, 148);
    stage.setScene(new Scene(
      StackPaneBuilder.create()
        .children(toggle)
        .style("-fx-padding:10; -fx-background-color: cornsilk;")
        .build()
    ));
    stage.show();
  }
}

这样做的一些优点是:

  1. 你可以获得默认的切换按钮行为,而不必自己重新实现它,通过添加自己的焦点样式、鼠标和键盘处理程序等。
  2. 如果你的应用程序被移植到不同的平台,比如移动设备,它会直接响应触摸事件,而不是鼠标事件等,毫无问题。
  3. 你的样式与应用逻辑分离,因此更容易重新设计你的应用程序。

另一个选择是不使用CSS,仍然使用ToggleButton,但在代码中设置图像。

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.*;
import javafx.scene.control.ToggleButton;
import javafx.scene.image.*;
import javafx.scene.layout.StackPaneBuilder;
import javafx.stage.Stage;

public class ToggleButtonImageViaGraphic extends Application {
  public static void main(String[] args) throws Exception { launch(args); }
  @Override public void start(final Stage stage) throws Exception {
    final ToggleButton toggle      = new ToggleButton();
    final Image        unselected  = new Image(
      "http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Pizza-icon.png"
    );
    final Image        selected    = new Image(
      "http://icons.iconarchive.com/icons/aha-soft/desktop-buffet/128/Piece-of-cake-icon.png"
    );
    final ImageView    toggleImage = new ImageView();
    toggle.setGraphic(toggleImage);
    toggleImage.imageProperty().bind(Bindings
      .when(toggle.selectedProperty())
        .then(selected)
        .otherwise(unselected)
    );
    
    stage.setScene(new Scene(
      StackPaneBuilder.create()
        .children(toggle)
        .style("-fx-padding:10; -fx-background-color: cornsilk;")
        .build()
    ));
    stage.show();
  }
}

如果你不熟悉CSS,那么基于代码的方法有优势。

为了获得最佳性能并易于在未签名的小程序和Webstart沙箱中移植,请将图像与您的应用程序捆绑在一起,并通过相对路径URL引用它们,而不是从网络上下载它们。


哇!我该说什么呢?这真是一个非常好的答案,非常感谢。我想我会选择第一种解决方案,在代码中将大小应用于我的按钮,这似乎在JavaFX应用程序中很常见,不像Swing。 - jerome
2
我不得不在CSS中添加“-fx-background-color:transparent;”以使我的按钮仅填充我的图像。 - jerome
这是一个很好的答案,但也展示了JavaFX的一个弱点。这种要求在21世纪是混乱和不必要的。 - Andrew S
1
安德鲁,你更喜欢哪种更强大的解决方案的例子? - jewelsea

7

您只需要创建一个继承自父类的自定义类。在该类上放置一个ImageView,在mousedown和mouseup事件中更改ImageView的图像即可。

public class ImageButton extends Parent {

    private static final Image NORMAL_IMAGE = ...;
    private static final Image PRESSED_IMAGE = ...;

    private final ImageView iv;

    public ImageButton() {
        this.iv = new ImageView(NORMAL_IMAGE);
        this.getChildren().add(this.iv);

        this.iv.setOnMousePressed(new EventHandler<MouseEvent>() {

            public void handle(MouseEvent evt) {
                iv.setImage(PRESSED_IMAGE);
            }

        });

        // TODO other event handlers like mouse up

    } 

}

4
之前两个回答的结合解决了问题,谢谢。创建一个继承自Button的新类。注意:在显示按钮之前应该调用updateImages()。
import javafx.event.EventHandler;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;

public class ImageButton extends Button {

    public void updateImages(final Image selected, final Image unselected) {
        final ImageView iv = new ImageView(selected);
        this.getChildren().add(iv);

        iv.setOnMousePressed(new EventHandler<MouseEvent>() {
            public void handle(MouseEvent evt) {
                iv.setImage(unselected);
            }
        });
        iv.setOnMouseReleased(new EventHandler<MouseEvent>() {
            public void handle(MouseEvent evt) {
                iv.setImage(selected);
            }
        });

        super.setGraphic(iv);
    }
}

0

由于某些原因,当我在场景中有许多这样的对象(比如工具栏)时,使用按钮在点击时处理图像交换总是表现得“迟缓”,而且动态调整大小非常缓慢,甚至有时会导致Java虚拟机本身崩溃。

相比之下,直接使用ImageView对象始终被证明非常响应,无论我在场景中有多少个。它们可以立即切换图像,可以实时调整大小并获得即时响应,使用起来感觉非常自然,不像按钮那样有点“阻力”。

以下是我的做法:

public void buildImageButtons() {

    double iconSize = 50;

    Image unSelectedOne = new Image(Main.class.getResourceAsStream("UnselectedImageOne.png"));
    Image selectedOne   = new Image(Main.class.getResourceAsStream("SelectedImageOne.png"));
    Image unSelectedTwo = new Image(Main.class.getResourceAsStream("UnselectedImageTwo.png"));
    Image selectedTwo   = new Image(Main.class.getResourceAsStream("SelectedImageTwo.png"));

    ImageView ivOne = newImageView(unSelectedOne, selectedOne, iconSize);
    ImageView ivTwo = newImageView(unSelectedTwo, selectedTwo, iconSize);

    ivOne.setOnMouseClicked(e -> methodOne());
    ivTwo.setOnMouseClicked(e -> methodTwo());
}

private ImageView newImageView(Image unselected, Image selected, double iconSize) {
    ImageView iv = new ImageView(unselected);

    iv.setPreserveRatio(true);
    iv.setFitWidth(iconSize);

    iv.setOnMousePressed(e->{
        iv.setImage(selected);
    });

    iv.setOnMouseReleased(e->{
        iv.setImage(unselected);
    });

    return iv;
}


private void methodOne() {
    System.out.println("Image One Clicked");
}

private void methodTwo() {
    System.out.println("Image Two Clicked");
}

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