English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
1. Sommario
DiffUtil è una nuova classe di strumenti in support-v7:24.2.0, utilizzata per confrontare due set di dati e trovare la quantità di cambiamento minima dal vecchio set di dati al nuovo set di dati.
Parlando dei set di dati, credo che tutti sappiano a chi sono associati, è il mio amore preferito, RecyclerView.
Secondo i miei giorni di utilizzo, il suo uso principale è di non aggiornare notificarDataChanged() senza pensare durante l'aggiornamento di RecyclerView.
Prima, notificarDataChanged() senza pensare aveva due difetti:
1. Non innerva gli animazioni di RecyclerView (cancellazione, aggiunta, spostamento, animazione di change)
2. La prestazione è bassa, poiché ha aggiornato senza pensare l'intero RecyclerView, in casi estremi: se il vecchio e il nuovo set di dati sono identici, l'efficienza è la più bassa.
Dopo l'uso di DiffUtil, cambia il codice come segue:
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);
Calcola automaticamente la differenza tra il vecchio e il nuovo set di dati e chiama automaticamente i seguenti quattro metodi in base alla situazione della differenza.
adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);
Evidentemente, questi quattro metodi eseguono animazioni di RecyclerView e sono tutti metodi di aggiornamento direzionale, aumentando notevolmente l'efficienza di aggiornamento.
Come sempre, iniziamo con l'immagine:
L'immagine 1 mostra l'effetto di notificarDataChanged() senza pensare, dove si può vedere che l'interazione di aggiornamento è molto dura e gli item compaiono improvvisamente in una posizione:
L'immagine 2 mostra l'effetto dell'uso di DiffUtils, dove è più evidente l'animazione di inserimento e spostamento degli item:
La conversione in GIF è piuttosto scadente, scarica il Demo alla fine per vedere l'effetto migliore.
Questo articolo includerà e non solo includerà i seguenti contenuti:
1 Introduco brevemente l'uso semplice di DiffUtil, per realizzare l'effetto di aggiornamento incrementale durante il refresh (l'aggiornamento incrementale è il mio nome).
2 L'uso avanzato di DiffUtil, quando un item ha solo la modifica del contenuto (data) e la posizione (position) non cambia, completa l'aggiornamento parziale (chiamato ufficialmente Partial bind, binding parziale).
3 Comprendere il metodo public void onBindViewHolder(VH holder, int position, List<Object> payloads) di RecyclerView.Adapter e padroneggiarlo.
4 Calcolare DiffResult in un thread secondario e aggiornare il RecyclerView nel thread principale.
5 Come rimuovere l'animazione fastidiosa di notifyItemChanged() che causa la luce bianca degli item.
6 Traduzione in italiano delle annotazioni ufficiali delle classi e dei metodi di DiffUtil
2 Utilizzo semplice di DiffUtil
Come menzionato in precedenza, DiffUtil aiuta a calcolare le differenze tra i set di dati nuovi e vecchi durante l'aggiornamento del RecyclerView e a chiamare automaticamente il metodo di aggiornamento di RecyclerView.Adapter per completare un aggiornamento efficiente con effetti di animazione degli item.
Quindi, prima di impararlo, dobbiamo fare alcune preparazioni, scrivere un demo di versione giovane senza cervello che utilizza notifyDataSetChanged() per aggiornare.
1 Un comune JavaBean, ma che implementa il metodo clone, utilizzato solo per scrivere Demo per simulare aggiornamenti, non necessario per progetti reali, poiché i dati vengono scaricati dalla rete durante l'aggiornamento.
class TestBean implements Cloneable { private String name; private String desc; ....//Metodi get set omessi //Scrivere DEMO per implementare il metodo di clonazione @Override public TestBean clone() throws CloneNotSupportedException { TestBean bean = null; try { bean = (TestBean) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return bean; }
2 Implementare un semplice RecyclerView.Adapter.
public class DiffAdapter extends RecyclerView.Adapter<DiffAdapter.DiffVH> { private final static String TAG = "zxt"; private List<TestBean> mDatas; private Context mContext; private LayoutInflater mInflater; public DiffAdapter(Context mContext, List<TestBean> mDatas) { this.mContext = mContext; this.mDatas = mDatas; mInflater = LayoutInflater.from(mContext); } public void setDatas(List<TestBean> mDatas) { this.mDatas = mDatas; } @Override public DiffVH onCreateViewHolder(ViewGroup parent, int viewType) { return new DiffVH(mInflater.inflate(R.layout.item_diff, parent, false)); } @Override public void onBindViewHolder(final DiffVH holder, final int position) { TestBean bean = mDatas.get(position); holder.tv1.setText(bean.getName()); holder.tv2.setText(bean.getDesc()); holder.iv.setImageResource(bean.getPic()); } @Override public int getItemCount() { return mDatas != null ? mDatas.size() : 0; } class DiffVH extends RecyclerView.ViewHolder { TextView tv1, tv2; ImageView iv; public DiffVH(View itemView) { super(itemView); tv1 = (TextView) itemView.findViewById(R.id.tv1); tv2 = (TextView) itemView.findViewById(R.id.tv2); iv = (ImageView) itemView.findViewById(R.id.iv); } } }
3 Codice Activity:;
public class MainActivity extends AppCompatActivity { private List<TestBean> mDatas; private RecyclerView mRv; private DiffAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initData(); mRv = (RecyclerView) findViewById(R.id.rv); mRv.setLayoutManager(new LinearLayoutManager(this)); mAdapter = new DiffAdapter(this, mDatas); mRv.setAdapter(mAdapter); } private void initData() { mDatas = new ArrayList<>(); mDatas.add(new TestBean("张旭童1", "Android", R.drawable.pic1)); mDatas.add(new TestBean("张旭童2", "Java", R.drawable.pic2)); mDatas.add(new TestBean("张旭童3", "背锅", R.drawable.pic3)); mDatas.add(new TestBean("张旭童4", "手撕产品", R.drawable.pic4)); mDatas.add(new TestBean("张旭童5", "手撕测试", R.drawable.pic5)); } /** * 模拟刷新操作 * * @param view */ public void onRefresh(View view) { try { List<TestBean> newDatas = new ArrayList<>(); for (TestBean bean : mDatas) { newDatas.add(bean.clone());//clone一遍旧数据 ,模拟刷新操作 } newDatas.add(new TestBean("赵子龙", "帅", R.drawable.pic6));//模拟新增数据 newDatas.get(0).setDesc("Android+"); newDatas.get(0).setPic(R.drawable.pic7); // Simulazione della modifica dei dati TestBean testBean = newDatas.get(1); // Simulazione del movimento dei dati newDatas.remove(testBean); newDatas.add(testBean); //Non dimenticare di dare i nuovi dati all'Adapter mDatas = newDatas; mAdapter.setDatas(mDatas); mAdapter.notifyDataSetChanged(); // Prima potevamo farlo così nella maggior parte dei casi } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }
È molto semplice, ma quando si costruisce il nuovo set di dati newDatas, si itera attraverso il vecchio set di dati mDatas, si chiama il metodo clone() di ogni data, per assicurarsi che i nuovi e vecchi set di dati siano dati identici ma indirizzi di memoria (puntatori) diversi, in modo che quando si modificano i valori di newDatas in seguito, non si influenzi anche i valori di mDatas.
4 activity_main.xml Rimuovere alcune proprietà di larghezza e altezza, è solo un RecyclerView e un Button per simulare l'aggiornamento.
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" > <android.support.v7.widget.RecyclerView android:id="@+id/rv" /> <Button android:id="@+id/btnRefresh" android:layout_alignParentRight="true" android:onClick="onRefresh" android:text="Simulazione aggiornamento" /> </RelativeLayout>
Il seguente è un demo che un giovane comune può scrivere facilmente, senza pensare a notifyDataSetChanged(), come mostrato nella figura 1 della sezione precedente.
Ma dobbiamo tutti competere per diventare giovani artisti, quindi
Iniziare a entrare nel tema principale, utilizzare semplicemente DiffUtil, dobbiamo e solo dobbiamo scrivere una classe extra.
Per diventare un giovane letterato, dobbiamo implementare una classe che eredita da DiffUtil.Callback e implementare i suoi quattro metodi abstract.
Anche se questo tipo si chiama Callback, è più appropriato interpretarlo come: una classe che definisce degli accordi (Contract) e delle regole (Rule) per confrontare se vecchi e nuovi Item sono uguali.
L'astratto classe DiffUtil.Callback è come segue:
public abstract static class Callback { public abstract int getOldListSize();//Dimensione del vecchio set di dati public abstract int getNewListSize();//Dimensione del nuovo set di dati public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);//Se l'Item di vecchi e nuovi set di dati è lo stesso oggetto nella stessa posizione (potrebbe essere diverso nel contenuto, se qui restituisce true, verrà chiamato il metodo seguente) public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);//Questo metodo viene chiamato solo se il metodo sopra restituisce true, la mia comprensione è che solo notifyItemRangeChanged() lo chiama, per determinare se il contenuto dell'item è cambiato //Questo metodo viene utilizzato nelle usi avanzati di DiffUtil, ma non ne parlerò ora @Nullable public Object getChangePayload(int oldItemPosition, int newItemPosition) { return null; } }
Questo Demo implementa DiffUtil.Callback come segue, con commenti in cinese e inglese per i metodi principali (in altre parole, ho tradotto i commenti ufficiali in inglese, per aiutare meglio a comprendere).
/** * Introduzione: Classe principale utilizzata per determinare se un Item vecchio e nuovo è uguale * Autore: zhangxutong * Email: [email protected] * Tempo: 12/09/2016. */ public class DiffCallBack extends DiffUtil.Callback { private List<TestBean> mOldDatas, mNewDatas;//根据名称 public DiffCallBack(List<TestBean> mOldDatas, List<TestBean> mNewDatas) { this.mOldDatas = mOldDatas; this.mNewDatas = mNewDatas; } // dimensione del vecchio set di dati @Override public int getOldListSize() { return mOldDatas != null ? mOldDatas.size() : 0; } // dimensione del nuovo set di dati @Override public int getNewListSize() { return mNewDatas != null ? mNewDatas.size() : 0; } /** * Chiamato da DiffUtil per decidere se due oggetti rappresentano lo stesso Item. * Chiamato da DiffUtil per determinare se due oggetti rappresentano lo stesso Item. * Ad esempio, se i tuoi elementi hanno id unici, questo metodo dovrebbe verificare l'uguaglianza degli id. * Ad esempio, se il tuo elemento ha un campo id unico, questo metodo verifica se l'id è uguale. * In questo esempio si verifica se il campo name è identico * * @param oldItemPosition La posizione dell'elemento nell'elenco precedente * @param newItemPosition The position of the item in the new list * @return True se due elementi rappresentano lo stesso oggetto o false se sono diversi. */ @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName()); } /** * Chiamato da DiffUtil quando vuole verificare se due elementi hanno dati identici. * Chiamato da DiffUtil per controllare se due elementi contengono dati identici * DiffUtil utilizza questa informazione per rilevare se il contenuto di un elemento è cambiato. * DiffUtil用返回的信息(true false)来检测当前item的内容是否发生了变化 * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)} * DiffUtil 用这个方法替代equals方法去检查是否相等。 * so that you can change its behavior depending on your UI. * 所以你可以根据你的UI去改变它的返回值 * For example, if you are using DiffUtil with a * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should * return whether the items‘ visual representations are the same. * 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的视觉表现是否相同。 * This method is called only if {@link #areItemsTheSame(int, int)} returns * {@code true} for these items. * 这个方法仅仅在areItemsTheSame()返回true时,才调用。 * @param oldItemPosition La posizione dell'elemento nell'elenco precedente * @param newItemPosition The position of the item in the new list which replaces the * oldItem * @return True if the contents of the items are the same or false if they are different. */ @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { TestBean beanOld = mOldDatas.get(oldItemPosition); TestBean beanNew = mNewDatas.get(newItemPosition); if (!beanOld.getDesc().equals(beanNew.getDesc())) { return false; //Se ci sono contenuti diversi, restituisce false } if (beanOld.getPic() != beanNew.getPic()) { return false; //Se ci sono contenuti diversi, restituisce false } return true; //Per default, i due contenuti di data sono gli stessi }
Con queste annotazioni così dettagliate e questo codice così semplice, è credibile che si possa capire a prima vista.
Poi, durante l'uso, commentare il metodo notifyDatasetChanged() che hai scritto prima, sostituirlo con il seguente codice:
//Nuovo amore dei giovani letterati //Utilizzare il metodo calculateDiff() di DiffUtil, passare un oggetto Callback di regola di DiffUtil e un booleano che indica se detectare il movimento dell'item, ottenere l'oggetto DiffUtil.DiffResult DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true); //Utilizzare il metodo dispatchUpdatesTo() dell'oggetto DiffResult di DiffUtil, passare l'Adapter di RecyclerView, diventa facilmente un giovane letterato diffResult.dispatchUpdatesTo(mAdapter); //Non dimenticare di dare i nuovi dati all'Adapter mDatas = newDatas; mAdapter.setDatas(mDatas);
Spiegazione:
Passaggio uno
Prima di assegnare newDatas all'Adapter, chiamare prima il metodo calculateDiff() di DiffUtil,Calcolare il set di aggiornamenti minimo tra il set di dati vecchio e nuovo, è
L'oggetto DiffResult di DiffUtil.
La definizione del metodo calculateDiff() di DiffUtil è la seguente:
Il primo parametro è l'oggetto Callback di DiffUtil,
Il secondo parametro rappresenta se detectare il movimento dell'Item, impostare su false per una maggiore efficienza dell'algoritmo, configurare a seconda delle necessità, qui è impostato su true.
public static DiffResult calculateDiff(Callback cb, boolean detectMoves)
Fase due
Poi utilizza il metodo dispatchUpdatesTo() dell'oggetto DiffUtil.DiffResult, passando l'Adapter di RecyclerView per sostituire il metodo mAdapter.notifyDataSetChanged() utilizzato dai giovani normali.
Analizzando il codice sorgente, si può vedere che questo metodo interna chiama i quattro metodi di aggiornamento direzionale dell'adapter.
public void dispatchUpdatesTo(final RecyclerView.Adapter adapter) { dispatchUpdatesTo(new ListUpdateCallback() { @Override public void onInserted(int position, int count) { adapter.notifyItemRangeInserted(position, count); } @Override public void onRemoved(int position, int count) { adapter.notifyItemRangeRemoved(position, count); } @Override public void onMoved(int fromPosition, int toPosition) { adapter.notifyItemMoved(fromPosition, toPosition); } @Override public void onChanged(int position, int count, Object payload) { adapter.notifyItemRangeChanged(position, count, payload); } }); }
Conclusione:
Quindi, DiffUtil non può solo essere utilizzato con RecyclerView, possiamo anche implementare le quattro méthode dell'interfaccia ListUpdateCallback per fare alcune cose. (Non mi assumo alcuna responsabilità per una scelta casuale, penso che potrebbe funzionare con il controllore di nove quadrati nel mio progetto? O ottimizzare il NestFullListView che ho scritto nell'articolo precedente? Piccola raccomandazione, vedere la soluzione elegante per l'incapsulamento di ListView, RecyclerView e ScrollView: http://blog.csdn.net/zxt0601/article/details/52494665)
Fino a questo punto, siamo evoluti in letterati, l'effetto di esecuzione è praticamente lo stesso della figura 2 del primo paragrafo,
L'unica differenza è che in questo momento adapter.notifyItemRangeChanged() avrà un'animazione di aggiornamento di Item bianco flash (il Demo di questo articolo è l'item in posizione 0). Questa animazione di flash dell'item piace a qualcuno e non piace a qualcun altro, ma non importa
Poiché abbiamo imparato l'uso avanzato di DiffUtil nel terzo paragrafo, che ne pensi dell'animazione di ItemChange, sarà portata via dal vento. (Non so se sia un bug ufficiale)
L'effetto è come nella figura 2 del primo paragrafo, il nostro item0 ha cambiato sia l'immagine che il testo, ma questa modifica non è accompagnata da alcun animazione.
Iniziamo il nostro viaggio verso il letterato tra i letterati.
Tre usi avanzati di DiffUtil
Teoria:
L'uso avanzato riguarda solo due metodi,
Dobbiamo implementare separatamente le Callback di DiffUtil.
Il metodo public Object getChangePayload(int oldItemPosition, int newItemPosition)
L'oggetto restituito rappresenta quali contenuti dell'Item sono cambiati.
Insieme a RecyclerView.Adapter
Il metodo public void onBindViewHolder(VH holder, int position, List<Object> payloads)
Completato il refresh direzionale. (Diventare un letterato tra i letterati, letterato letterato.)
Ecco a voi, è una nuova metodo, notate che ha tre parametri, i primi due ci sono familiari, il terzo parametro contiene l'oggetto che restituiamo in getChangePayload().
Bene, allora vediamo chi è questo metodo:
Nel codice sorgente di v7-24.2.0, ha questo aspetto:
/** * Chiamato da RecyclerView per visualizzare i dati alla posizione specificata. Questo metodo * dovrebbe aggiornare il contenuto di {@link ViewHolder#itemView} per riflettere l'elemento in * la posizione fornita. * <p> * Notare che a differenza di {@link android.widget.ListView}, RecyclerView non chiamerà questo metodo * di nuovo se la posizione dell'elemento cambia nel set di dati a meno che l'elemento stesso sia * invalidato o la nuova posizione non può essere determinata. Per questo motivo, dovresti utilizzare solo * utilizzare il parametro <code>position</code> mentre si acquisisce l'elemento dati correlato all'interno * questo metodo e non dovrebbe mantenere una copia di esso. Se hai bisogno della posizione di un elemento in seguito * su (ad esempio in un listener di click), utilizzare {@link ViewHolder#getAdapterPosition()} che restituirà * avere la posizione aggiornata dell'adattatore. * <p> * Bind parziale rispetto a bind completo: * <p> * Il parametro payloads è una lista di merge da {@link #notifyItemChanged(int, Object)} o * {@link #notifyItemRangeChanged(int, int, Object)}. Se l'elenco dei carichi non è vuoto, * il ViewHolder è attualmente vincolato ai dati vecchi e l'adattatore può eseguire un bind parziale efficiente * aggiornare utilizzando le informazioni sul carico. Se il carico è vuoto, l'adattatore deve eseguire un bind completo. * L'adattatore non dovrebbe presupporre che i carichi passati nei metodi notify verranno ricevuti da * onBindViewHolder()。例如,当视图未附加到屏幕上时, * 通知项更改()中的payload将被简单地丢弃。 * * @param holder 应该更新以表示负载内容的ViewHolder。 * 在数据集中给定位置的项目。 * @param position 项在适配器数据集中的位置。 * @param payloads 一个非空的数据合并负载列表。如果需要完整的 * 更新。 */ public void onBindViewHolder(VH holder, int position, List<Object> payloads) { onBindViewHolder(holder, position); }
原来它内部就仅仅调用了两个参数的onBindViewHolder(holder, position) ,(题外话,哎哟喂,我的NestFullListView 的Adapter也有几分神似这种写法,看来我离Google大神又近了一步)
看到这我才明白,其实onBind的入口,就是这个方法,它才是和onCreateViewHolder对应的方法,
源码往下翻几行可以看到有个public final void bindViewHolder(VH holder, int position),它内部调用了三参的onBindViewHolder。
关于RecyclerView.Adapter,也不是三言两句就能说清楚的。(其实我只掌握到这里)
好啦,不再跑题,回到我们的三参数的onBindViewHolder(VH holder, int position, List<Object> payloads),这个方法头部有一大堆英文注释,我一直觉得阅读这些英文注释对理解方法很有用处,于是我翻译了一下,
Traduzione:
Chiamato da RecyclerView per visualizzare i dati in una posizione specificata.
Questo metodo dovrebbe aggiornare il contenuto di ItemView nel ViewHolder per riflettere le variazioni dell'item specificato.
Attenzione, a differenza di ListView, se i dati della lista all'indirizzo specificato cambiano, RecyclerView non chiama questo metodo di nuovo a meno che l'item stesso non sia invalidato (invalidated) o che la nuova posizione non possa essere determinata.
Per questo motivo, in questo metodo, dovresti usare solo il parametro position per ottenere i dati dell'item correlato e non dovresti mantenere una copia di questo item di dati.
Se in seguito hai bisogno della posizione di questo item, ad esempio per impostare un clickListener, dovresti usare ViewHolder.getAdapterPosition(), che può fornire la posizione aggiornata.
(Due pene, ho visto qui che si sta spiegando il metodo onbindViewHolder a due parametri)
Di seguito è riportata la parte unica di questo metodo a tre parametri:)
**Binding parziale (partial) vs Binding completo (full)**
Il parametro payloads è una lista combinata ottenuta da (notifyItemChanged(int, Object) o notifyItemRangeChanged(int, int, Object)).
Se la lista payloads non è vuota, il ViewHolder e l'Adapter attualmente vincolati ai dati vecchi possono utilizzare i dati del payload per eseguire un aggiornamento parziale efficiente.
Se il payload è vuoto, l'Adapter deve eseguire un binding completo (chiamare il metodo a due parametri).
L'Adapter non dovrebbe presupporre (pensare senza prove) che i payload trasmessi dai metodi notifyxxxx siano ricevuti nel metodo onBindViewHolder() (QQA, la traduzione non è facile, guardate l'esempio).
Per esempio, quando la View non è attaccata allo schermo, è sufficiente gettare via semplicemente il payload proveniente da notifyItemChange().
L'oggetto payloads non è nullo, ma potrebbe essere vuoto (empty), in questo caso è necessario un binding completo (quindi nel metodo è sufficiente verificare isEmpty, senza dover ricontrollare vuoto più volte).
Autore: Questo metodo è un metodo efficiente. Sono un traduttore inefficace, ho guardato per 40+ minuti prima di capire che la parte importante è evidenziata in grassetto.
Pratica:
Nonostante tutte queste parole, in realtà è molto semplice da usare:
Prima di tutto, vediamo come utilizzare il metodo getChangePayload(), con注释双语
/** * Quando {@link #areItemsTheSame(int, int)} restituisce true per due elementi e * Quando {@link #areContentsTheSame(int, int)} restituisce false per loro, DiffUtil * chiama questo metodo per ottenere un payload sul cambiamento. * * Quando {@link #areItemsTheSame(int, int)} restituisce true e {@link #areContentsTheSame(int, int)} restituisce false, DiffUtils chiama questo metodo per ottenere un payload sul cambiamento * per ottenere i payload (quale) dell'elemento che sono cambiati. * * Ad esempio, se si utilizza DiffUtil con {@link RecyclerView}, si può restituire il * campo specifico che è cambiato nell'elemento e il tuo * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} può utilizzare queste * informazioni necessarie per eseguire l'animazione corretta. * * Ad esempio, se si utilizza RecyclerView con DiffUtils, si può restituire questo campo dell'elemento che è cambiato * {@link android.support.v7.widget.RecyclerView.ItemAnimator ItemAnimator} può utilizzare queste informazioni per eseguire l'animazione corretta * * Implementazione predefinita che restituisce null. * L'implementazione predefinita restituisce null * * @param oldItemPosition La posizione dell'elemento nell'elenco precedente * @param newItemPosition The position of the item in the new list * @return A payload object that represents the change between the two items. * Returns a payload object representing the change content between the old and new items. */ @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) { // By implementing this method, you can become the literary youth among the literary youth // Partial update in directional refresh // Most efficient // It just doesn't have the white flash animation of ItemChange (I don't think it's very important anyway) TestBean oldBean = mOldDatas.get(oldItemPosition); TestBean newBean = mNewDatas.get(newItemPosition); // Here, there is no need to compare the core fields, they are definitely equal Bundle payload = new Bundle(); if (!oldBean.getDesc().equals(newBean.getDesc())) { payload.putString("KEY_DESC", newBean.getDesc()); } if (oldBean.getPic() != newBean.getPic()) { payload.putInt("KEY_PIC", newBean.getPic()); } if (payload.size() == 0) // If there are no changes, pass an empty one return null; return payload; // }
In simple terms, this method returns an Object type payload that contains the changed content of a certain item.
We use Bundle to save these changes here.
In Adapter, rewrite the three-argument onBindViewHolder as follows:
@Override public void onBindViewHolder(DiffVH holder, int position, List<Object> payloads) { if (payloads.isEmpty()) {}} onBindViewHolder(holder, position); } else { //Il giovane letterario tra i giovani letterari Bundle payload = (Bundle) payloads.get(0); TestBean bean = mDatas.get(position); for (String key : payload.keySet()) { switch (key) { case "KEY_DESC": //Puoi utilizzare i dati del payload, ma anche i dati nuovi possono essere utilizzati holder.tv2.setText(bean.getDesc()); break; case "KEY_PIC": holder.iv.setImageResource(payload.getInt(key)); break; default: break; } } } }
I payloads trasmessi qui sono una lista, come indicato nei commenti, non può essere null, quindi possiamo determinare se è vuoto
Se è vuoto, chiamiamo la funzione a due parametri per eseguire un Full Bind.
Se non è vuoto, eseguiamo il partial bind
Estraiamo il payload restituito nel metodo getChangePayload tramite l'indice 0, quindi esaminiamo le chiavi del payload, effettuiamo la ricerca in base alla chiave, se il payload contiene le modifiche corrispondenti, le estraiamo e le aggiorniamo nell'ItemView.
(Qui, i dati della sorgente più recente ottenuti tramite mDatas possono essere aggiornati con i dati del payload o i dati nuovi).
Fino a questo punto, abbiamo padroneggiato il metodo di aggiornamento di RecyclerView, il tipo più letterario tra i giovani letterari.
Quattro: utilizzare DiffUtil in un thread secondario
Le informazioni su DiffUtil sono introdotte nei commenti di testa del codice sorgente di DiffUtil
DiffUtil utilizza l'algoritmo di differenza di Eugene W. Myers, ma questo algoritmo non può rilevare gli item di movimento, quindi Google ha migliorato su di esso per supportare la rilevazione degli item di movimento, ma la rilevazione degli item di movimento richiede più risorse.
Con 1000 elementi di dati e 200 modifiche, il tempo di esecuzione di questo algoritmo è:
Quando la rilevazione del movimento è attivata: media: 27.07ms, mediana: 26.92ms.
Quando la rilevazione del movimento è disattivata: media: 13.54ms, mediana: 13.36ms.
Chi è interessato può leggere i commenti all'inizio del codice sorgente, quelli che ci sono utili sono alcuni di essi,}
Se la nostra lista è molto grande, il tempo necessario per calcolare DiffResult è piuttosto lungo, quindi dovremmo mettere il processo di ottenere DiffResult in un thread secondario e aggiornare RecyclerView nella thread principale.
In questo caso ho utilizzato Handler insieme a DiffUtil:
Il codice è il seguente:
private static final int H_CODE_UPDATE = 1; private List<TestBean> mNewDatas;//Aggiungere una variabile per memorizzare newList private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case H_CODE_UPDATE: //Estrai il Result DiffUtil.DiffResult diffResult = (DiffUtil.DiffResult) msg.obj; diffResult.dispatchUpdatesTo(mAdapter); //Non dimenticare di dare i nuovi dati all'Adapter mDatas = mNewDatas; mAdapter.setDatas(mDatas); break; } } }); new Thread(new Runnable() { @Override public void run() { //Mettere il calcolo di DiffResult in un thread secondario DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, mNewDatas), true); Message message = mHandler.obtainMessage(H_CODE_UPDATE); message.obj = diffResult;//obj contiene DiffResult message.sendToTarget(); } }).start();
È un uso semplice di Handler, non è necessario spiegare ulteriormente.
Puntata 5 Sommario e altro
1 Infatti il codice di questo articolo è molto piccolo, puoi scaricare il Demo per vedere, ci sono solo quattro classi in totale.
Ma senza accorgermene, l'ho fatto così lungo, principalmente riguardante la traduzione dei commenti delle sorgenti, per aiutare la comunità a comprendere meglio.
2 DiffUtil è molto adatto a questo tipo di scena di aggiornamento in scorrimento
L'efficienza dell'aggiornamento è migliorata, ci sono anche animazioni, e~ non devi nemmeno pensare a calcolare tutto.
Ma se si vuole fare solo una rimozione, un like, non c'è bisogno di DiffUtils. Basta ricordare la posizione, controllare se la posizione è nello schermo, e chiamare i metodi di aggiornamento diretti.
3 In realtà, DiffUtil non può essere utilizzato solo con RecyclerView.Adapter,
Possiamo implementare noi stessi l'interfaccia ListUpdateCallback, utilizzare DiffUtil per trovare la differenza minima tra i set di dati vecchi e nuovi per fare altre cose.
4 Attenzione: quando si scrive un DEMO, il set di dati vecchi e nuovi da confrontare, non solo ArrayList è diverso, ma anche ogni data all'interno deve essere diversa. Altrimenti non verrà attivato changed.
Non si incontra in progetti reali, perché i nuovi dati vengono spesso ricevuti dalla rete.
5 Oggi è l'ultimo giorno di Mid-Autumn Festival, la nostra azienda ha iniziato a lavorare!!! Dopo essere rimasto indignato, ho scritto un articolo su DiffUtil, non ho nemmeno bisogno di DiffUtil per confrontare facilmente le differenze tra la nostra azienda e altre aziende. QWQ, e oggi non ero nel mio meglio, ci sono volute 8 ore per completarlo. Pensavo che questo articolo potesse essere incluso nella raccolta di micro-essays, ma è stato piuttosto lungo. Chi non ha la pazienza può scaricare il DEMO per guardarlo, non ci sono molti codici, e è ancora facile da usare.
Link GitHub:
https://github.com/mcxtzhang/DiffUtils
Ecco la raccolta di materiali relativi a DiffUtil della classe Utility di Android7.0. Continueremo a integrare ulteriori materiali relativi, grazie per il supporto della nostra community!