Jak animować dodawanie lub usuwanie wierszy Android ListView


212

W iOS istnieje bardzo łatwa i wydajna funkcja do animowania dodawania i usuwania wierszy UITableView, oto klip z filmu na YouTube pokazujący domyślną animację. Zwróć uwagę, jak otaczające rzędy zapadają się na usunięty wiersz. Ta animacja pomaga użytkownikom śledzić, co zmieniło się na liście i gdzie na liście patrzyli, kiedy zmieniły się dane.

Odkąd pracuję na Androidzie, nie znalazłem odpowiedniego narzędzia do animowania pojedynczych wierszy w TableView . Wywołanie notifyDataSetChanged()mojego adaptera powoduje, że ListView natychmiast aktualizuje swoją zawartość o nowe informacje. Chciałbym pokazać prostą animację nowego wiersza wciskającego się lub wysuwającego, gdy zmieniają się dane, ale nie mogę znaleźć żadnego udokumentowanego sposobu, aby to zrobić. Wygląda na to, że LayoutAnimationController może mieć klucz do uruchomienia tego, ale kiedy ustawię LayoutAnimationController na moim ListView (podobnie do LayoutAnimation2 ApiDemo ) i usuwam elementy z mojego adaptera po wyświetleniu listy, elementy znikają natychmiast, zamiast zostać animowane .

Próbowałem też wykonać następujące czynności, aby animować pojedynczy element po jego usunięciu:

@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);
}

Jednak rzędy otaczające animowany rząd nie zmieniają pozycji, dopóki nie zostaną przeskoczone na nowe pozycje, gdy notifyDataSetChanged()zostanie wywołane. Wygląda na to, że ListView nie aktualizuje swojego układu po umieszczeniu jego elementów.

Podczas pisania własnej implementacji / rozwidlenia ListView przyszło mi do głowy, wydaje się, że nie powinno to być takie trudne.

Dzięki!


czy ktoś znalazł na to odpowiedź? pls shareee
AndroidGecko

@Alex: czy zrobiłeś taką animację jak ten film na youtube (jak iPhone), jeśli masz jakieś demo lub link do Androida, proszę daj mi, próbowałem użyć animateLayoutChanges w pliku xml, ale to nie jest dokładnie tak jak iPhone
Jayesh

@Jayesh, niestety nie pracuję już nad rozwojem Androida. Nie testowałem żadnej z odpowiedzi na to pytanie, które zostały napisane po około 2012 roku.
Alex Pretzlav

Odpowiedzi:


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());

do animacji od góry do dołu:

<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>

70
Lepiej używać anim.setAnimationListener(new AnimationListener() { ... })zamiast Handlera
Olega Waszkiewicza

1
po co populateList()?
Wasilij Soczinsky

5
@MrAppleBR z kilku powodów. Po pierwsze, nie tworzysz niepotrzebnego Handler. Po drugie, sprawia, że ​​kod jest mniej skomplikowany dzięki użyciu wbudowanego wywołania zwrotnego na zakończenie animacji. Po trzecie, nie wiesz, jak wdrożenie Handleri Animationbędzie działać na każdej platformie; możliwe, że opóźnione uruchomienie nastąpi przed zakończeniem animacji.
Oleg Vaskevich

7
„Jednak wiersze otaczające animowany wiersz nie przesuwają pozycji, dopóki nie przeskoczą na nowe pozycje, gdy wywołana zostanie funkcja powiadomienieDataSetChanged ().” ten problem nadal występuje w twoim rozwiązaniu.
Boris Rusev

5
Co to jest klasa FavouritesManager i metoda populateList ()?
Jayesh

14

1
Myślę, że jest to prosty sposób, gdy potrzebujesz prostej animacji, ale możesz również dostosować animację za pomocą metody RecyclerView.setItemAnimator ().
qatz

2
Ostatnim krokiem jest brakujące ogniwo, które miałem. stabilne Id. Dzięki!
Amir Uval

To świetny przykładowy projekt, który dobrze pokazuje interfejs API.
Mr-IDE,


2

Ponieważ ListViewssą wysoce zoptymalizowane, myślę, że nie jest to możliwe. Czy próbowałeś utworzyć „ListView” według kodu (tj. Nadmuchując swoje wiersze z xml i dołączając je do a LinearLayout) i animując je?


6
Ponowne wdrożenie widoku listy nie ma sensu, aby dodać animacje.
Macarse

