Search code examples
androidlistviewanimationlistadapter

Dynamically add and animate new ListView item on the beginning - scrolling issue


I have a ListFragment, with a simple ListView. I'm adding new items to this ListView dynamically, when there is a new message on a WebSocket. I'm adding the new row to the beginning of my ArrayAdapter, and there is a simple animation when new item added.

The problem is, when animation is going on, and I'm trying to scroll, older items are also animated on the ListView, only when I'm scrolling fast.

Here is the ListAdapter:

package adapters;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.ArrayList;

import custom_view.CompassView;
import hu.horadiz.ehoghu.blitzinfo.R;
import logger.AndroidLogger;
import model.BlitzLive;

/**
 * Created by ehog on 2014.08.13..
 */
public class LiveArrayAdapter extends ArrayAdapter<BlitzLive> {

    private ArrayList<BlitzLive> mLiveData;
    private Context mContext;
    private String[] mCompass;
    private Integer mRecentAddedIndex;
    private BlitzLive mRecentAdded;
    private boolean mRecentAnimated;

    private final static DateFormat DATE_FORMAT = new SimpleDateFormat("HH:MM:SS");


    public LiveArrayAdapter(Context context, ArrayList<BlitzLive> mLiveData) {
        super(context, 0, mLiveData);
        this.mLiveData = mLiveData;
        this.mContext = context;
        mCompass = mContext.getResources().getStringArray(R.array.compass);
    }

    @Override
    public synchronized void add(BlitzLive object) {
        mLiveData.add(0,object);
        if (mLiveData.size() > 120)
        {
            mLiveData.remove(mLiveData.size()-1);
        }
        mRecentAddedIndex = 0;
        mRecentAnimated = true;
        mRecentAdded = object;
        notifyDataSetChanged();
    }

    private String coords(Double lat, Double lng)
    {
        String latitude_text = lat >= 0 ? mCompass[0] : mCompass[8];
        String longitude_text = lng >= 0 ? mCompass[4] : mCompass[12];

        DecimalFormat nf = new DecimalFormat();
        DecimalFormatSymbols p=new DecimalFormatSymbols();
        p.setDecimalSeparator('.');
        nf.setDecimalFormatSymbols(p);

        nf.setMaximumFractionDigits(3);
        nf.setMinimumFractionDigits(3);

        return  String.format("%s %s, %s %s",nf.format(lat),latitude_text,nf.format(lng),longitude_text);
    }

    @Override
    public int getCount() {
        return mLiveData.size();
    }


    @Override
    public synchronized View getView(int i, View view, ViewGroup viewGroup) {
        if (view == null) {
            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            view = inflater.inflate(R.layout.holder_blitzes_live, null);
        }

        TextView settlem = (TextView)view.findViewById(R.id.live_settlem);
        TextView data = (TextView)view.findViewById(R.id.live_data);
        TextView distancemarker = (TextView)view.findViewById(R.id.live_distancemarker);

        DecimalFormat nf = new DecimalFormat();
        DecimalFormatSymbols p=new DecimalFormatSymbols();
        p.setDecimalSeparator('.');
        nf.setDecimalFormatSymbols(p);

        nf.setMaximumFractionDigits(2);
        nf.setMinimumFractionDigits(2);

        if (!mLiveData.get(i).Settlement.equals(""))
        {
            settlem.setText(mLiveData.get(i).Settlement);
            data.setText(String.format("%s km, %s (%s)", nf.format(mLiveData.get(i).Distance), mCompass[(int)((((mLiveData.get(i).Bearing + 11.0) / 22.5) % 16))],coords(mLiveData.get(i).Latitude,mLiveData.get(i).Longitude)));
        }
        else
        {
            settlem.setText(coords(mLiveData.get(i).Latitude,mLiveData.get(i).Longitude));
            data.setText(String.format("%s km, %s", nf.format(mLiveData.get(i).Distance), mCompass[(int)((((mLiveData.get(i).Bearing + 11.0) / 22.5) % 16))]));
        }
        CompassView compass = (CompassView)view.findViewById(R.id.live_compass);
        compass.setBaseBearing(new Float(mLiveData.get(i).Bearing));
        ImageView flag = (ImageView)view.findViewById(R.id.live_flag);
        if (mLiveData.get(i).CountryCode.equals("--"))
        {
            flag.setImageResource(mContext.getResources().getIdentifier(String.format("_cf_big_%s","empty"), "drawable", "hu.horadiz.ehoghu.blitzinfo"));
        }
        else
        {
            flag.setImageResource(mContext.getResources().getIdentifier(String.format("_cf_big_%s", mLiveData.get(i).CountryCode), "drawable", "hu.horadiz.ehoghu.blitzinfo"));
        }

        if (mLiveData.get(i).Distance >= 1000.0)
        {
            distancemarker.setBackgroundResource(R.drawable.groupedblitzes_count_background_1000plus);
        }
        else if (mLiveData.get(i).Distance >= 200.0 && mLiveData.get(i).Distance < 1000.0)
        {
            distancemarker.setBackgroundResource(R.drawable.groupedblitzes_count_background_200plus);
        }
        else if (mLiveData.get(i).Distance >= 50.0 && mLiveData.get(i).Distance < 200.0)
        {
            distancemarker.setBackgroundResource(R.drawable.groupedblitzes_count_background_50plus);
        }
        else if (mLiveData.get(i).Distance >= 20.0 && mLiveData.get(i).Distance < 50.0)
        {
            distancemarker.setBackgroundResource(R.drawable.groupedblitzes_count_background_20plus);
        }
        else if (mLiveData.get(i).Distance >= 5.0 && mLiveData.get(i).Distance < 20.0)
        {
            distancemarker.setBackgroundResource(R.drawable.groupedblitzes_count_background_5plus);
        }
        else if (mLiveData.get(i).Distance >= 0.0 && mLiveData.get(i).Distance < 5.0)
        {
            distancemarker.setBackgroundResource(R.drawable.groupedblitzes_count_background_0plus);
        }

        //This if is false when the wrong item is animates, but the animation is still occurs
        if (mRecentAddedIndex == i && mRecentAnimated && mRecentAdded.equals(mLiveData.get(i)))
        {
            mRecentAnimated = false;
            Animation ANIM_APPEAR = AnimationUtils.loadAnimation(getContext(), R.anim.push_right_in);
            ANIM_APPEAR.setDuration(500);
            view.findViewById(R.id.live_holder_parent).startAnimation(ANIM_APPEAR);
            AndroidLogger.PushDebugMessage("Animáció megtörtént: "+mRecentAddedIndex+" "+mLiveData.get(i).Settlement);
        }


        return view;
    }
}

