Skip to content

Commit 3b78ec9

Browse files
authored
修复 JFXRadioButton 在 Windows 平台上中心圆点偏移的问题 (#4874)
1 parent cfcbe6e commit 3b78ec9

1 file changed

Lines changed: 191 additions & 0 deletions

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//
2+
// Source code recreated from a .class file by IntelliJ IDEA
3+
// (powered by FernFlower decompiler)
4+
//
5+
6+
package com.jfoenix.skins;
7+
8+
import com.jfoenix.controls.JFXRadioButton;
9+
import com.jfoenix.controls.JFXRippler;
10+
import com.jfoenix.controls.JFXRippler.RipplerMask;
11+
import javafx.animation.Interpolator;
12+
import javafx.animation.KeyFrame;
13+
import javafx.animation.KeyValue;
14+
import javafx.animation.Timeline;
15+
import javafx.geometry.HPos;
16+
import javafx.geometry.Insets;
17+
import javafx.geometry.VPos;
18+
import javafx.scene.control.RadioButton;
19+
import javafx.scene.control.skin.RadioButtonSkin;
20+
import javafx.scene.layout.AnchorPane;
21+
import javafx.scene.layout.StackPane;
22+
import javafx.scene.paint.Color;
23+
import javafx.scene.shape.Circle;
24+
import javafx.scene.text.Text;
25+
import javafx.util.Duration;
26+
27+
public class JFXRadioButtonSkin extends RadioButtonSkin {
28+
private static final double PADDING = 15.0;
29+
30+
private boolean invalid = true;
31+
private final JFXRippler rippler;
32+
private final Circle radio;
33+
private final Circle dot;
34+
private Timeline timeline;
35+
private final AnchorPane container = new AnchorPane();
36+
private final double labelOffset = -10.0;
37+
38+
public JFXRadioButtonSkin(JFXRadioButton control) {
39+
super(control);
40+
double radioRadius = 7.0;
41+
this.radio = new Circle(radioRadius);
42+
this.radio.getStyleClass().setAll("radio");
43+
this.radio.setStrokeWidth(2.0);
44+
this.radio.setFill(Color.TRANSPARENT);
45+
46+
this.dot = new Circle(4);
47+
this.dot.getStyleClass().setAll("dot");
48+
this.dot.fillProperty().bind(control.selectedColorProperty());
49+
this.dot.setScaleX(0.0);
50+
this.dot.setScaleY(0.0);
51+
52+
StackPane boxContainer = new StackPane();
53+
boxContainer.getChildren().addAll(this.radio, this.dot);
54+
boxContainer.setPadding(new Insets(PADDING));
55+
this.rippler = new JFXRippler(boxContainer, RipplerMask.CIRCLE);
56+
this.container.getChildren().add(this.rippler);
57+
AnchorPane.setRightAnchor(this.rippler, this.labelOffset);
58+
59+
this.updateChildren();
60+
control.focusedProperty().addListener((o, oldVal, newVal) -> {
61+
if (newVal) {
62+
if (!this.getSkinnable().isPressed()) {
63+
this.rippler.showOverlay();
64+
}
65+
} else {
66+
this.rippler.hideOverlay();
67+
}
68+
69+
});
70+
control.pressedProperty().addListener((o, oldVal, newVal) -> this.rippler.hideOverlay());
71+
this.registerChangeListener(control.selectedColorProperty(), ignored -> {
72+
this.updateAnimation();
73+
boolean isSelected = this.getSkinnable().isSelected();
74+
Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
75+
Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
76+
this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
77+
if (isSelected) {
78+
this.radio.strokeProperty().set(selectedColor);
79+
}
80+
});
81+
82+
this.registerChangeListener(control.unSelectedColorProperty(), ignored -> {
83+
this.updateAnimation();
84+
boolean isSelected = this.getSkinnable().isSelected();
85+
Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
86+
Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
87+
this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
88+
if (!isSelected) {
89+
this.radio.strokeProperty().set(unSelectedColor);
90+
}
91+
92+
});
93+
this.registerChangeListener(control.selectedProperty(), ignored -> {
94+
boolean isSelected = this.getSkinnable().isSelected();
95+
Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
96+
Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
97+
this.rippler.setRipplerFill(isSelected ? selectedColor : unSelectedColor);
98+
if (this.timeline == null) {
99+
this.updateAnimation();
100+
}
101+
102+
this.playAnimation();
103+
});
104+
}
105+
106+
protected void updateChildren() {
107+
super.updateChildren();
108+
if (this.radio != null) {
109+
this.removeRadio();
110+
this.getChildren().add(this.container);
111+
}
112+
}
113+
114+
protected void layoutChildren(double x, double y, double w, double h) {
115+
RadioButton radioButton = this.getSkinnable();
116+
double contWidth = this.snapSizeX(this.container.prefWidth(-1.0)) + (double) (this.invalid ? 2 : 0);
117+
double contHeight = this.snapSizeY(this.container.prefHeight(-1.0)) + (double) (this.invalid ? 2 : 0);
118+
double computeWidth = Math.min(radioButton.prefWidth(-1.0), radioButton.minWidth(-1.0)) + this.labelOffset + 2.0 * this.PADDING;
119+
double labelWidth = Math.min(computeWidth - contWidth, w - this.snapSizeX(contWidth)) + this.labelOffset + 2.0 * PADDING;
120+
double labelHeight = Math.min(radioButton.prefHeight(labelWidth), h);
121+
double maxHeight = Math.max(contHeight, labelHeight);
122+
double xOffset = computeXOffset(w, labelWidth + contWidth, radioButton.getAlignment().getHpos()) + x;
123+
double yOffset = computeYOffset(h, maxHeight, radioButton.getAlignment().getVpos()) + x;
124+
if (this.invalid) {
125+
this.initializeComponents();
126+
this.invalid = false;
127+
}
128+
129+
this.layoutLabelInArea(xOffset + contWidth, yOffset, labelWidth, maxHeight, radioButton.getAlignment());
130+
((Text) this.getChildren().get(this.getChildren().get(0) instanceof Text ? 0 : 1)).textProperty().set(this.getSkinnable().textProperty().get());
131+
this.container.resize(this.snapSizeX(contWidth), this.snapSizeY(contHeight));
132+
this.positionInArea(this.container, xOffset, yOffset, contWidth, maxHeight, 0.0, radioButton.getAlignment().getHpos(), radioButton.getAlignment().getVpos());
133+
}
134+
135+
private void initializeComponents() {
136+
Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
137+
Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
138+
this.radio.setStroke(unSelectedColor);
139+
this.rippler.setRipplerFill(this.getSkinnable().isSelected() ? selectedColor : unSelectedColor);
140+
this.updateAnimation();
141+
this.playAnimation();
142+
}
143+
144+
private void playAnimation() {
145+
this.timeline.setRate(this.getSkinnable().isSelected() ? 1.0 : -1.0);
146+
this.timeline.play();
147+
}
148+
149+
private void updateAnimation() {
150+
Color unSelectedColor = ((JFXRadioButton) this.getSkinnable()).getUnSelectedColor();
151+
Color selectedColor = ((JFXRadioButton) this.getSkinnable()).getSelectedColor();
152+
this.timeline = new Timeline(
153+
new KeyFrame(Duration.ZERO,
154+
new KeyValue(this.dot.scaleXProperty(), 0, Interpolator.EASE_BOTH),
155+
new KeyValue(this.dot.scaleYProperty(), 0, Interpolator.EASE_BOTH),
156+
new KeyValue(this.radio.strokeProperty(), unSelectedColor, Interpolator.EASE_BOTH)),
157+
new KeyFrame(Duration.millis(200.0),
158+
new KeyValue(this.dot.scaleXProperty(), 1, Interpolator.EASE_BOTH),
159+
new KeyValue(this.dot.scaleYProperty(), 1, Interpolator.EASE_BOTH),
160+
new KeyValue(this.radio.strokeProperty(), selectedColor, Interpolator.EASE_BOTH))
161+
);
162+
}
163+
164+
private void removeRadio() {
165+
this.getChildren().removeIf(node -> "radio".equals(node.getStyleClass().get(0)));
166+
}
167+
168+
protected double computeMinWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
169+
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.minWidth(-1.0)) + this.labelOffset + 2.0 * PADDING;
170+
}
171+
172+
protected double computePrefWidth(double height, double topInset, double rightInset, double bottomInset, double leftInset) {
173+
return super.computePrefWidth(height, topInset, rightInset, bottomInset, leftInset) + this.snapSizeX(this.radio.prefWidth(-1.0)) + this.labelOffset + 2.0 * PADDING;
174+
}
175+
176+
static double computeXOffset(double width, double contentWidth, HPos hpos) {
177+
return switch (hpos) {
178+
case LEFT -> 0.0;
179+
case CENTER -> (width - contentWidth) / 2.0;
180+
case RIGHT -> width - contentWidth;
181+
};
182+
}
183+
184+
static double computeYOffset(double height, double contentHeight, VPos vpos) {
185+
return switch (vpos) {
186+
case TOP, BASELINE -> 0.0;
187+
case CENTER -> (height - contentHeight) / 2.0;
188+
case BOTTOM -> height - contentHeight;
189+
};
190+
}
191+
}

0 commit comments

Comments
 (0)