You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX:如何让自定义ToggleSwitch组件在SceneBuilder中可调整大小?

解决自定义ToggleSwitch组件无法调整大小的问题

你的代码已经继承了Region,但没正确实现Region的尺寸逻辑,也没在resize时同步更新内部元素状态,导致无法调整大小。按以下步骤修改:

1. 重写Region的尺寸计算方法

Region需要明确告知布局系统自身的首选、最小尺寸,SceneBuilder才能识别并提供调整选项。添加以下方法:

@Override
protected double computePrefWidth(double height) {
    return PREFERRED_WIDTH;
}

@Override
protected double computePrefHeight(double width) {
    return PREFERRED_HEIGHT;
}

@Override
protected double computeMinWidth(double height) {
    return PREFERRED_HEIGHT; // 最小宽度等于高度,保证滑块能正常显示
}

@Override
protected double computeMinHeight(double width) {
    return 20; // 设置合理的最小高度
}

2. 完善resize方法,同步更新内部元素

原来的resize方法只更新了变量,没有实际修改背景和滑块的尺寸、位置。修改resize方法:

private void resize() {
    // 计算扣除内边距后的可用空间
    width = getWidth() - getInsets().getLeft() - getInsets().getRight();
    height = getHeight() - getInsets().getTop() - getInsets().getBottom();

    if (width > 0 && height > 0) {
        // 更新背景矩形的尺寸、圆角和位置
        background.setWidth(width);
        background.setHeight(height);
        background.setArcWidth(height); // 圆角半径等于高度,保持胶囊形状
        background.setArcHeight(height);
        background.setTranslateX(getInsets().getLeft());
        background.setTranslateY(getInsets().getTop());

        // 更新滑块的半径和位置
        trigger.setRadius(height / 2);
        trigger.setCenterX(getInsets().getLeft() + (switchedOn.get() ? (width - height) : height / 2));
        trigger.setCenterY(getInsets().getTop() + height / 2);

        // 更新动画的目标位置(基于当前组件尺寸)
        translateAnimation.setToX(switchedOn.get() ? (width - height) : 0);
    }
}

3. 修正开关状态变化的动画逻辑

原来的动画目标位置写死了100-50,改成动态计算的width - height,确保组件大小变化时动画位置正确:

switchedOn.addListener((observableValue, oldState, newState) -> {
    boolean isOn = newState;
    translateAnimation.setToX(isOn ? (width - height) : 0);
    fillAnimation.setFromValue(isOn ? Color.WHITE : Color.LIGHTGREEN);
    fillAnimation.setToValue(isOn ? Color.LIGHTGREEN : Color.WHITE);
    animation.play();
});

4. 初始化时监听内边距变化(可选但推荐)

在构造函数里添加内边距监听,确保内边距变化时也能触发resize:

public ToggleSwitch(){
    // 其他初始化代码...
    
    // 监听内边距变化
    insetsProperty().addListener(o -> resize());
    
    // 初始调用一次resize,保证布局正确
    resize();
}

修改后的完整代码

import javafx.animation.FillTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.util.Duration;

public class ToggleSwitch extends Region {

    private final double PREFERRED_WIDTH = 100;
    private final double PREFERRED_HEIGHT = 50;

    private final BooleanProperty switchedOn = new SimpleBooleanProperty(false);
    private final TranslateTransition translateAnimation = new TranslateTransition(Duration.seconds(0.25));
    private final FillTransition fillAnimation = new FillTransition(Duration.seconds(0.25));
    private final ParallelTransition animation = new ParallelTransition(translateAnimation, fillAnimation);

    private double width;
    private double height;
    private Rectangle background;
    private Circle trigger;


    public ToggleSwitch() {
        width = PREFERRED_WIDTH;
        height = PREFERRED_HEIGHT;

        // 初始化背景(不设置固定尺寸,交给resize处理)
        background = new Rectangle();
        background.setFill(Color.WHITE);
        background.setStroke(Color.LIGHTGRAY);

        // 初始化滑块(不设置固定尺寸)
        trigger = new Circle();
        trigger.setFill(Color.WHITE);
        trigger.setStroke(Color.LIGHTGRAY);

        translateAnimation.setNode(trigger);
        fillAnimation.setShape(background);

        getChildren().addAll(background, trigger);

        switchedOn.addListener((observableValue, oldState, newState) -> {
            boolean isOn = newState;
            translateAnimation.setToX(isOn ? (width - height) : 0);
            fillAnimation.setFromValue(isOn ? Color.WHITE : Color.LIGHTGREEN);
            fillAnimation.setToValue(isOn ? Color.LIGHTGREEN : Color.WHITE);
            animation.play();
        });

        setOnMouseClicked(event -> {
            switchedOn.set(!switchedOn.get());
        });

        // 监听尺寸和内边距变化
        widthProperty().addListener(o -> resize());
        heightProperty().addListener(o -> resize());
        insetsProperty().addListener(o -> resize());

        // 初始布局
        resize();
    }

    private void resize() {
        width = getWidth() - getInsets().getLeft() - getInsets().getRight();
        height = getHeight() - getInsets().getTop() - getInsets().getBottom();

        if (width > 0 && height > 0) {
            // 更新背景
            background.setWidth(width);
            background.setHeight(height);
            background.setArcWidth(height);
            background.setArcHeight(height);
            background.setTranslateX(getInsets().getLeft());
            background.setTranslateY(getInsets().getTop());

            // 更新滑块
            trigger.setRadius(height / 2);
            trigger.setCenterX(getInsets().getLeft() + (switchedOn.get() ? (width - height) : height / 2));
            trigger.setCenterY(getInsets().getTop() + height / 2);

            // 更新动画目标位置
            translateAnimation.setToX(switchedOn.get() ? (width - height) : 0);
        }
    }

    @Override
    protected double computePrefWidth(double height) {
        return PREFERRED_WIDTH;
    }

    @Override
    protected double computePrefHeight(double width) {
        return PREFERRED_HEIGHT;
    }

    @Override
    protected double computeMinWidth(double height) {
        return PREFERRED_HEIGHT;
    }

    @Override
    protected double computeMinHeight(double width) {
        return 20;
    }

    public BooleanProperty switchedOnProperty() {
        return switchedOn;
    }
}

修改后重新打包jar,导入SceneBuilder就能正常调整组件大小,内部的背景和滑块也会跟着组件尺寸自适应变化。

内容的提问来源于stack exchange,提问作者AnotherByte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 18:45:20