Android listview item倒计时功能

前段时间项目有个需求是需要在listview的item中做个60s倒计时功能,并且倒计时的时间由本地记录,无关服务端。网上找了一些demo,有倒计时的功能,但总有些问题,也无法满足需求,最后自己改进后满足了需求,然后就想着记录下开发过程中遇到的一些问题以及最后的成品。

项目需求背景:app是关于视频会议,视频会议列表由listview来展示。本次需求是需要在会议列表的item中加一个再次通知的功能,点击后有个60s倒计时,等倒计时结束才可以再次点击。

试了几种后,感觉使用CountDownTimer来实现是比较好的。

demo:

import android.content.Context;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.sfyc.countdownlist.R;
import com.sfyc.countdownlist.entity.TimerItem;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;

import static com.sfyc.countdownlist.R.id.toolbar;


public class CountDownListActivity extends AppCompatActivity {

    private Context mContext;

    @BindView(toolbar)
    Toolbar mToolbar;

    @BindView(R.id.list_view)
    ListView mListView;

    MyAdapter mAdapter;
    private ArrayList<TimerItem> lstTimerItems;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_view);
        ButterKnife.bind(this);
        mContext = this;
        mToolbar.setTitle(R.string.title_list_view_countdown);
        initDadas();
        mAdapter = new MyAdapter(mContext, lstTimerItems);
        mListView.setAdapter(mAdapter);
    }

    private void initDadas() {
        lstTimerItems = new ArrayList<>();
        lstTimerItems.add(new TimerItem("A", 0));
        lstTimerItems.add(new TimerItem("B", 0));
        lstTimerItems.add(new TimerItem("C", 0));
        lstTimerItems.add(new TimerItem("D", 0));
        lstTimerItems.add(new TimerItem("E", 0));
        lstTimerItems.add(new TimerItem("F", 0));
        lstTimerItems.add(new TimerItem("G", 0));
        lstTimerItems.add(new TimerItem("H", 0));
        lstTimerItems.add(new TimerItem("I", 0));
        lstTimerItems.add(new TimerItem("J", 0));
        lstTimerItems.add(new TimerItem("K", 0));
        lstTimerItems.add(new TimerItem("L", 0));
        lstTimerItems.add(new TimerItem("M", 0));
        lstTimerItems.add(new TimerItem("N", 0));
        lstTimerItems.add(new TimerItem("O", 0));
    }

    public static class MyAdapter extends BaseAdapter {
        private List<TimerItem> mDatas;
        private Context mContext;
        //用于退出activity,避免countdown,造成资源浪费。
        private SparseArray<CountDownTimer> countDownCounters;

        public MyAdapter(Context mContext, List<TimerItem> mDatas) {
            this.mContext = mContext;
            this.mDatas = mDatas;
            this.countDownCounters = new SparseArray<>();
        }

        /**
         * 清空资源
         */
        public void cancelAllTimers() {
            if (countDownCounters == null) {
                return;
            }
            Log.e("TAG", "size :  " + countDownCounters.size());
            for (int i = 0, length = countDownCounters.size(); i < length; i++) {
                CountDownTimer cdt = countDownCounters.get(countDownCounters.keyAt(i));
                if (cdt != null) {
                    cdt.cancel();
                }
            }
        }

        @Override
        public int getCount() {
            if (mDatas != null && !mDatas.isEmpty()) {
                return mDatas.size();
            }
            return 0;
        }

        @Override
        public Object getItem(int position) {
            if (mDatas != null && !mDatas.isEmpty()) {
                return mDatas.get(position);
            }
            return null;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            if (convertView == null) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item_common2, parent, false);
                viewHolder = new ViewHolder();
                viewHolder.btn = (TextView) convertView.findViewById(R.id.btn);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            final TimerItem data = mDatas.get(position);

            viewHolder.btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    data.setExpirationTime(60*1000);
                    notifyDataSetChanged();
                }
            });
            CountDownTimer countDownTimer = countDownCounters.get(viewHolder.btn.hashCode());
            //将前一个缓存清除
            if (countDownTimer != null) {
                countDownTimer.cancel();
            }
            long timer = data.getExpirationTime();
