Last active
May 25, 2018 22:26
-
-
Save j05u3/e0e5ea40740d749f0f9dd6f115a36766 to your computer and use it in GitHub Desktop.
ObservableList to animate item movements inside a LinearLayout
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package pe.tumicro.android.ui.common; | |
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.Collection; | |
import java.util.Collections; | |
import java.util.List; | |
// Borrowed from https://github.com/evant/binding-collection-adapter/blob/master/bindingcollectionadapter-recyclerview/src/main/java/me/tatarka/bindingcollectionadapter2/collections/DiffObservableList.java | |
// Modified to support add, clear, remove, .. operations (from ObservableArrayList) | |
// Should be used with https://github.com/google/android-ui-toolkit-demos/tree/master/DataBinding/DataBoundList | |
/** | |
* 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 ArrayList<T> list = new ArrayList<>(20); | |
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 ArrayList<T> newItems) { | |
final ArrayList<T> frozenList; | |
synchronized (LIST_LOCK) { | |
frozenList = new ArrayList<>(list); | |
} | |
return doCalculateDiff(frozenList, newItems); | |
} | |
private DiffUtil.DiffResult doCalculateDiff(final ArrayList<T> oldItems, final ArrayList<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(ArrayList<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(ArrayList)} followed by {@link #update(ArrayList, 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(ArrayList)} on a background thread and then | |
* {@link #update(ArrayList, DiffUtil.DiffResult)} on the main thread. | |
* | |
* @param newItems The items to set this list to. | |
*/ | |
@MainThread | |
public void update(ArrayList<T> newItems) { | |
final ArrayList<T> frozenList; | |
synchronized (LIST_LOCK) { | |
frozenList = new ArrayList<>(list); | |
} | |
DiffUtil.DiffResult diffResult = doCalculateDiff(frozenList, newItems); | |
synchronized (LIST_LOCK) { | |
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); | |
} | |
} | |
@Override | |
public boolean add(T object) { | |
list.add(object); | |
notifyAdd(size() - 1, 1); | |
return true; | |
} | |
@Override | |
public void add(int index, T object) { | |
list.add(index, object); | |
notifyAdd(index, 1); | |
} | |
@Override | |
public boolean addAll(Collection<? extends T> collection) { | |
int oldSize = size(); | |
boolean added = list.addAll(collection); | |
if (added) { | |
notifyAdd(oldSize, size() - oldSize); | |
} | |
return added; | |
} | |
@Override | |
public boolean addAll(int index, Collection<? extends T> collection) { | |
boolean added = list.addAll(index, collection); | |
if (added) { | |
notifyAdd(index, collection.size()); | |
} | |
return added; | |
} | |
@Override | |
public void clear() { | |
int oldSize = size(); | |
list.clear(); | |
if (oldSize != 0) { | |
notifyRemove(0, oldSize); | |
} | |
} | |
@Override | |
public T remove(int index) { | |
T val = list.remove(index); | |
notifyRemove(index, 1); | |
return val; | |
} | |
@Override | |
public boolean remove(Object object) { | |
int index = indexOf(object); | |
if (index >= 0) { | |
remove(index); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
@Override | |
public T set(int index, T object) { | |
T val = list.set(index, object); | |
if (listeners != null) { | |
listeners.notifyChanged(this, index, 1); | |
} | |
return val; | |
} | |
@Override | |
protected void removeRange(int fromIndex, int toIndex) { | |
throw new UnsupportedOperationException(); | |
} | |
private void notifyAdd(int start, int count) { | |
if (listeners != null) { | |
listeners.notifyInserted(this, start, count); | |
} | |
} | |
private void notifyRemove(int start, int count) { | |
if (listeners != null) { | |
listeners.notifyRemoved(this, start, count); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment