package me.tatarka.bindingcollectionadapter2.collections; import android.databinding.ListChangeRegistry; import android.databinding.ObservableList; import android.support.annotation.MainThread; import android.support.v7.util.DiffUtil; import android.support.v7.util.ListUpdateCallback; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * An {@link ObservableList} that uses {@link DiffUtil} to calculate and dispatch it's change * updates. */ public class DiffObservableList<T> extends AbstractList<T> implements ObservableList<T> { private final Object LIST_LOCK = new Object(); private List<T> list = Collections.emptyList(); private final Callback<T> callback; private final boolean detectMoves; private final ListChangeRegistry listeners = new ListChangeRegistry(); private final ObservableListUpdateCallback listCallback = new ObservableListUpdateCallback(); /** * Creates a new DiffObservableList of type T. * * @param callback The callback that controls the behavior of the DiffObservableList. */ public DiffObservableList(Callback<T> callback) { this(callback, true); } /** * Creates a new DiffObservableList of type T. * * @param callback The callback that controls the behavior of the DiffObservableList. * @param detectMoves True if DiffUtil should try to detect moved items, false otherwise. */ public DiffObservableList(Callback<T> callback, boolean detectMoves) { this.callback = callback; this.detectMoves = detectMoves; } /** * Calculates the list of update operations that can convert this list into the given one. * * @param newItems The items that this list will be set to. * @return A DiffResult that contains the information about the edit sequence to covert this * list into the given one. */ public DiffUtil.DiffResult calculateDiff(final List<T> newItems) { final ArrayList<T> frozenList; synchronized (LIST_LOCK) { frozenList = new ArrayList<>(list); } return doCalculateDiff(frozenList, newItems); } private DiffUtil.DiffResult doCalculateDiff(final List<T> oldItems, final List<T> newItems) { return DiffUtil.calculateDiff(new DiffUtil.Callback() { @Override public int getOldListSize() { return oldItems.size(); } @Override public int getNewListSize() { return newItems != null ? newItems.size() : 0; } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { T oldItem = oldItems.get(oldItemPosition); T newItem = newItems.get(newItemPosition); return callback.areItemsTheSame(oldItem, newItem); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { T oldItem = oldItems.get(oldItemPosition); T newItem = newItems.get(newItemPosition); return callback.areContentsTheSame(oldItem, newItem); } }, detectMoves); } /** * Updates the contents of this list to the given one using the DiffResults to dispatch change * notifications. * * @param newItems The items to set this list to. * @param diffResult The diff results to dispatch change notifications. */ @MainThread public void update(List<T> newItems, DiffUtil.DiffResult diffResult) { synchronized (LIST_LOCK) { list = newItems; } diffResult.dispatchUpdatesTo(listCallback); } /** * Sets this list to the given items. This is a convenience method for calling {@link * #calculateDiff(List)} followed by {@link #update(List, DiffUtil.DiffResult)}. * <p> * <b>Warning!</b> If the lists are large this operation may be too slow for the main thread. In * that case, you should call {@link #calculateDiff(List)} on a background thread and then * {@link #update(List, DiffUtil.DiffResult)} on the main thread. * * @param newItems The items to set this list to. */ @MainThread public void update(List<T> newItems) { DiffUtil.DiffResult diffResult = doCalculateDiff(list, newItems); list = newItems; diffResult.dispatchUpdatesTo(listCallback); } @Override public void addOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> listener) { listeners.add(listener); } @Override public void removeOnListChangedCallback(OnListChangedCallback<? extends ObservableList<T>> listener) { listeners.remove(listener); } @Override public T get(int i) { return list.get(i); } @Override public int size() { return list.size(); } /** * A Callback class used by DiffUtil while calculating the diff between two lists. */ public interface Callback<T> { /** * Called by the DiffUtil to decide whether two object represent the same Item. * <p> * For example, if your items have unique ids, this method should check their id equality. * * @param oldItem The old item. * @param newItem The new item. * @return True if the two items represent the same object or false if they are different. */ boolean areItemsTheSame(T oldItem, T newItem); /** * Called by the DiffUtil when it wants to check whether two items have the same data. * DiffUtil uses this information to detect if the contents of an item has changed. * <p> * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)} so * that you can change its behavior depending on your UI. * <p> * This method is called only if {@link #areItemsTheSame(T, T)} returns {@code true} for * these items. * * @param oldItem The old item. * @param newItem The new item which replaces the old item. * @return True if the contents of the items are the same or false if they are different. */ boolean areContentsTheSame(T oldItem, T newItem); } class ObservableListUpdateCallback implements ListUpdateCallback { @Override public void onChanged(int position, int count, Object payload) { listeners.notifyChanged(DiffObservableList.this, position, count); } @Override public void onInserted(int position, int count) { modCount += 1; listeners.notifyInserted(DiffObservableList.this, position, count); } @Override public void onRemoved(int position, int count) { modCount += 1; listeners.notifyRemoved(DiffObservableList.this, position, count); } @Override public void onMoved(int fromPosition, int toPosition) { listeners.notifyMoved(DiffObservableList.this, fromPosition, toPosition, 1); } } }