How to Animate Addition or Removal of Android ListView Rows
Asked Answered
E

14

218

In iOS, there is a very easy and powerful facility to animate the addition and removal of UITableView rows, here's a clip from a youtube video showing the default animation. Note how the surrounding rows collapse onto the deleted row. This animation helps users keep track of what changed in a list and where in the list they were looking at when the data changed.

Since I've been developing on Android I've found no equivalent facility to animate individual rows in a TableView. Calling notifyDataSetChanged() on my Adapter causes the ListView to immediately update its content with new information. I'd like to show a simple animation of a new row pushing in or sliding out when the data changes, but I can't find any documented way to do this. It looks like LayoutAnimationController might hold a key to getting this to work, but when I set a LayoutAnimationController on my ListView (similar to ApiDemo's LayoutAnimation2) and remove elements from my adapter after the list has displayed, the elements disappear immediately instead of getting animated out.

I've also tried things like the following to animate an individual item when it is removed:

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

However, the rows surrounding the animated row don't move position until they jump to their new positions when notifyDataSetChanged() is called. It appears ListView doesn't update its layout once its elements have been placed.

While writing my own implementation/fork of ListView has crossed my mind, this seems like something that shouldn't be so difficult.

Thanks!

Epperson answered 13/10, 2010 at 21:29 Comment(3)
did someone find the answer to this? pls shareeeTonina
@Alex: have you done that animation like that youtube video (like iphone), If you have any demo or link for android then please give me, I have tried using animateLayoutChanges in xml file, but it is not exactly like iphoneMonniemono
@Jayesh, unfortunately I no longer work on Android development. I haven't tested any of the answers to this question that were written after around 2012.Epperson
B
127
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());

for top-to-down animation 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>
Brominate answered 28/7, 2011 at 10:51 Comment(11)
It's better to use anim.setAnimationListener(new AnimationListener() { ... }) instead of using a HandlerLee
what is populateList() for?Melva
@OlegVaskevich and why is that?Campanile
@MrAppleBR for several reasons. First, you're not creating an unneeded Handler. Second, it makes the code less complex by already using a built-in callback for when the animation finishes. Third, you don't know how the implementation of Handler and Animation will work on each platform; it may be possible that your delayed runnable happens before the animation finishes.Lee
@OlegVaskevich allright.. i really didn't know why it was better.. well explained!Campanile
"However, the rows surrounding the animated row don't move position until they jump to their new positions when notifyDataSetChanged() is called." this problem still persists in your solution.Tropine
What is FavouritesManager class and populateList() method ??Monniemono
The delete animation is not working for me. If the ListView is scrolled to index 0, it works fine. However, if I scroll to some arbitrary part of the list and delete an item, the animation will happen on a different list item.Hadfield
While this animates the view, the height of the view remains the same until the animation ends. A more professional solution would reduce the height of the row during animation. A more convoluted solution that does that can be found at: youtube.com/…Encouragement
@Hadfield I had to get the position of the element deleted and subtract from listView.getFirstVisiblePosition() to get the visible index. works like a charm past that!Depew
Can anyone please tell whether to add this code in onCreate()?Trinetta
P
15

The RecyclerView takes care of adding, removing, and re-ordering animations!

RecyclerView in action

This simple AndroidStudio project features a RecyclerView. take a look at the commits:

  1. commit of the classic Hello World Android app
  2. commit, adding a RecyclerView to the project (content not dynamic)
  3. commit, adding functionality to modify content of RecyclerView at runtime (but no animations)
  4. and finally...commit adding animations to the RecyclerView
Payton answered 7/7, 2015 at 0:50 Comment(3)
I think this is the simple way when you need a simple animation, but you can also custom the animation using the RecyclerView.setItemAnimator() method.Metanephros
The final step is the missing link I had. stable Id's. Thanks!Unzip
This is a great sample project that demonstrates the API well.Abruption
P
10

Take a look at the Google solution. Here is a deletion method only.

ListViewRemovalAnimation project code and Video demonstration

It needs Android 4.1+ (API 16). But we have 2014 outside.

