https://blogs.oracle.com/jfxprg/entry/dynamical_layouts_in_fx
JavaFXのレイアウトコードを引き継いでから、私は、動的レイアウト(Dynamical Layouts)に関連するバグ報告があり、多くの人々が実装に苦労されていることに気付きました。動的レイアウトとは、レイアウトPane(layout Pane)が子のサイズ変更や位置を決めるだけでなく、取得したサイズによっては子のリストを操作するものです。
標準Layout Paneにおける典型的な流れは以下の通りです。
- 親ペインがprefWidth/prefHeightを呼び出す。preferred sizeは現在の子とPaneプロパティや制約の組み合わせから計算される。
- 親が実際の幅や高さを割り当てる
- レイアウトを幅や高さ、手順1で使ったものと同じ子のセットで計算する
- preferred sizeは、固定もしくはPaneプロパティもしくは(子を作成するベースとなる)アイテムによって変化する
- 親は実際の幅や高さを割り当てる
- レイアウトを幅や高さから計算し、それに応じて子を作成する
private class GrowingPane extends Pane { private static final int PREF_NUMBER = 2; private static final int RECTANGLE_SIZE = 50; private static final int PADDING = 10; @Override protected double computePrefWidth(double height) { return PREF_NUMBER * RECTANGLE_SIZE + (2 + PREF_NUMBER - 1) * PADDING; } @Override protected double computePrefHeight(double width) { return RECTANGLE_SIZE + 2 * PADDING; } @Override protected void layoutChildren() { final double w = getWidth(); getChildren().clear(); // count the number of Rectangles that will fit final int num = (int) (w - PADDING) / (RECTANGLE_SIZE + PADDING); int curX = PADDING; // Do the layout for (int i = 0; i < num; ++i) { Rectangle rec = new Rectangle(RECTANGLE_SIZE, RECTANGLE_SIZE, Color.LIGHTGREEN); getChildren().add(rec); rec.relocate(curX, PADDING); curX += RECTANGLE_SIZE + PADDING; } } }
今度はもっと洗練されたバージョン、固定サイズではないボタンを持つ場合を見てみましょう。このサンプルのために、Buttonテキストを生成しています。後続の各ボタンのテキストは倍のサイズになっています。computePrefWidth/computePrefHeightの違いに注意してください。
private class GrowingPane2 extends Pane { private static final int PREF_NUMBER = 2; private static final String BASE_STRING = "A"; private double prefWidth = -1; private double prefHeight = -1; private static final int PADDING = 10; @Override protected double computePrefWidth(double height) { if (prefWidth == -1) { computePrefSize(); } return prefWidth; } private void computePrefSize() { String str = BASE_STRING; prefHeight = snapSpace(2 * PADDING + computeHeight()); prefWidth = PADDING; for (int i = 0; i < PREF_NUMBER; ++i) { prefWidth += computeWidthForString(str) + PADDING; str += str; } prefWidth = snapSpace(prefWidth); } private double computeHeight() { Button b = new Button(BASE_STRING); getChildren().add(b); b.applyCss(); double result = b.prefHeight(-1); getChildren().remove(b); return result; } private double computeWidthForString(String str) { Button b = new Button(str); getChildren().add(b); b.applyCss(); double result = b.prefWidth(-1); getChildren().remove(b); return result; } @Override protected double computePrefHeight(double width) { if (prefHeight == -1) { computePrefSize(); } return prefHeight; } @Override protected void layoutChildren() { final double w = getWidth(); getChildren().clear(); double widthLeft = w - PADDING; double curX = PADDING; String s = BASE_STRING; while(widthLeft >= PADDING) { double bw = computeWidthForString(s); if (bw + PADDING > widthLeft) { break; } Button b = new Button(s); getChildren().add(b); b.applyCss(); s += s; b.autosize(); b.relocate(curX, PADDING); curX += bw + PADDING; widthLeft -= bw + PADDING; } } }
現実には、ボタンやその他諸々をアイテムリストや他のモデルを基にして生成し、このモデルの変化によって計算されたサイズを無効にしたいということがあるかと思います。
計算を見てみると、preferred sizeを計算する前に、子の追加や削除、 applyCss() を呼び出していたことにお気付きかもしれません。これは、Layout Passの後にCSS Passを実行するため、Layout Passで子を変更するためには、すべての新しいノードに対し、明示的にCSS Passを実行する必要があります。CSSは、コンテキスト(シーングラフにおける位置)に依存しますので、まず最初に子を追加する必要があります。もちろん、絶対的に正確であるために、子を正しい位置に追加し、計算終了時にリストをクリアする必要があります。
0 件のコメント:
コメントを投稿