10

I try to animate new items in my ListView. I have stable id-s, so I know exactly which element to animate. The problem comes from the recycle mechanism of ListView. I call startAnimation on the View when I know I got a recently inserted element. But then, the view got recycled, filled with different data. It results on the UI animating the wrong row. At some point the view was holding the right data, but then got recycled. I confirmed this via logcat. Is there any way to solve this?

EDIT:

public ExpensCursorAdapter(Context context, Cursor c, boolean autoRequery,
        CopyOnWriteArraySet<String> fadeAnimateTags) {
    super(context, c, autoRequery);
    this.mFadeAnimTags = fadeAnimateTags;
}

@Override
public boolean hasStableIds() {
    return true;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
    setup(view, context, cursor);
}

private void setup(View view, Context context, Cursor cursor) {
    final String id = cursor.getString(4);
    if (LOCAL_LOGV) Log.v(TAG, String.format("Create item for %s. Received view: %s", id, view.toString()));
    view.setTag(id);
    final TextView dateText = (TextView) view.findViewById(R.id.date);
    final TextView timeText = (TextView) view.findViewById(R.id.time);
    final TextView title = (TextView) view.findViewById(R.id.title);
    final TextView amount = (TextView) view.findViewById(R.id.amount);
    final Date date = new Date(cursor.getLong(0));
    title.setText(cursor.getString(1));
    dateText.setText(dFormat.format(date));
    timeText.setText(tFormat.format(date));
    amount.setText(String.format("%d Ft", cursor.getInt(2)));
    if (cursor.getInt(3) == 1) {
        timeText.setTextColor(Color.LTGRAY);
        title.setTextColor(Color.LTGRAY);
        dateText.setTextColor(Color.LTGRAY);
        amount.setTextColor(Color.LTGRAY);
    } else {
        timeText.setTextColor(Color.BLACK);
        title.setTextColor(Color.BLACK);
        dateText.setTextColor(Color.BLACK);
        amount.setTextColor(Color.BLACK);
    }
    if (mFadeAnimTags.contains(id)) {
     view.setAnimation(AnimationUtils.loadAnimation(context, R.anim.fade));
     mFadeAnimTags.remove(id);
    }

}

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
    final LayoutInflater inflater = LayoutInflater.from(context);
    View view = inflater.inflate(R.layout.expense_list_item, parent, false);
    setup(view, context, cursor);
    return view;
}
Mate Gulyas
  • 609
  • 2
  • 8
  • 22
  • Where are you starting the animation? I think it should be on the getView() method of the Adapter. – manelizzard Aug 14 '11 at 08:45
  • I do start there. Code added to the question. – Mate Gulyas Aug 14 '11 at 13:37
  • Have you tried creating new Animation objects each time you "setup" the row? Maybe because you are using a static method, it always references the same animation and it changes from one row to another. – manelizzard Aug 14 '11 at 15:21
  • I create a new animation every time. I wouldn't see anything after the first one, if it has been the same. It's not. The problem is that due to view recycle, the view I set the animation for holds different data. – Mate Gulyas Aug 14 '11 at 17:42
  • did you solve the problem? I have the same code with bindView and newView and the same issue caused by view recycle, with a countdown textView on each row instead of your animation :( Please HELPPPPP! – Vale Aug 27 '11 at 19:00
  • I ended up converting the my whole layout with LinearLayout. And now I have 100 elements tops, and they are okay in LinearLayout. – Mate Gulyas Jan 20 '12 at 11:39
  • @gmate accept which one answer helps you. or post your answer & accept it. – Ranjithkumar Jun 20 '16 at 08:32
  • @RanjithKumar Unfortunately I don't know what was the solution. :( – Mate Gulyas Dec 04 '16 at 11:54
  • @gmate my answer was really beautiful animation. Try and then accept – Ranjithkumar Dec 05 '16 at 07:16

3 Answers3

8

If you use getView() simple code from http://kylewbanks.com/blog/Implementing-Google-Plus-Style-ListView-Animations-on-Android, (No need custom library)

private int lastPosition = -1;

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    //Load your view, populate it, etc...
    View view = ...;

    Animation animation = AnimationUtils.loadAnimation(getContext(), (position > lastPosition) ? R.anim.up_from_bottom : R.anim.down_from_top);
    view.startAnimation(animation);
    lastPosition = position;

    return view;
}

up_from_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
        android:fromXDelta="0%" android:toXDelta="0%"
        android:fromYDelta="100%" android:toYDelta="0%"
        android:duration="400" />
</set>

down_from_bottom.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:shareInterpolator="@android:anim/decelerate_interpolator">
    <translate
        android:fromXDelta="0%" android:toXDelta="0%"
        android:fromYDelta="-100%" android:toYDelta="0%"
        android:duration="400" />
</set>
Henry Ecker
  • 34,399
  • 18
  • 41
  • 57
Ranjithkumar
  • 16,071
  • 12
  • 120
  • 159
3

Have a look at the ListViewAnimations library to animate your ListView items.

Basically, you would only have to add two more lines to have your items animated:

Before:

MyListAdapter mAdapter = new MyListAdapter(this, getItems());
getListView().setAdapter(mAdapter);

After:

MyListAdapter mAdapter = new MyListAdapter(this, getItems());
AlphaInAnimationAdapter alphaInAnimationAdapter = new AlphaInAnimationAdapter(mAdapter);
alphaInAnimationAdapter.setAbsListView(getListView());
getListView().setAdapter(alphaInAnimationAdapter);
nhaarman
  • 98,571
  • 55
  • 246
  • 278
  • 1
    +1 Really nice library. Small concern, once listview loaded, I need to add additional items to listview, when adding additional items, I need those items to be animated(animate only newly added items while adding), does that support in this library? – Yasitha Waduge Aug 10 '13 at 14:26
1

I had a similar problem. I'm not 100% sure if this is the right way to go, but you can try clearing the animation on the view before starting a new one to cancel any leftover animation from the recycled view. This helped me.

if(flag == false) {
    v.clearAnimation();
    Animation animation = AnimationUtils.loadAnimation(getActivity(), R.anim.slide_top_to_bottom);
    v.startAnimation(animation);
}
tokudu
  • 2,250
  • 23
  • 22