How to handle multiple countdown timers in ListView?
Asked Answered
I

4

18

I have a listview (with a custom list adapter), I need to display a countdown on every row.

For example, if my list contains 4 items, I will have 4 rows. At this point, I need to handle 4 different countdowns (one for each row) because time is different.

enter image description here

So far, I'm handling it the following way : in the Custom List Adapter, inside getView() method I create a new CountDownTimer and display remaining time inside TextView.

But the problem is that it slows the activity a lot, I can't even scroll correctly in the worst cases (because each time a row is displayed, it creates a new CountDownTimer).

I searched a lot for a better solution, but no one was satisfying.

Is there a cleaner and smoother solution to handle multiple countdown timers inside a listView ?

Thanks

Inland answered 25/6, 2015 at 19:34 Comment(0)
B
37

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>
Bs answered 25/6, 2015 at 20:19 Comment(9)
anyone can do that with recyclerview ,, i've been working 2 days on it please help !!Kilmarnock
@AhmadAlkhateeb did u get a solution?Drudgery
i am also doing the same.can u post me the code for recycler view?Drudgery
@AhmadAlkhateeb did you get any solution for recyclerviewSmokeproof
thanx i found solution using your logicSmokeproof
I try your solution with a recycler view and it works perfect! Thank youMelaniamelanic
@EldhoseMBabu What part of your solution code above updates only those items that are visible? Is it the "getView" section?Weimer
Awesome, just what I was looking for, great solution!Pitman
Hey, how do I remove a holder from the lst ? Apparently it keeps updating that holder even though that item is removed... :(Proxy
M
17

It might be late but this is recyclerView version using php and json based on excellent answer of @Eldhose M Babu. Hope to be helpful :)

Multiple countdown photo display

Adapter.java

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

    private Context context;
    private final List<ViewHolder> lstHolders;
    public List<Model> lst;

    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 Adapter(List<Model> lst, Context context){
        super();
        this.lst = lst;
        this.context = 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 ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list, parent, false);
        ViewHolder viewHolder = new ViewHolder(v);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.setData(lst.get(position));
        synchronized (lstHolders) {
            lstHolders.add(holder);
        }
        holder.updateTimeRemaining(System.currentTimeMillis());
    }

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

    class ViewHolder extends RecyclerView.ViewHolder{
        public TextView textViewName;
        public TextView tvTimeRemaining;
        Model mModel;

        public void setData(Model item) {
            mModel = item;
            textViewName.setText(item.name);
            updateTimeRemaining(System.currentTimeMillis());
        }

        public void updateTimeRemaining(long currentTime) {
            long timeDiff = mModel.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!!");
            }
        }

        public ViewHolder(View itemView) {
            super(itemView);
            tvTimeRemaining = (TextView) itemView.findViewById(R.id.cd);
            textViewName = (TextView) itemView.findViewById(R.id.textViewName);
        }
    }
}

MainActivity.java

public class MainActivity extends AppCompatActivity {

private List<Model> lst;
private RecyclerView recyclerView;
private RecyclerView.LayoutManager layoutManager;
private RecyclerView.Adapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
    recyclerView.setHasFixedSize(true);
    layoutManager = new LinearLayoutManager(this);
    recyclerView.setLayoutManager(layoutManager);
    lst = new ArrayList<>();
    getData();


    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    fab.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG).setAction("Action", null).show();
        }
    });
}

private void getData(){
    final ProgressDialog loading = ProgressDialog.show(this,"Loading Data", "Please wait...",false,false);

    JsonArrayRequest jsonArrayRequest = new JsonArrayRequest("http://192.168.200.102/api.php",
            new Response.Listener<JSONArray>() {
                @Override
                public void onResponse(JSONArray response) {
                    loading.dismiss();
                    parseData(response);
                }
            },
            new Response.ErrorListener() {
                @Override
                public void onErrorResponse(VolleyError error) {

                }
            });

    RequestQueue requestQueue = Volley.newRequestQueue(this);
    requestQueue.add(jsonArrayRequest);
}

private void parseData(JSONArray array){
    for(int i = 0; i<array.length(); i++) {
        Model model = new Model();
        JSONObject json = null;
        try {
            json = array.getJSONObject(i);
            model.setexpirationTime(json.getLong("expirationTime"));
            model.setName(json.getString("name"));
        } catch (JSONException e) {
            e.printStackTrace();
        }
        lst.add(model);
    }

    adapter = new Adapter(lst, this);
    recyclerView.setAdapter(adapter);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.menu_main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int id = item.getItemId();

    if (id == R.id.action_settings) {
        return true;
    }

    return super.onOptionsItemSelected(item);
}}

