Skip to content

Commit

Permalink
Added feature to be able to drag and reorder columns on the a board
Browse files Browse the repository at this point in the history
  • Loading branch information
woxblom committed Apr 6, 2018
1 parent 4748a34 commit 895cf0a
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 55 deletions.
23 changes: 22 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ YouTube demo video<br>
}

dependencies {
compile 'com.github.woxthebox:draglistview:1.5.4'
compile 'com.github.woxthebox:draglistview:1.6.0'
}

Add this to proguard rules, otherwise animations won't work correctly
Expand Down Expand Up @@ -186,6 +186,21 @@ List and Grid layouts are used as example in the sample project.
public void onFocusedColumnChanged(int oldColumn, int newColumn) {
Toast.makeText(getContext(), "Focused column changed from " + oldColumn + " to " + newColumn, Toast.LENGTH_SHORT).show();
}

@Override
public void onColumnDragStarted(int position) {
Toast.makeText(getContext(), "Column drag started from " + position, Toast.LENGTH_SHORT).show();
}

@Override
public void onColumnDragChangedPosition(int oldPosition, int newPosition) {
Toast.makeText(getContext(), "Column changed from " + oldPosition + " to " + newPosition, Toast.LENGTH_SHORT).show();
}

@Override
public void onColumnDragEnded(int position) {
Toast.makeText(getContext(), "Column drag ended at " + position, Toast.LENGTH_SHORT).show();
}
});
mBoardView.setBoardCallback(new BoardView.BoardCallback() {
@Override
Expand All @@ -203,6 +218,12 @@ List and Grid layouts are used as example in the sample project.
...
mBoardView.addColumnList(listAdapter, header, false);

To enable dragging and reordering of columns you need to provide a column drag view when adding the column. It is the view that will
start the column drag process when long pressed on. You can also implement a custom column drag item to control the visuals and animations.
Check out the sample app to see how it is done. If no custom drag item is used a screenshot of the column will be used instead.

mBoardView.setCustomColumnDragItem(new MyColumnDragItem(getActivity(), R.layout.column_drag_layout));
mBoardView.addColumnList(listAdapter, header, columnDragView, false);

For your adapter, extend DragItemAdapter and call setItemList() with a List<T> type. setItemList() can be called anytime later to change the list.

Expand Down
4 changes: 2 additions & 2 deletions library/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
VERSION_NAME=1.5.4
VERSION_CODE=46
VERSION_NAME=1.6.0
VERSION_CODE=47
GROUP=com.github.woxthebox

POM_DESCRIPTION=Drag and drop to re-order items in a list, grid or board.
Expand Down
209 changes: 168 additions & 41 deletions library/src/main/java/com/woxthebox/draglistview/BoardView.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package com.woxthebox.draglistview;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
Expand All @@ -26,7 +28,6 @@
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.GestureDetector;
import android.view.LayoutInflater;
import android.view.MotionEvent;
Expand Down Expand Up @@ -59,6 +60,12 @@ public interface BoardListener {
void onItemChangedColumn(int oldColumn, int newColumn);

void onFocusedColumnChanged(int oldColumn, int newColumn);

void onColumnDragStarted(int position);

void onColumnDragChangedPosition(int oldPosition, int newPosition);

void onColumnDragEnded(int position);
}

public static abstract class BoardListenerAdapter implements BoardListener {
Expand All @@ -81,6 +88,18 @@ public void onItemChangedColumn(int oldColumn, int newColumn) {
@Override
public void onFocusedColumnChanged(int oldColumn, int newColumn) {
}

@Override
public void onColumnDragStarted(int position) {
}

@Override
public void onColumnDragChangedPosition(int oldPosition, int newPosition) {
}

@Override
public void onColumnDragEnded(int position) {
}
}

public enum ColumnSnapPosition {
Expand All @@ -94,9 +113,10 @@ public enum ColumnSnapPosition {
private FrameLayout mRootLayout;
private LinearLayout mColumnLayout;
private ArrayList<DragItemRecyclerView> mLists = new ArrayList<>();
private SparseArray<View> mHeaders = new SparseArray<>();
private ArrayList<View> mHeaders = new ArrayList<>();
private DragItemRecyclerView mCurrentRecyclerView;
private DragItem mDragItem;
private DragItem mDragColumn;
private BoardListener mBoardListener;
private BoardCallback mBoardCallback;
private boolean mSnapToColumnWhenScrolling = true;
Expand All @@ -106,6 +126,7 @@ public enum ColumnSnapPosition {
private int mCurrentColumn;
private float mTouchX;
private float mTouchY;
private float mDragColumnStartScrollX;
private int mColumnWidth;
private int mDragStartColumn;
private int mDragStartRow;
Expand Down Expand Up @@ -144,6 +165,8 @@ protected void onFinishInflate() {
mAutoScroller.setAutoScrollMode(snapToColumnWhenDragging() ? AutoScroller.AutoScrollMode.COLUMN : AutoScroller.AutoScrollMode
.POSITION);
mDragItem = new DragItem(getContext());
mDragColumn = new DragItem(getContext());
mDragColumn.setSnapToTouch(false);

mRootLayout = new FrameLayout(getContext());
mRootLayout.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
Expand Down Expand Up @@ -219,7 +242,11 @@ private boolean handleTouchEvent(MotionEvent event) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mAutoScroller.stopAutoScroll();
mCurrentRecyclerView.onDragEnded();
if (isDraggingColumn()) {
endDragColumn();
} else {
mCurrentRecyclerView.onDragEnded();
}
if (snapToColumnWhenScrolling()) {
scrollToColumn(getColumnOfList(mCurrentRecyclerView), true);
}
Expand Down Expand Up @@ -261,8 +288,12 @@ public void computeScroll() {

// If auto scrolling at the same time as the scroller is running,
// then update the drag item position to prevent stuttering item
if (mAutoScroller.isAutoScrolling()) {
mDragItem.setPosition(getListTouchX(mCurrentRecyclerView), getListTouchY(mCurrentRecyclerView));
if (mAutoScroller.isAutoScrolling() && isDragging()) {
if (isDraggingColumn()) {
mDragColumn.setPosition(mTouchX + getScrollX() - mDragColumnStartScrollX, mTouchY);
} else {
mDragItem.setPosition(getRelativeViewTouchX((View) mCurrentRecyclerView.getParent()), getRelativeViewTouchY(mCurrentRecyclerView));
}
}

ViewCompat.postInvalidateOnAnimation(this);
Expand Down Expand Up @@ -295,33 +326,43 @@ public void onAutoScrollColumnBy(int columns) {
}

private void updateScrollPosition() {
// Updated event to scrollview coordinates
DragItemRecyclerView currentList = getCurrentRecyclerView(mTouchX + getScrollX());
if (mCurrentRecyclerView != currentList) {
int oldColumn = getColumnOfList(mCurrentRecyclerView);
int newColumn = getColumnOfList(currentList);
long itemId = mCurrentRecyclerView.getDragItemId();

// Check if it is ok to drop the item in the new column first
int newPosition = currentList.getDragPositionForY(getListTouchY(currentList));
if (mBoardCallback == null || mBoardCallback.canDropItemAtPosition(mDragStartColumn, mDragStartRow, newColumn, newPosition)) {
Object item = mCurrentRecyclerView.removeDragItemAndEnd();
if (item != null) {
mCurrentRecyclerView = currentList;
mCurrentRecyclerView.addDragItemAndStart(getListTouchY(mCurrentRecyclerView), item, itemId);
mDragItem.setOffset(((View) mCurrentRecyclerView.getParent()).getLeft(), mCurrentRecyclerView.getTop());

if (mBoardListener != null) {
mBoardListener.onItemChangedColumn(oldColumn, newColumn);
if (isDraggingColumn()) {
DragItemRecyclerView currentList = getCurrentRecyclerView(mTouchX + getScrollX());
if (mCurrentRecyclerView != currentList) {
moveColumn(getColumnOfList(mCurrentRecyclerView), getColumnOfList(currentList));
}
// Need to subtract with scrollX at the beginning of the column drag because of how drag item position is calculated
mDragColumn.setPosition(mTouchX + getScrollX() - mDragColumnStartScrollX, mTouchY);
} else {
// Updated event to scrollview coordinates
DragItemRecyclerView currentList = getCurrentRecyclerView(mTouchX + getScrollX());
if (mCurrentRecyclerView != currentList) {
int oldColumn = getColumnOfList(mCurrentRecyclerView);
int newColumn = getColumnOfList(currentList);
long itemId = mCurrentRecyclerView.getDragItemId();

// Check if it is ok to drop the item in the new column first
int newPosition = currentList.getDragPositionForY(getRelativeViewTouchY(currentList));
if (mBoardCallback == null || mBoardCallback.canDropItemAtPosition(mDragStartColumn, mDragStartRow, newColumn, newPosition)) {
Object item = mCurrentRecyclerView.removeDragItemAndEnd();
if (item != null) {
mCurrentRecyclerView = currentList;
mCurrentRecyclerView.addDragItemAndStart(getRelativeViewTouchY(mCurrentRecyclerView), item, itemId);
mDragItem.setOffset(((View) mCurrentRecyclerView.getParent()).getLeft(), mCurrentRecyclerView.getTop());

if (mBoardListener != null) {
mBoardListener.onItemChangedColumn(oldColumn, newColumn);
}
}
}
}
}

// Updated event to list coordinates
mCurrentRecyclerView.onDragging(getListTouchX(mCurrentRecyclerView), getListTouchY(mCurrentRecyclerView));
// Updated event to list coordinates
mCurrentRecyclerView.onDragging(getRelativeViewTouchX((View) mCurrentRecyclerView.getParent()), getRelativeViewTouchY(mCurrentRecyclerView));
}

float scrollEdge = getResources().getDisplayMetrics().widthPixels * 0.14f;
boolean isPortrait = getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
float scrollEdge = getResources().getDisplayMetrics().widthPixels * (isPortrait ? 0.18f : 0.14f);
if (mTouchX > getWidth() - scrollEdge && getScrollX() < mColumnLayout.getWidth()) {
mAutoScroller.startAutoScroll(AutoScroller.ScrollDirection.LEFT);
} else if (mTouchX < scrollEdge && getScrollX() > 0) {
Expand All @@ -332,12 +373,12 @@ private void updateScrollPosition() {
invalidate();
}

private float getListTouchX(DragItemRecyclerView list) {
return mTouchX + getScrollX() - ((View) list.getParent()).getLeft();
private float getRelativeViewTouchX(View view) {
return mTouchX + getScrollX() - view.getLeft();
}

private float getListTouchY(DragItemRecyclerView list) {
return mTouchY - list.getTop();
private float getRelativeViewTouchY(View view) {
return mTouchY - view.getTop();
}

private DragItemRecyclerView getCurrentRecyclerView(float x) {
Expand Down Expand Up @@ -412,8 +453,12 @@ private boolean snapToColumnWhenDragging() {
return mSnapToColumnWhenDragging && (isPortrait || mSnapToColumnInLandscape);
}

private boolean isDraggingColumn() {
return mCurrentRecyclerView != null && mDragColumn.isDragging();
}

private boolean isDragging() {
return mCurrentRecyclerView != null && mCurrentRecyclerView.isDragging();
return mCurrentRecyclerView != null && (mCurrentRecyclerView.isDragging() || isDraggingColumn());
}

public RecyclerView getRecyclerView(int column) {
Expand Down Expand Up @@ -654,20 +699,102 @@ public void setBoardCallback(BoardCallback callback) {
mBoardCallback = callback;
}

/**
* Set a custom drag item to control the visuals and animations when dragging a list item.
*/
public void setCustomDragItem(DragItem dragItem) {
DragItem newDragItem;
if (dragItem != null) {
newDragItem = dragItem;
} else {
newDragItem = new DragItem(getContext());
}

DragItem newDragItem = dragItem != null ? dragItem : new DragItem(getContext());
newDragItem.setSnapToTouch(mDragItem.isSnapToTouch());
mDragItem = newDragItem;
mRootLayout.removeViewAt(1);
mRootLayout.addView(mDragItem.getDragItemView());
}

/**
* Set a custom drag item to control the visuals and animations when dragging a column.
*/
public void setCustomColumnDragItem(DragItem dragItem) {
mDragColumn = dragItem != null ? dragItem : new DragItem(getContext());
}

private void startDragColumn(DragItemRecyclerView recyclerView, float posX, float posY) {
mDragColumnStartScrollX = getScrollX();
mCurrentRecyclerView = recyclerView;

View columnView = mColumnLayout.getChildAt(getColumnOfList(recyclerView));
mDragColumn.startDrag(columnView, posX, posY);
mRootLayout.addView(mDragColumn.getDragItemView());
columnView.setAlpha(0);

if (mBoardListener != null) {
mBoardListener.onColumnDragStarted(getColumnOfList(mCurrentRecyclerView));
}
}

private void endDragColumn() {
mDragColumn.endDrag(mDragColumn.getRealDragView(), new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mDragColumn.getRealDragView().setAlpha(1);
mDragColumn.hide();
mRootLayout.removeView(mDragColumn.getDragItemView());

if (mBoardListener != null) {
mBoardListener.onColumnDragEnded(getColumnOfList(mCurrentRecyclerView));
}
}
});
}

private void moveColumn(final int fromIndex, final int toIndex) {
DragItemRecyclerView list = mLists.remove(fromIndex);
mLists.add(toIndex, list);

View header = mHeaders.remove(fromIndex);
mHeaders.add(toIndex, header);

final View column1 = mColumnLayout.getChildAt(fromIndex);
final View column2 = mColumnLayout.getChildAt(toIndex);
mColumnLayout.removeViewAt(fromIndex);
mColumnLayout.addView(column1, toIndex);

mColumnLayout.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
mColumnLayout.removeOnLayoutChangeListener(this);
column2.setTranslationX(column2.getTranslationX() + column1.getLeft() - column2.getLeft());
column2.animate().translationX(0).setDuration(350).start();
}
});

if (mBoardListener != null) {
mBoardListener.onColumnDragChangedPosition(fromIndex, toIndex);
}
}

/**
* Adds a column to the board.
*
* @param adapter Adapter with the items for the column.
* @param header Header view that will be positioned above the column.
* @param columnDragView View that will act as handle to drag and drop columns.
* @param hasFixedItemSize If the items will have a fixed or dynamic size.
* @return The created DragItemRecyclerView.
*/
public DragItemRecyclerView addColumnList(final DragItemAdapter adapter, final View header, View columnDragView, boolean hasFixedItemSize) {
final DragItemRecyclerView recyclerView = addColumnList(adapter, header, hasFixedItemSize);
if (columnDragView != null) {
columnDragView.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
startDragColumn(recyclerView, mTouchX, mTouchY);
return true;
}
});
}
return recyclerView;
}

public DragItemRecyclerView addColumnList(final DragItemAdapter adapter, final View header, boolean hasFixedItemSize) {
final DragItemRecyclerView recyclerView = (DragItemRecyclerView) LayoutInflater.from(getContext()).inflate(R.layout.drag_item_recycler_view, this, false);
recyclerView.setId(getColumnCount());
Expand Down Expand Up @@ -731,7 +858,7 @@ public boolean canDropItemAtPosition(int dropPosition) {
adapter.setDragStartedListener(new DragItemAdapter.DragStartCallback() {
@Override
public boolean startDrag(View itemView, long itemId) {
return recyclerView.startDrag(itemView, itemId, getListTouchX(recyclerView), getListTouchY(recyclerView));
return recyclerView.startDrag(itemView, itemId, getRelativeViewTouchX((View) recyclerView.getParent()), getRelativeViewTouchY(recyclerView));
}

@Override
Expand All @@ -745,7 +872,7 @@ public boolean isDragging() {
layout.setLayoutParams(new LayoutParams(mColumnWidth, LayoutParams.MATCH_PARENT));
if (header != null) {
layout.addView(header);
mHeaders.put(mLists.size(), header);
mHeaders.add(header);
}
layout.addView(recyclerView);

Expand Down
Loading

0 comments on commit 895cf0a

Please sign in to comment.