Android: Múltiples temporizadores de cuenta atrás simultáneos en un ListView

Estoy creando una aplicación que requiere un ListView con un número indeterminado de elementos, cada uno de los cuales tiene un temporizador que cuenta desde un número variable. Puedo hacer con éxito uno de ellos cuenta atrás, pero no puedo averiguar cómo incluir un temporizador en cada elemento de la ListView.

Actualmente estoy utilizando un CountDownTimer (asegúrese de capitalizar el D si la copia desde el sitio web, lo tienen mal).

Cualquier código o fuentes que me apunten en la dirección correcta son muy apreciados.

Esta es mi clase EventAdapter actual, que establece el texto que se muestra en TextView de cada elemento ListView. Lo que tengo que hacer es hacer el conteo de TextView abajo cada segundo. Como cada elemento de ListView muestra algo diferente, supongo que necesito una forma de diferenciar cada elemento.

Podría actualizar toda la lista cada segundo, pero hay otros elementos que no he incluido, como las imágenes cargadas desde Internet que sería poco práctico para actualizar cada segundo.

private class EventAdapter extends ArrayAdapter<Event> { private ArrayList<Event> items; public EventAdapter(Context context, int textViewResourceId, ArrayList<Event> items) { super(context, textViewResourceId, items); this.items = items; } @Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(R.layout.row, null); } Event e = items.get(position); if (e != null) { TextView tv = (TextView) v.findViewById(R.id.text); if (tv != null) tv.setText(e.getName()); } return v; } } 

Por favor, eche un vistazo aquí en mi blog donde encontrará un ejemplo de cómo lograr esto.

Una solución es poner el TextView que representa cada contador en un HashMap junto con su posición en la lista como la clave.

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

A continuación, puede actualizar los contadores mediante un controlador y donde se publica un ejecutable.

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

