天天看點

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

Android ListView與RecyclerView局部重新整理

一、ListView

之前寫過一篇關于ListView局部重新整理的部落格,這部分對其進行完善

,之前的連結為:Android模拟ListView點選下載下傳和局部重新整理

平時在寫ListView的時候需要更改某些資料,這種情況我們一般會調用

notifyDataSetChanged()方法進行重新整理,調用notifydatasetchange其實會導緻adpter的getView方法被多次調用(畫面上能顯示多少就會被調用多少次),并且在有擷取網絡圖檔的情況下會可能造成大量閃動或卡頓,極大的影響使用者體驗(圖檔重新加載并閃動在ImageLoader架構中會出現,在glide架構中沒有出現)。

是以我們需要做單行重新整理來進行優化

這個是Google官方給出的解決方案:

private void updateSingleRow(ListView listView, long id) {  

        if (listView != null) {  
            int start = listView.getFirstVisiblePosition();  
            for (int i = start, j = listView.getLastVisiblePosition(); i <= j; i++)  
                if (id == ((Messages) listView.getItemAtPosition(i)).getId()) {  
                    View view = listView.getChildAt(i - start);  
                    getView(i, view, listView);  
                    break;  
                }  
        }  
    }
           

對于這個方法可以參考這個部落格:android ListView 單條重新整理方法實踐及原了解析

可以看出來谷歌的方案是通過listview的getView方法将單行的所有内容都重新整理一遍,但是這樣如果是有加載網絡圖檔的話可能也會造成閃動重新加載,是以我們需要單獨重新整理某個item中的某個控件來實作局部重新整理

是以我們在Adapter中添加一個局部重新整理的方法

/**
     * 局部重新整理
     *
     * @param mListView
     * @param posi
     */
    public void updateSingleRow(ListView mListView, int posi) {
        if (mListView != null) {
            //擷取第一個顯示的item
            int visiblePos = mListView.getFirstVisiblePosition();
            //計算出目前選中的position和第一個的差,也就是目前在螢幕中的item位置
            int offset = posi - visiblePos;
            int lenth = mListView.getChildCount();
            // 隻有在可見區域才更新,因為不在可見區域得不到Tag,會出現空指針,是以這是必須有的一個步驟
            if ((offset < ) || (offset >= lenth)) return;
            View convertView = mListView.getChildAt(offset);
            ViewHolder viewHolder = (ViewHolder) convertView.getTag();
            //以下是處理需要處理的控件方法。。。。。
        }
    }
           

舉個例子,簡單的模拟在清單中點選下載下傳并更新清單的demo

1.首先定義一個Bean對象

這裡有一個圖檔url供Glide加載

public class Game {

    private String gameName;
    private long gameTotalSize;
    private long gameDownSize;
    private String gameUrl;
    private int state;

    public Game(String gameName, long gameTotalSize) {
        this.gameName = gameName;
        this.gameTotalSize = gameTotalSize;
        gameUrl = "http://dynamic-image.yesky.com/600x-/uploadImages/upload/20140912/upload/201409/smkxwzdt1n1jpg.jpg";
        this.gameDownSize = ;
    }

    public String getGameUrl() {
        return gameUrl;
    }

    public String getGameName() {
        return gameName;
    }

    public long getGameTotalSize() {
        return gameTotalSize;
    }

    public long getGameDownSize() {
        return gameDownSize;
    }

    public void setGameDownSize(long gameDownSize) {
        this.gameDownSize = gameDownSize;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }
}
           

2.實作一個下載下傳的模拟器(單例類)

public class DownLoadManager {

    public interface DownCallBack{
        void update(int posi,DownFile downFile);
    }

    private DownCallBack downCallBack;

    public void setDownCallBack(DownCallBack downCallBack) {
        this.downCallBack = downCallBack;
    }

    /**
     * 下載下傳檔案類,不過在正常的項目中應該有一個ID或者url,
     * 用來判斷檔案,在這個demo中就用清單的position來判斷了
     */
    public static class DownFile {
        public long total;
        public long downSize;
        public int downState;

        public DownFile(long total, long downSize, int downState) {
            this.total = total;
            this.downSize = downSize;
            this.downState = downState;
        }
    }

    public static final int DOWN_WATE = ;
    public static final int DOWN_PAUST = ;
    public static final int DOWN_FINISH = ;
    public static final int DOWN_DOWNLOADING = ;