//            timer = timer - System.currentTimeMillis();
            if (timer > 0) {
                countDownTimer = new CountDownTimer(timer, 1000) {
                    public void onTick(long millisUntilFinished) {
                        viewHolder.btn.setText(millisUntilFinished/1000 +"");
                        Log.e("TAG", data.name + " :  " + millisUntilFinished);
                    }
                    public void onFinish() {
                        viewHolder.btn.setText("开始倒计时");
                    }
                }.start();
                countDownCounters.put(viewHolder.btn.hashCode(), countDownTimer);
            } else {
                viewHolder.btn.setText("开始倒计时");
            }
            return convertView;
        }

        public class ViewHolder {
            public TextView btn;
        }
    }


}

效果:
在这里插入图片描述
但是这里有几个问题:
1.当item不可见时,CountDownTimer会停止运行,直至item可见时继续运行,这样倒计时的时间就会不准确。比如在55s的时候item不可见,过了20s后item可见了,你会发现倒计时的时间还在55s。
2.刷新问题,由于本项目做的是会议列表,因此有每隔5s自动刷新会议列表,并且用户也可以手动下拉刷新列表。这样不管之前的倒计时状态是怎样,刷新后都会变为未倒计时状态。

针对上述问题的解决方案:
1.因为之前按钮点击后我是给了个60s的时间,然后利用这个时间去做递减才会出现的这个情况。后来改成点击的时候赋予当前系统+60s的时间。然后去判断这个时间与当前系统时间的差值,如果差值大于0的话就执行倒计时。这样不管item不可见多久,时间的差值都不会有问题。
2.刷新问题一直都没有很好的解决,所以后面我用了一个笨办法,就是另外建立一个数组,每次点击按钮赋值的时候,在新建的数组中保存该对象。然后在每次刷新数据的时候,将数据与保存数据的数组一一对比,如果数据一致就将时间赋值过去。因为会议系统中会议id是唯一的,因此我这里是根据id去判断的。

改进后代码:

import android.content.Context;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.sfyc.countdownlist.R;
import com.sfyc.countdownlist.entity.TimerItem;

import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;

import static com.sfyc.countdownlist.R.id.toolbar;


public class CountDownListActivity extends AppCompatActivity {

    private Context mContext;


    @BindView(toolbar)
    Toolbar mToolbar;

    @BindView(R.id.list_view)
    ListView mListView;

