jueves, 29 de marzo de 2012

Algo no encaja

Una forma de interpretar la filosofía TDD (Desarrollo orientado a pruebas) es como una metodología que bien planteada lleva “la problemática del resultado” hasta los desarrolladores.

¿Qué es la problemática del resultado? La problemática del resultado es el requerimiento que se le hace a un responsable de proyecto de que el producto tiene que estar. Esta definición parece una afirmación obvia pero encierra uno de los grandes problemas del desarrollo de proyectos. Por su parte el responsable siente como su deber que el producto este listo en un plazo (es decir ve todo el proyecto) mientras que las personas que están a su cargo no lo entienden así ya que entienden que están desarrollando un producto pero solo se sienten responsables de la tarea que estén realizando.

Cuanto mas bajo este una persona en el escalafón del proyecto sus tareas son mas cortas y por lo tanto esa persona ve como su trabajo ya esta hecho cuando termina una tarea determinada (es decir ve solo la tarea que está haciendo en ese momento).

Volviendo con TDD, si la forma de marcar el fin de la tarea de un subordina fuese que “lo que ha desarrollado” cumpla unos determinados ejemplos y no el simple hecho de decirle:”¿has terminado? ” y el diga:”si” entonces no solo conviertes algo subjetivo en al cuantificable (lo subjetivo es el “si creo que esta terminado” y lo cuantificable es un test que se cumple o no se cumple y por lo tanto es cuantificable) es que además haces que el desarrollador entienda que es la problemática del resultado.

domingo, 25 de marzo de 2012

JavaFX 2.0. Vamos a crear una historia. Parte II

Ahora vamos con las implementaciones. Por ahora voy a incluir las implementaciones de actor y de la cabeza. No hay cuerpo pero ya se puede hablar y mover la cabeza.












- ActorImpl:


package es.historia.mueve.impl;

import es.historia.IActor;
import es.historia.ICabeza;
import es.historia.ICuerpo;
import javafx.scene.Group;

import javafx.scene.media.MediaPlayer;
import javafx.scene.media.Media;
import javafx.util.Duration;