    private ExecutorService executorService;
    private SparseArray<DownFile> downFileSparseArray;
    private SparseArray<DownTask> downTaskSparseArray;
    private static volatile DownLoadManager singleton;
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            int posi = msg.arg1;
            DownLoadManager.DownFile downFile = (DownLoadManager.DownFile) msg.obj;
            if (downCallBack!=null){
                downCallBack.update(posi,downFile);
            }
        }
    };

    private DownLoadManager() {
        executorService = Executors.newFixedThreadPool();
        downFileSparseArray = new SparseArray<>();
        downTaskSparseArray = new SparseArray<>();
    }

    public static DownLoadManager getInstance() {
        if (singleton == null) {
            synchronized (DownLoadManager.class) {
                if (singleton == null) {
                    singleton = new DownLoadManager();
                }
            }
        }
        return singleton;
    }

    public void start(int posi, DownFile downFile) {
        if (downFile.downState == DOWN_WATE||downFile.downState == DOWN_FINISH){
            return;
        }
        //首先設定為排隊中的狀态
        downFile.downState = DOWN_WATE;
        update(posi, downFile);
        downFileSparseArray.put(posi, downFile);
        DownTask downTask = new DownTask(posi);
        downTaskSparseArray.put(posi, downTask);
        executorService.submit(downTask);
    }

    public void pause(int posi, DownFile downFile) {
        downTaskSparseArray.get(posi).stop();
        downTaskSparseArray.remove(posi);
        downFile.downState = DOWN_PAUST;
        update(posi, downFile);
    }

    public void update(int posi, DownFile downFile) {
        Message msg = handler.obtainMessage();
        msg.obj = downFile;
        msg.arg1 = posi;
        msg.sendToTarget();
    }

    public void stopAll(){
        for (int i = ;i<downTaskSparseArray.size();i++) {
            downTaskSparseArray.valueAt(i).stop();
        }
        downTaskSparseArray.clear();
    }


    private class DownTask implements Runnable {

        private int posi;
        private boolean isWorking;
        private DownFile downFile;

        public DownTask(int posi) {
            this.posi = posi;
            isWorking = true;
            downTaskSparseArray.put(posi, this);
        }

        public void stop() {
            this.isWorking = false;
        }

        @Override
        public void run() {

            //一旦成功進入到線程裡就變為下載下傳中狀态
            downFile = downFileSparseArray.get(posi);
            downFile.downState = DOWN_DOWNLOADING;
            while (isWorking) {
                update(posi, downFile);
                if (downFile.downSize < downFile.total) {
                    ++downFile.downSize;
                } else {
                    downFile.downState = DOWN_FINISH;
                    downFileSparseArray.remove(posi);
                    downTaskSparseArray.remove(posi);
                    isWorking = false;
                    break;
                }
                try {
                    Thread.sleep();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    downFile.downState = DOWN_PAUST;
                    downFileSparseArray.remove(posi);
                    downTaskSparseArray.remove(posi);
                    isWorking = false;
                }
            }
        }
    }

}
           

3.建立adapter

public class LvAdapter extends BaseAdapter implements DownLoadManager.DownCallBack{

    private Context context;
    private List<Game> games;
    private LayoutInflater layoutInflater;
    private ListView listView;
    private DownLoadManager downLoadManager;

    public LvAdapter(Context context, ListView listView, List<Game> games) {
        this.context = context;
        this.games = games;
        this.listView = listView;
        layoutInflater = LayoutInflater.from(context);
        downLoadManager = DownLoadManager.getInstance();
        downLoadManager.setDownCallBack(this);
    }

    @Override
    public int getCount() {
        return games.size();
    }

    @Override
    public Object getItem(int position) {
        return games.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = layoutInflater.inflate(R.layout.item, parent, false);
            viewHolder = new ViewHolder(convertView);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        setItemView(viewHolder, games.get(position), position);

        return convertView;
    }