Este es un ejemplo de cómo lo hago y funciona perfectamente:

 public class TestCounterActivity extends ListActivity { TestAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Example values ArrayList<Date> values = new ArrayList<Date>(); values.add(new Date(1482464366239L)); values.add(new Date(1480464366239L)); values.add(new Date(1470464366239L)); values.add(new Date(1460464366239L)); values.add(new Date(1450464366239L)); values.add(new Date(1440464366239L)); values.add(new Date(1430464366239L)); values.add(new Date(1420464366239L)); values.add(new Date(1410464366239L)); values.add(new Date(1490464366239L)); adapter = new TestAdapter(this, values); setListAdapter(adapter); } @Override protected void onStop() { super.onStop(); // Dont forget to cancel the running timers adapter.cancelAllTimers(); } } 

Y este es el adaptador

 public class TestAdapter extends ArrayAdapter<Date> { private final Activity context; private final List<Date> values; private HashMap<TextView,CountDownTimer> counters; static class TestViewHolder { public TextView tvCounter; } public TestAdapter(Activity context, List<Date> values) { super(context, R.layout.test_row, values); this.context = context; this.values = values; this.counters = new HashMap<TextView, CountDownTimer>(); } @Override public View getView(int position, View convertView, ViewGroup parent) { View rowView = convertView; if(rowView == null) { LayoutInflater inflater = context.getLayoutInflater(); rowView = inflater.inflate(R.layout.test_row, null); final TestViewHolder viewHolder = new TestViewHolder(); viewHolder.tvCounter = (TextView) rowView.findViewById(R.id.tvCounter); rowView.setTag(viewHolder); } TestViewHolder holder = (TestViewHolder) rowView.getTag(); final TextView tv = holder.tvCounter; CountDownTimer cdt = counters.get(holder.tvCounter); if(cdt!=null) { cdt.cancel(); cdt=null; } Date date = values.get(position); long currentDate = Calendar.getInstance().getTime().getTime(); long limitDate = date.getTime(); long difference = limitDate - currentDate; cdt = new CountDownTimer(difference, 1000) { @Override public void onTick(long millisUntilFinished) { int days = 0; int hours = 0; int minutes = 0; int seconds = 0; String sDate = ""; if(millisUntilFinished > DateUtils.DAY_IN_MILLIS) { days = (int) (millisUntilFinished / DateUtils.DAY_IN_MILLIS); sDate += days+"d"; } millisUntilFinished -= (days*DateUtils.DAY_IN_MILLIS); if(millisUntilFinished > DateUtils.HOUR_IN_MILLIS) { hours = (int) (millisUntilFinished / DateUtils.HOUR_IN_MILLIS); } millisUntilFinished -= (hours*DateUtils.HOUR_IN_MILLIS); if(millisUntilFinished > DateUtils.MINUTE_IN_MILLIS) { minutes = (int) (millisUntilFinished / DateUtils.MINUTE_IN_MILLIS); } millisUntilFinished -= (minutes*DateUtils.MINUTE_IN_MILLIS); if(millisUntilFinished > DateUtils.SECOND_IN_MILLIS) { seconds = (int) (millisUntilFinished / DateUtils.SECOND_IN_MILLIS); } sDate += " "+String.format("%02d",hours)+":"+String.format("%02d",minutes)+":"+String.format("%02d",seconds); tv.setText(sDate.trim()); } @Override public void onFinish() { tv.setText("Finished"); } }; counters.put(tv, cdt); cdt.start(); return rowView; } public void cancelAllTimers() { Set<Entry<TextView, CountDownTimer>> s = counters.entrySet(); Iterator it = s.iterator(); while(it.hasNext()) { try { Map.Entry pairs = (Map.Entry)it.next(); CountDownTimer cdt = (CountDownTimer)pairs.getValue(); cdt.cancel(); cdt = null; } catch(Exception e){} } it=null; s=null; counters.clear(); } } 

Después de revisar algunas maneras de hacer que esta es una solución creativa que escribí. Es simple y funciona perfectamente.

La idea de que para comprobar si el Runnable que actualiza los datos está actualizando el mismo TextView y si el TextView está relacionado con la vista diferente el Runnable se detendrá y de esta manera no habrá Thread extra en el fondo por lo que no habrá Parpadeo de texto o pérdida de memoria.

1. Dentro de su getView() agregue cada etiqueta de TextView con su posición.

 text = (TextView) view .findViewById(R.id.dimrix); text.setTag(position); 

2. Create class que implementa Runnable para que podamos pasar parámetros.

 public class mUpdateClockTask implements Runnable { private TextView tv; final Handler mClockHandler = new Handler(); String tag; public mUpdateClockTask(TextView tv, String tag) { this.tv = tv; this.tag = tag; } public void run() { if (tv.getTag().toString().equals(tag)) { // do what ever you want to happen every second mClockHandler.postDelayed(this, 1000); } } }; 

Así que lo que ocurre aquí es a menos que el TextView no es igual a la etiqueta original que el Runnable se detendrá.

3. Volver a su getView()

  final Handler mClockHandler = new Handler(); mUpdateClockTask clockTask = new mUpdateClockTask(text, activeDraw, text.getTag().toString()); mClockHandler.post(clockTask); 

Eso es todo, el trabajo es perfecto!

Aquí hay otra solución de usar ListView con CountDownTimer múltiple. En primer lugar, creamos una clase MyCustomTimer que contiene el CountDownTimer:

 public class MyCustomTimer{ public MyCustomTimer() { } public void setTimer(TextView tv, long time) { new CountDownTimer(time, 1000) { public void onTick(long millisUntilFinished) { //Set formatted date to your TextView tv.setText(millisUntilFinished); } public void onFinish() { tv.setText("Done!"); } }.start(); } } 

A continuación, initilize la clase creada en su adaptador:

 public class MyAdapter extends BaseAdapter { private LayoutInflater mInflater; private MyCustomTimer myTimer; private ArrayList<Item> myItems; public MyAdapter(Context context, ArrayList<Item> data) { mInflater = LayoutInflater.from(context); myTimer= new MyCustomTimer(); myItems = data; } //... implementation of other methods @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = mInflater.inflate(R.layout.listview_row, null); TextView tvTimer = (TextView) convertView.findViewById(R.id.textview_timer); TextView tvName = (TextView) convertView.findViewById(R.id.textview_name); Item item = data.get(position); tvName.setText(item.getName()); myTimer.setTimer(tvTimer, item.getTime()); return convertView; } } 

Aquí está mi contador independiente de la solución (código lleno) 20 en ListView que puede ser comienzo, parada, recomenzar, restablecer independientemente.

También he sufrido este problema hace unos días, pero finalmente encontré solución de problema. He creado una aplicación que con 20 temporizador en vista de lista que puede trabajar de forma independiente por sólo un CountDownTimer. Cada temporizador puede iniciar, detener, reiniciar, reiniciar independientemente. Cada temporizador se ajusta a 2 minutos. Aquí está mi código completo


** haz clic para ver la captura de pantalla de la aplicación **


Paso 1: crear una aplicación para Android con actividad en blanco


Paso 2: Mi código de archivo de diseño MainActivity


 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <ListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/lv"/> </RelativeLayout> 

Paso 3: crear un archivo de diseño que se utiliza como un elemento de lista para su listaVer (Mi nombre de archivo res / layout / list_item.xml)


 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@color/colorAccent" android:padding="5dp" android:layout_margin="5dp" android:textDirection="firstStrong"> <Button android:layout_width="100dp" android:layout_height="wrap_content" android:text="RESET" android:id="@+id/resetBtn" android:layout_alignParentLeft="true" android:background="@color/colorAccent" android:layout_weight=".2" android:textColor="#ffffff" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tv" android:layout_toRightOf="@+id/resetBtn" android:text="Time : 02:00" android:textAlignment="center" android:textColor="#ffffff" android:layout_weight=".6" android:padding="@dimen/activity_vertical_margin" android:focusableInTouchMode="false" android:focusable="false" android:enabled="false" /> <Button android:layout_width="100dp" android:layout_height="wrap_content" android:textColor="#ffffff" android:id="@+id/startStopBtn" android:text="START" android:layout_weight=".2" android:background="@color/colorAccent" android:layout_alignParentRight="true" /> </LinearLayout> 

Paso 4: crear una clase que almacene la información de cada temporizador (Mi nombre de clase CounterInfo)

  package com.example.rahul.timerapp1; import java.util.ArrayList; import java.util.List; public class CounterInfo { public boolean status; public int time; public static List<CounterInfo> getCounterListInfo() { List<CounterInfo> l = new ArrayList<CounterInfo>(); for(int i=0; i<20;i++) { CounterInfo c = new CounterInfo(); c.status=false; c.time=-1; l.add(c); } return l; } } 

Paso 5: crear el adaptador para listView (mi nombre de adaptador MyAdapter.java)


 import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.TextView; import java.util.List; public class MyAdapter extends BaseAdapter { List<CounterInfo> counterInfo; LayoutInflater inflater; Context context; MyAdapter(MainActivity mainActivity , List<CounterInfo> c) { context=mainActivity; counterInfo=c; } @Override public int getCount() { return counterInfo.size(); } @Override public Object getItem(int position) { return (CounterInfo)counterInfo.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(final int position, View convertView, ViewGroup parent) { if(convertView==null) { inflater =(LayoutInflater)context.getSystemService(context.LAYOUT_INFLATER_SERVICE); convertView=inflater.inflate(R.layout.list_item, parent ,false); } Button resetBtn =(Button)convertView.findViewById(R.id.resetBtn); resetBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { counterInfo.get(position).time=-1; counterInfo.get(position).status=false; notifyDataSetChanged(); } }); Button startStopBtn = (Button)convertView.findViewById(R.id.startStopBtn); startStopBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if ((counterInfo.get(position)).status) { (counterInfo.get(position)).status = false; } else { (counterInfo.get(position)).status = true; if(counterInfo.get(position).time==-1 || counterInfo.get(position).time==-2) (counterInfo.get(position)).time = 119; } notifyDataSetChanged(); } }); TextView title=(TextView) convertView.findViewById(R.id.tv); title.setText(getRemainingTime(((CounterInfo)counterInfo.get(position)).time)); if(getRemainingTime(counterInfo.get(position).time).equals("done!")) { counterInfo.get(position).status=false; counterInfo.get(position).time=-2; } if((counterInfo.get(position).status)) { startStopBtn.setText("STOP"); } else { if(counterInfo.get(position).time==-2) startStopBtn.setText("RESTART"); else { if(counterInfo.get(position).time==-1) startStopBtn.setText("START"); else startStopBtn.setText("RESTART"); } } return convertView; } public String getRemainingTime(int seconds) { String time; if(seconds==-1) { time="02:00"; } else { if (seconds >= 60) { time = "01"; time += ":" + (seconds - 60); } else { if (seconds==-2) { time="done!"; } else { time = "00:" + seconds; } } } if(time.length()==4) time=time.substring(0,3)+"0"+time.charAt(3); if(time.equals("00:00")) { time="done!"; } return time; } } 

Paso 6: ¡ejecuta tu aplicación!

  • Android - ¿Cómo enlazar un ListView a una colección de objetos personalizados?
  • Android.view.InflateException: Línea de archivo XML binario # 9: Error al inflar la clase <unknown>
  • Falta el botón colocado después de ListView
  • Listview se bloquea en modo retrato cuando se hace clic en elementos (con fragmentos)
  • Mejor utilizar el adaptador Cursor o el adaptador Array
  • cómo agregar varias vistas de imagen en la fila de vista de lista con vista de desplazamiento horizontal
  • Listview no se puede hacer clic cuando se puede seleccionar la vista de texto dentro de esa vista de lista
  • SwipeRefreshLayout - Tirar desde el fondo
  • ListView: setItemChecked sólo funciona con ArrayAdapter estándar - NO funciona cuando se utiliza ArrayAdapter personalizado?
  • Checkbox auto call onCheckedChange cuando se desplaza el listview?
  • Cómo hacer encabezados de sección pegajosa (como iOS) en Android?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.