0

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


}
Adel
  • 19
  • 7

2 Answers2

1

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

W4R10CK
  • 5,502
  • 2
  • 19
  • 30
  • Thank you so much man, it worked! I've seen a lot of posts about the same problem, and your solution is by far the best one. However, I don't know why it worked just by overriding these methods (would be great if you tell me)... But you saved me so much time, thanks again! – Adel Sep 02 '18 at 11:01
  • @Adel Its alright. I'll explain in this post later. Don't forget to accept answer and upvote. – W4R10CK Sep 02 '18 at 11:04
  • It is worth noting that while this works, it is essentially disabling view recycling. The better solution would be to properly implement view recycling (separate the data displayed from the view used) since this example uses the same layout for each row anyway. That means not putting so much data on the `ViewHolder`, it should just hold views. – Tyler V Sep 02 '18 at 15:03
0
Hey you can create list of viewHolder class and and handle each view with timer.


public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {
    private final List<MyViewHolder> lstHolders;
    private Context context;
    public MyRecyclerAdapter(Context context) {
        super();
        this.context = context;
        lstHolders = new ArrayList<>();
        startUpdateTimer();
    }
    private Handler mHandler = new Handler();
    private Runnable updateRemainingTimeRunnable = new Runnable() {
        @Override
        public void run() {
            synchronized (lstHolders) {
                long currentTime = System.currentTimeMillis();
                for (MyViewHolder holder : lstHolders) {
                    holder.updateTimeRemaining(currentTime);
                }
            }
        }
    };
    private void startUpdateTimer() {
        Timer tmr = new Timer();
        tmr.schedule(new TimerTask() {
            @Override
            public void run() {
                mHandler.post(updateRemainingTimeRunnable);
            }
        }, 1000, 1000);
    }
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recycler_view_item,parent,false);

        return new MyViewHolder(view);
    }
    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.setData(position);
        synchronized (lstHolders) {
            lstHolders.add(holder);
        }
        holder.updateTimeRemaining(System.currentTimeMillis());
    }
    @Override
    public int getItemCount() {
        return 50;
    }
    public class MyViewHolder extends RecyclerView.ViewHolder{
        TextView tvTimeRemaining;
        public MyViewHolder(View itemView) {
            super(itemView);
            tvTimeRemaining = itemView.findViewById(R.id.txt_view);
        }
        public void updateTimeRemaining(long currentTime) {
            String result = getData("09/21/2018 15:00:53");
            String[] terms = result.split(",");
            long expirationTime = getInitialTimeSecond(terms);
           /* if(Integer.parseInt(terms[0])>=1){
                expirationTime = 24*60*60*60*1000;
            }else{
                expirationTime   = getInitialTimeSecond(terms);
            }*/
            long timeDiff = expirationTime;
            if (timeDiff > 0) {
                int seconds = (int) (timeDiff / 1000) % 60;
                int minutes = (int) ((timeDiff / (1000 * 60)) % 60);
                int hours = (int) ((timeDiff / (1000 * 60 * 60)) % 24);
                Spannable hourString = new SpannableString(String.valueOf(hours));
                BackgroundColorSpan backgroundSpan = new BackgroundColorSpan(Color.RED);
                hourString.setSpan(backgroundSpan, 0, hourString.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);


                tvTimeRemaining.setText(hourString + " : " + minutes + " : " + seconds + "");
            } else {
                tvTimeRemaining.setText("Expired!!");
            }
        }
        public void setData(int position) {
            updateTimeRemaining(System.currentTimeMillis());
        }
    }
    private long getInitialTimeSecond(String[] terms) {
        long mInitialTime = DateUtils.DAY_IN_MILLIS * Integer.parseInt(terms[0]) +
                DateUtils.HOUR_IN_MILLIS * Integer.parseInt(terms[1]) +
                DateUtils.MINUTE_IN_MILLIS * Integer.parseInt(terms[2]) +
                DateUtils.SECOND_IN_MILLIS * Integer.parseInt(terms[3]);
        return mInitialTime;
    }
private String getData(String dateStop) {
        //Calendar c = Calendar.getInstance();
        //HH converts hour in 24 hours format (0-23), day calculation
        SimpleDateFormat format = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
        Date d1 = Calendar.getInstance().getTime();
        Date d2 = null;
        String result = null;
        try {
            // d1 = format.parse(c.getTime().toString());
            d2 = format.parse(dateStop);
            //in milliseconds
            long diff = d2.getTime() - d1.getTime();
            long diffSeconds = diff / 1000 % 60;
            long diffMinutes = diff / (60 * 1000) % 60;
            long diffHours = diff / (60 * 60 * 1000) % 24;
            long diffDays = diff / (24 * 60 * 60 * 1000);
            result = diffDays + "," + diffHours + "," + diffMinutes + "," + diffSeconds;
            System.out.print(diffDays + " days, ");
            System.out.print(diffHours + " hours, ");
            System.out.print(diffMinutes + " minutes, ");
            System.out.print(diffSeconds + " seconds.");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
if you have any doubt,Let me know.
Mangal
  • 56
  • 3