O documento apresenta uma aplicação JavaFX para criar círculos que desaparecem ao clicar, construída usando Java e Scala:
1) A aplicação JavaFX é implementada em Java usando a API JavaFX e animações definidas por tempo;
2) Em Scala, a aplicação é definida de forma declarativa usando a biblioteca ScalaFX, com propriedades definidas inline e animações como expressões;
3) ScalaFX fornece uma sintaxe mais concisa para definir interfaces gráficas, animações, eventos e vinculação de propried
1. JavaFX e Scala – Como Leite com Bolacha
Stephen Chin Rafael Afonso
Independente, Magna
JavaFX Evangelist, Oracle Sistemas
steveonjava@gmail.com rafael.afonso@gmail.com
tweet: @rucafonso
tweet: @steveonjava
2. Conheça os Apresentadores
Stephen Chin Rafael Afonso
Programador
Homem de Java desde 2001
Família
Motorciclista
Interessado em
Scala desde 2008
3. Plataforma JavaFX 2.0
Experiências de Aplicações imersivas
> Animações, Videos e Gráficos
Cross-platform
> Integra Java, JavaScript e
HTML5 na mesma aplicação
> Nova pilha gráfica toma
vantagem da aceleração de
hardware para aplicações 2D e
3D
> Use sua IDE favorita: NetBeans,
Eclipse, IntelliJ, etc.
6. JavaFX em Java
> A API do JavaFX usa uma melhora do
padrão JavaBeans
> Similar a outros UI toolkits (Swing, Pivot,
etc.)
> Usa o Design Pattern Builder para
minimizar a parte monótona.
8. Esqueleto da Aplicação
public class VanishingCircles extends Application {
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) {
primaryStage.setTitle("Vanishing Circles");
Group root = new Group();
Scene scene = new Scene(root, 800, 600, Color.BLACK);
[cria os círculos…]
root.getChildren().addAll(circles);
primaryStage.setScene(scene);
primaryStage.show();
[inicia a animação…]
}
}
9. Criação dos Círculos
List<Circle> circles = new ArrayList<Circle>();
for (int i = 0; i < 50; i++) {
final Circle circle = new Circle(150);
circle.setCenterX(Math.random() * 800);
circle.setCenterY(Math.random() * 600);
circle.setFill(new Color(Math.random(), Math.random(),
Math.random(), .2));
circle.setEffect(new BoxBlur(10, 10, 3));
circle.setStroke(Color.WHITE);
[configura os bindings…]
[configura os event listeners…]
circles.add(circle);
}
9
14. Java vs. Scala DSL
public class VanishingCircles extends Application { object VanishingCircles extends JFXApp {
var circles: Seq[Circle] = null
public static void main(String[] args) { stage = new Stage {
Application.launch(args); title = "Vanishing Circles"
} width = 800
height = 600
@Override scene = new Scene {
public void start(Stage primaryStage) { fill = BLACK
primaryStage.setTitle("Vanishing Circles"); circles = for (i <- 0 until 50) yield new Circle {
Group root = new Group(); centerX = random * 800
Scene scene = new Scene(root, 800, 600, Color.BLACK); centerY = random * 600
List<Circle> circles = new ArrayList<Circle>(); radius = 150
for (int i = 0; i < 50; i++) { fill = color(random, random, random, .2)
final Circle circle = new Circle(150); effect = new BoxBlur(10, 10, 3)
40 Linhas
circle.setCenterX(Math.random() * 800);
circle.setCenterY(Math.random() * 600);
circle.setFill(new Color(Math.random(), Math.random(), Math.random(), .2));
circle.setEffect(new BoxBlur(10, 10, 3));
33 Linhas
strokeWidth <== when (hover) then 4 otherwise 0
stroke = WHITE
onMouseClicked = {
Timeline(at (3 s) {radius -> 0}).play()
1299 Caracteres
circle.addEventHandler(MouseEvent.MOUSE_CLICKED, new
EventHandler<MouseEvent>() {
public void handle(MouseEvent t) {
KeyValue collapse = new KeyValue(circle.radiusProperty(), 0); }
}
}
591 Caracteres
content = circles
new Timeline(new KeyFrame(Duration.seconds(3), collapse)).play(); }
}
}); new Timeline {
circle.setStroke(Color.WHITE); cycleCount = INDEFINITE
circle.strokeWidthProperty().bind(Bindings.when(circle.hoverProperty()) autoReverse = true
.then(4) keyFrames = for (circle <- circles) yield at (40 s) {
.otherwise(0)); Set(
circles.add(circle); circle.centerX -> random * stage.width,
} circle.centerY -> random * stage.height
root.getChildren().addAll(circles); )
primaryStage.setScene(scene); }
primaryStage.show(); }.play();
}
Timeline moveCircles = new Timeline();
for (Circle circle : circles) {
KeyValue moveX = new KeyValue(circle.centerXProperty(), Math.random() *
800);
KeyValue moveY = new KeyValue(circle.centerYProperty(), Math.random() *
600);
moveCircles.getKeyFrames().add(new KeyFrame(Duration.seconds(40), moveX,
moveY));
}
moveCircles.play();
}
}
14
15. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
height = 600
scene = new Scene {
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
15
16. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
Classe base para
height = 600
scene = new Scene { ScalaFX
aplicações
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
16
17. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
height = 600 Definição Declarativa
scene = new Scene {
fill = BLACK
do Stage
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
17
18. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800
Definições de
height = 600 propriedades inline
scene = new Scene {
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
18
19. object VanishingCircles extends JFXApp {
stage = new Stage {
title = "Disappearing Circles"
width = 800 Criação de Sequência
height = 600 Via Loop
scene = new Scene {
fill = BLACK
content = for (i <- 0 until 50) yield new Circle {
centerX = random * 800
centerY = random * 600
radius = 150
fill = color(random, random, random, 0.2)
effect = new BoxBlur(10, 10, 3)
}
}
}
}
19
20. Binding em Scala
Adição/Subtração/Multiplicação/Divisão Infixas:
height <== rect1.height + rect2.height
Operadores de Agregação:
width <== max(rect1.width, rect2.width,
rect3.width)
Expressões Condicionais:
strokeWidth <== when (hover) then 4 otherwise 0
Expressões Compostas:
text <== when (rect.hover || circle.hover &&
!disabled) then textField.text + " is enabled"
otherwise "disabled"
20
21. Animação em Scala
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield at
(40 s) {
Set(
circle.centerX -> random * stage.width.get,
circle.centerY -> random * stage.height.get
)
}
}
timeline.play();
21
22. Sintaxe de animação como no
Animação em Scala JavaFX Script:
at (duração) {keyframes}
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield at (40 s) {
Set(
circle.centerX -> random * stage.width.get,
circle.centerY -> random * stage.height.get
)
}
}
timeline.play();
22
23. Animação em Scala
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield at (40 s) {
Set(
circle.centerX -> random * stage.width.get,
circle.centerY -> random * stage.height.get
)
}
}
timeline.play();
Sobrecarga de Operador
para sintaxe de animação
23
24. Animação em Scala
val timeline = new Timeline {
cycleCount = INDEFINITE
autoReverse = true
keyFrames = for (circle <- circles) yield at (40 s) {
Set(
circle.centerX -> random * stage.width tween EASE_BOTH,
circle.centerY -> random * stage.height tween EASE_IN
)
}
}
timeline.play();
Sintaxe tween
opcional
24
25. Event Listeners em Scala
> Suporte a sintaxe de Closure
> Argumentos para objetos eventos
> 100% type-safe
onMouseClicked = { (e: MouseEvent) =>
Timeline(at(3 s){radius->0}).play()
}
25
26. Event Listeners em Scala
Parâmetro evento
> Suporte a sintaxe de Closure opcional:
{(event) => body}
> Argumentos para objetos eventos
> 100% type-safe
onMouseClicked = { (e: MouseEvent) =>
Timeline(at(3 s){radius->0}).play()
}
26
27. Color Selector
Red Control
Green Control Pre-defined colors (from
Color JavaFX class)
Blue Control
Opacity (Alpha)
Control
Syncronizer
Values goes from 0 to 255 Enable/Disable Opacity Colors Formatation:
• Hexadecimal: #ADFF2F
• RGB: rgba(173, 255, 47, 0,61)
• Percent: rgba( 67%, 100%, 18%, 0,61)
• HSB: hsba( 84, 82%, 100%, 0,61)
27
28. Color Selector
> Versões anteriores feitas em Java Swing, JavaFX 1.3,
HTML4 e HTML5.
> Objetivo é alterar os parâmetros da cor do retângulo pelos
componentes Vermelho (R), Verde (G), Azul (B) e
Opacidade (A – Alpha), com valores variando de 0 a 255.
> É possível escolher uma cor pré-definida no objeto
scalafx.scene.paint.Color (que é um wrapper da
classe javafx.scene.paint.Color).
> Também é possível exibir o valor da cor tal como usado
no HTML e ainda escolher a formatação (Hexedecimal,
RGB, HSL).
28
29. SliderControl
Propriedade usada Wrapper de
Internamente
class SliderControl extends HBox { javafx.beans.property.DoubleProperty
val realValue = new DoubleProperty(new SimpleDoubleProperty)
def value = this.realValue
def value_=(d: Double) {
if (d < Min) {
value() = Min
} else if (d > Max) { getter e setter do valor (equivalente
às properties do C#).
value() = Max
} else {
value() = d
Equivalente a fazer realValue.set(d)
}
}
val sldValue = new Slider {
// ...
value <==> realValue
} Operador de
} Duplo Binding
29
30. Cor do Retângulo
val currentColor = new ObjectProperty[Color](Color.WHITE, "Color")
currentColor.onChange(rectangleRegion.setStyle("-fx-background-color: " +
RgbFormatter.format(currentColor(), !this.chbDisableAlpha.selected.get)))
private def changeColor {
val newAlphaValue =
if (controlAlpha.disabled.get()) 1.0
else (controlAlpha.value.toDouble / 255)
this.currentColor() = Color.rgb(controlRed.value.toInt,
controlGreen.value.toInt,
controlBlue.value.toInt,
Se fosse em Java, teríamos que escrever:
newAlphaValue) controlRed.valueProperty.addListener(
} new ChangeListener<DoubleProperty>() {
@Override public void changed(ObservableDouble d,
Double old,
val controlRed = new SliderControl("R") { Double new) {
value = 255 changeColor();
}
} }
controlRed.value.onChange(changeColor) });
O ScalaFX já faz isso por nós usando closures.
30
31. Sincronização de Valores
Buffer que reúne os
val synchronizedValue = new DoubleProperty(new SimpleDoubleProperty) controle sincronizados
val synchronizedControls = new ObservableBuffer[SliderControl]
synchronizedControls.onChange((buffer, changes) => synchronizedValues(buffer, changes))
private def controlSelected(control: SliderControl) { // Método chamado ao clicar no Checkbox de
sincronização
if (control.selectedControl.get) synchronizedControls.add(control) Versão ScalaFX do Super trait das
else synchronizedControls.remove(control) ObservableList do JavaFX listas em Scala
}
// Método chamado ao adicionar/remover um elemento
private def synchronizeValues(buffer: ObservableBuffer[SliderControl], changes: Seq[Change]) {
changes(0) match {
case Add(pos, added) => {
val media = buffer.map(_.value.get).sum / buffer.size
added.last.asInstanceOf[SliderControl].value <==> synchronizedValue Adiciona duplo binding
buffer.foreach(_.value = media)
}
case Remove(pos, removed) => {
removed.last.asInstanceOf[SliderControl].value unbind synchronizedValue Remove duplo binding
}
}
}
controlRed.selectedControl.onChange(controlSelected(controlRed))
31
35. Exibição da formatação das cores
private def formatColor {
this.txfColorValue.text() =
this.cmbColorFormat.value.get.format(this.currentColo
r.get, !this.chbDisableAlpha.selected.get)
}
val cmbColorFormat = new
ComboBox[Formatter](Formatter.formatters) {
promptText = "Color Format"
converter = StringConverter.toStringConverter((f:
Formatter) => f.description)
value = RgbFormatter
onAction = formatColor
}
35
36. Funcionamento do ScalaFX
Ou: Como escrever sua própria DSL em Scala.
Com citações de Stephen Colebourne (@jodastephen) para o
bem de nossa sanidade!
Aviso: Declarações extraídas de http://blog.joda.org e podem não refletir exatamente sua opnião ou ponto de vista.
36
37. Inicialização da Aplicação
> JavaFX requer que todo código de UI seja
executado na Thread da aplicação.
> Mas nossa Aplicação ScalaFX não possui método
start:
object VanishingCircles extends JFXApp {
stage = new Stage {
…
}
}
Como esse código funciona?!?
37
38. DelayedInit
> Introduzido no Scala 2.9
> Como usar:
1. Estender um trait especial chamado DelayedInit
2. Implementar um método do tipo:
def delayedInit(x: => Unit): Unit
3. Guardar a closure init e chamá-la no Thread da
Aplicação
Joda diz…
Para mim, Scala não inova o suficiente e adiciona demais – uma
combinação letal.
38
39. Hierarquia de Conversões implicitas
> ScalaFX define um conjunto de proxies que
espelham a hierarquia de JavaFX
> As classes JavaFX são “implicitamente”
acondicionadas (wrapped) quando se chama a
API do ScalaFX
> Mas a prioridade de implicit de Scala ignora a
hierarquia de tipos!
JFXNode SFXNode
JFXShape
? SFXShape
JFXCircle
! SFXCircle
39
40. Precedência de Implicits de N Níveis
> Scala dispara uma exceção se dois implicits têm
a mesma precedência.
> Classes que são estendidas têm uma
precedência menor
object HighPriorityIncludes extends LowerPriorityIncludes {…}
trait LowerPriorityIncludes {…}
> Você pode empilhar os traits com profundidade
de n níveis para reduzir a precisão para n.
Joda diz…
Bem, pode ser type safe, mas é também silencioso e bem mortal.
40
41. Propriedades
> JavaFX suporta propriedades do tipo Boolean,
Integer, Long, Float, Double, String, e Object
> Propriedades usam Genéricos para segurança de
tipos.
> Mas genéricos não suportam primitivos…
> JavaFX soluciona isso com 20 interfaces e 44
classes para todos os tipos de combinações de
tipos somente-leitura ou graváveis.
> Podemos melhorar?
41
42. @specialized
> Anotação especial que gera variantes primitivas
da classe.
> Melhora a performance ao evitar boxing/unboxing
trait ObservableValue[@specialized(Int, Long,
Float, Double, Boolean) T, J]
> Diminui a duplicação de código (ScalaFX tem
apenas 18 classes de Propriedades/Valores)
Joda diz…
Qualquer que seja o problema o sistema de tipos deve ser parte da solução.
42
43. Bindings
> Como Scala sabe a ordem de avaliação?
text <== when (rect.hover || circle.hover
&& !disabled) then textField.text + " is
enabled" otherwise " disabled"
E por que esse operador esquisito de
binding?!?
43
44. Regra de Precedência de Operadores
> Primeiro caractere determina a precedência
Menor Precedência
Exceção são os
10. (all letters)
operadores de atribuição
9. |
cuja prioridade é menor
8. ^ ainda…
7. &
6. < > 11. Operadoresde atribuição
5. = !
que terminam com igual
4. :
> Mas não começam com
igual
3. + *
> E não podem ser:
2. / %
<=
1. (todos os outros
>=
caracteres especiais)
!=
Maior Precedência 44
45. Precedência de Operadores
text <== when (rect.hover || circle.hover
11 10 9
&& !disabled) then textField.text + " is
7 5 10 3
enabled" otherwise "disabled"
10
Joda diz…
Pessoalmente, acho que o objetivo de sintaxe aberta e flexível (DSLs
arbitrárias) não compensam o esforço
45
46. Conclusão
> Você pode usar Scala e JavaFX juntos.
> ScalaFX fornece APIs mais simples, feita sob
medida para Scala.
> Experimente ScalaFX hoje e ajude a contribuir
com APIs para a futura versão 1.0!
http://code.google.com/p/scalafx/
47. Obrigado
Stephen Chin
steveonjava@gmail.com
tweet: @steveonjava
Desconto especial de 40% para o JustJava.
Entre em apress.com e digite o código PJVF73 47