RecyclerView倒计时解决方案

在开中难免遇到RecyclerView中带有倒计时的场景,有时候不止一个。那么如何实现一个带有倒计时的RecyclerView便是本文讨论的课题

1.在RecyclerView中实现高质量倒计时需要解决哪些问题。

  • 当列表中有多个倒计时如何处理,开启多个还是统一管理?如果每个item都开启倒计时是否造成了资源浪费?不在展示区的倒计时又该如何取消?不在屏幕上的倒计时如何保证时间的准确性?只使用一个倒计时如何通知所有需要更新的item?如何保证不在屏幕上而不被通知的item的时间的准确性?能否在退出后台或者跳转到其他页面的时候暂停减少资源的浪费?

2.带着这些问题着手设计我们的倒计时方案。

  • 首先从大的逻辑上我们优选单任务倒计时方案。

  • 计时器的选择,考虑到可能频繁启停,我们选择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不会产生错位

3. 整体思路整理完毕,接下来代码实现

  • Helper类代码实现
public class RvCountDownHelper {

    private List<Index> 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<Index> 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<MyViewHolder> {

        List<TestData> 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);
            }
        }
    }


}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在RecyclerView中实现倒计时,可以通过在RecyclerView的Adapter中使用Handler和Runnable来实现。以下是一个简单的示例: 1. 在ViewHolder中添加一个TextView用于显示倒计时的时间,例如: ```java public class MyViewHolder extends RecyclerView.ViewHolder { TextView countdownTextView; Handler handler; Runnable runnable; public MyViewHolder(View itemView) { super(itemView); countdownTextView = itemView.findViewById(R.id.countdown_textview); handler = new Handler(); runnable = new Runnable() { @Override public void run() { // 更新倒计时的时间 countdownTextView.setText(getTimeLeft()); handler.postDelayed(this, 1000); // 每隔1秒更新一次 } }; } // 计算距离结束时间还剩下的时间 private String getTimeLeft() { long timeLeft = endTime - System.currentTimeMillis(); if (timeLeft <= 0) { return "倒计时已结束"; } else { long seconds = timeLeft / 1000; long minutes = seconds / 60; long hours = minutes / 60; long days = hours / 24; return String.format(Locale.getDefault(), "%02d:%02d:%02d:%02d", days, hours % 24, minutes % 60, seconds % 60); } } // 开始倒计时 public void startCountdown(long endTime) { this.endTime = endTime; handler.post(runnable); } // 停止倒计时 public void stopCountdown() { handler.removeCallbacks(runnable); } } ``` 2. 在Adapter中重写onBindViewHolder方法,在其中调用ViewHolder的startCountdown方法开始倒计时,例如: ```java @Override public void onBindViewHolder(MyViewHolder holder, int position) { // 获取当前item的结束时间 long endTime = data.get(position).getEndTime(); holder.startCountdown(endTime); } ``` 3. 在Activity或Fragment中,在RecyclerView被销毁的时候,调用ViewHolder的stopCountdown方法停止倒计时,例如: ```java @Override protected void onDestroy() { super.onDestroy(); for (int i = 0; i < recyclerView.getChildCount(); i++) { MyViewHolder holder = (MyViewHolder) recyclerView.getChildViewHolder(recyclerView.getChildAt(i)); holder.stopCountdown(); } } ``` 这样就能在RecyclerView中实现倒计时了。当然,还可以根据不同的需求进行更加灵活的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值