Android RecyclerView與ListView局部刷新

Android ListView與RecyclerView局部刷新


一议慰、ListView

之前寫過一篇關(guān)于ListView局部刷新的博客,這部分對(duì)其進(jìn)行完善
,之前的鏈接為:Android模擬ListView點(diǎn)擊下載和局部刷新

平時(shí)在寫ListView的時(shí)候需要更改某些數(shù)據(jù),這種情況我們一般會(huì)調(diào)用
notifyDataSetChanged()方法進(jìn)行刷新,調(diào)用notifydatasetchange其實(shí)會(huì)導(dǎo)致adpter的getView方法被多次調(diào)用(畫面上能顯示多少就會(huì)被調(diào)用多少次)区匠,并且在有獲取網(wǎng)絡(luò)圖片的情況下會(huì)可能造成大量閃動(dòng)或卡頓,極大的影響用戶體驗(yàn)(圖片重新加載并閃動(dòng)在ImageLoader框架中會(huì)出現(xiàn),在glide框架中沒有出現(xiàn))驰弄。

所以我們需要做單行刷新來進(jìn)行優(yōu)化

這個(gè)是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;  
                }  
        }  
    }

對(duì)于這個(gè)方法可以參考這個(gè)博客:android ListView 單條刷新方法實(shí)踐及原理解析

可以看出來谷歌的方案是通過listview的getView方法將單行的所有內(nèi)容都刷新一遍麻汰,但是這樣如果是有加載網(wǎng)絡(luò)圖片的話可能也會(huì)造成閃動(dòng)重新加載,所以我們需要單獨(dú)刷新某個(gè)item中的某個(gè)控件來實(shí)現(xiàn)局部刷新

所以我們?cè)贏dapter中添加一個(gè)局部刷新的方法

/**
     * 局部刷新
     *
     * @param mListView
     * @param posi
     */
    public void updateSingleRow(ListView mListView, int posi) {
        if (mListView != null) {
            //獲取第一個(gè)顯示的item
            int visiblePos = mListView.getFirstVisiblePosition();
            //計(jì)算出當(dāng)前選中的position和第一個(gè)的差戚篙,也就是當(dāng)前在屏幕中的item位置
            int offset = posi - visiblePos;
            int lenth = mListView.getChildCount();
            // 只有在可見區(qū)域才更新,因?yàn)椴辉诳梢妳^(qū)域得不到Tag,會(huì)出現(xiàn)空指針,所以這是必須有的一個(gè)步驟
            if ((offset < 0) || (offset >= lenth)) return;
            View convertView = mListView.getChildAt(offset);
            ViewHolder viewHolder = (ViewHolder) convertView.getTag();
            //以下是處理需要處理的控件方法五鲫。。岔擂。位喂。。
        }
    }

舉個(gè)例子乱灵,簡(jiǎn)單的模擬在列表中點(diǎn)擊下載并更新列表的demo
1.首先定義一個(gè)Bean對(duì)象
這里有一個(gè)圖片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 = 0;
    }

    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.實(shí)現(xiàn)一個(gè)下載的模擬器(單例類)

public class DownLoadManager {

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

    private DownCallBack downCallBack;

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

