摘要:本文到此結(jié)束不不不,還早著呢,咱們理智分析一下首先這個(gè)方法是執(zhí)行在主線程的,如果新舊數(shù)據(jù)比較大,那么這個(gè)方法鐵定是會(huì)阻塞主線程的計(jì)算出后,咱們必須要將新數(shù)據(jù)設(shè)置給,然后才能調(diào)用刷新,然而很多人都會(huì)忘記這一步。
版權(quán)聲明:本文已授權(quán)微信公眾號(hào):Android必修課,轉(zhuǎn)載請(qǐng)申明出處簡(jiǎn)單粗暴的刷新方式自Android5.0以來(lái),RecyclerView漸漸取代ListView成為Android開(kāi)發(fā)中使用最多的列表控件,對(duì)于RecyclerView的使用相信大家都不陌生,但對(duì)于RecyclerView的高效刷新,卻是很多人不知道的。
Adapter.notifyDataSetChanged();
這種方式想必是大家曾經(jīng)用的最多的一種刷新Adapter的方式,它的缺點(diǎn)很明顯:
無(wú)腦刷新整個(gè)RecyclerView可視區(qū)域,每個(gè)item重繪,如果你的onBindViewHolder邏輯處理稍微復(fù)雜一些,則容易造成卡頓
無(wú)法觸發(fā)RecyclerView的item動(dòng)畫(huà),用戶體驗(yàn)極差。
局部刷新方式為了解決上述問(wèn)題,RecyclerView推出了局部刷新的方式
Adapter.notifyItemChanged(int) Adapter.notifyItemInserted(int) Adapter.notifyItemRangeChanged(int, int) Adapter.notifyItemRangeInserted(int, int) Adapter.notifyItemRangeRemoved(int, int)
局部刷新只會(huì)刷新指定position的item,這樣完美解決了上述簡(jiǎn)單粗暴刷新方式的缺點(diǎn),但是:
局部刷新需要指定item的position,如果你只更新了一條數(shù)據(jù),那么你可以很容易知道position位置,但是如果你更新的是整個(gè)列表,你需要計(jì)算出所有你需要刷新的position,那么這將是一場(chǎng)災(zāi)難
DiffUtilGoogle似乎也注意到了這一點(diǎn),因此在support-recyclerview-v7:24.2.0中,推出了一個(gè)用于計(jì)算哪些位置需要刷新的工具類:DiffUtil。
使用DiffUtil,有3個(gè)步驟
1.自實(shí)現(xiàn)DiffUtil.callbackprivate DiffUtil.Callback diffCallback = new DiffUtil.Callback() { @Override public int getOldListSize() { // 返回舊數(shù)據(jù)的長(zhǎng)度 return oldList == null ? 0 : oldList.size(); } @Override public int getNewListSize() { // 返回新數(shù)據(jù)的長(zhǎng)度 return newList == null ? 0 : newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { // 返回兩個(gè)item是否相同 // 例如:此處兩個(gè)item的數(shù)據(jù)實(shí)體是User類,所以以id作為兩個(gè)item是否相同的依據(jù) // 即此處返回兩個(gè)user的id是否相同 return TextUtils.equals(oldList.get(oldItemPosition).getId(), newList.get(oldItemPosition).getId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { // 當(dāng)areItemsTheSame返回true時(shí),我們還需要判斷兩個(gè)item的內(nèi)容是否相同 // 此處以User的age作為兩個(gè)item內(nèi)容是否相同的依據(jù) // 即返回兩個(gè)user的age是否相同 return oldList.get(oldItemPosition).getAge() == newList.get(newItemPosition).getAge(); } };2.計(jì)算得到DiffResult
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);3.將DiffResult設(shè)置給Adapter
// 注意此處一定要將新數(shù)據(jù)設(shè)置給Adapter // 否則會(huì)造成ui刷新了但數(shù)據(jù)未更新的bug mAdapter.setData(newList); diffResult.dispatchUpdatesTo(mAdapter);
這樣我們就實(shí)現(xiàn)了局部刷新位置的計(jì)算和局部刷新的實(shí)現(xiàn),相比notifyDataSetChanged(),性能大大提高。
本文到此結(jié)束?
不不不,還早著呢,咱們理智分析一下:
首先DiffUtil.calculateDiff()這個(gè)方法是執(zhí)行在主線程的,如果新舊數(shù)據(jù)List比較大,那么這個(gè)方法鐵定是會(huì)阻塞主線程的
計(jì)算出DiffResult后,咱們必須要將新數(shù)據(jù)設(shè)置給Adapter,然后才能調(diào)用DiffResult.dispatchUpdatesTo(Adapter)刷新ui,然而很多人都會(huì)忘記這一步。
AsyncListDiffDiffUtil已經(jīng)很好用了,但是有上述兩個(gè)問(wèn)題,想必Google的工程師也是看不下去的,雖然上述兩個(gè)問(wèn)題不難解決,但是很容易遺漏。
因此Google又推出了一個(gè)新的類AsyncListDiff
先來(lái)看一波AsyncListDiff的使用方式:
public class UserAdapter extends RecyclerView.Adapter{ private AsyncListDiffer mDiffer; private DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback () { @Override public boolean areItemsTheSame(User oldItem, User newItem) { return TextUtils.equals(oldItem.getId(), newItem.getId()); } @Override public boolean areContentsTheSame(User oldItem, User newItem) { return oldItem.getAge() == newItem.getAge(); } }; public UserAdapter() { mDiffer = new AsyncListDiffer<>(this, diffCallback); } @Override public int getItemCount() { return mDiffer.getCurrentList().size(); } public void submitList(List data) { mDiffer.submitList(data); } public User getItem(int position) { return mDiffer.getCurrentList().get(position); } @NonNull @Override public UserAdapter.UserViewHodler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user_list, parent, false); return new UserViewHodler(itemView); } @Override public void onBindViewHolder(@NonNull UserAdapter.UserViewHodler holder, int position) { holder.setData(getItem(position)); } class UserViewHodler extends RecyclerView.ViewHolder { private TextView tvName; private TextView tvAge; public UserViewHodler(View itemView) { super(itemView); tvName = itemView.findViewById(R.id.tv_name); tvAge = itemView.findViewById(R.id.tv_age); } public void setData(User data) { tvName.setText(data.getName()); tvAge.setText(String.valueOf(data.getAge())); } } }
這里使用了一個(gè)簡(jiǎn)單的Adapter例子,不做封裝,是為了更好地說(shuō)明AsyncListDiffer。
不難看出,AsyncListDiffer的使用步驟:
自實(shí)現(xiàn)DiffUtil.ItemCallback,給出item差異性計(jì)算條件
將所有對(duì)數(shù)據(jù)的操作代理給AsyncListDiffer,可以看到這個(gè)Adapter是沒(méi)有List數(shù)據(jù)的
使用submitList()更新數(shù)據(jù),并刷新ui
ok,咱們看一下效果:
首先我們給Adapter設(shè)置數(shù)據(jù)
Listusers = new ArrayList<>(); for (int i = 0; i < 10; i++) { users.add(new User(String.valueOf(i), "用戶" + i, i + 20)); } mAdapter.submitList(users);
然后修改數(shù)據(jù)
Listusers = new ArrayList<>(); for (int i = 0; i < 10; i++) { users.add(new User(String.valueOf(i), "用戶" + i, i % 3 == 0 ? i + 10: i + 20)); } mAdapter.submitList(users);
跑起來(lái)看一哈
ok,我們看到只有被3整除的position被刷新了,完美的局部刷新。
那么問(wèn)題來(lái)了,AsyncListDiffer是如何解決我們上述的兩個(gè)問(wèn)題的呢?
解惑我們走進(jìn)AsyncListDiffer的源碼看一下:
public class AsyncListDiffer{ private final ListUpdateCallback mUpdateCallback; private final AsyncDifferConfig mConfig; public AsyncListDiffer(@NonNull RecyclerView.Adapter adapter, @NonNull DiffUtil.ItemCallback diffCallback) { mUpdateCallback = new AdapterListUpdateCallback(adapter); mConfig = new AsyncDifferConfig.Builder<>(diffCallback).build(); } private List mList; private List mReadOnlyList = Collections.emptyList(); private int mMaxScheduledGeneration; public List getCurrentList() { return mReadOnlyList; } public void submitList(final List newList) { if (newList == mList) { // 如果新舊數(shù)據(jù)相同,則啥事不做 return; } // 用于控制計(jì)算線程,防止在上一次submitList未完成時(shí), // 又多次調(diào)用submitList,這里只返回最后一個(gè)計(jì)算的DiffResult final int runGeneration = ++mMaxScheduledGeneration; if (newList == null) { // 如果新數(shù)據(jù)集為空,此種情況不需要計(jì)算diff // 直接清空數(shù)據(jù)即可 // 通知item remove mUpdateCallback.onRemoved(0, mList.size()); mList = null; mReadOnlyList = Collections.emptyList(); return; } if (mList == null) { // 如果舊數(shù)據(jù)集為空,此種情況不需要計(jì)算diff // 直接將新數(shù)據(jù)添加到舊數(shù)據(jù)集即可 // 通知item insert mUpdateCallback.onInserted(0, newList.size()); mList = newList; mReadOnlyList = Collections.unmodifiableList(newList); return; } final List oldList = mList; // 在子線程中計(jì)算DiffResult mConfig.getBackgroundThreadExecutor().execute(new Runnable() { @Override public void run() { final DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { return oldList.size(); } @Override public int getNewListSize() { return newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return mConfig.getDiffCallback().areItemsTheSame( oldList.get(oldItemPosition), newList.get(newItemPosition)); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { return mConfig.getDiffCallback().areContentsTheSame( oldList.get(oldItemPosition), newList.get(newItemPosition)); } }); // 在主線程中更新數(shù)據(jù) mConfig.getMainThreadExecutor().execute(new Runnable() { @Override public void run() { if (mMaxScheduledGeneration == runGeneration) { latchList(newList, result); } } }); } }); } private void latchList(@NonNull List newList, @NonNull DiffUtil.DiffResult diffResult) { diffResult.dispatchUpdatesTo(mUpdateCallback); mList = newList; mReadOnlyList = Collections.unmodifiableList(newList); } }
線程部分源碼:
private static class MainThreadExecutor implements Executor { final Handler mHandler = new Handler(Looper.getMainLooper()); @Override public void execute(@NonNull Runnable command) { mHandler.post(command); } } @NonNull public AsyncDifferConfigbuild() { if (mMainThreadExecutor == null) { mMainThreadExecutor = sMainThreadExecutor; } if (mBackgroundThreadExecutor == null) { synchronized (sExecutorLock) { if (sDiffExecutor == null) { sDiffExecutor = Executors.newFixedThreadPool(2); } } mBackgroundThreadExecutor = sDiffExecutor; } return new AsyncDifferConfig<>( mMainThreadExecutor, mBackgroundThreadExecutor, mDiffCallback); }
ui刷新部分源碼:
public final class AdapterListUpdateCallback implements ListUpdateCallback { @NonNull private final RecyclerView.Adapter mAdapter; public AdapterListUpdateCallback(@NonNull RecyclerView.Adapter adapter) { mAdapter = adapter; } @Override public void onInserted(int position, int count) { mAdapter.notifyItemRangeInserted(position, count); } @Override public void onRemoved(int position, int count) { mAdapter.notifyItemRangeRemoved(position, count); } @Override public void onMoved(int fromPosition, int toPosition) { mAdapter.notifyItemMoved(fromPosition, toPosition); } @Override public void onChanged(int position, int count, Object payload) { mAdapter.notifyItemRangeChanged(position, count, payload); } }
源碼實(shí)現(xiàn)很簡(jiǎn)單,總結(jié)一下:
首先排除新舊數(shù)據(jù)為空的情況,這種情況不需要計(jì)算diff
在子線程中計(jì)算DiffResult,在主線程將DiffResult設(shè)置給Adapter,解決主線程阻塞問(wèn)題
將Adapter的數(shù)據(jù)代理給AsyncListDiffer,解決Adapter與DiffUtil的數(shù)據(jù)一致性問(wèn)題
完結(jié),撒花
喜歡這篇文章記得給我一個(gè)小心心哦
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/77164.html
摘要:前言上一期我們給大家講解了的使用,這一期我們?yōu)榇蠹抑v解一下相對(duì)布局的使用,是的六大布局之一,也是我們常用的布局之一,下面我們一起開(kāi)始學(xué)習(xí)吧簡(jiǎn)介相對(duì)布局允許子元素指定它們相對(duì)于其父元素或兄弟元素的位置,這是實(shí)際布局中最常用的布局方式之一。 前言 上一期我們給大家講解了FrameLayout的使用,這一期我們?yōu)榇蠹抑v解一下RelativeLayout(相對(duì)布局)的使用,RelativeLa...
摘要:模式的核心是為了將模型從視圖控制器中分離出來(lái),從而使得模型獨(dú)立于它們,因此模型不包含對(duì)視圖和控制的引用。 寫(xiě)在最前面的那些話 相信對(duì)于大多數(shù)小白來(lái)說(shuō),關(guān)于MVP、MVC設(shè)計(jì)模式肯定是聽(tīng)過(guò)也看到過(guò)很多次了,也許也有過(guò)一些簡(jiǎn)單了解,但關(guān)于TA的具體概念,如何使用以及具體應(yīng)用等都毫無(wú)所知,所以本著許多小伙伴一看到mvp、mvc就一臉懵逼的表情(當(dāng)然也包括本人了⊙▽⊙)#),最近上手一個(gè)基于m...
閱讀 3258·2021-10-21 17:50
閱讀 3264·2021-10-08 10:05
閱讀 3400·2021-09-22 15:04
閱讀 589·2019-08-30 14:00
閱讀 1951·2019-08-29 17:01
閱讀 1517·2019-08-29 15:16
閱讀 3228·2019-08-26 13:25
閱讀 860·2019-08-26 11:44