Layout panes

TL;DR

Em JavaFX, pane = layout: cada classe de pane carrega embutida sua própria política de posicionamento dos filhos. Não há manager plugável — escolhe-se o pane correto e ele cuida do resto. Esse é o contraste central com o Swing, onde Container e LayoutManager são objetos separados (ver Layout managers (Swing)). Dominar dois pilares — qual pane usar e como expressar growth via constraints estáticos — resolve 90% dos problemas de layout.

O que é

A hierarquia de contenção relevante para layout é:

Node  (abstrato — todo elemento visual)
 └── Parent  (abstrato — pode ter filhos)
      └── Region  (tem filhos + calcula layout + CSS de fundo/borda)
           └── Pane  (expõe getChildren() como lista pública)
                ├── HBox
                ├── VBox
                ├── BorderPane
                ├── GridPane
                ├── StackPane
                ├── FlowPane
                ├── TilePane
                └── AnchorPane

Region é o ponto onde o layout entra em cena: ela conhece seu tamanho, calcula prefWidth/prefHeight dos filhos e os posiciona. Pane simplesmente torna getChildren() público para que subclasses e código cliente possam adicionar nós diretamente. Cada subclasse concreta implementa o algoritmo de posicionamento específico à sua política.

Group — mencionado na nota anterior 02 - Scene graph — stage, scene e nodesnão é um pane de layout: ele não calcula posições, é apenas agrupamento lógico.

Por que importa

Layout quebrado em resize é o bug visual nº 1 de interfaces JavaFX. Quando um TextField não estica ao redimensionar a janela, quando colunas de formulário não alinham, ou quando um painel de sobreposição não centraliza — quase sempre a causa é escolha errada de pane ou ausência de constraint de growth.

Em entrevista de migração Swing → JavaFX, a comparação entre os modelos de layout é pergunta frequente. A resposta esperada articula a diferença estrutural (pane vs. container + manager) e demonstra conhecimento prático de pelo menos GridPane, BorderPane e HBox/VBox.

Como funciona

Os lineares — VBox e HBox

VBox empilha filhos em coluna; HBox enfileira em linha. Ambos compartilham os mesmos parâmetros estruturais:

PropriedadeO que controla
spacingespaço entre filhos consecutivos (px)
paddingespaço interno ao redor de todos os filhos
alignmentalinhamento do bloco de filhos dentro do pane
fillWidth / fillHeightse os filhos devem preencher a largura/altura perpendicular
VBox coluna = new VBox(8);              // spacing = 8px
coluna.setPadding(new Insets(12));
coluna.setAlignment(Pos.TOP_LEFT);
 
HBox linha = new HBox(4, btn1, btn2);  // spacing + filhos no construtor
linha.setAlignment(Pos.CENTER_RIGHT);

BorderPane e StackPane

BorderPane divide o espaço em cinco regiões fixas: top, bottom, left, right e center. O center recebe todo o espaço restante — ideal para estrutura de janela de aplicação (toolbar no topo, tabela/editor no centro, barra de status no rodapé).

BorderPane frame = new BorderPane();
frame.setTop(toolbar);
frame.setCenter(tableView);
frame.setBottom(statusBar);

Cada região aceita um único nó (que pode ser, ele mesmo, um pane com múltiplos filhos).

StackPane empilha todos os filhos de trás para frente, centralizando-os por padrão. Casos de uso: sobrepor um indicador de loading sobre um formulário, badge sobre ícone, watermark sobre conteúdo.

StackPane overlay = new StackPane(conteudo, loadingIndicator);
// loadingIndicator aparece na frente, centralizado

GridPane — células, span e ColumnConstraints

GridPane organiza filhos em grade com linhas e colunas independentes. O posicionamento usa o método add(node, colIndex, rowIndex) — e as variantes com colspan/rowspan:

GridPane grid = new GridPane();
grid.setHgap(8);    // espaço horizontal entre colunas
grid.setVgap(6);    // espaço vertical entre linhas
 
grid.add(labelNome,   0, 0);          // col 0, row 0
grid.add(fieldNome,   1, 0);          // col 1, row 0
grid.add(labelEmail,  0, 1);
grid.add(fieldEmail,  1, 1);
// botão ocupa 2 colunas: add(node, col, row, colspan, rowspan)
grid.add(btnSalvar,   0, 2, 2, 1);

ColumnConstraints controla como cada coluna se comporta no resize:

ColumnConstraints col0 = new ColumnConstraints();
col0.setPercentWidth(30);             // label: 30% da largura
 
ColumnConstraints col1 = new ColumnConstraints();
col1.setPercentWidth(70);             // campo: 70%
col1.setHgrow(Priority.ALWAYS);      // estica com a janela
 
grid.getColumnConstraints().addAll(col0, col1);

RowConstraints funciona de forma análoga para controle de altura por linha.

FlowPane, TilePane e AnchorPane

FlowPane distribui filhos em fluxo que quebra linha (ou coluna) ao atingir o limite do container — comportamento próximo ao FlowLayout do Swing, mas com controle de orientação, hgap e vgap. Útil para conjuntos de tags, botões de filtro, galerias de thumbnails onde a quantidade de itens varia.

TilePane também quebra em grade, mas força todas as células ao mesmo tamanho (o maior item define o tile). Indicado para galerias com itens uniformes — ícones de aplicativos, avatares.

AnchorPane ancora as bordas de cada filho a offsets fixos das bordas do pane:

AnchorPane.setTopAnchor(btn, 8.0);
AnchorPane.setRightAnchor(btn, 8.0);

Parece conveniente, mas resulta em layout absoluto disfarçado — ver Armadilhas.

Growth e constraints estáticos

Constraints em JavaFX são métodos estáticos no pane que os honra — não propriedades do nó filho. Isso permite que o mesmo nó se comporte diferente conforme o container onde é inserido.

// Dentro de um HBox: campo de busca estica horizontalmente
TextField busca = new TextField();
HBox.setHgrow(busca, Priority.ALWAYS);
busca.setMaxWidth(Double.MAX_VALUE);   // sem isso, o campo não estica mesmo com ALWAYS
 
// Margem externa ao redor de um botão
HBox.setMargin(btnBuscar, new Insets(0, 0, 0, 4));
 
// Dentro de um VBox: área de conteúdo estica verticalmente
VBox.setVgrow(scrollPane, Priority.ALWAYS);
scrollPane.setMaxHeight(Double.MAX_VALUE);

Priority tem três valores: ALWAYS (estica sempre que há espaço sobrando), SOMETIMES (estica apenas se não houver nó com ALWAYS), NEVER (tamanho preferido fixo).

Pane = layout vs. container + manager

No Swing, layout é bipartido: Container guarda os filhos; LayoutManager (plugável, trocável em runtime) calcula posições. O mesmo JPanel pode usar FlowLayout, BorderLayout ou GridBagLayout conforme o manager atribuído.

Em JavaFX, a política de layout é parte da classe do pane — não há manager separado. Trocar a política significa trocar o pane. Isso simplifica o modelo mental e elimina a indireção do manager, mas exige escolher o pane correto desde o início.

Detalhes do modelo Swing estão em Layout managers (Swing) — esta nota não os re-explica.

Na prática

Formulário com GridPane

GridPane form = new GridPane();
form.setHgap(8);
form.setVgap(6);
form.setPadding(new Insets(16));
 
// Coluna de labels (30%) e coluna de campos (70%)
ColumnConstraints c0 = new ColumnConstraints();
c0.setPercentWidth(30);
ColumnConstraints c1 = new ColumnConstraints();
c1.setPercentWidth(70);
c1.setHgrow(Priority.ALWAYS);
form.getColumnConstraints().addAll(c0, c1);
 
TextField fieldCustomer = new TextField();
fieldCustomer.setMaxWidth(Double.MAX_VALUE);   // permite esticar
 
TextField fieldOrder = new TextField();
fieldOrder.setMaxWidth(Double.MAX_VALUE);
 
form.add(new Label("Customer:"), 0, 0);
form.add(fieldCustomer,          1, 0);
form.add(new Label("Order #:"),  0, 1);
form.add(fieldOrder,             1, 1);
 
Button btnSave = new Button("Save");
form.add(btnSave, 0, 2, 2, 1);   // span 2 colunas
GridPane.setHalignment(btnSave, HPos.RIGHT);

Janela de aplicação com BorderPane

ToolBar toolbar = new ToolBar(new Button("New"), new Button("Open"));
TableView<Order> table = new TableView<>();
Label status = new Label("Ready");
 
BorderPane root = new BorderPane();
root.setTop(toolbar);
root.setCenter(table);
root.setBottom(status);
BorderPane.setMargin(status, new Insets(4, 8, 4, 8));

TextField que estica em HBox

