8

I have a Recyclerview , and I need to display a countdown on every row.

Here is a similar question coutndown timers in listview It has a good solution , but I need that with recyclerview

enter image description here

Edit:

Here is my code Adapter MyAdapter:

 public class AdapterItems extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

private ArrayList<TopCompetitions> mListItems = new ArrayList<>();
private ImageLoader mImageLoader;
private Context context;
private Handler handler;
/******************************************/
String current_date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d1 = null;
Date d2 = null;
long diff;
long diffSeconds;
long diffMinutes;
long diffHours;
long diffDays;

String reachableDate = "";
/******************************************/
private ScheduledFuture updateFuture;

public AdapterItems(Context context) {

    this.context = context;
    mImageLoader = AppController.getInstance().getImageLoader();

}

public void setmListItems(ArrayList<TopCompetitions> mListItems) {
    this.mListItems = mListItems;
    //update the adapter to reflect the new set of mListItems
    notifyDataSetChanged();
}


@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {


    View itemView = LayoutInflater.
            from(parent.getContext()).
            inflate(R.layout.custom_horizontal_row, parent, false);
    return new ItemHolder(itemView);


}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {


    final TopCompetitions currentItem = mListItems.get(position);
    final ItemHolder itemHolder = (ItemHolder) holder;

   /* start_date , name_com_ar , name_com_en,
            question_en,answer_ar1,answer_ar2,answer_ar3
            ,answer_en1,answer_en2,answer_en3,right_answer;
    */
    itemHolder.item_id.setText(currentItem.getPrize_id());
    itemHolder.item_description.setText(currentItem.getName_com_ar());
    itemHolder.start_date.setText(currentItem.getStart_date());
    itemHolder.end_date.setText(currentItem.getEnd_date());
    itemHolder.name_com_ar.setText(currentItem.getName_com_ar());
    itemHolder.name_com_en.setText(currentItem.getName_com_en());
    itemHolder.answer_en1.setText(currentItem.getAnswer_en1());
    itemHolder.answer_en2.setText(currentItem.getAnswer_en2());
    itemHolder.answer_en3.setText(currentItem.getAnswer_en3());
    itemHolder.answer_ar1.setText(currentItem.getAnswer_ar1());
    itemHolder.answer_ar2.setText(currentItem.getAnswer_ar2());
    itemHolder.answer_ar3.setText(currentItem.getAnswer_ar3());
    itemHolder.right_answer.setText(currentItem.getRight_answer());
    itemHolder.question_en.setText(currentItem.getQuestion_en());
    itemHolder.question_ar.setText(currentItem.getQuestion_ar());
    itemHolder.desc_ar.setText(currentItem.getPrize_desc_ar());
    itemHolder.desc_en.setText(currentItem.getPrize_desc_en());

    String urlLogo = currentItem.getPrize_pic1();
    loadImages(urlLogo, itemHolder);
    setDefferinceTimer(itemHolder , currentItem.getEnd_date());


    if (updateFuture == null) {
        final Handler mainHandler = new Handler(Looper.getMainLooper());
        updateFuture = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                setDefferinceTimer(itemHolder , currentItem.getEnd_date());
                notifyDataSetChanged();
                mainHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        notifyDataSetChanged();
                    }
                });
            }
        }, 0, 1000, TimeUnit.MILLISECONDS);
    }

  /*  new Handler(Looper.getMainLooper()).post(new Runnable() {
        @Override
        public void run() {
            new CountDownTimer(20000, 1000) {

                public void onTick(long millisUntilFinished) {
                    startCountDown(itemHolder, currentItem.getEnd_date() + " 00:00:00");
                    notifyDataSetChanged();
                }

                public void onFinish() {
                    //counterTextView.setText("done!");
                }
            }.start();
        }
    });
       */

}

public void setDefferinceTimer(final RecyclerView.ViewHolder holder , String itemEndDate){

    final ItemHolder itemHolder = (ItemHolder) holder;

    current_date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
   // reachableDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(itemEndDate);

    try {
        d1 = format.parse(current_date);
        d2 = format.parse(itemEndDate+" 00:00:00");
    } catch (ParseException e) {
        e.printStackTrace();
    }

    diff = d2.getTime() - d1.getTime();

    diffSeconds = diff / 1000 % 60;
    diffMinutes = diff / (60 * 1000) % 60;
    diffHours = diff / (60 * 60 * 1000) % 24;
    diffDays = diff / (24 * 60 * 60 * 1000);

    itemHolder.days_tf.setText(""+diffDays);
    itemHolder.hours_tf.setText(""+diffHours);
    itemHolder.minutes_tf.setText(""+diffMinutes);
    itemHolder.seconds_tf.setText(""+diffSeconds);

}