Pathognomy answered 31/7, 2014 at 17:36 Comment(0)
M
3

Since ListViews are highly optimized i think this is not possible to accieve. Have you tried to create your "ListView" by code (ie by inflating your rows from xml and appending them to a LinearLayout) and animate them?

Manyplies answered 16/10, 2010 at 23:31 Comment(3)
It doesn't make sense to reimplement the listview just to add animations.Thrawn
I certainly could just use a LinearLayout, however with very large datasets LinearLayout's performance will become abysmal. ListView has lots of smart optimizations around view recycling that allow it to handle large data sets (iOS's UITableView has the same optimizations). Writing my own view recycler falls under the category of reimplementing ListView :(Epperson
I know it doesnt make sense - but if you work with just some few rows the benefit of having nice in/out animations could be worth the try.Manyplies
E
3

Have you considered animating a sweep to the right? You could do something like drawing a progressively larger white bar across the top of the list item, then removing it from the list. The other cells would still jerk into place, but it'd better than nothing.

Exertion answered 17/10, 2010 at 22:3 Comment(0)
K
3

I hacked together another way to do it without having to manipulate list view. Unfortunately, regular Android Animations seem to manipulate the contents of the row, but are ineffectual at actually shrinking the view. So, first consider this handler:

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

Add this collection to your List adapter:

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

and add

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

Next, wherever it is that you want to hide a row, add this:

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

That's it! You can change the speed of the animation by altering the number of pixels that it shrinks the height at once. Not real sophisticated, but it works!

Kim answered 17/3, 2011 at 3:47 Comment(0)
C
3

call listView.scheduleLayoutAnimation(); before changing the list

Chessy answered 13/12, 2012 at 6:7 Comment(0)
D
3

After inserting new row to ListView, I just scroll the ListView to new position.

ListView.smoothScrollToPosition(position);
Dynamiter answered 21/1, 2015 at 11:1 Comment(0)
H
2

Since Android is open source, you don't actually need to reimplement ListView's optimizations. You can grab ListView's code and try to find a way to hack in the animation, you can also open a feature request in android bug tracker (and if you decided to implement it, don't forget to contribute a patch).

FYI, the ListView source code is here.

Hallee answered 17/10, 2010 at 5:20 Comment(1)
This isn't the way that such changes should be implemented. Before anything, you should have the AOSP project building - which is quite a heavy handed approach to adding animation to list views. Please look at implementations in various open source libraries.Geniculate
B
2

I haven't tried it but it looks like animateLayoutChanges should do what you're looking for. I see it in the ImageSwitcher class, I assume it's in the ViewSwitcher class as well?

Borodino answered 19/6, 2011 at 15:38 Comment(1)
Hey thanks, I'll look into that. Unfortunately it looks like animateLayoutChanges is new as of API Level 11 aka Honeycomb aka can't-use-this-on-any-phones-yet :(Epperson
H
2

As i had explained my approach in my site i shared the link.Anyways the idea is create bitmaps by getdrawingcache .have two bitmap and animate the lower bitmap to create the moving effect

Please see the following code:

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

For understanding with images see this

Thanks.

Handset answered 4/11, 2012 at 15:24 Comment(0)
E
2

Here's the source code to let you delete rows and reorder them.

A demo APK file is also available. Deleting rows is done more along the lines of Google's Gmail app that reveals a bottom view after swiping a top view. The bottom view can have an Undo button or whatever you want.

Encouragement answered 10/7, 2015 at 12:44 Comment(0)
R
0

I have done something similar to this. One approach is to interpolate over the animation time the height of the view over time inside the rows onMeasure while issuing requestLayout() for the listView. Yes it may be be better to do inside the listView code directly but it was a quick solution (that looked good!)

Refection answered 4/5, 2012 at 13:9 Comment(0)
P
0

Just sharing another approach:

First set the list view's android:animateLayoutChanges to true:

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

Then I use a handler to add items and update the listview with delay:

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;
}
Poulard answered 10/8, 2016 at 16:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.