TextField search = new TextField();
search.setPromptText("Filter orders…");
search.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(search, Priority.ALWAYS);
 
Button btnGo = new Button("Search");
HBox bar = new HBox(8, search, btnGo);
bar.setPadding(new Insets(8));
bar.setAlignment(Pos.CENTER_LEFT);

Armadilhas

(1) AnchorPane para tudo — layout absoluto disfarçado

O problema: anchorar todos os componentes parece dar controle preciso, mas produz layout absoluto: ao redimensionar a janela ou mudar o tamanho de fonte do sistema, os componentes não se redistribuem — aparecem sobrepostos ou cortados. AnchorPane não redistribui o espaço disponível entre os filhos.

// PROBLEMA: botões se sobrepõem se a janela encolher
AnchorPane.setLeftAnchor(btn1, 10.0);
AnchorPane.setLeftAnchor(btn2, 10.0);   // mesmo âncora — btn2 fica sobre btn1
AnchorPane.setTopAnchor(btn1, 10.0);
AnchorPane.setTopAnchor(btn2, 10.0);

Fix: usar um pane com política de distribuição (HBox, VBox, GridPane) como container dos filhos. Reservar AnchorPane para o caso específico e legítimo de “fixar um nó à borda do container” — por exemplo, um botão flutuante sempre no canto inferior direito.


(2) Esquecer setMaxWidth(Double.MAX_VALUE) com setHgrow(Priority.ALWAYS)

O problema: o constraint HBox.setHgrow(node, Priority.ALWAYS) instrui o HBox a oferecer espaço extra ao nó, mas o nó só o aceita se seu maxWidth permitir. O maxWidth padrão de TextField, Button e outros controls é USE_PREF_SIZE — o campo continua com seu tamanho preferido mesmo que haja espaço sobrando.

// PROBLEMA: busca não estica — maxWidth está travado em USE_PREF_SIZE
TextField busca = new TextField();
HBox.setHgrow(busca, Priority.ALWAYS);   // sem efeito visível

Fix: setar setMaxWidth(Double.MAX_VALUE) (ou setMaxHeight para crescimento vertical) explicitamente após o constraint:

busca.setMaxWidth(Double.MAX_VALUE);
HBox.setHgrow(busca, Priority.ALWAYS);   // agora funciona

(3) Aninhamento profundo de panes para simular grade

O problema: para criar um formulário de 2 colunas, é tentador aninhar vários HBox dentro de um VBox. Além do custo extra no layout pass (cada container faz sua própria passagem de medição), o FXML resultante fica ilegível e as colunas não alinham — cada HBox calcula a largura da coluna de labels independentemente.

// PROBLEMA: labels da coluna esquerda têm larguras diferentes por linha
VBox form = new VBox(6,
    new HBox(8, new Label("Customer:"), new TextField()),
    new HBox(8, new Label("Order number:"), new TextField())
);

Fix: usar um único GridPane. Ele resolve grades completas sem aninhamento, alinha colunas automaticamente e aceita ColumnConstraints por coluna:

// CORRETO: coluna 0 sempre com a mesma largura em todas as linhas
GridPane form = new GridPane();
form.setHgap(8);
form.add(new Label("Customer:"),     0, 0);
form.add(new TextField(),            1, 0);
form.add(new Label("Order number:"), 0, 1);
form.add(new TextField(),            1, 1);

Em entrevista

Frase pronta (inglês)

“In JavaFX, the layout policy is baked into the pane class itself — you pick VBox for vertical stacking, GridPane for a form grid, BorderPane for the application frame. There’s no pluggable LayoutManager like in Swing: swapping the policy means swapping the pane. The most common layout bug I see is a field that doesn’t stretch on resize — it almost always means the developer set HBox.setHgrow with Priority.ALWAYS but forgot to also call setMaxWidth(Double.MAX_VALUE), because the control’s default maxWidth is USE_PREF_SIZE and won’t accept extra space. For complex forms, GridPane with ColumnConstraints and percentWidth is the right tool — nesting HBoxes inside a VBox produces misaligned columns and extra layout passes.”

Vocabulário

Termo PTTermo EN
pane de layoutlayout pane
constraint de crescimentogrowth constraint
esticar / crescergrow / stretch
prioridade de crescimentogrow priority
largura percentualpercent width
restrição de colunacolumn constraint
span de colunacolumn span
espaçamento internopadding
espaçamento entre filhosspacing / gap
ancorar bordaanchor edge

Veja também

Referências