diff --git a/layouts/src/main/java/org/lucasr/twowayview/widget/OnReorderingListener.java b/layouts/src/main/java/org/lucasr/twowayview/widget/OnReorderingListener.java
new file mode 100644
index 0000000..288846f
--- /dev/null
+++ b/layouts/src/main/java/org/lucasr/twowayview/widget/OnReorderingListener.java
@@ -0,0 +1,16 @@
+package org.lucasr.twowayview.widget;
+
+/**
+ * Listen for changes in a {@link TwoWayView}'s reordering state.
+ */
+public interface OnReorderingListener {
+ /**
+ * Called when a {@link TwoWayView} has started reordering.
+ */
+ public void onStartReordering();
+
+ /**
+ * Called when a {@link TwoWayView} has stopped reordering.
+ */
+ public void onStopReordering();
+}
diff --git a/layouts/src/main/java/org/lucasr/twowayview/widget/ReorderableAdapter.java b/layouts/src/main/java/org/lucasr/twowayview/widget/ReorderableAdapter.java
new file mode 100644
index 0000000..16fc9f1
--- /dev/null
+++ b/layouts/src/main/java/org/lucasr/twowayview/widget/ReorderableAdapter.java
@@ -0,0 +1,16 @@
+package org.lucasr.twowayview.widget;
+
+/**
+ * Any {@link android.support.v7.widget.RecyclerView.Adapter} that is passed to one of
+ * {@link TwoWayView#setReorderableAdapter} or {@link TwoWayView#swapReorderableAdapter}
+ * should implement {@code ReorderableAdapter} (or its subinterfaces).
+ */
+public interface ReorderableAdapter {
+ /**
+ * Called when an item that was being dragged is dropped. Most implementations will want to
+ * remove the item from the dataset at {@code from} and reinsert it at {@code to}.
+ * @param from the position the item was dragged from
+ * @param to the position the item was dropped on
+ */
+ public void onItemDropped(int from, int to);
+}
diff --git a/layouts/src/main/java/org/lucasr/twowayview/widget/ReorderableAdapterViewDecorator.java b/layouts/src/main/java/org/lucasr/twowayview/widget/ReorderableAdapterViewDecorator.java
new file mode 100644
index 0000000..e54f7c5
--- /dev/null
+++ b/layouts/src/main/java/org/lucasr/twowayview/widget/ReorderableAdapterViewDecorator.java
@@ -0,0 +1,29 @@
+package org.lucasr.twowayview.widget;
+
+import android.view.View;
+
+/**
+ * A {@link ReorderableAdapter} that allows for overriding the decoration applied to an item at a "drop position."
+ * If an item is dragged over another, that view will slide over 1 position, leaving an "empty space."
+ * The default implementation calls {@link View#setVisibility} with {@link View#INVISIBLE} for the {@code View}
+ * at the drop position, and then calls {@code View.setVisibility} with {@link View#VISIBLE} when it is no longer
+ * at the drop position. If an implementation uses a {@code View}'s visibility, or wants to apply custom animations
+ * to this transition, its {@link android.support.v7.widget.RecyclerView.Adapter} should implement
+ * this instead of {@link ReorderableAdapter}.
+ * @see org.lucasr.twowayview.widget.ReorderableAdapter
+ */
+public interface ReorderableAdapterViewDecorator extends ReorderableAdapter {
+ /**
+ * Alter the {@code View}'s appearance when its position is the "drop position."
+ * @param view the {@link View} that the dragged item is currently over
+ */
+ public void applyDropPositionDecoration(View view);
+
+ /**
+ * Undo whatever alterations were made in {@link ReorderableAdapterViewDecorator#applyDropPositionDecoration}.
+ * This is called everytime its position is invalidated; not just after {@code applyDropPositionDecoration},
+ * so it may be best to check if the alterations were already undone.
+ * @param view a {@link View} that the dragged item is not currently over
+ */
+ public void undoDropPositionDecoration(View view);
+}
diff --git a/layouts/src/main/java/org/lucasr/twowayview/widget/Reorderer.java b/layouts/src/main/java/org/lucasr/twowayview/widget/Reorderer.java
new file mode 100644
index 0000000..7ab17d0
--- /dev/null
+++ b/layouts/src/main/java/org/lucasr/twowayview/widget/Reorderer.java
@@ -0,0 +1,228 @@
+package org.lucasr.twowayview.widget;
+
+import android.annotation.TargetApi;
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.os.Build;
+import android.os.Handler;
+import android.support.v7.widget.RecyclerView;
+import android.view.DragEvent;
+import android.view.View;
+import android.widget.AbsListView;
+import org.lucasr.twowayview.TwoWayLayoutManager;
+
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+/*package*/ final class Reorderer implements View.OnDragListener {
+ /**
+ * Allows for system wide drag-drop compatability.
+ * http://developer.android.com/guide/topics/ui/drag-drop.html#HandleDrop
+ */
+ /*package*/ final static String MIME_TYPE = "x-org-lucasr-twowayview-widget/item";
+
+ private final TwoWayView twv;
+
+ private boolean mIsReordering = false;
+
+ /**
+ * Allows for system wide drag-drop compatability.
+ * http://developer.android.com/guide/topics/ui/drag-drop.html#HandleDrop
+ */
+ private ClipData mMyClipData;
+
+ /**
+ * Creates a new {@code Reorderer} for a given {@link TwoWayView}.
+ */
+ /*package*/ Reorderer(TwoWayView twv) {
+ this.twv = twv;
+ this.twv.setOnDragListener(this);
+ this.mMyClipData = new ClipData(new ClipDescription("", new String[] {MIME_TYPE}), new ClipData.Item(""));
+ }
+
+ /**
+ * As per this bug - https://code.google.com/p/android/issues/detail?id=25073
+ */
+ /*package*/ boolean dispatchDragEvent(DragEvent ev) {
+ boolean r = twv.superDispatchDragEvent(ev);
+ if (r && (ev.getAction() == DragEvent.ACTION_DRAG_STARTED
+ || ev.getAction() == DragEvent.ACTION_DRAG_ENDED)){
+ // If we got a start or end and the return value is true, our
+ // onDragEvent wasn't called by ViewGroup.dispatchDragEvent
+ // So we do it here.
+ onDragEvent(ev);
+ return true;
+ }
+ else {
+ return true;
+ }
+ }
+
+ /*package*/ boolean onDragEvent(DragEvent ev) {
+ return onDrag(null, ev);
+ }
+
+ private Handler scrollHandler = new Handler();
+ private int scrollDistance;
+ private boolean isScrolling = false;
+ private Runnable scrollRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if(isScrolling) {
+ if(twv.getOrientation() == TwoWayLayoutManager.Orientation.VERTICAL) {
+ twv.smoothScrollBy(0, scrollDistance);
+ }
+ else {
+ twv.smoothScrollBy(scrollDistance, 0);
+ }
+ scrollHandler.postDelayed(this, 250);
+ }
+ }
+ };
+
+ private int lastKnownPosition = TwoWayView.NO_POSITION;
+ @Override
+ public boolean onDrag(View v, DragEvent ev) {
+ ClipDescription desc = ev.getClipDescription();
+ // if this isn't our dragged item, ignore it
+ if(desc != null && !desc.hasMimeType(MIME_TYPE)) {
+ return false;
+ }
+
+ int x = (int) ev.getX();
+ int y = (int) ev.getY();
+
+ RecyclerView.ViewHolder viewHolderToSendToAdapter = null;
+
+ if(ev.getAction() == DragEvent.ACTION_DRAG_ENTERED) {
+ // reset our last known position for a new drag
+ lastKnownPosition = AbsListView.INVALID_POSITION;
+ }
+ else if(ev.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
+ boolean isVertical = (twv.getOrientation() == TwoWayLayoutManager.Orientation.VERTICAL);
+ int pointer = (isVertical ? y : x);
+ int bottomBound = (isVertical ? twv.getHeight() : twv.getWidth());
+ int bottomOffset = bottomBound - pointer;
+
+ View firstVisibleView = twv.getChildAt(twv.getFirstVisiblePosition());
+ scrollDistance = bottomBound / 8;
+ int scrollThreshold = scrollDistance / 2;
+ if(firstVisibleView != null) {
+ int bound = (isVertical ? firstVisibleView.getHeight() : firstVisibleView.getWidth());
+ scrollDistance = ((bound * 2) + (bound / 2));
+ scrollThreshold = bound / 8;
+ }
+
+ if(pointer <= scrollThreshold && bottomOffset >= scrollThreshold) {
+ scrollDistance = -scrollDistance;
+ if(!isScrolling) {
+ scrollHandler.post(scrollRunnable);
+ isScrolling = true;
+ }
+ }
+ else if(pointer >= scrollThreshold && bottomOffset <= scrollThreshold) {
+ if(!isScrolling) {
+ scrollHandler.post(scrollRunnable);
+ isScrolling = true;
+ }
+ }
+ else {
+ scrollHandler.removeCallbacks(scrollRunnable);
+ isScrolling = false;
+ }
+
+ // get the view that the dragged item is over
+ View viewAtCurrentPosition = twv.findChildViewUnder(ev.getX(), ev.getY());
+ if(viewAtCurrentPosition != null) {
+ // and get its position
+ int currentPosition = twv.getChildPosition(viewAtCurrentPosition);
+ // this is an optimization so that we don't keep sending the same ViewHolder to the adapter
+ // for every pixel that it moves (which would be redundant anyway)
+ if(currentPosition != lastKnownPosition) {
+ // if it's a valid position, use it to get the ViewHolder to send to the adapter
+ if(currentPosition != TwoWayView.NO_POSITION) {
+ viewHolderToSendToAdapter = twv.findViewHolderForPosition(currentPosition);
+ }
+ // this position is now our known position
+ lastKnownPosition = currentPosition;
+ }
+ }
+ else { // if we can't get the view we're over, we don't have a known position
+ lastKnownPosition = TwoWayView.NO_POSITION;
+ }
+ }
+ else if(ev.getAction() == DragEvent.ACTION_DROP) {
+ // stop scrolling
+ scrollHandler.removeCallbacks(scrollRunnable);
+ isScrolling = false;
+
+ // get the view that the dragged item is over
+ View viewAtCurrentPosition = twv.findChildViewUnder(ev.getX(), ev.getY());
+ if(viewAtCurrentPosition != null) {
+ // and get its position
+ int currentPosition = twv.getChildPosition(viewAtCurrentPosition);
+ // if it's a valid position, use it to get the ViewHolder to send to the adapter
+ if(currentPosition != TwoWayView.NO_POSITION) {
+ viewHolderToSendToAdapter = twv.findViewHolderForPosition(currentPosition);
+ }
+ }
+ // reset our last known position since we dropped
+ lastKnownPosition = TwoWayView.NO_POSITION;
+ }
+ else if(ev.getAction() == DragEvent.ACTION_DRAG_ENDED) {
+ setIsReordering(false);
+
+ // stop scrolling
+ scrollHandler.removeCallbacks(scrollRunnable);
+ isScrolling = false;
+
+ // reset our last known position since we're done
+ lastKnownPosition = TwoWayView.NO_POSITION;
+ }
+
+ ReordererAdapterDecorator adapter = twv.getReordererAdapter();
+ return adapter != null && adapter.onDrag(viewHolderToSendToAdapter, ev);
+ }
+
+ /*package*/ boolean isReordering() {
+ return mIsReordering;
+ }
+
+ /*package*/ void setIsReordering(boolean isReordering) {
+ this.mIsReordering = isReordering;
+ OnReorderingListener reorderingListener = twv.getOnReorderingListener();
+ if(reorderingListener != null) {
+ if(isReordering) {
+ reorderingListener.onStartReordering();
+ }
+ else {
+ reorderingListener.onStopReordering();
+ }
+ }
+ }
+
+ /*package*/ final boolean startReorder(int position) {
+ if(isReordering()) {
+ throw new IllegalStateException("Cannot start reordering if a reordering operation is already in progress");
+ }
+ if(twv.getAdapter() == null) {
+ throw new IllegalStateException("Cannot start a reorder operation if there is no adapter set");
+ }
+ if(position < 0 || position >= twv.getAdapter().getItemCount()) {
+ throw new IndexOutOfBoundsException("Cannot start a reorder operation if the position is out of the bounds of the adapter");
+ }
+
+ // TODO: custom DragShadowBuilder
+
+ View.DragShadowBuilder dragShadowBuilder;
+ View viewAtReorderPosition = twv.findViewHolderForPosition(position).itemView;
+ dragShadowBuilder = new View.DragShadowBuilder(viewAtReorderPosition);
+
+ boolean success = twv.startDrag(mMyClipData, dragShadowBuilder, null, 0);
+
+ if(success) {
+ setIsReordering(true);
+ twv.getReordererAdapter().startReordering(position);
+ }
+
+ return success;
+ }
+}
diff --git a/layouts/src/main/java/org/lucasr/twowayview/widget/ReordererAdapterDecorator.java b/layouts/src/main/java/org/lucasr/twowayview/widget/ReordererAdapterDecorator.java
new file mode 100644
index 0000000..7461a83
--- /dev/null
+++ b/layouts/src/main/java/org/lucasr/twowayview/widget/ReordererAdapterDecorator.java
@@ -0,0 +1,237 @@
+package org.lucasr.twowayview.widget;
+
+import android.annotation.TargetApi;
+import android.content.ClipDescription;
+import android.os.Build;
+import android.support.v7.widget.RecyclerView;
+import android.view.DragEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A decorator for {@link android.support.v7.widget.RecyclerView.Adapter}. It adds reordering capabilities.
+ * @param the type of ViewHolder the wrapped {@code Adapter} holds
+ * (inferred from {@link ReordererAdapterDecorator#decorateAdapter})
+ */
+@TargetApi(Build.VERSION_CODES.HONEYCOMB)
+/*package*/ final class ReordererAdapterDecorator extends RecyclerView.Adapter {
+ private static final int INVALID = -1;
+
+ // the Adapter that we're decorating
+ private final RecyclerView.Adapter decoratedAdapter;
+
+ private int mReorderPosition = INVALID;
+ private int mDropPosition = INVALID;
+
+ private ReordererAdapterDecorator(final RecyclerView.Adapter adapterToDecorate) {
+ this.decoratedAdapter = adapterToDecorate;
+ }
+
+ /**
+ * Returns a {@code ReordererAdapterDecorator} that decorates the
+ * {@link android.support.v7.widget.RecyclerView.Adapter}.
+ * {@code adapterToDecorate} cannot be {@code null} and must be-a {@link ReorderableAdapter}.
+ * @param adapterToDecorate the {@code Adapter} to decorate
+ * @param the type of ViewHolder that {@code adapterToDecorate} holds
+ * @throws java.lang.NullPointerException if {@code adapterToDecorate} is {@code null}
+ * @throws java.lang.IllegalArgumentException if {@code adapterToDecorate} is not a {@code ReorderableAdapter}
+ */
+ /*package*/ static ReordererAdapterDecorator
+ decorateAdapter(final RecyclerView.Adapter adapterToDecorate) {
+ if(adapterToDecorate == null) {
+ throw new NullPointerException("reorderable adapter cannot be null");
+ }
+ if(!(adapterToDecorate instanceof ReorderableAdapter)) {
+ throw new IllegalArgumentException("reorderable adapter must implement ReorderableAdapter");
+ }
+ return new ReordererAdapterDecorator(adapterToDecorate);
+ }
+
+ /**
+ * Called when a reordering operation is started on the {@link TwoWayView}.
+ * @param position the position that the reordering operation was started on
+ */
+ /*package*/ void startReordering(int position) {
+ mReorderPosition = position;
+ mDropPosition = position;
+ decoratedAdapter.notifyItemChanged(position);
+ }
+
+ private boolean isReordering() {
+ return mReorderPosition != INVALID;
+ }
+
+ /**
+ * Notifies any observers about the range of items that were changed.
+ */
+ private void notifyPositionRangeChanged(int initialRangePosition, int currentRangePosition) {
+ if(currentRangePosition == INVALID) {
+ return;
+ }
+
+ if(initialRangePosition == INVALID) {
+ initialRangePosition = currentRangePosition;
+ }
+
+ if(initialRangePosition == currentRangePosition) {
+ decoratedAdapter.notifyItemChanged(currentRangePosition);
+ return;
+ }
+
+ int max = Math.max(initialRangePosition, currentRangePosition);
+ int min = Math.min(initialRangePosition, currentRangePosition);
+
+ int changedCount = (max + 1) - min;
+ decoratedAdapter.notifyItemRangeChanged(min, changedCount);
+ }
+
+ /**
+ * Receive drag events from {@link Reorderer#onDrag}.
+ * @param holder the {@link android.support.v7.widget.RecyclerView.ViewHolder} at this drag position, or null
+ * @param e the {@link DragEvent}
+ * @param the type of {@code ViewHolder}
+ * @return whether the drag event was handled
+ */
+ /*package*/ boolean onDrag(T holder, DragEvent e) {
+ ClipDescription desc = e.getClipDescription();
+ // if this isn't our dragged item, ignore it
+ if(desc != null && !desc.hasMimeType(Reorderer.MIME_TYPE)) {
+ return false;
+ }
+
+ if(e.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
+ int oldDropPosition = mDropPosition;
+ if(holder != null) {
+ mDropPosition = holder.getPosition();
+ }
+
+ notifyPositionRangeChanged(oldDropPosition, mDropPosition);
+ }
+ else if(e.getAction() == DragEvent.ACTION_DROP) {
+ int oldDropPosition = mDropPosition;
+ if(holder != null) {
+ mDropPosition = holder.getPosition();
+ }
+
+ notifyPositionRangeChanged(oldDropPosition, mDropPosition);
+ if(mDropPosition != INVALID) {
+ ((ReorderableAdapter) decoratedAdapter).onItemDropped(mReorderPosition, mDropPosition);
+ notifyPositionRangeChanged(mReorderPosition, mDropPosition);
+ }
+ mReorderPosition = INVALID;
+ mDropPosition = INVALID;
+ }
+ else if(e.getAction() == DragEvent.ACTION_DRAG_ENDED) {
+ notifyPositionRangeChanged(mReorderPosition, mDropPosition);
+ mReorderPosition = INVALID;
+ mDropPosition = INVALID;
+ }
+ return true;
+ }
+
+ /*package*/ RecyclerView.Adapter getDecoratedAdapter() {
+ return decoratedAdapter;
+ }
+
+ @Override
+ public int getItemCount() {
+ return decoratedAdapter.getItemCount();
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return decoratedAdapter.getItemId(position);
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return decoratedAdapter.getItemViewType(position);
+ }
+
+ @Override
+ public void onBindViewHolder(VH holder, int position) {
+ if(!isReordering()) { //if we're not reordering, don't do anything to the view (unless we have to undo the drop position decoration)
+ decoratedAdapter.onBindViewHolder(holder, position);
+ if(decoratedAdapter instanceof ReorderableAdapterViewDecorator) {
+ ((ReorderableAdapterViewDecorator) decoratedAdapter).undoDropPositionDecoration(holder.itemView);
+ }
+ else {
+ if(holder.itemView.getVisibility() == View.INVISIBLE) {
+ holder.itemView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ else if(holder.getPosition() == mDropPosition) { // if it's the drop position, apply the drop position decoration
+ decoratedAdapter.onBindViewHolder(holder, position);
+ if(decoratedAdapter instanceof ReorderableAdapterViewDecorator) {
+ ((ReorderableAdapterViewDecorator) decoratedAdapter).applyDropPositionDecoration(holder.itemView);
+ }
+ else {
+ holder.itemView.setVisibility(View.INVISIBLE);
+ }
+ }
+ else { //if we're reordering but it's not the drop position, resolve the position (and undo the drop position decoration if needed)
+ decoratedAdapter.onBindViewHolder(holder, resolvePosition(position));
+ if(decoratedAdapter instanceof ReorderableAdapterViewDecorator) {
+ ((ReorderableAdapterViewDecorator) decoratedAdapter).undoDropPositionDecoration(holder.itemView);
+ }
+ else {
+ if(holder.itemView.getVisibility() == View.INVISIBLE) {
+ holder.itemView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ }
+
+ /**
+ * Resolve the position to where it needs to be (shifted +/- 1 because of drop position, or not affected).
+ * @param position the position to resolve
+ * @return the resolved position
+ */
+ private int resolvePosition(final int position) {
+ if(mDropPosition < mReorderPosition && position > mDropPosition && position <= mReorderPosition) {
+ return position - 1;
+ }
+ else if(mDropPosition > mReorderPosition && position < mDropPosition && position >= mReorderPosition) {
+ return position + 1;
+ }
+ else {
+ return position;
+ }
+ }
+
+ @Override
+ public VH onCreateViewHolder(ViewGroup parent, int viewType) {
+ return decoratedAdapter.onCreateViewHolder(parent, viewType);
+ }
+
+ @Override
+ public void onViewAttachedToWindow(VH holder) {
+ decoratedAdapter.onViewAttachedToWindow(holder);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(VH holder) {
+ decoratedAdapter.onViewDetachedFromWindow(holder);
+ }
+
+ @Override
+ public void onViewRecycled(VH holder) {
+ decoratedAdapter.onViewRecycled(holder);
+ }
+
+ @Override
+ public void registerAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
+ decoratedAdapter.registerAdapterDataObserver(observer);
+ }
+
+ @Override
+ public void setHasStableIds(boolean hasStableIds) {
+ decoratedAdapter.setHasStableIds(hasStableIds);
+ }
+
+ @Override
+ public void unregisterAdapterDataObserver(RecyclerView.AdapterDataObserver observer) {
+ decoratedAdapter.unregisterAdapterDataObserver(observer);
+ }
+}
diff --git a/layouts/src/main/java/org/lucasr/twowayview/widget/TwoWayView.java b/layouts/src/main/java/org/lucasr/twowayview/widget/TwoWayView.java
index cc71d3b..5c44c19 100644
--- a/layouts/src/main/java/org/lucasr/twowayview/widget/TwoWayView.java
+++ b/layouts/src/main/java/org/lucasr/twowayview/widget/TwoWayView.java
@@ -16,12 +16,14 @@
package org.lucasr.twowayview.widget;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.util.AttributeSet;
-
+import android.view.DragEvent;
import org.lucasr.twowayview.TwoWayLayoutManager;
import org.lucasr.twowayview.TwoWayLayoutManager.Orientation;
@@ -35,6 +37,9 @@ public class TwoWayView extends RecyclerView {
final Object[] sConstructorArgs = new Object[2];
+ private Reorderer mReorderer;
+ private OnReorderingListener mReorderingListener;
+
public TwoWayView(Context context) {
this(context, null);
}
@@ -112,4 +117,149 @@ public int getLastVisiblePosition() {
TwoWayLayoutManager layout = (TwoWayLayoutManager) getLayoutManager();
return layout.getLastVisiblePosition();
}
+
+ /*
+ The following methods all relate to reordering, and are only applicable to API 11+
+ */
+
+ /**
+ * Returns whether this {@code TwoWayView} is in middle of reordering.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public boolean isReordering() {
+ return mReorderer.isReordering();
+ }
+
+ @Override
+ public Adapter getAdapter() {
+ if(mReorderer == null) {
+ return super.getAdapter();
+ }
+ else {
+ return ((ReordererAdapterDecorator) super.getAdapter()).getDecoratedAdapter();
+ }
+ }
+
+ /*pacakge*/ ReordererAdapterDecorator getReordererAdapter() {
+ if(mReorderer != null) {
+ return (ReordererAdapterDecorator) super.getAdapter();
+ }
+ else {
+ return null;
+ }
+ }
+
+ /**
+ * Should only be used for API 11+.
+ * Set a new adapter to provide child views on demand.
+ * When adapter is changed, all existing views are recycled back to the pool.
+ * If the pool has only one adapter, it will be cleared.
+ * {@code adapter} must be a {@link ReorderableAdapter}.
+ * @param adapter the new adapter to set
+ * @throws java.lang.IllegalArgumentException if {@code adapter} is not a {@code ReorderableAdapter}
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void setReorderableAdapter(Adapter adapter) {
+ // if its null, we can't go into reorderable mode
+ if(adapter == null) {
+ setAdapter(null);
+ return;
+ }
+
+ super.setAdapter(ReordererAdapterDecorator.decorateAdapter(adapter));
+ mReorderer = new Reorderer(this);
+ }
+
+ @Override
+ public void setAdapter(Adapter adapter) {
+ if(mReorderer != null) {
+ mReorderer = null;
+ }
+ super.setAdapter(adapter);
+ }
+
+ /**
+ * Should only be used for API 11+.
+ * Swaps the current adapter with the provided one. It is similar to setAdapter(Adapter) but assumes
+ * existing adapter and the new adapter uses the same RecyclerView.ViewHolder and does not clear the RecycledViewPool.
+ *
Note that it still calls onAdapterChanged callbacks.
+ * {@code adapter} must be a {@link ReorderableAdapter}.
+ * @param adapter the new adapter to set
+ * @throws java.lang.NullPointerException if {@code adapter} is {@code null}
+ * @throws java.lang.IllegalArgumentException if {@code adapter} is not a {@code ReorderableAdapter}
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void swapReorderableAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
+ //we don't check for null here because we want to propogate the NPE from ReordererAdapterDecorator
+ super.swapAdapter(ReordererAdapterDecorator.decorateAdapter(adapter), removeAndRecycleExistingViews);
+ mReorderer = new Reorderer(this);
+ }
+
+ @Override
+ public void swapAdapter(Adapter adapter, boolean removeAndRecycleExistingViews) {
+ if(mReorderer != null) {
+ mReorderer = null;
+ }
+ super.swapAdapter(adapter, removeAndRecycleExistingViews);
+ }
+
+ /**
+ * Sets a {@link OnReorderingListener}.
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void setOnReorderingListener(OnReorderingListener onReorderingListener) {
+ this.mReorderingListener = onReorderingListener;
+ }
+
+ /*package*/ OnReorderingListener getOnReorderingListener() {
+ return mReorderingListener;
+ }
+
+ /**
+ * Should only be used for API 11+.
+ * Start a reordering operation.
+ * {@link TwoWayView#setReorderableAdapter} or {@link TwoWayView#swapReorderableAdapter} must be called at some
+ * point before this.
+ * @param position the position to drag
+ * @return whether the reordering operation was started successfully
+ * @throws java.lang.IllegalStateException if {@code setReorderableAdapter} or
+ * {@code swapReorderableAdapter} has not been called
+ */
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public final boolean startReorder(int position) {
+ if(mReorderer == null) {
+ throw new IllegalStateException("Cannot call startReorder if setReorderableAdapter or " +
+ "swapReorderableAdapter has not been called");
+ }
+
+ return mReorderer.startReorder(position);
+ }
+
+ // As per this bug - https://code.google.com/p/android/issues/detail?id=25073
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public boolean dispatchDragEvent(DragEvent ev) {
+ if(mReorderer == null) {
+ return super.dispatchDragEvent(ev);
+ }
+ else {
+ return mReorderer.dispatchDragEvent(ev);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ /*package*/ boolean superDispatchDragEvent(DragEvent ev) {
+ return super.dispatchDragEvent(ev);
+ }
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public boolean onDragEvent(DragEvent ev) {
+ if(mReorderer == null) {
+ return super.onDragEvent(ev);
+ }
+ else {
+ return mReorderer.onDragEvent(ev);
+ }
+ }
}
diff --git a/sample/src/main/java/org/lucasr/twowayview/sample/LayoutAdapter.java b/sample/src/main/java/org/lucasr/twowayview/sample/LayoutAdapter.java
index baabd25..2d0e4f8 100644
--- a/sample/src/main/java/org/lucasr/twowayview/sample/LayoutAdapter.java
+++ b/sample/src/main/java/org/lucasr/twowayview/sample/LayoutAdapter.java
@@ -22,16 +22,16 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-
import org.lucasr.twowayview.TwoWayLayoutManager;
-import org.lucasr.twowayview.widget.TwoWayView;
+import org.lucasr.twowayview.widget.ReorderableAdapter;
import org.lucasr.twowayview.widget.SpannableGridLayoutManager;
import org.lucasr.twowayview.widget.StaggeredGridLayoutManager;
+import org.lucasr.twowayview.widget.TwoWayView;
import java.util.ArrayList;
import java.util.List;
-public class LayoutAdapter extends RecyclerView.Adapter {
+public class LayoutAdapter extends RecyclerView.Adapter implements ReorderableAdapter {
private static final int COUNT = 100;
private final Context mContext;
@@ -40,6 +40,12 @@ public class LayoutAdapter extends RecyclerView.Adapter