terça-feira, 24 de julho de 2012

Animando aplicativos com Animation - Parte 2

Olá. Na Parte 1 deste tutorial foi mostrado como Animation é importante para um aplicativo profissional e como é possível criar animações pelo xml ou pelo código java e mandar executa-las. Com isso é possível fazer todo tipo de animação, mais ainda não é possível, pelo menos não é fácil, integrar varias animações, mudanças de layout, reposicionamento de views, etc.

O que veremos agora é: primeiramente um defeito desse tipo de animação, a seguir veremos sua solução e algumas dicas.

O problema com a Animation é que ela modifica apenas a forma que a view é exibida, e não nenhuma propriedade interna. Isso quer dizer que durante a animação, ou mesmo depois se o fillAfter for true, não importa como sua view está sendo exibida, a resposta a toques, tamanho, comportamento serão como se não houvesse nenhuma mudança. Então não é possível esconder ou mudar de lugar uma view de forma correta usando apenas Animation.

A solução para esse problema é fazer as mudanças no seu layout como se não existisse Animation e depois simplesmente adicionar a animação. Isso nem sempre é possível, mas é um bom começo para entender a lógica da solução. O jeito simples de fazer isso é aplicar a mudança e depois fazer a animação do que tava antes para o atual. Por exemplo: se quiser mover uma view 80% para direita primeiro mova-a (o que não é feito diretamente para Android < 3.0, mas pode ser feito deixando uma view do tamanho desejado que antes tinha visibilidade gone com a visibilidade visible) e depois aplique a animação de translação saindo de -80% para 0.

Outra forma de lidar com isso, e que também é uma ferramenta para outros problemas é utilizar o AnimationListener, onde você pode implementar métodos a serem executados quando a animação começar, repetir ou parar. A seguir um exemplo:

Animation anim = AnimationUtils.loadAnimation(this, R.anim.to_left_100);
anim.setAnimationListener(new Animation.AnimationListener() {

    @Override
    public void onAnimationStart(Animation animation) {
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        mLayoutList.setVisibility(View.GONE);
    }
});


Nesse exemplo, depois da animação ser executada o mLayoutList deve sumir. Mas há um problema aqui também. O método onAnimationEnd não é executado exatamente quando a animação termina, de forma que se sua view precisa de um método que será executado no onAnimationEnd para setar sua posição, por exemplo, você verá sua view efetuando um pisque, um flash, quando a animação terminar. Isso acontece pois como o onAnimationEnd não é executado imediatamente a view volta pro local de origem e milissegundos depois vai pro lugar que você quer. Então, sempre que possível faça as alterações antes de executar a animação, não deixe isso pro onAnimationEnd.

Destaquei esse ponto pois fiquei um bom tempo travado nele até achar a solução. Mas nem sempre a solução que falei acima é possível ser feita, então há uma outra solução mais drástica: sobrescrever o método onAnimationEnd da view que está sendo animada, pois esse sim é executado imediatamente após a animação ter terminado, de forma que se você colocar os mesmos comandos aqui não ocorrerá o flash. Mas como fazer isso? Você criar uma subclasse de todas os tipos de view que você irá animar, mas provavelmente isso não será muito prático. A melhor solução que encontrei foi criar uma subclasse de FrameLayout que permite que eu adicione manualmente o meu OnAnimationEnd, dessa forma:

public class AnimateFrameLayout extends FrameLayout {
    
    private OnAnimationEnd mOnAnimationEnd;

    public AnimateFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onAnimationEnd() {
        super.onAnimationEnd();
        if(mOnAnimationEnd != null)
            mOnAnimationEnd.onAnimationEnded();
    }
    
    public void setOnAnimationEnd(OnAnimationEnd listener){
        mOnAnimationEnd = listener;
    }
}


Tive também que criar a interface OnAnimationEnd:

public interface OnAnimationEnd {
    
    public void onAnimationEnded();
}


Então quando eu preciso animar uma view que está com esse problema eu coloco a view dentro desse AnimateFrameLayout e animo esse layout ao invés da view e seto meu listener de onAnimationEnd pro AnimateFrameLayout que estou animando e não diretamente na instancia de Animation. O xml ficaria assim caso eu queira animar uma ImageView:

<com.inmap.AnimateFrameLayout
        android:id="@+id/layout_map"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" >

        <ImageView
            android:id="@+id/map"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:src="@drawable/mapl2" />

    </com.inmap.AnimateFrameLayout>


E no código basta eu fazer isso:

        AnimateFrameLayout mLayoutMap = (AnimateFrameLayout) findViewById(R.id.layout_map);
        mLayoutMap.setOnAnimationEnd(new OnAnimationEnd() {

            @Override
            public void onAnimationEnded() {
                if(!isShowingList)
                    mLayoutList.setVisibility(View.GONE);
            }
        });


Perceba que estou setando o OnAnimationEnd na view e não mais no animation como era antes. Isso implica que esse método será executado sempre que qualquer animação aplicada na view (nesse caso é o mLayoutMap) terminar sua execução, então você precisa de meios para garantir que apenas uma animação seja realizada na view ou verificar qual animação foi realizada para executar o código corretamente.

Feito isso você poderá usufruir de animações como quiser, desde que se atente aos detalhes. A partir do Android 3.0 foi incluída um novo tipo de animação que realmente altera as propriedades da view animada, não sendo necessário usar esses métodos mostrados aqui. Mas isso é assunto para um outro tutorial.

3 comentários:

Gilian at 2 de dezembro de 2012 15:10 disse...

Primeiro parabéns pelo tutorial!
Mas que animação é essa que altera as propriedades no android 3+ ? Só pra eu começar a pesquisar.. vlw!

Gustavo Carvalho at 6 de dezembro de 2012 04:31 disse...

@Gilian: Obrigado

Procure por Animator (e suas subclasses que realmente implementam a animação). Há também a biblioteca NineOldAndroids ( http://nineoldandroids.com/ )que permite utilizar o mesmo sistema do Animator em versões anteriores.

Agora eu recomendo que toda animação seja feita com o NineOld.

Rafael at 1 de junho de 2013 16:59 disse...

Excelente post..
Através dele consegui fazer o efeito do menu do Facebook Android.
Perfeito.. Muito Obrigado!!!

Postar um comentário

 
© 2011 Tutoriandroid | Recode by Ardhiansyam | Based on Android Developers Blog