Z pewnością mógłbym po prostu użyć LinearLayout, jednak przy bardzo dużych zestawach danych LinearLayoutwydajność będzie fatalna. ListViewma wiele inteligentnych optymalizacji w zakresie recyklingu widoków, które pozwalają mu obsługiwać duże zestawy danych (UITableView na iOS ma te same optymalizacje). Pisanie własnego widoku recykler należy do kategorii ListView
::

Wiem, że to nie ma sensu - ale jeśli pracujesz tylko z kilkoma wierszami, warto mieć fajne animacje we / wy.
whlk

2

Czy zastanawiałeś się nad animacją przeciągnięcia w prawo? Możesz zrobić coś takiego jak narysowanie coraz większego białego paska u góry elementu listy, a następnie usunięcie go z listy. Pozostałe komórki nadal szarpnęły się w miejscu, ale lepiej niż nic.



2

Zhakowałem razem inny sposób, aby to zrobić bez konieczności manipulowania widokiem listy. Niestety zwykłe animacje na Androida zdają się manipulować zawartością wiersza, ale nieefektywnie zmniejszają widok. Więc najpierw rozważ ten moduł obsługi:

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);
}

Dodaj tę kolekcję do adaptera listy:

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

i dodaj

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

Następnie, gdziekolwiek chcesz ukryć wiersz, dodaj to:

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));

Otóż ​​to! Szybkość animacji można zmienić, zmieniając liczbę pikseli zmniejszających wysokość jednocześnie. Nie bardzo wyrafinowane, ale działa!


2

Po wstawieniu nowego wiersza do ListView, po prostu przewijam ListView do nowej pozycji.

ListView.smoothScrollToPosition(position);

1

Nie próbowałem tego, ale wygląda na to, że animateLayoutChanges powinien zrobić to, czego szukasz. Widzę to w klasie ImageSwitcher, zakładam, że to także w klasie ViewSwitcher?


Hej dzięki, przyjrzę się temu. Niestety wygląda na to, animateLayoutChanges jest nowy jak API Level 11 aka aka Honeycomb can not-use-to-on-any-komórkowe, jeszcze :(
Alex Pretzlav

1

Ponieważ Android jest open source, tak naprawdę nie trzeba ponownie wdrażać optymalizacji ListView. Możesz pobrać kod ListView i spróbować znaleźć sposób na włamanie się do animacji, możesz także otworzyć żądanie funkcji w Android trackerze błędów (a jeśli zdecydowałeś się go wdrożyć, nie zapomnij dodać łatki ).

Do Twojej wiadomości, kod źródłowy ListView jest tutaj .


1
Nie w ten sposób należy wprowadzić takie zmiany. Przede wszystkim powinieneś mieć budynek projektu AOSP - co jest dość ciężkim podejściem do dodawania animacji do widoków listy. Proszę spojrzeć na implementacje w różnych bibliotekach open source.
OrhanC1

1

Oto kod źródłowy, który umożliwia usuwanie wierszy i zmianę ich kolejności.

Dostępny jest również plik demonstracyjny APK. Usuwanie wierszy odbywa się bardziej zgodnie z aplikacją Gmaila Google, która odsłania widok z dołu po przesunięciu widoku z góry. Widok z dołu może mieć przycisk Cofnij lub cokolwiek chcesz.


1

Tak jak wyjaśniłem moje podejście na mojej stronie, udostępniłem link. W każdym razie chodzi o tworzenie map bitowych przez getdrawingcache. Posiadanie dwóch map bitowych i animowanie dolnej mapy bitowej w celu uzyskania efektu ruchomego

Zobacz następujący kod:

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);
        }
    });

Aby to zrozumieć, zobacz obrazy

Dzięki.


0

Zrobiłem coś podobnego do tego. Jednym z podejść jest interpolacja w czasie animacji wysokości widoku w czasie wewnątrz wierszy onMeasurepodczas wystawiania requestLayout()dla listView. Tak, może być lepiej zrobić bezpośrednio na liścieView code, ale było to szybkie rozwiązanie (wyglądało dobrze!)


0

Po prostu podziel się innym podejściem:

Pierwszy set widoku listy w Androidzie: animateLayoutChanges się prawdą :

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

Następnie używam modułu obsługi do dodawania elementów i aktualizowania widoku listy z opóźnieniem:

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;
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.