androidlistviewandroid-recyclerviewconvertview

Items confusing when scrolling Listview (recycler)


I display a countdown timer in each item of my listvew (in a TextView), that I update every second. It works perfectly as every item has its very own correct timer. But whenever the listview gets longer and obliges me to scroll down or up, the items get confused and the first hidden item displays not his timer but rather that of the first one, it's pretty much confusing to get what is happening. I know it has something to do with the listview recycler, and that this is how listview works. But I need to solve this problem and I don't know how.

Also, if I remove the statement if(convertView == null), it'll get fixed. But the listview will become extremely slow to load when I scroll.

Here's the custom adapter I'm using :

public class TicketAdapter extends ArrayAdapter<TicketModel> implements View.OnClickListener{

    private ArrayList<TicketModel> dataSet;
    Context mContext;


    // View lookup cache
    private class ViewHolder {
        TextView txtName;
        TextView txtType;
        TextView txtTempsRestant;
        TextView txtDate;
        TextView txtSLA;
        ImageView info;
        RelativeLayout layout;

        Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {

                Bundle bundle = msg.getData();
                long timeLeftMS = bundle.getLong("time");


                int day = (int) ((timeLeftMS / (24*3600000)));
                int hour = (int) ((timeLeftMS / (1000*60*60)) % 24);
                int minute = (int) ((timeLeftMS / (60000)) % 60);
                int second = (int)timeLeftMS % 60000 / 1000;

                String timeLeftText = "";

                if (day<10) timeLeftText += "0";
                timeLeftText += day;
                timeLeftText += ":";
                if (hour<10) timeLeftText += "0";
                timeLeftText += hour;
                timeLeftText += ":";
                if (minute<10) timeLeftText += "0";
                timeLeftText += minute;
                timeLeftText += ":";
                if (second<10) timeLeftText += "0";
                timeLeftText += seconde;

                txtTempsRestant.setText(timeLeftText); //----- This is where I'm updating my textview every second ------


            }
        };