private void loadImages(String urlThumbnail, final RecyclerView.ViewHolder holder) {
    final ItemHolder itemHolder = (ItemHolder) holder;
    mImageLoader.get(urlThumbnail, new ImageLoader.ImageListener() {
        @Override
        public void onResponse(ImageLoader.ImageContainer response, boolean isImmediate) {
            itemHolder.item_image.setImageBitmap(response.getBitmap());
            //holder.salon_gender.setImageBitmap(response.getBitmap());
        }

        @Override
        public void onErrorResponse(VolleyError error) {

        }
    });
}


@Override
public int getItemCount() {
    return mListItems.size();
}


private class ItemHolder extends RecyclerView.ViewHolder {

    public TextView item_id, item_description, end_date,
            start_date, name_com_ar, name_com_en, question_ar,
            question_en, answer_ar1, answer_ar2, answer_ar3, answer_en1,
            answer_en2, answer_en3, right_answer , desc_ar , desc_en;
    public TextView days_tf, hours_tf, minutes_tf, seconds_tf;
    public CircleImageView item_image;

    public ItemHolder(View itemView) {
        super(itemView);

        start_date = (TextView) itemView.findViewById(R.id.start_date);
        end_date = (TextView) itemView.findViewById(R.id.end_date);
        name_com_ar = (TextView) itemView.findViewById(R.id.name_com_ar);
        name_com_en = (TextView) itemView.findViewById(R.id.name_com_en);
        question_en = (TextView) itemView.findViewById(R.id.question_en);
        question_ar = (TextView) itemView.findViewById(R.id.question_ar);
        desc_ar = (TextView) itemView.findViewById(R.id.desc_ar);
        desc_en = (TextView) itemView.findViewById(R.id.desc_en);
        answer_ar1 = (TextView) itemView.findViewById(R.id.answer_ar1);
        answer_ar2 = (TextView) itemView.findViewById(R.id.answer_ar2);
        answer_ar3 = (TextView) itemView.findViewById(R.id.answer_ar3);
        answer_en1 = (TextView) itemView.findViewById(R.id.answer_en1);
        answer_en2 = (TextView) itemView.findViewById(R.id.answer_en2);
        answer_en3 = (TextView) itemView.findViewById(R.id.answer_en3);
        right_answer = (TextView) itemView.findViewById(R.id.right_answer);


        item_id = (TextView) itemView.findViewById(R.id.item_id);
        item_description = (TextView) itemView.findViewById(R.id.item_description);
        item_image = (CircleImageView) itemView.findViewById(R.id.item_image);


        days_tf = (TextView) itemView.findViewById(R.id.days_tf);
        hours_tf = (TextView) itemView.findViewById(R.id.hours_tf);
        minutes_tf = (TextView) itemView.findViewById(R.id.minutes_tf);
        seconds_tf = (TextView) itemView.findViewById(R.id.seconds_tf);

    }



}
Ahmad Alkhatib
  • 1,230
  • 2
  • 14
  • 31

6 Answers6

3

here i am extending AppCompatTextView and adding the CountDownTimer inside it. and I am handling onStop and onStart so that CountDownTimer doesnt work in background.

class TimeCounterView : androidx.appcompat.widget.AppCompatTextView {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

var countDownTimer: CountDownTimer? = null

fun startCountDown(expireTimeStamp: Long, removeItem: () -> Unit) {
    stopCounter()
    text = calculateTimeExpire(expireTimeStamp)//custom method to calculate time until expireTime
    countDownTimer = object : CountDownTimer(
        expireTimeStamp * 1000 - System.currentTimeMillis(),
        ONE_MINUTE_MILLIS / 16
    ) {
        override fun onTick(millisUntilFinished: Long) {
            text = calculateTimeExpire(expireTimeStamp)
        }

        override fun onFinish() {
            text = context.getString(R.string.choice_finished)
            removeItem()
        }
    }
    countDownTimer?.start()
}

fun stopCounter() {
    countDownTimer?.cancel()
}

companion object {
    const val ONE_MINUTE_MILLIS: Long = 60000
}
}