/**
*
* @author Propietario
*/
public class ActorImpl implements IActor {
private ICabeza cabeza;
private ICuerpo cuerpo;
@Override
public Group dibujar() {
Group salida =new Group();
if (cabeza != null){
salida.getChildren().add(cabeza.dibujar());
cabeza.empezarMovimientoFijo();
}
if (cuerpo != null)
salida.getChildren().add(cuerpo.dibujar());
return salida;
}

@Override
public void mover() {
if (cabeza!= null)
cabeza.mover();
}

@Override
public void definir(ICabeza cabeza, ICuerpo cuerpo) {
this.cabeza = cabeza;
this.cuerpo= cuerpo;
}

@Override
public void hablar(String frase , final ICabeza cabeza) {
final MediaPlayer player = new MediaPlayer(new Media( frase));
player.play();
cabeza.empiezaHablar();
player.seek(Duration.ZERO);
player.setOnEndOfMedia(new Runnable() {

@Override
public void run() {
cabeza.terminaHablar();
}
});
}

@Override
public void establecerPosicion(int i, int i0) {
throw new UnsupportedOperationException("Not supported yet.");
}

Es esta clase podemos ver la funcionalidad de reproducir audio. Lo mas destacable del objeto MediaPlayer es que nos permite lanzar un hilo con una funcionalidad al "suceder" uno de los siguientes eventos:












- CabezaImpl:

package es.historia.parte.impl;

import es.historia.HistoriaExcepcion;
import es.historia.ICabeza;
import javafx.animation.Interpolator;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.scene.Group;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.transform.Affine;
import javafx.util.Duration;

/**
*
* @author Propietario
*/
public class CabezaImpl implements ICabeza{
private Timeline timelineTodo;
private Timeline timelineBoca;
private ImageView visorParteSuperior;
private ImageView visorParteInferior;

@Override
public void establecerCara(String caraInferior, String caraSuperior) throws HistoriaExcepcion {
Image imagen1 = new Image(caraSuperior);
if (imagen1 == null || imagen1.isError())
throw new HistoriaExcepcion("La imagen " + caraSuperior + " no es accesible");
Image imagen2 = new Image(caraInferior);
if (imagen2 == null || imagen2.isError())
throw new HistoriaExcepcion("La imagen " + caraInferior + " no es accesible");
visorParteSuperior= new ImageView (imagen1);
visorParteInferior= new ImageView (imagen2);
}

@Override
public Group dibujar() {
Group salida =new Group();
salida.getChildren().add(visorParteSuperior);
salida.getChildren().add(visorParteInferior);
return salida;
}

@Override
public void mover() {
}

@Override
public void establecerPosicion(int x, int y) {
visorParteInferior.setLayoutX(x);
visorParteInferior.setLayoutY(y + visorParteSuperior.getImage().getHeight());
visorParteSuperior.setLayoutX(x);
visorParteSuperior.setLayoutY(y);
}

@Override
public void empezarMovimientoFijo() {
timelineTodo = new Timeline();
timelineTodo.setCycleCount(Timeline.INDEFINITE);
timelineTodo.setAutoReverse(true);
final Affine reflectTransform = new Affine();
//
visorParteSuperior.getTransforms().add(reflectTransform);
visorParteInferior.getTransforms().add(reflectTransform);
timelineTodo.getKeyFrames().addAll(new KeyFrame(Duration.ZERO,
new KeyValue(reflectTransform.mxxProperty(), -1, Interpolator.DISCRETE),
new KeyValue(reflectTransform.txProperty(), visorParteSuperior.getBoundsInLocal().getWidth(),
Interpolator.DISCRETE)
),
new KeyFrame(new Duration(2000L),
new KeyValue(reflectTransform.mxxProperty(), 1, Interpolator.DISCRETE),
new KeyValue(reflectTransform.txProperty(), 0, Interpolator.DISCRETE)),
new KeyFrame(new Duration(4000L),
new KeyValue(reflectTransform.mxxProperty(), -1, Interpolator.DISCRETE),
new KeyValue(reflectTransform.txProperty(), visorParteSuperior.getBoundsInLocal().getWidth(),
Interpolator.DISCRETE)
));
timelineTodo.play();
}

@Override
public void empiezaHablar() {
timelineBoca = new Timeline();
timelineBoca.setCycleCount(Timeline.INDEFINITE);
timelineBoca.setAutoReverse(true);
timelineBoca.getKeyFrames().addAll(new KeyFrame(Duration.ZERO,
new KeyValue(visorParteInferior.rotateProperty(), 40, Interpolator.DISCRETE),
new KeyValue(visorParteInferior.translateYProperty(), 30, Interpolator.DISCRETE)),
new KeyFrame(new Duration(100L),
new KeyValue(visorParteInferior.rotateProperty(), 0, Interpolator.DISCRETE),
new KeyValue(visorParteInferior.translateYProperty(), 0, Interpolator.DISCRETE)),
new KeyFrame(new Duration(200L),
new KeyValue(visorParteInferior.rotateProperty(), -40, Interpolator.DISCRETE),
new KeyValue(visorParteInferior.translateYProperty(), 30, Interpolator.DISCRETE)),
new KeyFrame(new Duration(300L),
new KeyValue(visorParteInferior.rotateProperty(), 0, Interpolator.DISCRETE),
new KeyValue(visorParteInferior.translateYProperty(), 0, Interpolator.DISCRETE)));

timelineBoca.play();
}

@Override
public void terminaHablar() {
timelineBoca.stop();
visorParteInferior.setRotate(0);
visorParteInferior.setTranslateY(0);
}

}

Lo mas destable es:

  • Se utilizan dos linas de tiempo. Una que no para nunca que cambia la orientacion de la cabeza y otra que solo funciona cuando se esta hablando y mueve la boca.
  • Las transformaciones. Siempre que se pueda se debe utilizar una propiedad (como setRotate) pero su la transformacion que se desea no se puede conseguir con una funcion que ofrece la clase entonces se puede usar transformaciones en forma de matriz(Affine).

viernes, 16 de marzo de 2012

JavaFX 2.0. Vamos a crear una historia. Parte I

Swing es una tecnología fácil y rápida para crear una interfaz grafica de una aplicación. En mi modesta opinión, JavaFX 2.0 ofrece la posibilidad de dar mas potencia a las interfaces graficas que puede ofrecer la tecnología Java. A JavaFX 2.0 lo único que le falta es tiempo de “incrustarse” en el uso cotidiano de los programadores.

Voy a explicar un framework muy sencillo para crear una historia con muñecos. Todo con JavaFX2.0. La idea es que busques una cara que quieres animar, como por ejemplo la de nuestro amigo George:















Recartar la cara con cualquier programa de edicion de imagenes, cortarla en dos partes (superior e inferior) y animarla poniendole voz:









En esta primera parte voy a centrarme en las interfaces; las implementaciones en el siguiente post.

El proyecto lo he creado con Netbeans 7.1. Se debe crear la siguiente estructura de clases:


















La idea es que en una clase donde se quiere crear una "historia" se debe crear actores que se componen de cuerpo y de cabeza.


- Main.java: Punto de entrada de la aplicación.

package es;

import es.historia.HistoriaExcepcion;
import es.historia.IActor;
import es.historia.ICabeza;
import es.historia.mueve.impl.ActorImpl;
import es.historia.parte.impl.CabezaImpl;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.stage.Stage;

/**
*
* @author Propietario
*/
public class Main extends Application {

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) throws HistoriaExcepcion {
primaryStage.setTitle("Dibujo!");

// Se crea el actor
final IActor juguete = new ActorImpl();

// Se crea la cabeza
final ICabeza cabeza= new CabezaImpl();

// imagen que es la parte superior e inferior de la cabeza
cabeza.establecerCara("c:/caraInferior.JPG", "c:/caraSuperior.JPG");

// donde ponerlo dentro de la pantalla
cabeza.establecerPosicion(50,50);

// se define la cabeza del actor. El cuerpo para otro post.
juguete.definir(cabeza, null);

// Un boton para que hable
Button btn = new Button("Hablar");
btn.setOnAction(new EventHandler(){

public void handle(ActionEvent event) {

juguete.hablar("file:/uno_comoestas.wav", cabeza);
}

});


// un actor dibujado no es mas que un grupo de componentes JavaFx 2.0
Group group = juguete.dibujar();
group.getChildren().add(btn);

// apartir de aqui ya es como cualquier otra aplicacion JavaFx 2.0
Scene scene = new Scene(group, 500, 500);
primaryStage.setScene(scene);
primaryStage.show();
}
}

- HistoriaExcepcion: Excepcion para el uso dentro del framework

package es.historia;

/**
*
* @author Propietario
*/
public class HistoriaExcepcion extends Exception{

public HistoriaExcepcion(String string) {
super(string);
}
}

package es.historia;

- IActor: Requerimientos de una clase que sea una implementacion de un actor.

/**
*
* @author Propietario
*/
public interface IActor extends IMovimiento{
// Un actor es siempre una cabeza y un cuerpo
public void definir(ICabeza cabeza, ICuerpo cuerpo);
// accion de hablar
public void hablar(String frase,final ICabeza cabeza);
}

- ICabeza: Requerimientos de una clase que sea una implementacion de una cabeza.


package es.historia;

/**
*
* @author Propietario
*/
public interface ICabeza extends IMovimiento{
//una cara es una imagen superio y una cara inferior
public void establecerCara (String caraInferior, String caraSuperior) throws HistoriaExcepcion;

// definir posicion
public void establecerPosicion(int i, int i0);

// movimiento que se sucede siempre
public void empezarMovimientoFijo();

//movimiento cuando se habla
public void empiezaHablar();

// movimiento cuando se termina de hablar
public void terminaHablar();
}
- ICuerpo: Requerimientos de una clase que sea una implementacion de un cuerpo.

package es.historia;

/**
*
* @author Propietario
*/
public interface ICuerpo extends IMovimiento{
}

- IMarco: Requerimientos de una clase que sea una implementacion de un cuerpo.
package es.historia;

/**
*
* @author Propietario
*/
public interface IMarco {
}


- IMovimiento: Que funcionalidad debe de tener implementaciones que se puedan mover (cabeza, cuerpo, actor, etc..)
package es.historia;

import javafx.scene.Group;

/**
*
* @author Propietario
*/
public interface IMovimiento {
// dibujar la unidad (cuerpo, cabeza, etc..)
public Group dibujar ();
// mover la unidad
public void mover();
}