        Handler handlerLate = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                Bundle bundle = msg.getData();
                long time = bundle.getLong("time");
                if (time == -1){
                    txtTempsRestant.setText("Ancient version");
                    txtTempsRestant.setTextColor(Color.parseColor("#434343"));
                }
                else {
                    txtTempsRestant.setText("Late");
                    txtTempsRestant.setTextColor(Color.parseColor("#434343"));
                    layout.setBackgroundColor(Color.parseColor("#3caa0000"));
                    info.setImageResource(R.drawable.haute);
                }
            }
        };

        Handler handlerFinishLate = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                Bundle bundle = msg.getData();
                String Nom = bundle.getString("name");
                String idTicket = bundle.getString("id");

                txtTempsRestant.setText("Late");
                txtTempsRestant.setTextColor(Color.parseColor("#434343"));
                layout.setBackgroundColor(Color.parseColor("#3caa0000"));
                info.setImageResource(R.drawable.haute);

            }
        };

        Handler handlerAttente = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                txtTempsRestant.setText("En attente...");
                txtTempsRestant.setTextColor(Color.parseColor("#434343"));
                layout.setBackgroundColor(Color.parseColor("#949494"));
                info.setImageResource(R.drawable.enattente);
            }
        };

        public void startTimer(long timeLeftMS, String statut, final String Nom, final String idTicket) {
            if(statut.equals("4")){
                handlerAttente.sendEmptyMessage(0);
            }
            else{
                if (timeLeftMS<0){
                    //handlerLate.sendEmptyMessage(0);
                    Bundle bundle = new Bundle();
                    bundle.putLong("time", timeLeftMS);
                    Message message = new Message();
                    message.setData(bundle);
                    handlerLate.sendMessage(message);
                }
                else{
                    CountDownTimer countDownTimer = new CountDownTimer(timeLeftMS, 1000) {

                        @Override
                        public void onTick(long l) {
                            Bundle bundle = new Bundle();
                            bundle.putLong("time", l);
                            bundle.putString("name", Nom);
                            bundle.putString("id", idTicket);
                            Message message = new Message();
                            message.setData(bundle);
                            handler.sendMessage(message);
                        }

                        @Override
                        public void onFinish() {
                            Bundle bundle = new Bundle();
                            bundle.putString("name", Nom);
                            bundle.putString("id", idTicket);
                            Message message = new Message();
                            message.setData(bundle);
                            handlerFinishLate.sendMessage(message);
                        }
                    }.start();
                }
            }

        }

    }


    public TicketAdapter(ArrayList<TicketModel> data, Context context) {
        super(context, R.layout.row_item_ticket, data);
        this.dataSet = data;
        this.mContext=context;
    }


    @Override
    public void onClick(View v) {
        int position=(Integer) v.getTag();
        Object object= getItem(position);
        TicketModel TicketModel=(TicketModel)object;

        switch (v.getId())
        {
            case R.id.item_info:

                Snackbar.make(v, "Late? : " +TicketModel.isTicketEnRetard(), Snackbar.LENGTH_LONG)
                        .setAction("No action", null).show();
                break;
        }
    }

    private int lastPosition = -1;


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        // Get the data item for this position
        TicketModel TicketModel = getItem(position);
        // Check if an existing view is being reused, otherwise inflate the view
        final ViewHolder viewHolder; // view lookup cache stored in tag

        final View result;
        long timeLeft;
        String Statut;
        String Nom;
        String idTicket;
        if (convertView == null) {

            viewHolder = new ViewHolder();
            LayoutInflater inflater = LayoutInflater.from(getContext());
            convertView = inflater.inflate(R.layout.row_item_ticket, parent, false);
            viewHolder.txtName = (TextView) convertView.findViewById(R.id.titreTV);
            viewHolder.txtDate = (TextView) convertView.findViewById(R.id.dateTV);
            viewHolder.txtSLA = (TextView) convertView.findViewById(R.id.slaTV);
            viewHolder.txtTempsRestant = (TextView) convertView.findViewById(R.id.SLARestantTV);
            viewHolder.info = (ImageView) convertView.findViewById(R.id.item_info);
            viewHolder.layout = (RelativeLayout) convertView.findViewById(R.id.backgroundRow);

            timeLeft = Long.valueOf(TicketModel.getTempsRestantTicket());
            Statut = TicketModel.getStatut();
            Nom = TicketModel.getTitreTicket();
            idTicket = TicketModel.getIdTicket();

            result=convertView;

            viewHolder.startTimer(timeLeft, Statut, Nom, idTicket);

            convertView.setTag(viewHolder);

        } else {
            viewHolder = (ViewHolder) convertView.getTag();
            result=convertView;
        }

        viewHolder.txtName = (TextView) convertView.findViewById(R.id.titreTV);
        viewHolder.txtDate = (TextView) convertView.findViewById(R.id.dateTV);
        viewHolder.txtSLA = (TextView) convertView.findViewById(R.id.slaTV);
        viewHolder.txtTempsRestant = (TextView) convertView.findViewById(R.id.SLARestantTV);
        viewHolder.info = (ImageView) convertView.findViewById(R.id.item_info);
        viewHolder.layout = (RelativeLayout) convertView.findViewById(R.id.backgroundRow);

        timeLeft = Long.valueOf(TicketModel.getTempsRestantTicket());
        Statut = TicketModel.getStatut();
        Nom = TicketModel.getTitreTicket();
        idTicket = TicketModel.getIdTicket();


        lastPosition = position;

        viewHolder.txtName.setText(TicketModel.getTitreTicket());
        viewHolder.txtDate.setText(TicketModel.getDateTicket());
        viewHolder.txtSLA.setText(TicketModel.getSlaTicket());
        if (Long.valueOf(TicketModel.getTempsRestantTicket())<0){
            viewHolder.txtTempsRestant.setText("Late");
        }

        viewHolder.layout.setBackgroundColor(getColorBG(TicketModel.isTicketEnRetard()));
        viewHolder.info.setOnClickListener(this);
        viewHolder.info.setTag(position);

        // Return the completed view to render on screen
        return convertView;
    }


}

Solution

  • Also, if I remove the statement if(convertView == null), it'll get fixed. But the listview will become extremely slow to load when I scroll.

    ListView works on the concept of recycling the scraped view. When you scroll up and the view present on 1st state gets scraped and the new view which appears on the bottom is the same view which was scraped before.

    enter image description here

    To overcome the issue, the the view know how many types of view :

    @Override
    public int getViewTypeCount() {
        return getCount();
    }
    
    @Override
    public int getItemViewType(int position) {
        return position;
    }
    
    @Override
    public int getCount() {
        return dataSet.size();
    }
    
    @Override
    public Object getItem(int position) {
        return dataSet.get(position);
    }
    
    @Override
    public long getItemId(int position) {
        return 0;
    }
    

    Also make sure you init. ArrayList as private ArrayList<TicketModel> dataSet = new ArrayList<>();