Como animar a adição ou remoção de linhas do Android ListView

212

No iOS, existe um recurso muito fácil e poderoso para animar a adição e remoção de linhas do UITableView. Aqui está um clipe de um vídeo do youtube mostrando a animação padrão. Observe como as linhas circundantes são recolhidas na linha excluída. Essa animação ajuda os usuários a acompanhar o que mudou em uma lista e onde eles estavam olhando quando os dados foram alterados.

Desde que desenvolvi no Android, não encontrei um recurso equivalente para animar linhas individuais em um TableView . A ligação notifyDataSetChanged()ao meu adaptador faz com que o ListView atualize imediatamente seu conteúdo com novas informações. Gostaria de mostrar uma animação simples de uma nova linha entrando ou saindo quando os dados mudam, mas não consigo encontrar nenhuma maneira documentada de fazer isso. Parece que o LayoutAnimationController pode ter uma chave para fazer com que isso funcione, mas quando defino um LayoutAnimationController no meu ListView (semelhante ao LayoutAnimation2 do ApiDemo ) e removo elementos do meu adaptador depois que a lista é exibida, os elementos desaparecem imediatamente em vez de serem animados .

Também tentei coisas como as seguintes para animar um item individual quando ele é removido:

@Override
protected void onListItemClick(ListView l, View v, final int position, long id) {
    Animation animation = new ScaleAnimation(1, 1, 1, 0);
    animation.setDuration(100);
    getListView().getChildAt(position).startAnimation(animation);
    l.postDelayed(new Runnable() {
        public void run() {
            mStringList.remove(position);
            mAdapter.notifyDataSetChanged();
        }
    }, 100);
}

No entanto, as linhas que cercam a linha animada não se movem até que pulem para suas novas posições quando notifyDataSetChanged()são chamadas. Parece que o ListView não atualiza seu layout depois que seus elementos foram colocados.

Enquanto escrevia minha própria implementação / fork do ListView me passou pela cabeça, isso parece algo que não deve ser tão difícil.

Obrigado!

Alex Pretzlav
fonte
alguém encontrou a resposta para isso? pls shareee
AndroidGecko
@Alex: você fez essa animação como o vídeo do youtube (como o iphone)? Se você tem alguma demonstração ou link para o Android, por favor, me dê uma chance, tentei usar animateLayoutChanges no arquivo xml, mas não é exatamente como o iphone
Jayesh
@ Jayesh, infelizmente eu não trabalho mais no desenvolvimento do Android. Eu não testei qualquer uma das respostas a esta pergunta que foram escritos depois por volta de 2012.
Alex Pretzlav

Respostas:

124
Animation anim = AnimationUtils.loadAnimation(
                     GoTransitApp.this, android.R.anim.slide_out_right
                 );
anim.setDuration(500);
listView.getChildAt(index).startAnimation(anim );

new Handler().postDelayed(new Runnable() {

    public void run() {

        FavouritesManager.getInstance().remove(
            FavouritesManager.getInstance().getTripManagerAtIndex(index)
        );
        populateList();
        adapter.notifyDataSetChanged();

    }

}, anim.getDuration());

para animação de cima para baixo, use:

<set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate android:fromYDelta="20%p" android:toYDelta="-20"
            android:duration="@android:integer/config_mediumAnimTime"/>
        <alpha android:fromAlpha="0.0" android:toAlpha="1.0"
            android:duration="@android:integer/config_mediumAnimTime" />
</set>
CARVALHO
fonte
70
É melhor usar anim.setAnimationListener(new AnimationListener() { ... })em vez de usar um Handler
Oleg Vaskevich
1
para que serve populateList()?
Vasily Sochinsky
5
@MrAppleBR por vários motivos. Primeiro, você não está criando um desnecessário Handler. Segundo, torna o código menos complexo já usando um retorno de chamada interno para quando a animação terminar. Terceiro, você não sabe como a implementação Handlere Animationfuncionará em cada plataforma; pode ser possível que a sua execução atrasada aconteça antes que a animação termine.
Oleg Vaskevich
7
"No entanto, as linhas que cercam a linha animada não se movem de posição até que elas pulem para suas novas posições quando o notifyDataSetChanged () é chamado." esse problema ainda persiste na sua solução.
Boris Rusev
5
O que é a classe FavouritesManager e o método populateList ()?
Jayesh
14

Ele RecyclerViewcuida de adicionar, remover e reordenar animações!

RecyclerView em ação

