2015-08-01 15 views
10

Recentemente ho voluto creare uno sfondo animato in JavaFX, simile all'esempio Swing visto here. Ho utilizzato uno Canvas su cui disegnare, come mostrato in Working with the Canvas API e uno AnimationTimer per il ciclo di disegno, come mostrato in Animation Basics. Sfortunatamente, non sono sicuro di come ridimensionare lo Canvas automaticamente quando viene ridimensionato lo Stage che si sta chiudendo. Qual è un buon approccio?Ridimensiona automaticamente la tela per riempire la Parent Parent allegata

image

risposta

9

Nell'esempio seguente, la classe innestata statica CanvasPane avvolge un'istanza di Canvas in un Pane e sovrascrive layoutChildren() per rendere le tela dimensioni corrispondono alla racchiude Pane. Si noti che Canvas restituisce da isResizable(), quindi "il genitore non può ridimensionarlo durante il layout" e Pane "non esegue il layout oltre il ridimensionamento dei bambini ridimensionabili alle dimensioni preferite." Il width e height utilizzati per costruire la tela diventano la sua dimensione iniziale. Un approccio simile viene utilizzato nella simulazione delle particelle Ensemble, Fireworks.java, per ridimensionare un'immagine di sfondo mantenendo il suo rapporto aspetto.

Come nota a parte, notare la differenza dall'utilizzo di colori completamente saturi rispetto all'originale. Questi numeri correlati examples illustrano i controlli di posizionamento in cima allo sfondo animato.

image

import java.util.LinkedList; 
import java.util.Queue; 
import java.util.Random; 
import javafx.animation.AnimationTimer; 
import javafx.application.Application; 
import javafx.beans.Observable; 
import javafx.scene.Scene; 
import javafx.scene.canvas.Canvas; 
import javafx.scene.canvas.GraphicsContext; 
import javafx.scene.control.CheckBox; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.layout.Pane; 
import javafx.scene.paint.Color; 
import javafx.stage.Stage; 

/** 
* @see https://stackoverflow.com/a/31761362/230513 
* @see https://stackoverflow.com/a/8616169/230513 
*/ 

public class Baubles extends Application { 

    private static final int MAX = 64; 
    private static final double WIDTH = 640; 
    private static final double HEIGHT = 480; 
    private static final Random RND = new Random(); 
    private final Queue<Bauble> queue = new LinkedList<>(); 
    private Canvas canvas; 

    @Override 
    public void start(Stage stage) { 
     CanvasPane canvasPane = new CanvasPane(WIDTH, HEIGHT); 
     canvas = canvasPane.getCanvas(); 
     BorderPane root = new BorderPane(canvasPane); 
     CheckBox cb = new CheckBox("Animate"); 
     cb.setSelected(true); 
     root.setBottom(cb); 
     Scene scene = new Scene(root); 
     stage.setScene(scene); 
     stage.show(); 

     for (int i = 0; i < MAX; i++) { 
      queue.add(randomBauble()); 
     } 
     AnimationTimer loop = new AnimationTimer() { 
      @Override 
      public void handle(long now) { 
       GraphicsContext g = canvas.getGraphicsContext2D(); 
       g.setFill(Color.BLACK); 
       g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); 
       for (Bauble b : queue) { 
        g.setFill(b.c); 
        g.fillOval(b.x, b.y, b.d, b.d); 
       } 
       queue.add(randomBauble()); 
       queue.remove(); 
      } 
     }; 
     loop.start(); 
     cb.selectedProperty().addListener((Observable o) -> { 
      if (cb.isSelected()) { 
       loop.start(); 
      } else { 
       loop.stop(); 
      } 
     }); 
    } 

    private static class Bauble { 

     private final double x, y, d; 
     private final Color c; 

     public Bauble(double x, double y, double r, Color c) { 
      this.x = x - r; 
      this.y = y - r; 
      this.d = 2 * r; 
      this.c = c; 
     } 
    } 

    private Bauble randomBauble() { 
     double x = RND.nextDouble() * canvas.getWidth(); 
     double y = RND.nextDouble() * canvas.getHeight(); 
     double r = RND.nextDouble() * MAX + MAX/2; 
     Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75); 
     return new Bauble(x, y, r, c); 
    } 

    private static class CanvasPane extends Pane { 

     private final Canvas canvas; 

     public CanvasPane(double width, double height) { 
      canvas = new Canvas(width, height); 
      getChildren().add(canvas); 
     } 

     public Canvas getCanvas() { 
      return canvas; 
     } 

     @Override 
     protected void layoutChildren() { 
      super.layoutChildren(); 
      final double x = snappedLeftInset(); 
      final double y = snappedTopInset(); 
      final double w = snapSize(getWidth()) - x - snappedRightInset(); 
      final double h = snapSize(getHeight()) - y - snappedBottomInset(); 
      canvas.setLayoutX(x); 
      canvas.setLayoutY(y); 
      canvas.setWidth(w); 
      canvas.setHeight(h); 
     } 
    } 

    public static void main(String[] args) { 
     launch(args); 
    } 
} 
+0

