Android:在ListView中同时使用多个倒计时器

11
我正在创建一个应用程序,需要一个ListView来显示未确定数量的元素,每个元素都有一个计时器从一个变量数字开始倒数。我已经成功地让其中一个计时器倒数,但我不知道如何在ListView的每个元素中包含一个计时器。
我目前正在使用CountDownTimer(如果从网站复制,请注意将D大写,他们搞错了)。
非常感谢任何代码或资源可以帮助我找到方向。
这是我的当前EventAdapter类,它设置了每个ListView元素的TextView中显示的文本。我需要做的是使TextView每秒钟倒数一次。由于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;
    }
}

你能给我们提供一些你已经写的代码示例吗?不知道你写了什么,很难猜出问题出在哪里。 - plowman
不是有什么问题,只是我不知道如何开始实现它。但我马上会发布一些代码。 - Kevin Cooper
我其实也遇到了同样的问题,打算使用单例CountDownTimer来运行我的rxJava中的myClickMethod()方法,每个视图都订阅该事件并收到通知以更新其状态。我只是将其视为某种解决方案。 - murt
请查看此答案:https://dev59.com/qF0Z5IYBdhLWcg3wyy11#40236877 - Dev
4个回答

11

这是我处理事情的方式示例,它非常有效:

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

1
感谢您提供如此出色的解决方案! - PaperThick
是的,这对我也很有帮助。谢谢。 - Ranjitsingh Chandel
谢谢,这是一个简单而棒的解决方案,节省了我的时间。 - mdDroid

7
请看这里的我的博客,您将找到有关如何实现此功能的示例。
一种解决方案是将表示每个计数器的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并在其中发布可运行项来更新计数器。
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);
        }
    }
};

5
请提供链接上下文。在回答中至少包括主要观点,最好包括一些代码示例。可以引用您的博客以获取更多详情,但是您的回答应该即使读者选择不跟随链接也能够理解。 - Gilles 'SO- stop being evil'

4

在检查了几种方法后,我写了一个创意解决方案。它简单而完美地工作。

这个想法是检查更新数据的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);

那就是这样了,完美运作!

谢谢,这是一个非常老的问题,我已经不再在这个项目上工作了,但也许你的答案会帮助其他人。 - Kevin Cooper
这就是想法,我相信你现在已经明白了 (-: - Jesus Dimrix
1
这是答案 - mikebertiean

1
这是使用带有多个CountDownTimer的ListView的另一种解决方案。首先,我们创建一个包含CountDownTimer的类MyCustomTimer
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;
    }
}

当然不会工作。它为每个视图创建新的CountDownTimer,而没有检查它是新视图还是现有视图。滚动一次后,每个视图上将有多个CountDownTimer。 - Jesus Dimrix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接