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 件のコメント:
コメントを投稿