/* * Copyright (C) 2015 Haruki Hasegawa * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.h6ah4i.android.widget.advrecyclerview.draggable; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.ViewGroup; import com.h6ah4i.android.widget.advrecyclerview.adapter.SimpleWrapperAdapter; import com.h6ah4i.android.widget.advrecyclerview.swipeable.RecyclerViewSwipeManager; import com.h6ah4i.android.widget.advrecyclerview.swipeable.SwipeableItemAdapter; import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultAction; import com.h6ah4i.android.widget.advrecyclerview.swipeable.action.SwipeResultActionDefault; import com.h6ah4i.android.widget.advrecyclerview.utils.CustomRecyclerViewUtils; import com.h6ah4i.android.widget.advrecyclerview.utils.WrapperAdapterUtils; import java.util.List; class DraggableItemWrapperAdapter<VH extends RecyclerView.ViewHolder> extends SimpleWrapperAdapter<VH> implements SwipeableItemAdapter<VH> { private static final String TAG = "ARVDraggableWrapper"; private static final int STATE_FLAG_INITIAL_VALUE = -1; private interface Constants extends DraggableItemConstants { } private static final boolean LOCAL_LOGV = false; private static final boolean LOCAL_LOGD = false; private static final boolean LOCAL_LOGI = true; private static final boolean DEBUG_BYPASS_MOVE_OPERATION_MODE = false; private RecyclerViewDragDropManager mDragDropManager; private DraggableItemAdapter mDraggableItemAdapter; private RecyclerView.ViewHolder mDraggingItemViewHolder; private DraggingItemInfo mDraggingItemInfo; private ItemDraggableRange mDraggableRange; private int mDraggingItemInitialPosition = RecyclerView.NO_POSITION; private int mDraggingItemCurrentPosition = RecyclerView.NO_POSITION; private int mItemMoveMode; public DraggableItemWrapperAdapter(RecyclerViewDragDropManager manager, RecyclerView.Adapter<VH> adapter) { super(adapter); if (manager == null) { throw new IllegalArgumentException("manager cannot be null"); } mDragDropManager = manager; } @Override protected void onRelease() { super.onRelease(); mDraggingItemViewHolder = null; mDraggableItemAdapter = null; mDragDropManager = null; } @Override public VH onCreateViewHolder(ViewGroup parent, int viewType) { final VH holder = super.onCreateViewHolder(parent, viewType); if (holder instanceof DraggableItemViewHolder) { ((DraggableItemViewHolder) holder).setDragStateFlags(STATE_FLAG_INITIAL_VALUE); } return holder; } @Override public void onBindViewHolder(VH holder, int position, List<Object> payloads) { if (isDragging()) { final long draggingItemId = mDraggingItemInfo.id; final long itemId = holder.getItemId(); final int origPosition = convertToOriginalPosition( position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition, mItemMoveMode); if (itemId == draggingItemId && holder != mDraggingItemViewHolder) { if (LOCAL_LOGI) { Log.i(TAG, "a new view holder object for the currently dragging item is assigned"); } mDraggingItemViewHolder = holder; mDragDropManager.onNewDraggingItemViewBound(holder); } int flags = Constants.STATE_FLAG_DRAGGING; if (itemId == draggingItemId) { flags |= Constants.STATE_FLAG_IS_ACTIVE; } if (mDraggableRange.checkInRange(position)) { flags |= Constants.STATE_FLAG_IS_IN_RANGE; } safeUpdateFlags(holder, flags); super.onBindViewHolder(holder, origPosition, payloads); } else { safeUpdateFlags(holder, 0); super.onBindViewHolder(holder, position, payloads); } } @Override public long getItemId(int position) { if (isDragging()) { final int origPosition = convertToOriginalPosition( position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition, mItemMoveMode); return super.getItemId(origPosition); } else { return super.getItemId(position); } } @Override public int getItemViewType(int position) { if (isDragging()) { final int origPosition = convertToOriginalPosition( position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition, mItemMoveMode); return super.getItemViewType(origPosition); } else { return super.getItemViewType(position); } } protected static int convertToOriginalPosition(int position, int dragInitial, int dragCurrent, int itemMoveMode) { if (dragInitial < 0 || dragCurrent < 0) { // not dragging return position; } else if (itemMoveMode == RecyclerViewDragDropManager.ITEM_MOVE_MODE_DEFAULT) { if ((dragInitial == dragCurrent) || ((position < dragInitial) && (position < dragCurrent)) || (position > dragInitial) && (position > dragCurrent)) { return position; } else if (dragCurrent < dragInitial) { if (position == dragCurrent) { return dragInitial; } else { return position - 1; } } else { // if (dragCurrent > dragInitial) if (position == dragCurrent) { return dragInitial; } else { return position + 1; } } } else if (itemMoveMode == RecyclerViewDragDropManager.ITEM_MOVE_MODE_SWAP) { if (position == dragCurrent) { return dragInitial; } else if (position == dragInitial) { return dragCurrent; } else { return position; } } else { throw new IllegalStateException("unexpected state"); } } @Override protected void onHandleWrappedAdapterChanged() { if (shouldCancelDragOnDataUpdated()) { cancelDrag(); } else { super.onHandleWrappedAdapterChanged(); } } @Override protected void onHandleWrappedAdapterItemRangeChanged(int positionStart, int itemCount) { if (shouldCancelDragOnDataUpdated()) { cancelDrag(); } else { super.onHandleWrappedAdapterItemRangeChanged(positionStart, itemCount); } } @Override protected void onHandleWrappedAdapterItemRangeInserted(int positionStart, int itemCount) { if (shouldCancelDragOnDataUpdated()) { cancelDrag(); } else { super.onHandleWrappedAdapterItemRangeInserted(positionStart, itemCount); } } @Override protected void onHandleWrappedAdapterItemRangeRemoved(int positionStart, int itemCount) { if (shouldCancelDragOnDataUpdated()) { cancelDrag(); } else { super.onHandleWrappedAdapterItemRangeRemoved(positionStart, itemCount); } } @Override protected void onHandleWrappedAdapterRangeMoved(int fromPosition, int toPosition, int itemCount) { if (shouldCancelDragOnDataUpdated()) { cancelDrag(); } else { super.onHandleWrappedAdapterRangeMoved(fromPosition, toPosition, itemCount); } } private boolean shouldCancelDragOnDataUpdated() { //noinspection SimplifiableIfStatement if (DEBUG_BYPASS_MOVE_OPERATION_MODE) { return false; } return isDragging(); } private void cancelDrag() { if (mDragDropManager != null) { mDragDropManager.cancelDrag(); } } // NOTE: This method is called from RecyclerViewDragDropManager /*package*/ void onDragItemStarted(DraggingItemInfo draggingItemInfo, RecyclerView.ViewHolder holder, ItemDraggableRange range, int wrappedItemPosition, int itemMoveMode) { if (LOCAL_LOGD) { Log.d(TAG, "onDragItemStarted(holder = " + holder + ")"); } if (DEBUG_BYPASS_MOVE_OPERATION_MODE) { return; } if (holder.getItemId() == RecyclerView.NO_ID) { throw new IllegalStateException("dragging target must provides valid ID"); } mDraggableItemAdapter = WrapperAdapterUtils.findWrappedAdapter(this, DraggableItemAdapter.class, wrappedItemPosition); if (mDraggableItemAdapter == null) { throw new IllegalStateException("DraggableItemAdapter not found!"); } mDraggingItemInitialPosition = mDraggingItemCurrentPosition = wrappedItemPosition; mDraggingItemInfo = draggingItemInfo; mDraggingItemViewHolder = holder; mDraggableRange = range; mItemMoveMode = itemMoveMode; notifyDataSetChanged(); } // NOTE: This method is called from RecyclerViewDragDropManager /*package*/ void onDragItemFinished(boolean result) { if (LOCAL_LOGD) { Log.d(TAG, "onDragItemFinished(result = " + result + ")"); } if (DEBUG_BYPASS_MOVE_OPERATION_MODE) { return; } if (result && (mDraggingItemCurrentPosition != mDraggingItemInitialPosition)) { // apply to wrapped adapter mDraggableItemAdapter.onMoveItem(mDraggingItemInitialPosition, mDraggingItemCurrentPosition); } mDraggingItemInitialPosition = RecyclerView.NO_POSITION; mDraggingItemCurrentPosition = RecyclerView.NO_POSITION; mDraggableRange = null; mDraggingItemInfo = null; mDraggingItemViewHolder = null; mDraggableItemAdapter = null; notifyDataSetChanged(); } @Override public void onViewRecycled(VH holder, int viewType) { if (isDragging()) { mDragDropManager.onItemViewRecycled(holder); mDraggingItemViewHolder = mDragDropManager.getDraggingItemViewHolder(); } super.onViewRecycled(holder, viewType); } // NOTE: This method is called from RecyclerViewDragDropManager /*package*/ @SuppressWarnings("unchecked") boolean canStartDrag(RecyclerView.ViewHolder holder, int position, int x, int y) { if (LOCAL_LOGV) { Log.v(TAG, "canStartDrag(holder = " + holder + ", position = " + position + ", x = " + x + ", y = " + y + ")"); } final DraggableItemAdapter draggableItemAdapter = WrapperAdapterUtils.findWrappedAdapter(this, DraggableItemAdapter.class, position); if (draggableItemAdapter == null) { return false; } return draggableItemAdapter.onCheckCanStartDrag(holder, position, x, y); } // NOTE: This method is called from RecyclerViewDragDropManager /*package*/ @SuppressWarnings("unchecked") boolean canDropItems(int draggingPosition, int dropPosition) { if (LOCAL_LOGV) { Log.v(TAG, "canDropItems(draggingPosition = " + draggingPosition + ", dropPosition = " + dropPosition + ")"); } return mDraggableItemAdapter.onCheckCanDrop(draggingPosition, dropPosition); } // NOTE: This method is called from RecyclerViewDragDropManager /*package*/ @SuppressWarnings("unchecked") ItemDraggableRange getItemDraggableRange(RecyclerView.ViewHolder holder, int position) { if (LOCAL_LOGV) { Log.v(TAG, "getItemDraggableRange(holder = " + holder + ", position = " + position + ")"); } final DraggableItemAdapter draggableItemAdapter = WrapperAdapterUtils.findWrappedAdapter(this, DraggableItemAdapter.class, position); if (draggableItemAdapter == null) { return null; } return draggableItemAdapter.onGetItemDraggableRange(holder, position); } // NOTE: This method is called from RecyclerViewDragDropManager /*package*/ void moveItem(int fromPosition, int toPosition, int layoutType) { if (LOCAL_LOGD) { Log.d(TAG, "onMoveItem(fromPosition = " + fromPosition + ", toPosition = " + toPosition + ")"); } if (DEBUG_BYPASS_MOVE_OPERATION_MODE) { mDraggableItemAdapter.onMoveItem(fromPosition, toPosition); return; } final int origFromPosition = convertToOriginalPosition( fromPosition, mDraggingItemInitialPosition, mDraggingItemCurrentPosition, mItemMoveMode); if (origFromPosition != mDraggingItemInitialPosition) { throw new IllegalStateException( "onMoveItem() - may be a bug or has duplicate IDs --- " + "mDraggingItemInitialPosition = " + mDraggingItemInitialPosition + ", " + "mDraggingItemCurrentPosition = " + mDraggingItemCurrentPosition + ", " + "origFromPosition = " + origFromPosition + ", " + "fromPosition = " + fromPosition + ", " + "toPosition = " + toPosition); } mDraggingItemCurrentPosition = toPosition; // NOTE: // Don't move items in wrapped adapter here. // notify to observers if (mItemMoveMode == RecyclerViewDragDropManager.ITEM_MOVE_MODE_DEFAULT && CustomRecyclerViewUtils.isLinearLayout(layoutType)) { notifyItemMoved(fromPosition, toPosition); } else { notifyDataSetChanged(); } } protected boolean isDragging() { return (mDraggingItemInfo != null); } /*package*/ int getDraggingItemInitialPosition() { return mDraggingItemInitialPosition; } /*package*/ int getDraggingItemCurrentPosition() { return mDraggingItemCurrentPosition; } private static void safeUpdateFlags(RecyclerView.ViewHolder holder, int flags) { if (!(holder instanceof DraggableItemViewHolder)) { return; } final DraggableItemViewHolder holder2 = (DraggableItemViewHolder) holder; final int curFlags = holder2.getDragStateFlags(); final int mask = ~Constants.STATE_FLAG_IS_UPDATED; // append UPDATED flag if ((curFlags == STATE_FLAG_INITIAL_VALUE) || (((curFlags ^ flags) & mask) != 0)) { flags |= Constants.STATE_FLAG_IS_UPDATED; } ((DraggableItemViewHolder) holder).setDragStateFlags(flags); } private int getOriginalPosition(int position) { int correctedPosition; if (isDragging()) { correctedPosition = convertToOriginalPosition( position, mDraggingItemInitialPosition, mDraggingItemCurrentPosition, mItemMoveMode); } else { correctedPosition = position; } return correctedPosition; } // // SwipeableItemAdapter implementations // @SuppressWarnings("unchecked") @Override public int onGetSwipeReactionType(VH holder, int position, int x, int y) { RecyclerView.Adapter adapter = getWrappedAdapter(); if (!(adapter instanceof SwipeableItemAdapter)) { return RecyclerViewSwipeManager.REACTION_CAN_NOT_SWIPE_ANY; } int correctedPosition = getOriginalPosition(position); SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter; return swipeableItemAdapter.onGetSwipeReactionType(holder, correctedPosition, x, y); } @SuppressWarnings("unchecked") @Override public void onSetSwipeBackground(VH holder, int position, int type) { RecyclerView.Adapter adapter = getWrappedAdapter(); if (!(adapter instanceof SwipeableItemAdapter)) { return; } int correctedPosition = getOriginalPosition(position); SwipeableItemAdapter<VH> swipeableItemAdapter = (SwipeableItemAdapter<VH>) adapter; swipeableItemAdapter.onSetSwipeBackground(holder, correctedPosition, type); } @SuppressWarnings("unchecked") @Override public SwipeResultAction onSwipeItem(VH holder, int position, int result) { RecyclerView.Adapter adapter = getWrappedAdapter(); if (!(adapter instanceof SwipeableItemAdapter)) { return new SwipeResultActionDefault(); } int correctedPosition = getOriginalPosition(position); return ((SwipeableItemAdapter) adapter).onSwipeItem(holder, correctedPosition, result); } }