then use it as normal view inside your itemview xml like this

....
    <com.example.custom.TimeCounterView
        android:id="@+id/timeTV"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="12dp"
        android:layout_weight="0"
        android:textColor="@color/colorText"
        tools:text="23:59" />
....

then inside your adapter you will need to implement very important method onViewRecycled which will be called to free the memory from the timer object.

 override fun onViewRecycled(holder: ChannelChoiceViewHolder) {
        super.onViewRecycled(holder)
        holder.stopCounter()
    }

then inside the holder you would do so

fun bind(item: ChannelPageChoice, position: Int) {
    //...
    binding.timeTV.startCountDown(item.expire){ removeItem(position) }
    //...
}
fun stopCounter() {
    binding.timeTV.stopCounter()
}

then inside the activity/fragment you must set the recyclerview to null in onStop and reattach it again in onStart by doing this onViewRecycled will be called on all your views and timer will be stopped if the recycler is not visible on screen.

override fun onStop() {
    super.onStop()
    currentItemPosition =
        (binding.recyclerView.layoutManager as? LinearLayoutManager)
            ?.findFirstVisibleItemPosition() ?: 0

    binding.recyclerView.adapter = null
}

override fun onStart() {
    super.onStart()
    binding.recyclerView.adapter = myCustomAdapter
    binding.recyclerView.scrollToPosition(currentItemPosition)
}
Amr
  • 1,068
  • 12
  • 21
  • I tried many ways to manage multiple countdown timer in a recycler view but this works as expected. – Yahya M May 31 '23 at 12:03
2

Add a CountDownTimer member in the ViewHolder. In onBindViewHolder() set and start the counter, and don't forget to cancel any existing one in the same instance of ViewHolder. In onTick() you need to update the value on the display, not start the counter.

Amr
  • 1,068
  • 12
  • 21
N.T.
  • 2,601
  • 1
  • 14
  • 20
  • does Recyclerview has a getView() method! – Ahmad Alkhatib Aug 29 '15 at 06:12
  • u mean onBindViewHolder() ? – Ahmad Alkhatib Aug 29 '15 at 06:42
  • Yes. I'm sorry, I was focused too much on the design of the solution...that I forgot you need a RecyclerView approach. ListView or RecyclerView, their adapters are similar - just that the getView is split in 2 (creation and value binding) and you are forced to use the ViewHolder pattern in the RecyclerView adapter. – N.T. Aug 29 '15 at 11:42
  • 1
    yes no problem.. thanks for replying and i just put on onCreateView() a notifyDatasetChange() with runnable and every thing is working fine – Ahmad Alkhatib Aug 29 '15 at 11:48
  • @AhmadAlkhateeb can you please provide an example...i want the same – H Raval Mar 08 '16 at 05:36
  • I would also appreciate an example and I want to ask also, did you encounter any problems when scrolling the list up and down and when the views get recycled? what do you do then? – Sandra Apr 21 '16 at 15:11
  • What happen if remove an item out of the list while counting down? – Tran Huu Phuoc Aug 26 '19 at 08:18
2

The Simple Solution Would Be:

Handler handler=new Handler();
handler.postDelayed(new UpdateTimerThread(holder),0); 

public Class UpdateTimerThread implements Runnable{
Holder holder;

    public UpdateTimerThread(Holder holder){
        this.holder=holder;
    }
    @Override
    public void run() {
        lgetCreatedTime = lgetCreatedTime + 1000;
        long diffSeconds;
        long diffMinutes;
        long diffHours;
        diffSeconds = lgetCreatedTime / 1000 % 60;
        diffMinutes = lgetCreatedTime / (60 * 1000) % 60;
        diffHours = lgetCreatedTime / (60 * 60 * 1000) % 24;
        holder.GameTimer.setText(String.format("%02d:%02d:%02d", diffHours,   diffMinutes, diffSeconds));
        handler.postDelayed(this,1000);
    }
}

Note: 1.holder is the object of ViewHolder class

2.Create a class UpdateTimerThread implements Runnable