    /**
     * 下載文件類塑崖,不過在正常的項(xiàng)目中應(yīng)該有一個(gè)ID或者url,
     * 用來判斷文件痛倚,在這個(gè)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 = 0X02;
    public static final int DOWN_PAUST = 0X03;
    public static final int DOWN_FINISH = 0X04;
    public static final int DOWN_DOWNLOADING = 0X05;

    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(3);
        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;
        }
        //首先設(shè)置為排隊(duì)中的狀態(tài)
        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 = 0;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() {

            //一旦成功進(jìn)入到線程里就變?yōu)橄螺d中狀態(tài)
            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(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    downFile.downState = DOWN_PAUST;
                    downFileSparseArray.remove(posi);
                    downTaskSparseArray.remove(posi);
                    isWorking = false;
                }
            }
        }
    }

}

3.創(chuàng)建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(2000).into(viewHolder.itemImg);
        viewHolder.itemDownBtn.setText(getGameState(game));

        if (game.getGameDownSize() > 0 && 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) {
            //獲取第一個(gè)顯示的item
            int visiblePos = mListView.getFirstVisiblePosition();
            //計(jì)算出當(dāng)前選中的position和第一個(gè)的差规婆,也就是當(dāng)前在屏幕中的item位置
            int offset = posi - visiblePos;
            int lenth = mListView.getChildCount();
            // 只有在可見區(qū)域才更新,因?yàn)椴辉诳梢妳^(qū)域得不到Tag,會(huì)出現(xiàn)空指針,所以這是必須有的一個(gè)步驟
            if ((offset < 0) || (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() > 0 && 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蝉稳,所以在下載的時(shí)候點(diǎn)擊按鈕沒有任何的反應(yīng)抒蚜,只有在刷新的間隙時(shí)間里點(diǎn)擊才可以執(zhí)行下載

這里寫圖片描述

可以看到,這里我點(diǎn)擊了好多回沒有效果耘戚,偶爾會(huì)成功嗡髓,這簡(jiǎn)直沒法用了~

局部刷新的效果:

這里寫圖片描述

一、RecyclerView

看到RecyclerView局部刷新可能大家會(huì)說收津,RecyclerView 中不是有notifyItemChanged(position)么饿这,沒錯(cuò),的確是用這個(gè)方法可以刷新一個(gè)item撞秋,但是這個(gè)方法也是有個(gè)坑长捧,同樣會(huì)刷新item中所有東西,并且在不斷刷新的時(shí)候會(huì)執(zhí)行刷新Item的動(dòng)畫部服,導(dǎo)致滑動(dòng)的時(shí)候會(huì)錯(cuò)開一下唆姐,還可能會(huì)造成圖片重新加載或者不必要的消耗拗慨。

下面講解一下我的方法:
一般我們刷新某個(gè)item的方法為

notifyItemChanged(position);

所以我們就從這里入手廓八,
首先查看源碼


這里寫圖片描述
這里寫圖片描述

這個(gè)方法里執(zhí)行了notifyItemRangeChanged方法,繼續(xù)跟蹤


這里寫圖片描述
這里寫圖片描述

發(fā)現(xiàn)這里執(zhí)行了另一個(gè)重載方法

最后一個(gè)參數(shù)為null赵抢?繼續(xù)跟進(jìn)


這里寫圖片描述
這里寫圖片描述

發(fā)現(xiàn)這里的參數(shù)為Object payload剧蹂,然后我看到了notifyItemChanged的另一個(gè)重載方法,這里也有一個(gè)Object payload參數(shù)烦却,看一下源碼:


這里寫圖片描述
這里寫圖片描述

payload 的解釋為:如果為null宠叼,則刷新item全部?jī)?nèi)容
那言外之意就是不為空就可以局部刷新了~!
繼續(xù)從payload跟蹤,發(fā)現(xiàn)在RecyclerView中有另一個(gè)onBindViewHolder的方法冒冬,多了一個(gè)參數(shù)伸蚯,payload!<蚩尽剂邮!這個(gè)不就是我要找的么~~!

看一下源碼:


這里寫圖片描述
這里寫圖片描述

發(fā)現(xiàn)它調(diào)用了另一個(gè)重載方法横侦,而另一個(gè)重載方法就是我們?cè)趯慳dapter中抽象的方法挥萌,那我們就可以直接從這里入手了。

1.重寫onBindViewHolder(VH holder, int position, List<Object> payloads)這個(gè)方法

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

2.執(zhí)行兩個(gè)參數(shù)的notifyItemChanged枉侧,第二個(gè)參數(shù)隨便什么都行引瀑,只要不讓它為空就OK~,這樣就可以實(shí)現(xiàn)只刷新item中某個(gè)控件了Uツ佟:┰浴!

這個(gè)是用一般的刷新item方法的效果圖:

這里寫圖片描述

雖然錄制的效果不是特別好辆影,但是可以看見明顯的浮動(dòng)錯(cuò)位現(xiàn)象

這個(gè)是用這個(gè)方法刷新的效果圖:

這里寫圖片描述

這樣就恢復(fù)正常了

RecyclerView adapter默認(rèn)的使用我就不在這里多說了徒像。主要是局部刷新。

今天的博客就說到這里蛙讥,有什么增加的日后再補(bǔ)充~~

源碼鏈接:https://github.com/asd7364645/PartialRefreshDemo

希望喜歡的小伙伴給個(gè)屎蛋(star)~~


我是Alex-蠟筆小劉锯蛀,一個(gè)android小白~!
有失誤或者錯(cuò)誤希望有大神能糾正次慢!也希望與其他“小白”共同進(jìn)步旁涤!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市迫像,隨后出現(xiàn)的幾起案子劈愚,更是在濱河造成了極大的恐慌,老刑警劉巖闻妓,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菌羽,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡由缆,警方通過查閱死者的電腦和手機(jī)注祖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來均唉,“玉大人是晨,你說我怎么就攤上這事√蚣” “怎么了罩缴?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我箫章,道長(zhǎng)烙荷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任檬寂,我火速辦了婚禮奢讨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘焰薄。我一直安慰自己拿诸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布塞茅。 她就那樣靜靜地躺著亩码,像睡著了一般。 火紅的嫁衣襯著肌膚如雪野瘦。 梳的紋絲不亂的頭發(fā)上描沟,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音鞭光,去河邊找鬼吏廉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惰许,可吹牛的內(nèi)容都是我干的席覆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼汹买,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼佩伤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起晦毙,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤生巡,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后见妒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體孤荣,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年须揣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了盐股。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡返敬,死狀恐怖遂庄,靈堂內(nèi)的尸體忽然破棺而出寥院,到底是詐尸還是另有隱情劲赠,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站凛澎,受9級(jí)特大地震影響霹肝,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塑煎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一沫换、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧最铁,春花似錦讯赏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至雀哨,卻和暖如春磕谅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雾棺。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工膊夹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人捌浩。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓放刨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親尸饺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宏榕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容