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)步旁涤!