and the ListFragment:

package fragments;

import android.app.Fragment;
import android.app.ListFragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import org.json.JSONObject;

import java.util.ArrayList;

import adapters.LiveArrayAdapter;
import hu.horadiz.ehoghu.blitzinfo.R;
import logger.AndroidLogger;
import model.BlitzLive;
import using_net.BlitzLiveDataAsyncTask;

/**
 * Created by ehog on 2014.08.12..
 */
public class LiveDataFragment extends ListFragment implements BlitzLiveDataAsyncTask.OnMessage
{
    private BlitzLiveDataAsyncTask _mLiveAsyncTask;
    private LiveArrayAdapter mListAdapter;


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View v = inflater.inflate(R.layout.fragment_live, container, false);
        return v;
    }

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        setHasOptionsMenu(true);
        super.onCreate(savedInstanceState);
        mListAdapter = new LiveArrayAdapter(getActivity(),new ArrayList<BlitzLive>());
        mListAdapter.setNotifyOnChange(true);
        setListAdapter(mListAdapter);
    }

    @Override
    public void onStart()
    {
        if (getActivity() != null)
        {
            getActivity().setTitle(getActivity().getResources().getString(R.string.title_liveview));
        }
        super.onStart();
        if (_mLiveAsyncTask != null)
        {
            _mLiveAsyncTask.cancel(true);
        }
        _mLiveAsyncTask = new BlitzLiveDataAsyncTask(this);
        _mLiveAsyncTask.execute();
    }

    @Override
    public void onPause() {
        super.onPause();
        if (_mLiveAsyncTask != null)
        {
            _mLiveAsyncTask.stop();
            _mLiveAsyncTask.cancel(true);
            AndroidLogger.PushDebugMessage("onPause cancel");
        }
    }

    @Override
    public void onCreateOptionsMenu (Menu menu, MenuInflater inflater)
    {
        menu.clear();
        inflater.inflate(R.menu.blitz_live, menu);
        super.onCreateOptionsMenu(menu, inflater);
    }


    @Override
    public void onMessage(final JSONObject message)
    {
        getActivity().runOnUiThread(new Runnable() {
            public void run() {
                ((LiveArrayAdapter)getListAdapter()).add(BlitzLive.fromJSON(message));
            }
        });

        AndroidLogger.PushDebugMessage(String.format("Websocket message: %s", message.toString()));
    }


}

Solution

  • yes recycling is the problem. When you add the animation in your adapter, you have to put a "remove animation code":

     if (mRecentAddedIndex == i && mRecentAnimated && mRecentAdded.equals(mLiveData.get(i)))
            {
                mRecentAnimated = false;
                Animation ANIM_APPEAR = AnimationUtils.loadAnimation(getContext(), R.anim.push_right_in);
                ANIM_APPEAR.setDuration(500);
                view.findViewById(R.id.live_holder_parent).startAnimation(ANIM_APPEAR);
                AndroidLogger.PushDebugMessage("Animáció megtörtént: "+mRecentAddedIndex+" "+mLiveData.get(i).Settlement);
            } else {
    // remove animation