我正在尝试使用JavaFX 8创建一组自定义控件。我对做某些事情的正确方法有些困惑,例如布局我定义的用于构建控件的子对象。
我用来覆盖layoutChildren()方法,在其中重新定位子元素并调整子元素的大小;但是阅读layoutChildren()的javadoc时,会这样写:

在布局传递期间调用,以布局此父级中的子级。默认情况下,它将仅将托管的,可调整大小的内容的大小设置为其首选大小,并且不进行任何节点定位。

因此,根据文档,我不能对子代执行任何重定位(“节点定位”)。

我想了解的是在我的自定义控件中定位和调整子级大小的正确方法。

我不明白的另一件事是何时以及多少次调用layoutChildren()。文档说“在布局过程中调用”,但我不知道何时执行“布局过程”。

我希望你能帮助我。

编辑@James_D

这是我在评论中说的一个例子

public class MyControl extends TextField {
    private Label label;

    public MyControl() {
        super();
        setSkin(new TextFieldSkin(this));

        label=new Label("This is my custom textfield");
        getChildren().add(label);
    }

    @Override
    protected void layoutChildren() {
        super.layoutChildren();
        label.relocate(0, -label.getHeight());

        System.out.println("I'm laying out children");
    }
}

如果运行它,您会注意到layoutChildren()每帧都会调用一次

最佳答案

您误解了Javadocs you quoted,它描述了Parent.layoutChildren()的功能。并不是说子类不能定位节点;实际上,下一个句子是

子类应重写此函数以根据需要布局内容。

因此,这正是您应该覆盖以布局子节点的方法。

我不了解何时执行“版式传递”。

package documentation for javafx.scene.layout :

一旦应用程序创建并显示Scene,场景图布局机制就会由系统自动驱动。场景图检测到影响布局的动态节点更改(例如大小或内容的更改),并调用requestLayout(),该标记将分支标记为需要布局,以便在下一个脉冲上,通过以下方式对该分支执行自上而下的布局遍历:在该分支的根上调用layout()。在该布局过程中,将在每个父级上调用layoutChildren()回调方法以布局其子级。通过确保在一次通过中合并并处理多个布局请求,而不是对每一分钟的更改执行重新布局,该机制旨在最大化布局效率。因此,应用程序不应直接在节点上调用布局。

因此,如果任何子节点的大小或内容发生更改,父节点将“自动”(*)将其自身标记为需要布局。在每个渲染脉冲上,如果父母需要布局,则将调用其layoutChildren()方法。这意味着您要做的就是实现layoutChildren()方法,并将在需要时为您调用该方法。

(*)尽管我实际上没有看过源代码,但我对它的工作方式的理解是父级绑定到其子节点的布局范围:如果任何子节点的范围无效,那么它将重新计算其下一个渲染脉冲的布局。反过来,如果内容更改,则节点将使其自身的布局范围无效(例如,如果文本更改,则标签将使其布局范围无效等)。换句话说,JavaFX observable properties and bindings驱动布局机制。

因此(TL; DR):layoutChildren()(或Parent,甚至Region,取决于您所需的功能)的子类的Pane方法正是调整子节点的大小和位置的正确位置。每当(且仅当)父级需要重新计算其布局时,才会在每次渲染场景时调用该方法。

08-05 20:20