Mostrando postagens com marcador Animation. Mostrar todas as postagens
Mostrando postagens com marcador Animation. Mostrar todas as postagens

segunda-feira, 16 de abril de 2012

Animações no Android 3+

Olá povo,

Essa semana, meu colega Felipe Vasconcelos me lembrou de um problema existente na API de animações do Android 2 (que falei nesse post).
Digamos que você tenha um botão em uma posição x/y, e que esse botão seja posicionado em outro lugar através de uma animação (uma TranslateAnimation pra ser mais exato). Se você clicar nesse botão após a animação, você não conseguirá. Isso se deve a uma característica (ou bug?) da API de animação do Android até o 2.x, que só altera o aspecto da View, mas não seu conteúdo interno. Isso quer dizer que, apesar do botão mudar a posição visualmente, ele continua na posição antiga. Ou seja, se você clicar no botão na posição anterior à animação, o evento de clique ocorrerá.

Para corrigir esse problema, o Google introduziu uma nova API de animação no Android 3.0, que traz ainda o benefício de acompanhar cada passo da animação, coisa que não acontecia na antiga API.
Para entender melhor o problema e a nova API, vamos criar um exemplo bem simples. Abaixo temos o arquivo XML que define a tela da nossa aplicação:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="horizontal">

  <Button
    android:id="@+id/btnAnimacaoAntiga"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="onClickAnimarAntigo"
    android:text="Animar 2.x" />

  <Button
    android:id="@+id/btnAnimarNova"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="onClickAnimarNovo"
    android:text="Animar Honeycomb" />

</LinearLayout>
A tela é bem simples, apenas dois botões que serão animados até a posição 200 na coordenada Y. O primeiro animará utilizando a API antiga, e o segundo a nova. Abaixo, temos a Activity da aplicação:
public class AnimationHoneycombActivity 
  extends Activity {
 
  private boolean b1Desceu;
  private boolean b2Desceu;
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
  }
    
  public void onClickAnimarAntigo(View v){
    int yInicial, yFinal;
    if (b1Desceu){
      yInicial = 200;
      yFinal = 0;
    } else {
      yInicial = 0;
      yFinal = 200;
    }
     
    TranslateAnimation animacao = 
      new TranslateAnimation(0, 0, yInicial, yFinal); 
    animacao.setDuration(2000);
    animacao.setFillAfter(true);
    v.startAnimation(animacao);
    b1Desceu = !b1Desceu;
  }
    
  public void onClickAnimarNovo(final View v){
      
    ValueAnimator animacao = 
      ValueAnimator.ofFloat(0, 200);

    animacao.setTarget(v);
    animacao.setDuration(2000);
    animacao.addUpdateListener(
      new ValueAnimator.AnimatorUpdateListener() {
   
        @Override
        public void onAnimationUpdate(
          ValueAnimator animation) {

          Float valor = (Float)
            animation.getAnimatedValue();
          v.setY(valor);
       }
     });

    if (b2Desceu){
      animacao.reverse();
    } else {
      animacao.start();
    }
    b2Desceu = !b2Desceu;
  }
}
No primeiro método, utilizamos a API antiga de animações. Utilizei uma flag para determinar se o botão desceu, para determinar a posição inicial e final na coordenada Y. Em seguida, criei uma TranslateAnimation, defini a duração, e em seguida, com o método setFillAfter determinei que ao terminar a animação, o botão deve permanecer onde a animação terminou. Por fim, mandei animar o botão.
Já no segundo método, estou usando a nova API de animações. A classe ValueAnimator é a chave dessa API, e ela tem basicamente: um valor inicial e final; um target, que é um objeto View; a duração em milissegundos; e um AnimatorUpdateListener. Esse último objeto é chamado N vezes para realizar a animação, onde N depende do valor final e sua respectiva duração. É nesse objeto que devemos alterar a propriedade da View que queremos animar, que no nosso caso, é a propriedade Y do botão.
No final do método vemos se o botão desceu, em caso positivo, solicitamos que a animação seja feita ao contrário com o método reverse; caso contrário, mandamos animar para a posição desejada.
Para observar o "bug" da antiga API de animação, clique no primeiro botão e ele descerá. Em seguida, clique no botão e você notará que nada acontecerá. Agora tente clicar onde o botão estava... Magicamente o botão voltará para a posição original.

Em resumo, se você precisar interagir com uma View que sofreu uma animação, ou ainda precisar obter informações internas sobre elas, você terá que contornar essa limitação da API antiga de animações. Mas se você estiver pensando em desenvolver para tablets Android ou ainda para os novos aparelhos com Android 4 ICS, utilize a nova API de animações.