    private void setItemView(ViewHolder viewHolder, final Game game, final int position) {

        viewHolder.itemProgress.setMax((int) game.getGameTotalSize());
        viewHolder.itemName.setText(game.getGameName());
        viewHolder.itemSize.setText(game.getGameTotalSize() + "");
        //加載圖檔
        Glide.with(context).load(game.getGameUrl()).placeholder(R.mipmap.ic_launcher).crossFade().into(viewHolder.itemImg);
        viewHolder.itemDownBtn.setText(getGameState(game));

        if (game.getGameDownSize() >  && game.getGameDownSize() < game.getGameTotalSize()) {
            viewHolder.itemProgress.setVisibility(View.VISIBLE);
            viewHolder.itemProgress.setProgress((int) game.getGameDownSize());
        } else {
            viewHolder.itemProgress.setVisibility(View.INVISIBLE);
        }

        viewHolder.itemDownBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (game.getState() == DownLoadManager.DOWN_DOWNLOADING)
                    downLoadManager.pause(position, new DownLoadManager.DownFile(game.getGameTotalSize(), game.getGameDownSize(), game.getState()));
                else
                    downLoadManager.start(position, new DownLoadManager.DownFile(game.getGameTotalSize(), game.getGameDownSize(), game.getState()));
            }
        });

    }

    private String getGameState(Game game) {
        switch (game.getState()) {
            case DownLoadManager.DOWN_DOWNLOADING:
                return "下載下傳中";
            case DownLoadManager.DOWN_FINISH:
                return "已完成";
            case DownLoadManager.DOWN_PAUST:
                return "暫停";
            case DownLoadManager.DOWN_WATE:
                return "等待中";
        }
        return "下載下傳";
    }

    /**
     * 局部重新整理
     *
     * @param mListView
     * @param posi
     */
    public void updateSingleRow(ListView mListView, int posi) {
        if (mListView != null) {
            //擷取第一個顯示的item
            int visiblePos = mListView.getFirstVisiblePosition();
            //計算出目前選中的position和第一個的差,也就是目前在螢幕中的item位置
            int offset = posi - visiblePos;
            int lenth = mListView.getChildCount();
            // 隻有在可見區域才更新,因為不在可見區域得不到Tag,會出現空指針,是以這是必須有的一個步驟
            if ((offset < ) || (offset >= lenth)) return;
            View convertView = mListView.getChildAt(offset);
            ViewHolder viewHolder = (ViewHolder) convertView.getTag();
            //以下是處理需要處理的控件
            System.out.println("posi = " + posi);
            Game game = games.get(posi);
            if (game.getGameDownSize() >  && game.getGameDownSize() < game.getGameTotalSize()) {
                viewHolder.itemProgress.setVisibility(View.VISIBLE);
                viewHolder.itemProgress.setProgress((int) game.getGameDownSize());
            } else {
                viewHolder.itemProgress.setVisibility(View.INVISIBLE);
            }

            viewHolder.itemDownBtn.setText(getGameState(game));
        }
    }

    @Override
    public void update(int posi, DownLoadManager.DownFile downFile) {
        Game game = games.get(posi);
        game.setGameDownSize(downFile.downSize);
        game.setState(downFile.downState);
//            notifyDataSetChanged();
        updateSingleRow(listView, posi);
    }

    private class ViewHolder {

        TextView itemName, itemSize;
        Button itemDownBtn;
        ProgressBar itemProgress;
        ImageView itemImg;

        public ViewHolder(View convertView) {
            itemImg = (ImageView) convertView.findViewById(R.id.itemImg);
            itemName = (TextView) convertView.findViewById(R.id.itemName);
            itemSize = (TextView) convertView.findViewById(R.id.itemSize);
            itemDownBtn = (Button) convertView.findViewById(R.id.itemDownBtn);
            itemProgress = (ProgressBar) convertView.findViewById(R.id.itemProgress);
        }
    }

}
           

基本代碼就是這些。

下面是notifyDataSetChanged後重新整理的效果:由于不斷的重新整理界面中所有的item,是以在下載下傳的時候點選按鈕沒有任何的反應,隻有在重新整理的間隙時間裡點選才可以執行下載下傳

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

可以看到,這裡我點選了好多回沒有效果,偶爾會成功,這簡直沒法用了~

局部重新整理的效果:

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

一、RecyclerView

看到RecyclerView局部重新整理可能大家會說,RecyclerView 中不是有notifyItemChanged(position)麼,沒錯,的确是用這個方法可以重新整理一個item,但是這個方法也是有個坑,同樣會重新整理item中所有東西,并且在不斷重新整理的時候會執行重新整理Item的動畫,導緻滑動的時候會錯開一下,還可能會造成圖檔重新加載或者不必要的消耗。

下面講解一下我的方法:

一般我們重新整理某個item的方法為

notifyItemChanged(position);
           

是以我們就從這裡入手,

首先檢視源碼

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

這個方法裡執行了notifyItemRangeChanged方法,繼續跟蹤

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

發現這裡執行了另一個重載方法

最後一個參數為null?繼續跟進

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

發現這裡的參數為Object payload,然後我看到了notifyItemChanged的另一個重載方法,這裡也有一個Object payload參數,看一下源碼:

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

payload 的解釋為:如果為null,則重新整理item全部内容

那言外之意就是不為空就可以局部重新整理了~!

繼續從payload跟蹤,發現在RecyclerView中有另一個onBindViewHolder的方法,多了一個參數,payload!!!這個不就是我要找的麼~~!

看一下源碼:

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

發現它調用了另一個重載方法,而另一個重載方法就是我們在寫adapter中抽象的方法,那我們就可以直接從這裡入手了。

1.重寫onBindViewHolder(VH holder, int position, List payloads)這個方法

@Override
    public void onBindViewHolder(MyViewHolder holder, final int position, List<Object> payloads) {
        super.onBindViewHolder(holder, position, payloads);
        if (payloads.isEmpty()){  
        //全部重新整理
        }else {
        //局部重新整理
        }
    }
           

2.執行兩個參數的notifyItemChanged,第二個參數随便什麼都行,隻要不讓它為空就OK~,這樣就可以實作隻重新整理item中某個控件了!!!

這個是用一般的重新整理item方法的效果圖:

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

雖然錄制的效果不是特别好,但是可以看見明顯的浮動錯位現象

這個是用這個方法重新整理的效果圖:

Android RecyclerView與ListView局部重新整理Android ListView與RecyclerView局部重新整理

這樣就恢複正常了

RecyclerView adapter預設的使用我就不在這裡多說了。主要是局部重新整理。

今天的部落格就說到這裡,有什麼增加的日後再補充~~

源碼連結:https://github.com/asd7364645/PartialRefreshDemo

希望喜歡的小夥伴給個屎蛋(star)~~

我是Alex-蠟筆小劉,一個android小白~!

有失誤或者錯誤希望有大神能糾正~!也希望與其他“小白”共同進步~!