Skip to content

Instantly share code, notes, and snippets.

@j05u3
Last active May 25, 2018 22:26
Show Gist options
  • Save j05u3/e0e5ea40740d749f0f9dd6f115a36766 to your computer and use it in GitHub Desktop.
Save j05u3/e0e5ea40740d749f0f9dd6f115a36766 to your computer and use it in GitHub Desktop.
ObservableList to animate item movements inside a LinearLayout
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