摘要:本文到此結束不不不,還早著呢,咱們理智分析一下首先這個方法是執行在主線程的,如果新舊數據比較大,那么這個方法鐵定是會阻塞主線程的計算出后,咱們必須要將新數據設置給,然后才能調用刷新,然而很多人都會忘記這一步。
版權聲明:本文已授權微信公眾號:Android必修課,轉載請申明出處簡單粗暴的刷新方式自Android5.0以來,RecyclerView漸漸取代ListView成為Android開發中使用最多的列表控件,對于RecyclerView的使用相信大家都不陌生,但對于RecyclerView的高效刷新,卻是很多人不知道的。
Adapter.notifyDataSetChanged();
這種方式想必是大家曾經用的最多的一種刷新Adapter的方式,它的缺點很明顯:
無腦刷新整個RecyclerView可視區域,每個item重繪,如果你的onBindViewHolder邏輯處理稍微復雜一些,則容易造成卡頓
無法觸發RecyclerView的item動畫,用戶體驗極差。
局部刷新方式為了解決上述問題,RecyclerView推出了局部刷新的方式
Adapter.notifyItemChanged(int) Adapter.notifyItemInserted(int) Adapter.notifyItemRangeChanged(int, int) Adapter.notifyItemRangeInserted(int, int) Adapter.notifyItemRangeRemoved(int, int)
局部刷新只會刷新指定position的item,這樣完美解決了上述簡單粗暴刷新方式的缺點,但是:
局部刷新需要指定item的position,如果你只更新了一條數據,那么你可以很容易知道position位置,但是如果你更新的是整個列表,你需要計算出所有你需要刷新的position,那么這將是一場災難
DiffUtilGoogle似乎也注意到了這一點,因此在support-recyclerview-v7:24.2.0中,推出了一個用于計算哪些位置需要刷新的工具類:DiffUtil。
使用DiffUtil,有3個步驟
1.自實現DiffUtil.callbackprivate DiffUtil.Callback diffCallback = new DiffUtil.Callback() { @Override public int getOldListSize() { // 返回舊數據的長度 return oldList == null ? 0 : oldList.size(); } @Override public int getNewListSize() { // 返回新數據的長度 return newList == null ? 0 : newList.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { // 返回兩個item是否相同 // 例如:此處兩個item的數據實體是User類,所以以id作為兩個item是否相同的依據 // 即此處返回兩個user的id是否相同 return TextUtils.equals(oldList.get(oldItemPosition).getId(), newList.get(oldItemPosition).getId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { // 當areItemsTheSame返回true時,我們還需要判斷兩個item的內容是否相同 // 此處以User的age作為兩個item內容是否相同的依據 // 即返回兩個user的age是否相同 return oldList.get(oldItemPosition).getAge() == newList.get(newItemPosition).getAge(); } };2.計算得到DiffResult
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);3.將DiffResult設置給Adapter
// 注意此處一定要將新數據設置給Adapter // 否則會造成ui刷新了但數據未更新的bug mAdapter.setData(newList); diffResult.dispatchUpdatesTo(mAdapter);
這樣我們就實現了局部刷新位置的計算和局部刷新的實現,相比notifyDataSetChanged(),性能大大提高。
本文到此結束?
不不不,還早著呢,咱們理智分析一下:
首先DiffUtil.calculateDiff()這個方法是執行在主線程的,如果新舊數據List比較大,那么這個方法鐵定是會阻塞主線程的
計算出DiffResult后,咱們必須要將新數據設置給Adapter,然后才能調用DiffResult.dispatchUpdatesTo(Adapter)刷新ui,然而很多人都會忘記這一步。
AsyncListDiffDiffUtil已經很好用了,但是有上述兩個問題,想必Google的工程師也是看不下去的,雖然上述兩個問題不難解決,但是很容易遺漏。
因此Google又推出了一個新的類AsyncListDiff
先來看一波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())); } } }
這里使用了一個簡單的Adapter例子,不做封裝,是為了更好地說明AsyncListDiffer。
不難看出,AsyncListDiffer的使用步驟:
自實現DiffUtil.ItemCallback,給出item差異性計算條件
將所有對數據的操作代理給AsyncListDiffer,可以看到這個Adapter是沒有List數據的
使用submitList()更新數據,并刷新ui
ok,咱們看一下效果:
首先我們給Adapter設置數據
Listusers = new ArrayList<>(); for (int i = 0; i < 10; i++) { users.add(new User(String.valueOf(i), "用戶" + i, i + 20)); } mAdapter.submitList(users);
然后修改數據
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);
跑起來看一哈
ok,我們看到只有被3整除的position被刷新了,完美的局部刷新。
那么問題來了,AsyncListDiffer是如何解決我們上述的兩個問題的呢?
解惑我們走進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) { // 如果新舊數據相同,則啥事不做 return; } // 用于控制計算線程,防止在上一次submitList未完成時, // 又多次調用submitList,這里只返回最后一個計算的DiffResult final int runGeneration = ++mMaxScheduledGeneration; if (newList == null) { // 如果新數據集為空,此種情況不需要計算diff // 直接清空數據即可 // 通知item remove mUpdateCallback.onRemoved(0, mList.size()); mList = null; mReadOnlyList = Collections.emptyList(); return; } if (mList == null) { // 如果舊數據集為空,此種情況不需要計算diff // 直接將新數據添加到舊數據集即可 // 通知item insert mUpdateCallback.onInserted(0, newList.size()); mList = newList; mReadOnlyList = Collections.unmodifiableList(newList); return; } final List oldList = mList; // 在子線程中計算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)); } }); // 在主線程中更新數據 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); } }
源碼實現很簡單,總結一下:
首先排除新舊數據為空的情況,這種情況不需要計算diff
在子線程中計算DiffResult,在主線程將DiffResult設置給Adapter,解決主線程阻塞問題
將Adapter的數據代理給AsyncListDiffer,解決Adapter與DiffUtil的數據一致性問題
完結,撒花
喜歡這篇文章記得給我一個小心心哦
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/77164.html
摘要:前言上一期我們給大家講解了的使用,這一期我們為大家講解一下相對布局的使用,是的六大布局之一,也是我們常用的布局之一,下面我們一起開始學習吧簡介相對布局允許子元素指定它們相對于其父元素或兄弟元素的位置,這是實際布局中最常用的布局方式之一。 前言 上一期我們給大家講解了FrameLayout的使用,這一期我們為大家講解一下RelativeLayout(相對布局)的使用,RelativeLa...
摘要:模式的核心是為了將模型從視圖控制器中分離出來,從而使得模型獨立于它們,因此模型不包含對視圖和控制的引用。 寫在最前面的那些話 相信對于大多數小白來說,關于MVP、MVC設計模式肯定是聽過也看到過很多次了,也許也有過一些簡單了解,但關于TA的具體概念,如何使用以及具體應用等都毫無所知,所以本著許多小伙伴一看到mvp、mvc就一臉懵逼的表情(當然也包括本人了⊙▽⊙)#),最近上手一個基于m...
閱讀 3242·2021-10-21 17:50
閱讀 3254·2021-10-08 10:05
閱讀 3380·2021-09-22 15:04
閱讀 581·2019-08-30 14:00
閱讀 1939·2019-08-29 17:01
閱讀 1508·2019-08-29 15:16
閱讀 3219·2019-08-26 13:25
閱讀 852·2019-08-26 11:44