Este projeto simples do AndroidStudio apresenta a RecyclerView. dê uma olhada nos commits :

  1. confirmar o aplicativo Android Hello World clássico
  2. confirmar, adicionando um RecyclerView ao projeto (conteúdo não dinâmico)
  3. confirmar, adicionando funcionalidade para modificar o conteúdo do RecyclerView em tempo de execução (mas sem animações)
  4. e finalmente ... confirmar adicionar animações ao RecyclerView
Eric
fonte
1
Eu acho que essa é a maneira mais simples quando você precisa de uma animação simples, mas também pode personalizar a animação usando o método RecyclerView.setItemAnimator ().
qatz
2
O passo final é o elo que faltava. Ids estáveis. Obrigado!
Amir Uval #
Este é um ótimo exemplo de projeto que demonstra bem a API.
Mr-IDE
9

Dê uma olhada na solução do Google. Aqui está apenas um método de exclusão.

Código do projeto ListViewRemovalAnimation e demonstração em vídeo

Ele precisa do Android 4.1 ou superior (API 16). Mas temos 2014 lá fora.

dimetil
fonte
2

Como ListViewssão altamente otimizados, acho que isso não é possível de ser feito. Você tentou criar seu "ListView" por código (ou seja, inflando suas linhas de xml e anexando-as a LinearLayout) e animando-as?

