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: запустите приложение