Model.java

public class Model {
public String name;
public long expirationTime;

public long getexpirationTime() {
    return expirationTime;
}

public void setexpirationTime(long expire) {
    this.expirationTime = expire;
}

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="here.math.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>

content_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="here.math.MainActivity"
    tools:showIn="@layout/activity_main">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
        </android.support.v7.widget.RecyclerView>
</RelativeLayout>

list.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">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="20dp">

        <LinearLayout
            android:padding="@dimen/activity_horizontal_margin"
            android:orientation="vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent">


            <TableLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TableRow>
                    <TextView
                        android:text="Expire time"
                        android:paddingRight="10dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <TextView
                        android:id="@+id/cd"
                        android:textStyle="bold"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                </TableRow>

                <TableRow>
                    <TextView
                        android:text="Name"
                        android:paddingRight="10dp"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                    <TextView
                        android:id="@+id/textViewName"
                        android:textStyle="bold"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content" />

                </TableRow>

            </TableLayout>
        </LinearLayout>
    </android.support.v7.widget.CardView>

</RelativeLayout>

and for url part you can do something kind of:

<?php

$arr = array(
array(
        'name' => "Android",
        'expirationTime' => 1456860322 * 1000, // timestamp multiply for display in milliseconds
),
array(
        'name' => "Android 2",
        'expirationTime' => 1456900522 * 1000,
),
array(
        'name' => "Android 3",
        'expirationTime' =>  1459509819 * 1000,
),
array(
        'name' => "Android 4",
        'expirationTime' => 1457021950 * 1000,
),
);

echo json_encode($arr);
Marja answered 1/3, 2016 at 10:50 Comment(11)
how i can do it for only 2 row onlyLuo
@andro, based on php script i posted, remove last two arrays. let me know if you stuck on something.Marja
in ur example problem is synchronized (lstHolders) { lstHolders.add(holder); } you are always adding holder to list . so ssay if der are 10 row when i swipe up down size of lstHolders will increase it should be max 10Luo
my problem statement is i have time from server now i have to update to but say i have 50 row but i have to update time for 1st 10 rows onlyLuo
say you have 10 rows from your json array, right? the size is 10 and it will display 10 and update all of them to finished state. i just test with 55 rows and it works. so you say when you add 50 rows only 10 of them works?Marja
nah i have json of 20 so size is 20 but i have to update 1o onlyLuo
since the holder is assigned to the list all of the time, you cant do this unless you set another key/value to your json like array('countable' => "true"), and catch it then with if statement counting down only rows with "true" values.Marja
ya but this time is still on even my 10 rows are not visibleLuo
its seems i cant extend this discussions on chat with my current rep, so contact me by my email: [email protected]Marja
Great solution, You have definitely saved ma head from loops :) :) @HamedOkhovvatVasques
@HamedOkhovvat I'm using your answer , but when I update/settext time textview the recycler view scrolls to the top. How can I avoid this?Webbed
C
1

Please have a look on this Countdown Timer in listview android example.

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<Adapter.MyViewHolder>{

private ArrayList<String> 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<String> 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();
}



}

Main Activity

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

Thanks

Chorea answered 25/10, 2016 at 9:57 Comment(0)
A
1

Adapter class:-


public class CountdownAdapter extends RecyclerView.Adapter { ArrayList mList; Context mContext;

public CountdownAdapter(ArrayList<Long> mList, Context mContext) {
    this.mList = mList;
    this.mContext = mContext;
}

public class ViewHolder extends RecyclerView.ViewHolder {
    CountDownTimer timerCount;
    TextView mCountDownTxt;

    public ViewHolder(View convertView) {
        super(convertView);
        mCountDownTxt = (TextView)convertView.findViewById(R.id.countdown_text);
    }
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    return null;
}

@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
    if(holder.timerCount==null) {
        holder.timerCount = new CountDownTimer(mList.get(position), 1000) {
            @Override
            public void onTick(long millis) {
                String hms = String.format("%02d:%02d",  TimeUnit.MILLISECONDS.toMinutes(millis) - TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(millis)), TimeUnit.MILLISECONDS.toSeconds(millis) - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis)));
                holder.mCountDownTxt.setText(hms);
            }

            @Override
            public void onFinish() {

            }
        };
    }
    holder.mCountDownTxt.setVisibility(View.VISIBLE);
    holder.timerCount.start();
}

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

}

Armilla answered 27/10, 2016 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.