3.Convert Date and Time to long and store into lgetCreatedTime

4.Run Handler for every 1 Sec to tick the Time

thepio
  • 6,193
  • 5
  • 35
  • 54
2

Here you can check and download the source code of Countdown timers in a RecyclerView

activity_main.xml

<RelativeLayout android:layout_width=”match_parent”
android:layout_height=”match_parent”
xmlns:android=”http://schemas.android.com/apk/res/android”&gt;

<android.support.v7.widget.RecyclerView
android:id=”@+id/recycler_view”
android:layout_width=”match_parent”
android:layout_height=”wrap_content”
android:scrollbars=”vertical” />

</RelativeLayout>

adapter_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="15dp"
    android:padding="10dp"
    android:id="@+id/tv_timer"/>

</LinearLayout>

MainActivity.java

package com.androidsolutionworld.multipletimer;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.LinearLayout;
import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private ArrayList al_data = new ArrayList<>();
private Adapter obj_adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
    al_data.add("1234");
    al_data.add("1257");
    al_data.add("100");
    al_data.add("1547");
    al_data.add("200");
    al_data.add("500");
    al_data.add("2000");
    al_data.add("1000");

    obj_adapter = new Adapter(al_data);
    LinearLayoutManager layoutManager = new LinearLayoutManager(getApplicationContext(),LinearLayoutManager.VERTICAL,false);
    recyclerView.setLayoutManager(layoutManager);
    recyclerView.setAdapter(obj_adapter);
}
}

Custom Adapter:

import android.os.CountDownTimer;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;

public class Adapter extends RecyclerView.Adapter{

private ArrayList al_data;

public class MyViewHolder extends RecyclerView.ViewHolder{
    public TextView tv_timer;
    CountDownTimer timer;

    public MyViewHolder (View view){
        super(view);
        tv_timer = (TextView)view.findViewById(R.id.tv_timer);

    }


}

public Adapter(ArrayList al_data) {
    this.al_data = al_data;
}

@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.adapter_layout,parent,false);


    return new MyViewHolder(view);
}

@Override
public void onBindViewHolder(final MyViewHolder holder, int position) {

    holder.tv_timer.setText(al_data.get(position));

    if (holder.timer != null) {
        holder.timer.cancel();
    }
     long timer = Long.parseLong(al_data.get(position));

    timer = timer*1000;

    holder.timer = new CountDownTimer(timer, 1000) {
        public void onTick(long millisUntilFinished) {
          holder.tv_timer.setText("" + millisUntilFinished/1000 + " Sec");
        }

        public void onFinish() {
            holder.tv_timer.setText("00:00:00");
        }
    }.start();


}

@Override
public int getItemCount() {
    return al_data.size();
}

}
Deepshikha Puri
  • 2,104
  • 22
  • 23
0

You can create CountDownTimer reference in ViewHolder class and create its instance in onBindViewHolder method of Adapter.

It works perfectly.

Tested Myself.

Kshitij Jain
  • 549
  • 6
  • 12
-1

in onbindviewholder :

                if (holder.countDownTimer != null) {
                holder.countDownTimer.cancel();
            }

            holder.countDownTimer = new CountDownTimer(time * 1000, 
                1000) {
                public void onTick(long millisUntilFinished) {
                    long seconds = millisUntilFinished / 1000;
                    long minutes = seconds / 60;
                    long hours = minutes / 60;
                    long days = hours / 24;
                    String something = days+" "+"days" +" :" +hours % 24 
                    + ":" + minutes % 60 + ":" + seconds % 60;
                    holder.tv_TimeRemained.setText(something);
                }

                public void onFinish() {
                    holder.tv_TimeRemained.setText("Time up!");
                }
            }.start();

it's still lag

ryan kun
  • 1
  • 1
  • 2
  • This does not really answer the question. If you have a different question, you can ask it by clicking [Ask Question](https://stackoverflow.com/questions/ask). To get notified when this question gets new answers, you can [follow this question](https://meta.stackexchange.com/q/345661). Once you have enough [reputation](https://stackoverflow.com/help/whats-reputation), you can also [add a bounty](https://stackoverflow.com/help/privileges/set-bounties) to draw more attention to this question. - [From Review](/review/late-answers/32898843) – user16217248 Oct 15 '22 at 01:18