Android: несколько одновременных таймеров обратного отсчета в ListView
Я создаю приложение, которое требует ListView с неопределенным количеством элементов, каждый из которых имеет таймер, который отсчитывает от переменного числа. Я могу успешно сделать один из них обратный отсчет, но я не могу понять, как включить таймер в каждый элемент ListView.
В настоящее время я использую CountDownTimer (убедитесь, что вы используете D для копирования с сайта, они ошибаются).
Приветствуется любой код или источники, указывающие мне в правильном направлении.
Вот мой текущий класс EventAdapter, он устанавливает текст, отображаемый в каждом элементе ListView TextView. То, что мне нужно сделать, - это сделать CountView каждый день. Поскольку каждый элемент ListView отображает что-то другое, я полагаю, мне нужен способ дифференцирования каждого элемента.
Я мог бы просто обновлять весь список каждую секунду, но есть и другие элементы, которые я не включил, например изображения, загруженные из Интернета, что было бы нецелесообразно обновлять каждую секунду.
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;
}
}
Ответы
Ответ 1
Пожалуйста, посмотрите здесь, в моем блоге, где вы найдете пример о том, как достичь этого.
Одним из решений является размещение TextView, который представляет каждый счетчик в HashMap вместе с его позицией в списке в качестве ключа.
В 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);
}
Затем вы можете обновить счетчики, используя Handler и где вы отправляете 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);
}
}
};
Ответ 2
Это пример того, как я это делаю, и он отлично работает:
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();
}
}
И это адаптер
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();
}
}
Ответ 3
после проверки нескольких способов сделать это, это творческое решение, которое я написал. просто и прекрасно работает.
идея проверить, обновляет ли Runnable
данные, обновляющие те же TextView
, и если TextView
связан с другим видом, то Runnable
остановится, и таким образом не будет никаких дополнительных Thread
в фоновом режиме, чтобы не было мигающего текста или утечки памяти.
1. внутри вашего getView()
добавить каждый тег TextView с его позицией.
text = (TextView) view
.findViewById(R.id.dimrix);
text.setTag(position);
2. создайте класс, который реализует Runnable, чтобы мы могли передавать параметры.
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);
}
}
};
так что случается здесь, если TextView
не равен исходному тегу Runnable
.
3. вернитесь к своему getView()
final Handler mClockHandler = new Handler();
mUpdateClockTask clockTask = new mUpdateClockTask(text,
activeDraw, text.getTag().toString());
mClockHandler.post(clockTask);
чтобы он работал отлично!
Ответ 4
Вот еще одно решение с использованием ListView с несколькими CountDownTimer. Во-первых, мы создаем класс MyCustomTimer, который содержит 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();
}
}
Затем инициализируйте созданный класс в своем адаптере:
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;
}
}
Ответ 5
Вот мое решение (полный код) 20 независимых счетчиков в ListView, которые можно запускать, останавливать, перезапускать, reset независимо.
Я также страдаю этой проблемой несколько дней назад, но, наконец, я нашел решение проблемы. Я создал приложение, которое имеет 20 таймеров в представлении списка, которые могут работать независимо только одним CountDownTimer. каждый таймер может запускаться, останавливаться, reset, перезапускать самостоятельно. Каждый таймер установлен на 2 минуты. вот мой полный код
** нажмите, чтобы увидеть снимок экрана приложения
Шаг 1: создайте приложение для Android с пустой активностью
Шаг 2: Файл макета My 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>
Шаг 3: создайте файл макета, который используется в качестве элемента списка для вашего списка (имя файла 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>
Шаг 4: создайте класс, в котором хранятся данные каждого таймера (имя моего класса 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;
}
}
Шаг 5: создайте adaper для listView (имя моего адаптера 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;
}
}
Шаг 6: запустите приложение