首先从大的逻辑上我们优选单任务倒计时方案。
计时器的选择,考虑到可能频繁启停,我们选择Rxjava提供的interval方法。内部基于线程池管理,避免使用Timer类会造成线程开关的成本过高。
敲黑板啦重点来了 计时器只负责定时通知相关item更新UI,不记录item剩余时间。由于前端获取的时间可能有时差或者被用户修改过是不可信的,网关下发的bean类中是剩余时间如2000秒。需要在bean类中手动增加一个变量 倒计时结束时间点 我们命名为endCountTime,在json映射为bean类的时候我们获取当前系统时间 让它加上 倒计时剩余时间 就是所需要的 endCountTime。 同时呢由于系统时间的不可信,也就是System.getCurrentMillions是不可信的,所以我们选择系统的开机时钟 SystemClock.elapsedRealtime()。这是我们能够得以实现随时暂停再开启倒计时 倒计时时间依然能够保证正确的关键因素。
使用一个List来管理需要通知的Item位置pos,我们命名为countDownPositions。在bindViewHolder的时候我们将需要更新的itemPosition添加到countDownPositions中,当计时器任务通知时来遍历countDownPositions,然后进行notityitemchanged。在这我们会遇到两个选择,不在屏幕上的item,notifyitemchange的时候会不会造成浪费。另一种选择的是在notifyitemchange 的时候判断是否在屏幕上。 判断是否在屏幕上和直接notify成本哪个更高,虽然有查阅相关资料,也做过一些测试。并没有分辨出来哪个更好,如有了解的还请指教,在本次实践中 我是判断是否在屏幕上来决定是否notify。
由于我们记录的item位置pos,当RecyclerView发生,增删的时候,我们记录的位置有可能会错位,所以我们给adaptert注册一个数据观察器,这样在数据发生变动的时候,可以保证需要更新的item不会产生错位
public class RvCountDownHelper {
private List countDownPositions = new ArrayList<>();
private RecyclerView.Adapter rvAdapter;
private RecyclerView recyclerView;
private Disposable countDownTask;
private OnTimeCollectListener mListener;
public RvCountDownHelper(RecyclerView.Adapter rvAdapter, RecyclerView recyclerView) {
this.recyclerView = recyclerView;
this.rvAdapter = rvAdapter;
rvAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
removeAllPosition();
super.onChanged();
Log.d("AdapterDataObserver", "onChanged");
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
super.onItemRangeChanged(positionStart, itemCount);
Log.d("AdapterDataObserver", "onItemRangeChanged");
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
super.onItemRangeChanged(positionStart, itemCount, payload);
Log.d("AdapterDataObserver", "onItemRangeChanged");
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
for (Index countDownPosition : countDownPositions) {
if (countDownPosition.index >= positionStart) {
countDownPosition.index += itemCount;
}
}
super.onItemRangeInserted(positionStart, itemCount);
Log.d("AdapterDataObserver", "onItemRangeInserted");
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
for (int i = countDownPositions.size() - 1; i >= 0; i--) {
Index temp = countDownPositions.get(i);
if (temp.index >= positionStart + itemCount) {
temp.index = temp.index - itemCount;
} else if (temp.index >= positionStart) {
removeCountDownPosition(temp.index);
}
}
super.onItemRangeRemoved(positionStart, itemCount);
Log.d("AdapterDataObserver", "onItemRangeRemoved");
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
Log.d("ItemMove", "frompos =" + fromPosition + " toPos =" + toPosition + " itemCount= " + itemCount);
for (Index countDownPosition : countDownPositions) {
if (countDownPosition.index == fromPosition) {
countDownPosition.index = toPosition;
}else if (countDownPosition.index == toPosition) {
countDownPosition.index = fromPosition;
}
}
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
Log.d("AdapterDataObserver", "onItemRangeMoved");
}
});
}
public void setOnTimeCollectListener(OnTimeCollectListener listener) {
this.mListener = listener;
}
/**
* 新增一个需要倒计时的item位置
* @param pos
*/
public void addPosition2CountDown(int pos) {
Index addPos = new Index(pos);
if (!countDownPositions.contains(addPos)) {
Log.d("CountDown", "新增pos-" + pos);
countDownPositions.add(addPos);
startCountDown();
}
}
/**
* 移除一个需要定时更新的item
* @param pos
*/
public void removeCountDownPosition(int pos) {
boolean remove = countDownPositions.remove(new Index(pos));
Log.d("CountDown", "移除pos-" + pos + "result = " + remove);
}
/**
* 移除所有需要定时更新的item
*/
public void removeAllPosition() {
countDownPositions.clear();
Log.d("CountDown", "移除所有标记位置");
}
/**
* 手动调用开始定时更新
*/
public void startCountDown() {
if (countDownTask == null || countDownTask.isDisposed()) {
countDownTask = Observable.interval(0, 1000, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aLong -> {
Log.d("倒计时--", "cur aLong= " + aLong);
if (countDownTask.isDisposed()) {
return;
}
if (countDownPositions.isEmpty()) {
countDownTask.dispose();
return;
}
for (Index countDownPosition : countDownPositions) {
RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
if (lm != null) {
View itemView = recyclerView.getLayoutManager().findViewByPosition(countDownPosition.index);
if (itemView != null) {
if (mListener != null) {
RecyclerView.ViewHolder viewHolder = recyclerView.findViewHolderForPosition(countDownPosition.index);
mListener.onTimeCollect(viewHolder, countDownPosition.index);
} else {
rvAdapter.notifyItemChanged(countDownPosition.index);
}
}
}
}
}, throwable -> Log.e("倒计时异常", throwable.getMessage()));
}
}
/**
* 手动调用停止定时更新
*/
public void stopCountDown() {
if (countDownTask != null && !countDownTask.isDisposed()) {
countDownTask.dispose();
}
}
/**
* 获取所有的item位置记录
*/
public List getAllRecordPos() {
return countDownPositions;
}
/**
* 销毁
*/
public void destroy() {
stopCountDown();
mListener = null;
countDownTask = null;
recyclerView = null;
rvAdapter = null;
}
interface OnTimeCollectListener {
void onTimeCollect(RecyclerView.ViewHolder vh,int pos);
}
static class Index {
int index;
public Index(int index) {
this.index = index;
}
@Override
public boolean equals(@Nullable Object obj) {
if(!(obj instanceof Index)) {
// instanceof 已经处理了obj = null的情况
return false;
}
Index indObj = (Index) obj;
// 地址相等
if (this == indObj) {
return true;
}
// 如果两个对象index相等
return indObj.index == this.index;
}
@Override
public int hashCode() {
return 128 * index;
}
}
}
public class MainActivity extends AppCompatActivity {
MyRvAdapter myRvAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView rvMyRv = findViewById(R.id.rvMyRv);
// rvMyRv.setItemAnimator(null);
((SimpleItemAnimator)rvMyRv.getItemAnimator()).setSupportsChangeAnimations(false);
rvMyRv.setLayoutManager(new LinearLayoutManager(this));
myRvAdapter = new MyRvAdapter(rvMyRv);
rvMyRv.setAdapter(myRvAdapter);
}
@Override
protected void onPause() {
super.onPause();
myRvAdapter.stopCountDown();
}
@Override
protected void onResume() {
super.onResume();
myRvAdapter.startCountDown();
}
public void addClick(View view) {
myRvAdapter.addItem();
}
public void removeClick(View view) {
myRvAdapter.deleteItem();
}
public void exchangeClick(View view) {
myRvAdapter.exchangeItem(4, 2);
}
static class MyRvAdapter extends RecyclerView.Adapter {
List times;
RvCountDownHelper countDownHelper;
RecyclerView mRecyclerView;
public MyRvAdapter(RecyclerView recyclerView) {
this.mRecyclerView = recyclerView;
times = new ArrayList<>();
countDownHelper = new RvCountDownHelper(this, mRecyclerView);
// countDownHelper.setOnTimeCollectListener((viewHolder,pos) -> {
// if (viewHolder instanceof MyViewHolder) {
// long curMillions = SystemClock.elapsedRealtime();
// long endMillions = times.get(pos).countDownEndTime;
//
// long tmp = endMillions - curMillions;
//
// if (tmp > 1000) {
// ((MyViewHolder) viewHolder).tvShowTime.setText("倒计时 " + getShowStr(tmp));
// }
// }
// });
long curMillions = SystemClock.elapsedRealtime();
for (int i = 0; i < 50; i++) {
if (i % 2 == 0) {
times.add(TestData.createRandomData(curMillions + (long) new Random().nextInt(30 * 60 * 1000)));
} else {
times.add(TestData.createRandomData(-1));
}
}
}
public void addItem() {
long curMillions = SystemClock.elapsedRealtime();
times.add(0, TestData.createRandomData(curMillions + (long) new Random().nextInt(30 * 60 * 1000)));
notifyItemInserted(0);
}
public void deleteItem() {
times.remove(0);
notifyItemRemoved(0);
}
public void exchangeItem(int fromPos, int toPos) {
Collections.swap(times,fromPos,toPos);
notifyItemRangeChanged(fromPos, toPos + 1 - fromPos);
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
View contentView = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.item_layout, viewGroup, false);
return new MyViewHolder(contentView);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder viewHolder, int i) {
TestData data = times.get(i);
if (data.isCountDownItem) {
long curMillions = SystemClock.elapsedRealtime();
long tmp = data.countDownEndTime - curMillions;
if (tmp > 1000) {
viewHolder.tvShowTime.setText("倒计时 " + getShowStr(tmp));
countDownHelper.addPosition2CountDown(i);
} else {
viewHolder.tvShowTime.setText("倒计时 00:00:00");
countDownHelper.removeCountDownPosition(i);
}
}else {
viewHolder.tvShowTime.setText("无倒计时");
}
}
@Override
public int getItemCount() {
return times.size();
}
private String getShowStr(long mis) {
mis = mis / 1000; //
long h = mis / 3600;
long m = mis % 3600 / 60;
long d = mis % 3600 % 60;
return h + ":" + m + ":" + d;
}
public void destroy() {
countDownHelper.destroy();
}
public void stopCountDown() {
countDownHelper.stopCountDown();
}
public void startCountDown() {
countDownHelper.startCountDown();
}
}
@Override
protected void onDestroy() {
myRvAdapter.destroy();
super.onDestroy();
}
static class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvShowTime;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
tvShowTime = itemView.findViewById(R.id.tvShowTime);
}
}
static class TestData {
public TestData(boolean isCountDownItem, long countDownEndTime) {
this.isCountDownItem = isCountDownItem;
this.countDownEndTime = countDownEndTime;
}
boolean isCountDownItem;
long countDownEndTime;
static TestData createRandomData(long endTime) {
if (endTime < 0) {
return new TestData(false, endTime);
} else {
return new TestData(true, endTime);
}
}
}
}
activity_main.xml
item_layout.xml