Nice demo .. + 1 .. – Reimeus

2

Non potresti farlo con un Binding come bene? Quanto segue sembra produrre gli stessi risultati senza dover aggiungere la classe derivata.

import java.util.LinkedList; 
import java.util.Queue; 
import java.util.Random; 
import javafx.animation.AnimationTimer; 
import javafx.application.Application; 
import javafx.beans.Observable; 
import javafx.beans.binding.DoubleBinding; 
import javafx.scene.Scene; 
import javafx.scene.canvas.Canvas; 
import javafx.scene.canvas.GraphicsContext; 
import javafx.scene.control.CheckBox; 
import javafx.scene.layout.BorderPane; 
import javafx.scene.paint.Color; 
import javafx.stage.Stage; 

/** 
* @see http://stackoverflow.com/a/31761362/230513 
* @see http://stackoverflow.com/a/8616169/230513 
*/ 

public class Baubles extends Application { 

private static final int MAX = 64; 
private static final double WIDTH = 640; 
private static final double HEIGHT = 480; 
private static final Random RND = new Random(); 
private final Queue<Bauble> queue = new LinkedList<>(); 
private Canvas canvas; 

@Override 
public void start(Stage stage) { 
    canvas = new Canvas(WIDTH, HEIGHT); 
    BorderPane root = new BorderPane(canvas); 
    CheckBox cb = new CheckBox("Animate"); 
    cb.setSelected(true); 
    root.setBottom(cb); 
    Scene scene = new Scene(root); 
    stage.setScene(scene); 
    stage.show(); 

    // Create bindings for resizing. 
    DoubleBinding heightBinding = root.heightProperty() 
      .subtract(root.bottomProperty().getValue().getBoundsInParent().getHeight()); 
    canvas.widthProperty().bind(root.widthProperty()); 
    canvas.heightProperty().bind(heightBinding); 

    for (int i = 0; i < MAX; i++) { 
     queue.add(randomBauble()); 
    } 
    AnimationTimer loop = new AnimationTimer() { 
     @Override 
     public void handle(long now) { 
      GraphicsContext g = canvas.getGraphicsContext2D(); 
      g.setFill(Color.BLACK); 
      g.fillRect(0, 0, canvas.getWidth(), canvas.getHeight()); 
      for (Bauble b : queue) { 
       g.setFill(b.c); 
       g.fillOval(b.x, b.y, b.d, b.d); 
      } 
      queue.add(randomBauble()); 
      queue.remove(); 
     } 
    }; 
    loop.start(); 
    cb.selectedProperty().addListener((Observable o) -> { 
     if (cb.isSelected()) { 
      loop.start(); 
     } else { 
      loop.stop(); 
     } 
    }); 
} 

private static class Bauble { 

    private final double x, y, d; 
    private final Color c; 

    public Bauble(double x, double y, double r, Color c) { 
     this.x = x - r; 
     this.y = y - r; 
     this.d = 2 * r; 
     this.c = c; 
    } 
} 

private Bauble randomBauble() { 
    double x = RND.nextDouble() * canvas.getWidth(); 
    double y = RND.nextDouble() * canvas.getHeight(); 
    double r = RND.nextDouble() * MAX + MAX/2; 
    Color c = Color.hsb(RND.nextDouble() * 360, 1, 1, 0.75); 
    return new Bauble(x, y, r, c); 
} 

public static void main(String[] args) { 
    launch(args); 
} 
} 
+0

Grazie per aver suggerito 'Binding'. Sono corretto dedurre che l'associazione dovrebbe cambiare per un diverso layout 'root'? Ho provato solo 'CanvasPane' in' BorderPane' e 'StackPane'. – trashgod

+0

I dettagli dovrebbero cambiare, ma qualcosa di simile dovrebbe funzionare. Inoltre, l'esempio non è perfettamente generale per un 'BorderPane' poiché alcuni dettagli dovrebbero essere modificati se si hanno nodi nelle posizioni superiore, sinistra o destra. – clartaq

+0

Ah, capisco; in uno 'StackPane', potrei' legare() '' canvas.heightProperty() 'direttamente a' root.heightProperty() '. Fino ad ora penso che 'CanvasPane' sia più flessibile, ma più uno per un'alternativa utile e un esempio concreto di aritmetica' Binding'. – trashgod