whlk
fonte
6
Não faz sentido reimplementar a exibição de lista apenas para adicionar animações.
Macarse 17/10/10
Eu certamente poderia usar um LinearLayout, no entanto, com um LinearLayoutdesempenho muito grande de conjuntos de dados , isso se tornará péssimo. ListViewpossui muitas otimizações inteligentes em torno da reciclagem de visualizações que permitem lidar com grandes conjuntos de dados (o UITableView do iOS tem as mesmas otimizações). Escrever meu próprio reciclador de exibição se enquadra na categoria de reimplementar o ListView :(
Alex Pretzlav
Eu sei que não faz sentido - mas se você trabalhar com apenas algumas linhas, o benefício de ter boas animações de entrada / saída pode valer a pena tentar.
whlk
2

Você já pensou em animar uma varredura para a direita? Você pode fazer algo como desenhar uma barra branca progressivamente maior na parte superior do item da lista e removê-la da lista. As outras células ainda se encaixavam no lugar, mas seria melhor que nada.

Sam Dufel
fonte
2

chamar listView.scheduleLayoutAnimation (); antes de mudar a lista

karabara
fonte
2

Eu hackeei outra maneira de fazer isso sem ter que manipular a exibição de lista. Infelizmente, as animações regulares do Android parecem manipular o conteúdo da linha, mas são ineficazes ao reduzir a exibição. Portanto, primeiro considere este manipulador:

private Handler handler = new Handler() {
@Override
public void handleMessage(Message message) {
    Bundle bundle = message.getData();

    View view = listView.getChildAt(bundle.getInt("viewPosition") - 
        listView.getFirstVisiblePosition());

    int heightToSet;
    if(!bundle.containsKey("viewHeight")) {
        Rect rect = new Rect();
        view.getDrawingRect(rect);
        heightToSet = rect.height() - 1;
    } else {
        heightToSet = bundle.getInt("viewHeight");
    }

    setViewHeight(view, heightToSet);

    if(heightToSet == 1)
        return;

    Message nextMessage = obtainMessage();
    bundle.putInt("viewHeight", (heightToSet - 5 > 0) ? heightToSet - 5 : 1);
    nextMessage.setData(bundle);
    sendMessage(nextMessage);
}

Adicione esta coleção ao seu adaptador de lista:

private Collection<Integer> disabledViews = new ArrayList<Integer>();

e adicione

public boolean isEnabled(int position) {
   return !disabledViews.contains(position);
}

Em seguida, onde quer que você queira ocultar uma linha, adicione isto:

Message message = handler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putInt("viewPosition", listView.getPositionForView(view));
message.setData(bundle);
handler.sendMessage(message);    
disabledViews.add(listView.getPositionForView(view));

É isso aí! Você pode alterar a velocidade da animação alterando o número de pixels que diminui a altura de uma só vez. Não é realmente sofisticado, mas funciona!

jkschneider
fonte
2

Depois de inserir uma nova linha no ListView, basta rolar o ListView para a nova posição.

ListView.smoothScrollToPosition(position);
Rafael
fonte
1

Eu não tentei, mas parece que o animateLayoutChanges deve fazer o que você está procurando. Eu o vejo na classe ImageSwitcher, presumo que também esteja na classe ViewSwitcher?

Yevgeny Simkin
fonte
Ei, obrigado, vou investigar isso. Infelizmente, parece que animateLayoutChanges é novo a partir de API Nível 11 aka Honeycomb aka não-pode-uso-a-qualquer-phones-ainda este :(
Alex Pretzlav
1

Como o Android é de código aberto, você não precisa realmente reimplementar as otimizações do ListView. Você pode pegar o código do ListView e tentar encontrar uma maneira de invadir a animação, também pode abrir uma solicitação de recurso no rastreador de erros do Android (e se você decidiu implementá-lo, não se esqueça de contribuir com um patch ).

Para sua informação, o código fonte do ListView está aqui .

Lie Ryan
fonte
1
Não é assim que essas mudanças devem ser implementadas. Antes de qualquer coisa, você deve ter a construção do projeto AOSP - que é uma abordagem bastante pesada para adicionar animação às visualizações de lista. Por favor, observe implementações em várias bibliotecas de código aberto.
OrhanC1
1

Aqui está o código fonte para permitir excluir linhas e reordená-las.

Um arquivo APK de demonstração também está disponível. A exclusão de linhas é feita mais ao longo das linhas do aplicativo Gmail do Google, que revela uma vista inferior depois de deslizar uma vista superior. A vista inferior pode ter um botão Desfazer ou o que você quiser.

AndroidDev
fonte
1

Sempre que a idéia é criar bitmaps por getdrawingcache. Tenha dois bitmap e anime o bitmap inferior para criar o efeito de movimento

Por favor, veja o seguinte código:

listView.setOnItemClickListener(new AdapterView.OnItemClickListener()
    {
        public void onItemClick(AdapterView<?> parent, View rowView, int positon, long id)
        {
            listView.setDrawingCacheEnabled(true);
            //listView.buildDrawingCache(true);
            bitmap = listView.getDrawingCache();
            myBitmap1 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), rowView.getBottom());
            myBitmap2 = Bitmap.createBitmap(bitmap, 0, rowView.getBottom(), bitmap.getWidth(), bitmap.getHeight() - myBitmap1.getHeight());
            listView.setDrawingCacheEnabled(false);
            imgView1.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap1));
            imgView2.setBackgroundDrawable(new BitmapDrawable(getResources(), myBitmap2));
            imgView1.setVisibility(View.VISIBLE);
            imgView2.setVisibility(View.VISIBLE);
            RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
            lp.setMargins(0, rowView.getBottom(), 0, 0);
            imgView2.setLayoutParams(lp);
            TranslateAnimation transanim = new TranslateAnimation(0, 0, 0, -rowView.getHeight());
            transanim.setDuration(400);
            transanim.setAnimationListener(new Animation.AnimationListener()
            {
                public void onAnimationStart(Animation animation)
                {
                }

                public void onAnimationRepeat(Animation animation)
                {
                }

                public void onAnimationEnd(Animation animation)
                {
                    imgView1.setVisibility(View.GONE);
                    imgView2.setVisibility(View.GONE);
                }
            });
            array.remove(positon);
            adapter.notifyDataSetChanged();
            imgView2.startAnimation(transanim);
        }
    });

Para entender as imagens, consulte este

Obrigado.

RAM
fonte
0

Eu fiz algo semelhante a isso. Uma abordagem é interpolar, durante o tempo da animação, a altura da exibição ao longo do tempo nas linhas onMeasuredurante a emissão requestLayout()do listView. Sim, pode ser melhor fazer diretamente dentro do código listView, mas foi uma solução rápida (que parecia boa!)

Dori
fonte
0

Apenas compartilhando outra abordagem:

Primeiro, defina o android da exibição de lista : animateLayoutChanges como true :

<ListView
        android:id="@+id/items_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true"/>

Então eu uso um manipulador para adicionar itens e atualizar a lista com atraso:

Handler mHandler = new Handler();
    //delay in milliseconds
    private int mInitialDelay = 1000;
    private final int DELAY_OFFSET = 1000;


public void addItem(final Integer item) {
    mHandler.postDelayed(new Runnable() {
        @Override
        public void run() {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    mDataSet.add(item);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mAdapter.notifyDataSetChanged();
                        }
                    });
                }
            }).start();

        }
    }, mInitialDelay);
    mInitialDelay += DELAY_OFFSET;
}
Mina Samy
fonte