我最近参与了一个有4个双重属性的项目。根据项目的性质,允许用户通过单向或双向绑定绑定这些属性。
在某些时候,如果这些属性中的2个或多个彼此绑定,则必须特别小心,即使它间接发生也是如此,直接意味着:
a.bindBidirectional(b);
间接地:
a.bindBidirectional(x);
x.bindBidirectional(b);
我成功的唯一方法是将它们逐个设置为某个幻数并且每次检查其他属性是否也发生了变化,但这不是一个非常好的解决方案,因为它会触发不需要的更改事件
我的问题:是否有任何开箱即用的方法可以检查某个属性是否与某些其他指定属性绑定 bidirictionally ?
我目前正在JavaFX中实现DrawerPane
,就像这样:
显示左侧抽屉完全打开。
您也可以设置其他3个边并控制它们。 很好地控制它们的关键是使用4个双重属性作为增量,其中0.0完全关闭,1.0完全打开。
此外,还有4种openXxx()
和closeXxx()
方法可以使用Timeline
将delta平滑地设置为1或0。
为了增加灵活性,我设计了具有公共读/写访问权限的属性,因此您可以绑定它们(在上面的图像中,它们是双向绑定到滑块)。另一个特征是将它们中的两个绑定在一起,如果它们中的任何一个被打开则彼此打开。想想一些Windows 10应用程序,当将鼠标移动到屏幕顶部时,底部的“App Commands”抽屉也会打开:
现在,有时您想要立即关闭所有抽屉(例如在中心点击阴影或失去焦点时),所以我实施了closeAll()
方法。
初稿:
public void closeAll() {
closeTop();
closeLeft();
closeBottom();
closeRight();
}
经过一些测试后,我发现如果2个增量被双向绑定,在两者上调用closeXxx()
,会使值略微波动,直到最终达到0,而不是平滑减少,为2 {{1}竞争设定他们的价值观。
虽然用户可能没有注意到这一点,但程序会根据确切的值设置一些CSS伪类,因此可能会给出错误的结果。
我的解决方案:
第二稿和最终稿:
Timelines
仍然有更清洁的方法吗?
答案 0 :(得分:1)
总结评论:
没有API(据我所知)确定属性是否双向绑定到另一个属性。
我可能会建议不要公开这些属性,或者至少不要以你所描述的方式以可写方式公开它们。您可以考虑公开描述所有四个值的ObjectProperty<Insets>
,并使用它注册侦听器,使其保持(双向)与您(现在的私有)属性的实际值同步。你还可以曝光
public void bindBidirectional(Side side1, Side side2) ;
和
public void unbindBidirectional(Side side1, Side side2) ;
提供方便的方法将两侧的插图锁定在一起的方法。
通过这种方式,您可以完全控制值的绑定方式等。
另外需要注意的是,您要描述创建四个单独的时间轴来动画&#34;滑动&#34;。它可以更好地创建单个时间轴并更改添加到时间轴的关键帧。
即。你可以这样做:
// convenience map for looking up appropriate property
private Map<Side, DoubleProperty> propLookup ;
public void close(Side... sides) {
Timeline t = new Timeline() ;
Duration d = Duration.seconds(0.5);
for (Side s : sides) {
t.getKeyFrames().add(new KeyFrame(d, new KeyValue(propLookup.get(s), 0));
}
t.play();
}
public void closeAll() {
close(Side.TOP, Side.RIGHT, Side.BOTTOM, Side.LEFT);
}
这是一个相关的演示。您可以拖动两个矩形,以及一个双向绑定/取消绑定其y坐标的切换按钮。
将蓝色矩形向下拖动到屏幕底部,然后绑定它们,然后将它们发送回顶部。
当前实现使用单个时间轴,您将看到y坐标的行为与您希望的一样(即以基本上统一的方式单调变化)。如果您注释掉sendToTop(rect1, rect2)
并取消注释它上面的行,那么您一起创建两个时间轴,您将看到您描述的行为类型,其中y坐标会发生变化,但会被绑定略微修改。
演示代码:
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ToggleButton;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;
public class AnimatingBidirectionallyBoundProperties extends Application {
@Override
public void start(Stage primaryStage) {
Rectangle rect1 = createDraggableRectangle();
rect1.setX(50); rect1.setY(50);
rect1.setFill(Color.CORNFLOWERBLUE);
Rectangle rect2 = createDraggableRectangle();
rect2.setX(350); rect2.setY(50);
rect2.setFill(Color.CORAL);
rect2.yProperty().addListener((obs, oldY, newY) -> System.out.println(oldY + " -> " + newY));
Pane pane = new Pane(rect1, rect2);
ToggleButton bind = new ToggleButton("Bind");
bind.selectedProperty().addListener((obs, wasBound, isNowBound) -> {
if (isNowBound) {
rect2.yProperty().bindBidirectional(rect1.yProperty());
} else {
rect2.yProperty().unbindBidirectional(rect1.yProperty());
}
});
Button sendBack = new Button("Send back to top");
sendBack.setOnAction(e -> {
// sendToTop(rect1);
// sendToTop(rect2);
sendToTop(rect1, rect2);
});
HBox buttons = new HBox(5, bind, sendBack);
buttons.setAlignment(Pos.CENTER);
buttons.setPadding(new Insets(5));
BorderPane root = new BorderPane(pane, null, null, buttons, null);
Scene scene = new Scene(root, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
private void sendToTop(Rectangle... rects) {
Timeline t = new Timeline();
for (Rectangle r : rects) {
t.getKeyFrames().add(new KeyFrame(Duration.seconds(0.5), new KeyValue(r.yProperty(), 50)));
}
t.play();
}
private Rectangle createDraggableRectangle() {
Rectangle rect = new Rectangle(100,50);
ObjectProperty<Point2D> mouseAnchor = new SimpleObjectProperty<>();
rect.setOnMousePressed(e -> mouseAnchor.set(new Point2D(e.getSceneX(), e.getSceneY())));
rect.setOnMouseDragged(e -> {
double deltaX = e.getSceneX() - mouseAnchor.get().getX();
double deltaY = e.getSceneY() - mouseAnchor.get().getY();
rect.setX(rect.getX() + deltaX);
rect.setY(rect.getY() + deltaY);
mouseAnchor.set(new Point2D(e.getSceneX(), e.getSceneY()));
});
return rect ;
}
public static void main(String[] args) {
launch(args);
}
}
单个时间轴的输出:
50.0 -> 456.0
456.0 -> 455.86466666666666
455.86466666666666 -> 446.256
446.256 -> 432.58733333333333
432.58733333333333 -> 419.866
419.866 -> 405.656
405.656 -> 391.9873333333333
391.9873333333333 -> 378.86
378.86 -> 365.462
365.462 -> 352.19933333333336
352.19933333333336 -> 338.12466666666666
338.12466666666666 -> 324.86199999999997
324.86199999999997 -> 310.92266666666666
310.92266666666666 -> 297.65999999999997
297.65999999999997 -> 283.856
283.856 -> 270.86400000000003
270.86400000000003 -> 256.92466666666667
256.92466666666667 -> 243.39133333333336
243.39133333333336 -> 229.72266666666667
229.72266666666667 -> 216.73066666666665
216.73066666666665 -> 202.656
202.656 -> 188.98733333333337
188.98733333333337 -> 175.72466666666668
175.72466666666668 -> 162.32666666666665
162.32666666666665 -> 148.7933333333333
148.7933333333333 -> 134.98933333333332
134.98933333333332 -> 121.32066666666668
121.32066666666668 -> 108.32866666666666
108.32866666666666 -> 94.65999999999997
94.65999999999997 -> 80.72066666666666
80.72066666666666 -> 67.45800000000003
67.45800000000003 -> 53.51866666666666
53.51866666666666 -> 50.0
有两个时间轴的输出:
50.0 -> 444.0
444.0 -> 443.7373333333333
443.7373333333333 -> 443.8686666666667
443.8686666666667 -> 434.28133333333335
434.28133333333335 -> 434.41266666666667
434.41266666666667 -> 420.36
420.36 -> 420.49133333333333
420.49133333333333 -> 407.358
407.358 -> 407.4893333333333
407.4893333333333 -> 394.0933333333333
394.0933333333333 -> 394.2246666666667
394.2246666666667 -> 381.09133333333335
381.09133333333335 -> 381.22266666666667
381.22266666666667 -> 367.95799999999997
367.95799999999997 -> 368.08933333333334
368.08933333333334 -> 354.562
354.562 -> 354.6933333333333
354.6933333333333 -> 341.56
341.56 -> 341.6913333333333
341.6913333333333 -> 328.558
328.558 -> 328.6893333333333
328.6893333333333 -> 315.42466666666667
315.42466666666667 -> 315.556
315.556 -> 302.29133333333334
302.29133333333334 -> 302.42266666666666
302.42266666666666 -> 289.28933333333333
289.28933333333333 -> 289.42066666666665
289.42066666666665 -> 275.8933333333333
275.8933333333333 -> 276.0246666666667
276.0246666666667 -> 263.28533333333337
263.28533333333337 -> 263.4166666666667
263.4166666666667 -> 249.88933333333335
249.88933333333335 -> 250.02066666666667
250.02066666666667 -> 236.88733333333334
236.88733333333334 -> 237.01866666666666
237.01866666666666 -> 224.01666666666665
224.01666666666665 -> 224.14799999999997
224.14799999999997 -> 210.358
210.358 -> 210.48933333333332
210.48933333333332 -> 197.22466666666668
197.22466666666668 -> 197.356
197.356 -> 184.09133333333335
184.09133333333335 -> 184.22266666666667
184.22266666666667 -> 170.95800000000003
170.95800000000003 -> 171.08933333333334
171.08933333333334 -> 157.82466666666664
157.82466666666664 -> 157.95600000000002
157.95600000000002 -> 145.08533333333332
145.08533333333332 -> 145.2166666666667
145.2166666666667 -> 131.558
131.558 -> 131.68933333333337
131.68933333333337 -> 118.68733333333336
118.68733333333336 -> 118.81866666666667
118.81866666666667 -> 104.89733333333334
104.89733333333334 -> 105.02866666666671
105.02866666666671 -> 92.15800000000002
92.15800000000002 -> 92.28933333333333
92.28933333333333 -> 78.762
78.762 -> 78.89333333333332
78.89333333333332 -> 66.154
66.154 -> 66.28533333333331
66.28533333333331 -> 52.75799999999998
52.75799999999998 -> 52.8893333333333
52.8893333333333 -> 50.0