    MyAdapter mAdapter;
    private ArrayList<TimerItem> lstTimerItems;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_view);
        ButterKnife.bind(this);
        mContext = this;
        mToolbar.setTitle(R.string.title_list_view_countdown);
        initDadas();
        mAdapter = new MyAdapter(mContext, lstTimerItems);
        mListView.setAdapter(mAdapter);
    }

    private void initDadas() {
        lstTimerItems = new ArrayList<>();
        lstTimerItems.add(new TimerItem("A", 0));
        lstTimerItems.add(new TimerItem("B", 0));
        lstTimerItems.add(new TimerItem("C", 0));
        lstTimerItems.add(new TimerItem("D", 0));
        lstTimerItems.add(new TimerItem("E", 0));
        lstTimerItems.add(new TimerItem("F", 0));
        lstTimerItems.add(new TimerItem("G", 0));
        lstTimerItems.add(new TimerItem("H", 0));
        lstTimerItems.add(new TimerItem("I", 0));
        lstTimerItems.add(new TimerItem("J", 0));
        lstTimerItems.add(new TimerItem("K", 0));
        lstTimerItems.add(new TimerItem("L", 0));
        lstTimerItems.add(new TimerItem("M", 0));
        lstTimerItems.add(new TimerItem("N", 0));
        lstTimerItems.add(new TimerItem("O", 0));
    }

    public static class MyAdapter extends BaseAdapter {
        private ArrayList<TimerItem> timerItemsSave = new ArrayList<>();
        private List<TimerItem> mDatas;
        private Context mContext;
        //用于退出activity,避免countdown,造成资源浪费。
        private SparseArray<CountDownTimer> countDownCounters;

        public MyAdapter(Context mContext, List<TimerItem> mDatas) {
            this.mContext = mContext;
            this.mDatas = mDatas;
            this.countDownCounters = new SparseArray<>();
        }

        /**
         * 清空资源
         */
        public void cancelAllTimers() {
            if (countDownCounters == null) {
                return;
            }
            Log.e("TAG", "size :  " + countDownCounters.size());
            for (int i = 0, length = countDownCounters.size(); i < length; i++) {
                CountDownTimer cdt = countDownCounters.get(countDownCounters.keyAt(i));
                if (cdt != null) {
                    cdt.cancel();
                }
            }
        }

        @Override
        public int getCount() {
            if (mDatas != null && !mDatas.isEmpty()) {
                return mDatas.size();
            }
            return 0;
        }

        @Override
        public Object getItem(int position) {
            if (mDatas != null && !mDatas.isEmpty()) {
                return mDatas.get(position);
            }
            return null;
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            final ViewHolder viewHolder;
            if (convertView == null) {
                convertView = LayoutInflater.from(mContext).inflate(R.layout.list_item_common2, parent, false);
                viewHolder = new ViewHolder();
                viewHolder.btn = (TextView) convertView.findViewById(R.id.btn);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            final TimerItem data = mDatas.get(position);
            viewHolder.btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    data.setExpirationTime(System.currentTimeMillis() + 60 * 1000);
                    notifyDataSetChanged();
                    timerItemsSave.add(data);
                }
            });
            //将当前item与保存起来的会议实体一一对比,如果会议id相同,则认为是同一个会议,那么去判断
            //如果按钮还需要倒计时,那么将时间赋值过去,如果不需要就从保存的列表中移除
            for (int i = 0; i < timerItemsSave.size(); i++) {
                if (timerItemsSave.get(i).getName().equals(data.getName())) {
                    if (timerItemsSave.get(i).getExpirationTime() - System.currentTimeMillis() > 0) {
                        //如果时间差大于0,按钮还需要倒计时,赋值
                        data.setExpirationTime(timerItemsSave.get(i).getExpirationTime());
                    } else {
                        //如果时间差小于等于0,不需要倒计时,将保存的对象移除(避免一次次的保存对象导致校验耗费时间)
                        timerItemsSave.remove(timerItemsSave.get(i));
                    }
                }

            }
            CountDownTimer countDownTimer = countDownCounters.get(viewHolder.btn.hashCode());
            //将前一个缓存清除
            if (countDownTimer != null) {
                countDownTimer.cancel();
            }
            long timer = data.getExpirationTime();
//            timer = timer - System.currentTimeMillis();
            if (timer > 0) {
                countDownTimer = new CountDownTimer(timer, 1000) {
                    public void onTick(long millisUntilFinished) {
                        viewHolder.btn.setText(millisUntilFinished/1000 +"");
                        Log.e("TAG", data.name + " :  " + millisUntilFinished);
                    }
                    public void onFinish() {
                        viewHolder.btn.setText("开始倒计时");
                    }
                }.start();
                countDownCounters.put(viewHolder.btn.hashCode(), countDownTimer);
            } else {
                viewHolder.btn.setText("开始倒计时");
            }
            return convertView;
        }

        public class ViewHolder {
            public TextView btn;
        }
    }


}

由于本身项目东西太多,因此就不贴出来而是写了demo。思路就是:点击按钮赋值同时保存对象,刷新数据的时候与保存的数据对比,需要倒计时的话就赋值。时间必须要用系统时间,可以避免倒计时数字错乱的问题。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值