Android 列表(ListView、RecyclerView)不斷刷新最佳實踐

本文微信公衆號「AndroidTraveler」首發。java

背景

在 Android 列表開發過程當中,有時候咱們的 Item 會有一些組件,好比倒計時。這類組件要求不斷刷新,這個時候因爲列表複用的機制,所以會有一些坑。那麼咱們本篇文章就給你們講兩個主題。git

第一個是列表複用是否必定有問題。 第二個是出現問題有哪些解決方案可供咱們選擇。github

小 Demo

因爲咱們的主題重點是爲了解決不斷刷新問題,所以關於 RecyclerView 的基本使用就再也不贅述,不清楚的小夥伴能夠看下我以前的文章:
RecyclerView基本使用apache

首先咱們看下效果圖:微信

很簡單,就是一個 RecyclerView 列表,列表項有兩個組件。分別表明第幾項和剩餘秒數。ide

這裏就是經過倒計時來演示刷新可能存在的問題。函數

重點代碼是 Adapter 裏面的顯示邏輯,初始爲:post

@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
    holder.mTvNum.setText(String.valueOf(position + 1));
    updateTime(holder, itemList.get(position));
}

private void updateTime(final RecyclerViewViewHolder holder, final long time) {
    String content;
    long remainTime = time - System.currentTimeMillis();
    remainTime /= 1000;
    if (remainTime <= 0) {
        content = "Time up";
        holder.mTxtTitle.setText(content);
        return;
    }

    content = "剩下"+remainTime+"秒";
    holder.mTxtTitle.setText(content);
}
複製代碼

所有代碼見:github.com/nesger/Recy…優化

接下來咱們增長刷新方法,有不少種,咱們一一說明。ui

1. 使用 handler 來實現倒計時刷新

修改顯示代碼,以下:

@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
    holder.mTvNum.setText(String.valueOf(position + 1));
    updateTime(holder, itemList.get(position));
}

private void updateTime(final RecyclerViewViewHolder holder, final long time) {
    String content;
    long remainTime = time - System.currentTimeMillis();
    remainTime /= 1000;
    if (remainTime <= 0) {
        content = "Time up";
        holder.mTxtTitle.setText(content);
        return;
    }

    content = "剩下"+remainTime+"秒";
    holder.mTxtTitle.setText(content);
    holder.mTxtTitle.postDelayed(new Runnable() {
        @Override
        public void run() {
            updateTime(holder, time);
        }
    }, 1000);
}
複製代碼

能夠看到經過 handler 延時一秒,而後每次更新時間也是減小一秒。

咱們看下效果圖:

能夠看到沒滾動以前還好,滾動以後會發現,倒計時都亂了。

固然有時候可能不會暴露出來,好比滾動數目少,或者只有部分組件有倒計時,不像咱們這個例子,全部項目都有倒計時,可是這也間接留下了可能的坑。

出現這個問題的緣由在於組件的複用,若是你用 ListView 演示,而且不用複用,那麼是不會錯亂的。

固然列表不復用這個確定是不推薦的。

所以,該方式不推薦

所有代碼見:github.com/nesger/Recy…

2. 使用 Timer 來實現倒計時刷新

@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
    holder.mTvNum.setText(String.valueOf(position + 1));
    updateTime(holder, itemList.get(position));
}

private void updateTime(final RecyclerViewViewHolder holder, final long time) {
    String content;
    long remainTime = time - System.currentTimeMillis();
    remainTime /= 1000;
    if (remainTime <= 0) {
        content = "Time up";
        holder.mTxtTitle.setText(content);
        return;
    }

    content = "剩下"+remainTime+"秒";
    holder.mTxtTitle.setText(content);
}
複製代碼

同樣不行,不推薦

所有代碼見:github.com/nesger/Recy…

3. 使用 Timer + View 集合

其實咱們簡單分析一下就知道,出現上面錯亂狀況的緣由大體是兩個:一個是複用,一個是代碼屢次調用。
因此若是可以解決這兩個問題,那麼這個問題就解決了。

由於咱們這裏的業務是倒計時監聽,全部 View 都是同樣的,就是一秒更新一次。

因此咱們的定時器不須要 N 個,只須要一個,在構造函數初始化便可。

另外爲了不復用和代碼屢次調用問題,咱們將 View 經過一個集合保存起來。

最後修改的代碼以下:

private Timer mTimer;
private Set<RecyclerViewViewHolder> mHolders;

public RecyclerViewAdapter(Activity activity, List<Long> itemList) {
    if (activity == null || itemList == null) {
        throw new IllegalArgumentException("params can't be null");
    }
    this.activity = activity;
    this.itemList = itemList;
    mHolders = new HashSet<>();
    mTimer = new Timer();
    mTimer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            for (RecyclerViewViewHolder holder : mHolders) {
                updateTime(holder, holder.getTime());
            }
        }
    }, 0, 1000);
}

@Override
public void onBindViewHolder(RecyclerViewViewHolder holder, int position) {
    holder.setTime(itemList.get(position));
    mHolders.add(holder);
    holder.mTvNum.setText(String.valueOf(position + 1));
    updateTime(holder, itemList.get(position));
}
複製代碼

效果圖以下:

能夠看到沒問題了。

固然這裏有些優化還沒處理,由於本篇主要是思路分析,這裏就不添加了。

待優化點:定時器的啓動和關閉跟生命週期關聯,無數據源不啓用定時器等。

所有代碼見:github.com/nesger/Recy…

該方法來自與一名朋友的分享。

4. 使用 ScheduledExecutorService + View 集合

這邊 AndroidStudio 有安裝阿里巴巴提供的一個代碼檢測插件,連接爲:plugins.jetbrains.com/plugin/1004…

在 AndroidStudio 輸入插件名字 Alibaba Java Coding Guidelines 查找安裝便可。

在方法 3 使用 Timer 時提示下面信息:

Run multiple TimeTask by using ScheduledExecutorService rather than Timer because Timer will kill all running threads in case of failing to catch exceptions. 
            
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
    new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
executorService.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
        //do something
    }
},initialDelay,period, TimeUnit.HOURS);
複製代碼

因此咱們這裏修改 Timer 爲 ScheduledExecutorService:

private ScheduledExecutorService mExecutorService;

public RecyclerViewAdapter(Activity activity, List<Long> itemList) {
    if (activity == null || itemList == null) {
        throw new IllegalArgumentException("params can't be null");
    }
    this.activity = activity;
    this.itemList = itemList;
    mHolders = new HashSet<>();
    mExecutorService = new ScheduledThreadPoolExecutor(1, new ThreadFactory() {
        @Override
        public Thread newThread(@NonNull Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("countdown");
            return thread;
        }
    });
    mExecutorService.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            for (RecyclerViewViewHolder holder : mHolders) {
                updateTime(holder, holder.getTime());
            }
        }
    }, 0, 1000, TimeUnit.MILLISECONDS);
}
複製代碼

所有代碼見:github.com/nesger/Recy…

有更多方法歡迎到上面的 GitHub 連接提 PR,能夠基於 feature/refresh 分支新建分支。

有另一位朋友提出了自定義 View 的處理方式,將倒計時的功能放到 View 裏面去處理,這個感興趣的小夥伴能夠實現而後提 PR 哈,這裏提供額外一種思路。

相關文章
相關標籤/搜索