How to Handle Multiple Countdown Timers in Listview

How to handle multiple countdown timers in ListView?

Instead of trying to show the remaining time for all, the idea is to update the remaining time for the items which are visible.

Please follow the following sample code and let me know :

MainActivity :

public class MainActivity extends Activity {

private ListView lvItems;
private List<Product> lstProducts;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

lvItems = (ListView) findViewById(R.id.lvItems);
lstProducts = new ArrayList<>();
lstProducts.add(new Product("A", System.currentTimeMillis() + 10000));
lstProducts.add(new Product("B", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("C", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("D", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("E", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("F", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("G", System.currentTimeMillis() + 30000));
lstProducts.add(new Product("H", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("I", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("J", System.currentTimeMillis() + 40000));
lstProducts.add(new Product("K", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("L", System.currentTimeMillis() + 50000));
lstProducts.add(new Product("M", System.currentTimeMillis() + 60000));
lstProducts.add(new Product("N", System.currentTimeMillis() + 20000));
lstProducts.add(new Product("O", System.currentTimeMillis() + 10000));

lvItems.setAdapter(new CountdownAdapter(MainActivity.this, lstProducts));
}

private class Product {
String name;
long expirationTime;

public Product(String name, long expirationTime) {
this.name = name;
this.expirationTime = expirationTime;
}
}

public class CountdownAdapter extends ArrayAdapter<Product> {

private LayoutInflater lf;
private List<ViewHolder> lstHolders;
private Handler mHandler = new Handler();
private Runnable updateRemainingTimeRunnable = new Runnable() {
@Override
public void run() {
synchronized (lstHolders) {
long currentTime = System.currentTimeMillis();
for (ViewHolder holder : lstHolders) {
holder.updateTimeRemaining(currentTime);
}
}
}
};

public CountdownAdapter(Context context, List<Product> objects) {
super(context, 0, objects);
lf = LayoutInflater.from(context);
lstHolders = new ArrayList<>();
startUpdateTimer();
}

private void startUpdateTimer() {
Timer tmr = new Timer();
tmr.schedule(new TimerTask() {
@Override
public void run() {
mHandler.post(updateRemainingTimeRunnable);
}
}, 1000, 1000);
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = lf.inflate(R.layout.list_item, parent, false);
holder.tvProduct = (TextView) convertView.findViewById(R.id.tvProduct);
holder.tvTimeRemaining = (TextView) convertView.findViewById(R.id.tvTimeRemaining);
convertView.setTag(holder);
synchronized (lstHolders) {
lstHolders.add(holder);
}
} else {
holder = (ViewHolder) convertView.getTag();
}

holder.setData(getItem(position));

return convertView;
}
}

private class ViewHolder {
TextView tvProduct;
TextView tvTimeRemaining;
Product mProduct;

public void setData(Product item) {
mProduct = item;
tvProduct.setText(item.name);
updateTimeRemaining(System.currentTimeMillis());
}

public void updateTimeRemaining(long currentTime) {
long timeDiff = mProduct.expirationTime - currentTime;
if (timeDiff > 0) {
int seconds = (int) (timeDiff / 1000) % 60;
int minutes = (int) ((timeDiff / (1000 * 60)) % 60);
int hours = (int) ((timeDiff / (1000 * 60 * 60)) % 24);
tvTimeRemaining.setText(hours + " hrs " + minutes + " mins " + seconds + " sec");
} else {
tvTimeRemaining.setText("Expired!!");
}
}
}
}

activity_main.xml :

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

<ListView
android:id="@+id/lvItems"
android:layout_width="match_parent"
android:layout_height="match_parent" />

</RelativeLayout>

list_item.xml

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

<TextView
android:id="@+id/tvProduct"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Product Name"
android:textSize="16dp"
android:textStyle="bold" />

<TextView
android:id="@+id/tvTimeRemaining"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:text="Time Remaining : " />

</LinearLayout>

Android: Multiple simultaneous count-down timers in a ListView

Please have a look here at my blog where you will find an example on how to achieve this.

One solution is to put the TextView that represents each counter into a HashMap together with it's position in the list as the key.

In getView()

TextView counter = (TextView) v.findViewById(R.id.myTextViewTwo);
if (counter != null) {
counter.setText(myData.getCountAsString());
// add the TextView for the counter to the HashMap.
mCounterList.put(position, counter);
}

Then you can update the counters by using a Handler and where you post a runnable.

private final Runnable mRunnable = new Runnable() {
public void run() {
MyData myData;
TextView textView;

// if counters are active
if (mCountersActive) {
if (mCounterList != null && mDataList != null) {
for (int i=0; i < mDataList.size(); i++) {
myData = mDataList.get(i);
textView = mCounterList.get(i);
if (textView != null) {
if (myData.getCount() >= 0) {
textView.setText(myData.getCountAsString());
myData.reduceCount();
}
}
}
}
// update every second
mHandler.postDelayed(this, 1000);
}
}
};

Multiple countdown timer in listview C# duplicating

What you are doing now is on each button click override the current endTime object by a new one like:

endTime = new DateTime(year,month,day); 

If you assign a new DateTime object to endTime. You override the old one. So the first button click will work but the second will create a new object of DateTime and assign it to endTime. Next you are calculating the time difference on that one object DateTime. So it is logic that it will be the same time for each listview items

If you want to have more than one DateTime use a List to store it in like

    List<DateTime> _times = new List<DateTime>();

In the button click method add the DateTime to the list

  // here add the datetime to the list
DateTime dateTime = new DateTime(year, month, day);
_times.Add(dateTime);

Next you can loop thru the dates and calculate for each one the time difference in the tick method:

        foreach (var dateTime in _times)
{
TimeSpan ts = dateTime.Subtract(DateTime.Now);
// etc..
}

Also you are creating a timer for each time to calculate after 500 ms. You now can use one timer this is more efficient than crating one for each time. Just assign this in the constructor

  public Form1()
{
InitializeComponent();

Timer t = new Timer();
t.Interval = 500;
t.Tick += new EventHandler(t_Tick);
t.Start();
}

Whole code

public partial class Form1 : Form
{
// This is the list where the DateTimes are stored so you can have more values
List<DateTime> _times = new List<DateTime>();
public Form1()
{
InitializeComponent();

// Assign the timer here
Timer t = new Timer();
t.Interval = 500;
t.Tick += new EventHandler(t_Tick);
t.Start();
}

private void buttonSave_Click(object sender, EventArgs e)
{
if (this.textBox_Task.Text != "")
{
listView1.View = View.Details;
ListViewItem lvwItem = listView1.Items.Add(dateTimePicker1.Text);
var day = dateTimePicker1.Value.Day;
var month = dateTimePicker1.Value.Month;
var year = dateTimePicker1.Value.Year;

// Add Datetime to list
DateTime dateTime = new DateTime(year, month, day);
_times.Add(dateTime);

lvwItem.SubItems.Add(textBox_Task.Text);
lvwItem.SubItems.Add(textBox_Note.Text);
lvwItem.SubItems.Add("");
this.dateTimePicker1.Focus();
this.textBox_Note.Focus();
this.textBox_Task.Focus();
this.textBox_Task.Clear();
this.textBox_Note.Clear();

}
else
{
MessageBox.Show("Please enter a task to add.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Information);
this.textBox_Task.Clear();
this.textBox_Note.Clear();
}
}
void t_Tick(object sender, EventArgs e)
{
// loop thru all datetimes and calculate the diffrence
foreach (var dateTime in _times)
{
// Call the specific date and subtract on it
TimeSpan ts = dateTime.Subtract(DateTime.Now);

var hari = dateTimePicker1.Value.Day;
Console.WriteLine(ts.Days);

for (int i = 0; i < listView1.Items.Count; i++)
{
if (ts.Days == 0)
{
listView1.Items[i].SubItems[3].Text = "DEADLINE";
}
else
{
listView1.Items[i].SubItems[3].Text = ts.ToString("d' Days 'h' Hours 'm' Minutes 's' Seconds to go'");
}
}
}
}
}

ListView and items with countdown timer

I solved this differently in my case. Instead of having a timer handler set inside your getView(), I just set the time difference between the current time and your desired time to the TextView every time getView() is called. So move this code of yours back inside getView():

long outputTime = Math.abs(promoAction.timer_end
- System.currentTimeMillis());
Date date = new java.util.Date(outputTime);
String result = new SimpleDateFormat("hh:mm:ss").format(date);
tv.setText(result);

Then create a handler in the activity to call notifyDatasetChanged() every one minute on the listview's adapter:

Handler timerHandler = new Handler();
Runnable timerRunnable = new Runnable() {
@Override
public void run() {
myAdapter.notifyDataSetChanged();
timerHandler.postDelayed(this, 60000); //run every minute
}
};

I stop this handler on onPause():

@Override
protected void onPause() {
timerHandler.removeCallbacks(timerRunnable);
super.onPause();
}

And I start it again on onResume():

@Override
protected void onResume() {
timerHandler.postDelayed(timerRunnable, 500);
super.onResume();
}

And that's it. :)

Hope it helps.

Multiple Countdown Timer in Expandable Listview Android not working

I got to say this first, your code is really hard to read.

  1. Try to split using methods the view creation code from your busyness code on getGroupView and getChildView. Also you could use ViewHolder pattern on getChildView to hold your views instead of thoses arrays (ll_whoaccpted, etc...)
  2. TextUtils.isEmpty() can help you check better is your String are empty or null

    if (TextUtils.isEmpty(headerTitle.getRemaining_completion_time()))

    //instead of

    if (headerTitle.getRemaining_completion_time().equalsIgnoreCase("") || headerTitle.getRemaining_completion_time() == null)
  3. Make Sent_ListAdapter implement OnClickListener instead of recreating the listener (new) each time Android gets a View for your list. Add a condition on the view parameter to check which view type was click.

  4. Work with Date and SimpleDataFormat on CountDownTimer, thoses manual string formatting are just awfull and unreadable

Well now to the main subject :

on scrolling expandable list view timer restarts

This happends because a ListView recreates the views/groups that are not showned (calling the getView/getGroup methods) to reuse the rows for other data.

If you want to avoid this you should create and start your counters outside the getView and getGroup (the constructor is a good place).

on expand and collapse timer restarts

Exactly the same thing that happends on the first problem.

on event on child button i need to update particular position's text
on parentview

I didn't really get this so am guessing that you want to change the text of a TextView on the main layout (the one containing the listview). Well you need to give a reference of that TextView to the Sent_ListAdapter.

Or, instead of passing a generic Context to the Sent_ListAdapter pass the Activity/Fragment it self :

public Sent_ListAdapter(MyActivity activty, List<SentModel> listDataHeader, HashMap<SentModel, List<SentModel>> listChildData) {
this._myactivity = myactivity;
this._listDataHeader = listDataHeader;
...
}

// The listener that handles all the events of your Sent_ListAdapter

@Override
public void onClick(View v) {
if(v.getTag() == "theBoutonThatShouldResetTheCounter") { //or other condition
myactivite.myTextView.setText("------");
}
}

Edit


But how will I get position in constructor for every row?

You need to think the other way around : the listview uses your datasource to show the rows. So you need to loop over the elements on your datasource (listDataHeader in your case). Something like :

private List<CountDownTimer> counters;

public Sent_ListAdapter(Context context, List<SentModel> listDataHeader, HashMap<SentModel, List<SentModel>> listChildData) {
...
counters = new ArrayList()<>;
for(SentModel model : listDataHeader) {
long milliseco = model.getRemainingTimeInMs(); //TODO: create something like this or figure out how to get your couters starting times on ms
CountDownTimer cdt = new MyCountDownTimer(milliseco, 1000);
cdt.start(); // I don't know when you want to start your counters, if they start at the same time it could be here
}
}

public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
...
datetime_element = (TextView) convertView.findViewById(R.id.datetime_element);
deadline = (TextView) convertView.findViewById(R.id.deadline);
...
CountDownTimer cdt = counters.get(groupPosition);
cdt.setTv1 = datetime_element;
cdt.setTv2 = deadline;
...
}

public class MyCountDownTimer extends CountDownTimer {

public TextView tv1; //TODO: make setters instead of public
public TextView tv2; //TODO: make setters instead of public

public MyCountDownTimer(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}

@Override
public void onTick(long millisUntilFinished) {
//Only use this if u have something to do each tick
SimpleDateFormat df = new SimpleDateFormat(" dd,hh:mm:ss");
Date timeRemaining = //TODO: figure out how you calculate your remaining time
if(tv1 != null) {
tv1.setText(df.format(timeRemaining));
}
}

@Override
public void onFinish() {
if(tv1 != null && tv2!= null) {
tv1.setText("Completion time over");
tv2.setVisibility(View.GONE);
}
}
}


Related Topics



Leave a reply



Submit