4br4ç05,
nglauber

sábado, 2 de abril de 2011

Animações no Android (2)

Olá povo,

Nesse post vou mostrar como utilizar algumas animações prontas que o Android disponibiliza em sua API no pacote android.view.animation. Começe criando um novo projeto e deixe o seu arquivo de layout conforme abaixo:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">

<Spinner
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/spnAnimation"/>
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"/>

<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="fill">

<ImageView
android:layout_width="wrap_content"
android:id="@+id/imageView1"
android:layout_height="wrap_content"
android:src="@drawable/icon"/>
</FrameLayout>
</LinearLayout>


Nosso arquivo de layout é bem simples. Ele consta de um Spinner (combobox) que vai listar as animações disponíveis na API. Em seguida temos um botão que iniciará a animação. Logo abaixo, temos uma ImageView dentro de um FrameLayout, utilizamos esse layout pois uma das animações permite que posicionemos uma View em uma posição absoluta da tela (X e Y). Mas você pode estar se perguntando: se vai usar posição absoluta, porque não usar o AbsoluteLayout?
O AbsoluteLayout é considerado obsoleto (deprecated) uma vez que podemos utilizar o FrameLayout que faz a mesma função, só que ao invés de usarmos as propriedades layout_x e layout_y, utilizamos layout_marginLeft e layout_marginTop respectivamente.

Explicado o layout, vamos agora ao código que reliza as animações. Abra a activity da aplicação e deixe-a de acordo com a classe abaixo:


public class ExemploAnimationActivity
extends Activity
implements OnClickListener, AnimationListener {

private Spinner spnAnimation;
private ImageView imgView;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

String[] animations = {
"Alpha", "Rotate", "Scale", "Translate"};

ArrayAdapter<String> adapter =
new ArrayAdapter<String>(
this,
android.R.layout.simple_spinner_item,
animations);
spnAnimation =
(Spinner)findViewById(R.id.spnAnimation);
spnAnimation.setAdapter(adapter);

findViewById(R.id.button1).
setOnClickListener(this);

imgView =
(ImageView)findViewById(R.id.imageView1);
}

@Override
public void onClick(View v) {
Animation animation = null;
Interpolator interpolator = null;

switch (spnAnimation.getSelectedItemPosition()) {
case 0: // Alpha
animation = new AlphaAnimation(1, 0);
break;

case 1: // Rotate
interpolator = new OvershootInterpolator(2);
animation = new RotateAnimation(0, 360,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
animation.setRepeatCount(3);
break;

case 2: // Scale
interpolator = new AccelerateInterpolator(4);
animation = new ScaleAnimation(
1, 3, 1, 3,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 0);
break;

case 3: // Translate
interpolator = new BounceInterpolator();
animation = new TranslateAnimation(
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 200,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 200);
}
animation.setInterpolator(interpolator);
animation.setAnimationListener(this);
animation.setDuration(3000);
imgView.setAnimation(animation);
animation.start();
}

@Override
public void onAnimationEnd(Animation animation) {

switch (spnAnimation.getSelectedItemPosition()) {
case 0: // Alpha
animation = new AlphaAnimation(0, 1);
break;

case 1: // Rotate
return;

case 2: // Scale
animation = new ScaleAnimation(3, 1, 3, 1);
break;

case 3: // Translate
animation = new TranslateAnimation(
Animation.ABSOLUTE, 200,
Animation.ABSOLUTE, 0,
Animation.ABSOLUTE, 200,
Animation.ABSOLUTE, 0);
}
animation.setDuration(3000);
imgView.setAnimation(animation);
animation.start();
}

@Override
public void onAnimationRepeat(Animation animation) { }

@Override
public void onAnimationStart(Animation animation) {}
}


Nossa classe implementa as interfaces OnClickListener e OnAnimationListener, a primeira nós já conhecemos, a segunda serve para notificar das mudanças de estado da animação. Com ela é possível saber quando a animação começou, terminou e quando foi repetida. Esses método estão declarados no final da nossa classe.
No método onCreate criamos um adapter para preencher nosso spinner com as 4 animações do pacote de animações (existe mais uma que é AnimationSet que não usarei aqui) . Depois setamos o evento de clique do botão e por fim obtemos a referência da ImageView que iremos animar.
No método onClick é que as coisas começam a ficar animadas (entederam o trocadilho? animações no Android... coisas animadas... dãããã... :)

Começamos declarando um obejto Animation que será atribuída mais adiante à nossa ImageView. Logo abaixo, declaramos um Interpolator. Esse objeto (opcional) serve para aplicar um efeito mais legal à nossa animação.

A instrução switch inicializa a animação de acordo com a opção selecionada no Spinner. A primeira opção cria um AlphaAnimation que recebe como parâmetros a transparência inicial e final (1 quer dizer opaco, 0 transparente). Nesse caso, essa animação fará a ImageView desaparecer completamente.
A segunda opção cria uma RotateAnimation que fará a View girar. Os parâmetros são o ângulo inicial e final, e uma posição X e Y para servir de eixo para a rotação. No nosso exemplo passamos uma posição relativa à imagem (RELATIVE_TO_SELF) baseada na metade do seu tamanho (0.5). Para essa animação criamos um interpolator que faz com que a animação faça um efeito antes parar (ficou difícil de explicar esse interpolator - vocês testando é melhor :).
A terceira animação é a ScaleAnimation que fará com que a ImageView aumente de tamanho. Ela recebe como argumento a proporção inicial e final em X, depois a proporção inicial e final em Y, e assim como a RotateAnimation, pede uma âncora para basear o aumento da imagem. No nosso caso colocamos a posição absoluta 0,0... O Interpolator usado aqui fará a animação acelerada.
A última animação é a TranslateAnimation que serve para movimentar a nossa ImageView para uma nova posição X e Y. Aqui informamos que ele deve se movimentar da posição X inicial 0 para 200 final, e Y inicial 0 e final 200. O Interpolator utilizado é o BounceInterpolator que fará o efeito de quicar.
Após iniciar o objeto animation, setamos seu interpolator, sua duração para 3 segundos (3000 milisegundos) e seu listener. Depois associamos a animação à ImageView e mandamos iniciar a animação. Quando a animação terminar, o método onAnimationEnd será chamado e fará a animação ao contrário. Analisem o código baseado na explicação acima que eu acho que vocês vão entender :)

Testem esse exemplo, explorem os Intrpolators com outras animações e não esqueçam de deixar seus comentários.

4br4ç05,
nglauber

segunda-feira, 5 de julho de 2010

Android - Animação (Sprite)

Olá povo,

Quem já programou (ou ainda programa) com Java ME, sabe que ele pode contar com uma API específica para desenvolvimento de Jogos. E nessa biblioteca temos uma classe chamada Sprite, que permite fazer uma animação a partir de uma sequência de imagens.

Pois bem, meu desafiador aluno Rodrigo Otávio, me perguntou como poderíamos fazer algo similar no Android. Como não tinha feito isso com Android ainda, fui em busca de resposta. Procurei na internet e achei algumas pessoas indicando como fazer a proeza utilizando Handler. Mas achei que o Android não podia ter deixado uma coisa tão simples, difícil de se fazer.

Falei com meu nobre colega Artur Botelho, que trabalhou em 15 milhões de projetos de jogos pra ver se ele já tinha visto algo. E eis que surge a solução. Vamos a ela!

Pegue as imagens que formarão sua animação. Coloquei as que eu utilizei aqui abaixo:



Depois é só definir nossa animação em um arquivo XML que deve ficar dentro do diretório /res/drawable do seu projeto Android.

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item 
  android:drawable="@drawable/crono1" 
  android:duration="200" />
<item 
  android:drawable="@drawable/crono2" 
  android:duration="200" />
<item 
  android:drawable="@drawable/crono3"
  android:duration="200" />
<item
  android:drawable="@drawable/crono4" 
  android:duration="200" />
</animation-list>


No arquivo acima, vemos que utilizamos uma imagem por frame (quadro) de animação que queremos utilizar, bem como o tempo que cada imagem deve ficar. Outro ponto interessante é o attributo oneshot, que se setado pra true, fará com que a animação só execute uma vez.

Agora é só utilizar a animação na sua ImageView. Vejamos abaixo como iniciar/parar uma animação ao tocar em um ImageView.

public class Sprite2dExample extends Activity {

  private AnimationDrawable spriteAnimation;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ImageView sprite = new ImageView(this);

    setContentView(sprite);

    sprite.setBackgroundResource(R.drawable.sprite);
    this.spriteAnimation = 
      (AnimationDrawable)sprite.getBackground();
  }

  public boolean onTouchEvent(MotionEvent event) {
    if(event.getAction() == MotionEvent.ACTION_DOWN){
      if (spriteAnimation.isRunning()){
        this.spriteAnimation.stop();
      } else {
        this.spriteAnimation.start();
      }
      return true;
    }
    return super.onTouchEvent(event);
  }
}


Espero que tenham gostado :)

4br4ç05,
nglauber