Universal Tween Engine, animazioni universali (anche per Android)

Universal Tween Engine (UTE) è un’utile e semplice libreria che permette di applicare delle trasformazioni agli attributi di un oggetto attraverso l’interpolazione, che detto diversamente, è ciò che consente di realizzare facilmente delle animazioni. Si tratta di uno strumento davvero universale perché può essere impiegato ovunque, richiede solo un oggetto Java (Swing, OpenGL, ecc) con delle proprietà numeriche. Per iniziare fatevi un’idea con questa demo.

UTE è stata principalmente sviluppata per l’utilizzo nei videogiochi e mira ad ottenere le migliori prestazioni con il minor utilizzo di memoria possibile, ma puo’ essere utilizzata tranquillamente anche in qualsiasi applicazione Android, come nell’esempio di questo post.

Gli elementi chiave da coinvolgere sono:

  • un’entità da manipolare;
  • TweenManager ovvero la nostra libreria;
  • TweenAccessor, che permette a TweenManager di accedere direttamente ai metodi della nostra entità.

La nostra entità sarà la classe ViewContainer ed avrà il compito di manipolare la posizione di un LinearLayout.

<LinearLayout
    android:id="@+id/main_cont"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >
</LinearLayout>

Per modificare la posizione di un LinearLayout dobbiamo agire direttamente modificandone i LayoutParams.

public class ViewContainer {

	private float x, y;
	public View view;

	public float getX() {
		return x;
	}

	public float getY() {
		return y;
	}

	public void setX(float x) {
		this.x = x;

		RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view
				.getLayoutParams();
		params.leftMargin = (int) x;
		view.setLayoutParams(params);

	}

	public void setY(float y) {
		this.y = y;

		RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view
				.getLayoutParams();
		params.topMargin = (int) y;
		view.setLayoutParams(params);

	}
}

Si noti che la nostra entità potrebbe esporre anche altri metodi, per esempio quelli necessari a cambiarne la trasparenza, UTE può agire anche su questi elementi. Quello che ora dobbiamo fare è creare una classe ViewContainerAccessor che implementi l’interfaccia TweenAccessor, tale classe si occuperà di valorizzare i metodi esposti dalla classe entity utilizzando TweenManager

public class ViewContainerAccessor implements
		TweenAccessor {

	public static final int POSITION_X = 1;
	public static final int POSITION_Y = 2;
	public static final int POSITION_XY = 3;

	@Override
	public int getValues(ViewContainer target, int tweenType,
			float[] returnValues) {
		switch (tweenType) {
		case POSITION_X:
			returnValues[0] = target.getX();
			return 1;
		case POSITION_Y:
			returnValues[0] = target.getY();
			return 1;
		case POSITION_XY:
			returnValues[0] = target.getX();
			returnValues[1] = target.getY();
			return 2;
		default:
			assert false;
			return -1;
		}
	}

	@Override
	public void setValues(ViewContainer target,
			int tweenType, float[] newValues) {
		switch (tweenType) {
		case POSITION_X:
			target.setX(newValues[0]);
			break;
		case POSITION_Y:
			target.setY(newValues[0]);
			break;
		case POSITION_XY:
			target.setX(newValues[0]);
			target.setY(newValues[1]);
			break;
		default:
			assert false;
			break;
		}
	}
}

Da qui il gioco è praticamente fatto. All’avvio dell’applicazione ci basta registrare la nostra entità sul TweenManager e poi creare un’istanza della libreria che ci servirà per definirne le animazioni.

private void setTweenEngine() {
    Tween.registerAccessor(ViewContainer.class, new ViewContainerAccessor());
    tweenManager = new TweenManager();
 }

Definiamo due spostamenti: il primo di durata 0.5 secondi (con rimbalzo finale) fino alla posizione x = 200, il secondo, in diagonale con destinazione (x,y) => (450, 200).

Timeline.createSequence()
	.push(Tween.to(cont,ViewContainerAccessor.POSITION_XY,0.5f).
target(200, 0).ease(Bounce.OUT).delay(0.0f) )
	.push(Tween.to(cont,ViewContainerAccessor.POSITION_XY,0.5f).
target(450,200).ease(aurelienribon.tweenengine.equations.Linear.INOUT).delay(0.0f) )
	.start(tweenManager).setCallback(new TweenCallback() {

			@Override
			public void onEvent(int arg0, BaseTween<?> arg1) {

				stopAnimationThread();
			}
		});

Infine dobbiamo notificare ogni update a TweenManager, (solitamente ad ogni rendering dell’applicazione), per fare questo lanciamo un thread che viene eseguito nell’UI thread di Android.

@Override
public void run() {

	while (isAnimationRunning) {

	  if (lastMillis > 0) {

	    long currentMillis = System.currentTimeMillis();
	    final float delta = (currentMillis - lastMillis) / 1000f;

	    runOnUiThread(new Runnable() {

	      @Override public void run() {
	        tweenManager.update(delta);

	        Log.v("TIME",String.valueOf(delta));

	      }
	    });

	    lastMillis = currentMillis;
	  } else {
	    lastMillis = System.currentTimeMillis();
	  }

	  try {
	    Thread.sleep(1000 / 60);
	  } catch (InterruptedException ex) {
	  }
  }
}
Il nostro risultato.

Il nostro risultato.

Conclusioni

Abbiamo visto in questo post come UTE ci permetta di realizzare velocemente e con poco sforzo delle semplici animazioni tramite interpolazioni degli attributi di generici oggetti. Per realizzare effetti più complessi è possibile utilizzare le TimeLine per le quali vi rimando alla documentazione ufficiale.

A questo punto sorge una domanda, perchè non utilizzare direttamente ObjectAnimator di Android? ObjectAnimator rappresenta un buon punto di partenza in Android se si vogliono eseguire velocemente semplici animazioni ma nel caso di animazioni più complesse UTE acquista una notevole importanza in quanto supporta animazioni in sequenza, animazioni multiple, controllo della velocità, ripetizioni e molto altro.

Il progetto d’esempio è disponibile su GitHub

Alessandro Verona

Appassionato di informatica, nostalgico (per modo di dire) del C64 e sviluppatore a tempo pieno