package com.wm.remusic.widget;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
public class DragSortRecycler extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener {
final String TAG = "DragSortRecycler";
final boolean DEBUG = false;
OnItemMovedListener moveInterface;
@Nullable
OnDragStateChangedListener dragStateChangedListener;
Paint bgColor = new Paint();
private int dragHandleWidth = 0;
private int selectedDragItemPos = -1;
private int fingerAnchorY;
RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
debugLog("Scrolled: " + dx + " " + dy);
fingerAnchorY -= dy;
}
};
private int fingerY;
private int fingerOffsetInViewY;
private float autoScrollWindow = 0.1f;
private float autoScrollSpeed = 0.5f;
private BitmapDrawable floatingItem;
private Rect floatingItemStatingBounds;
private Rect floatingItemBounds;
private float floatingItemAlpha = 0.5f;
private int floatingItemBgColor = 0;
private int viewHandleId = -1;
private boolean isDragging;
private void debugLog(String log) {
if (DEBUG)
Log.d(TAG, log);
}
public RecyclerView.OnScrollListener getScrollListener() {
return scrollListener;
}
/*
* Set the item move interface
*/
public void setOnItemMovedListener(OnItemMovedListener swif) {
moveInterface = swif;
}
public void setViewHandleId(int id) {
viewHandleId = id;
}
public void setLeftDragArea(int w) {
dragHandleWidth = w;
}
public void setFloatingAlpha(float a) {
floatingItemAlpha = a;
}
public void setFloatingBgColor(int c) {
floatingItemBgColor = c;
}
/*
Set the window at top and bottom of list, must be between 0 and 0.5
For example 0.1 uses the top and bottom 10% of the lists for scrolling
*/
public void setAutoScrollWindow(float w) {
autoScrollWindow = w;
}
/*
Set the autoscroll speed, default is 0.5
*/
public void setAutoScrollSpeed(float speed) {
autoScrollSpeed = speed;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView rv, RecyclerView.State state) {
super.getItemOffsets(outRect, view, rv, state);
debugLog("getItemOffsets");
debugLog("View top = " + view.getTop());
if (selectedDragItemPos != -1) {
int itemPos = rv.getChildLayoutPosition(view);
debugLog("itemPos =" + itemPos);
if (!canDragOver(itemPos)) {
return;
}
//Movement of finger
float totalMovement = fingerY - fingerAnchorY;
if (itemPos == selectedDragItemPos) {
view.setVisibility(View.INVISIBLE);
} else {
//Make view visible incase invisible
view.setVisibility(View.VISIBLE);
//Find middle of the floatingItem
float floatMiddleY = floatingItemBounds.top + floatingItemBounds.height() / 2;
//Moving down the list
//These will auto-animate if the device continually sends touch motion events
// if (totalMovment>0)
{
if ((itemPos > selectedDragItemPos) && (view.getTop() < floatMiddleY)) {
float amountUp = (floatMiddleY - view.getTop()) / (float) view.getHeight();
// amountUp *= 0.5f;
if (amountUp > 1)
amountUp = 1;
outRect.top = -(int) (floatingItemBounds.height() * amountUp);
outRect.bottom = (int) (floatingItemBounds.height() * amountUp);
}
}//Moving up the list
// else if (totalMovment < 0)
{
if ((itemPos < selectedDragItemPos) && (view.getBottom() > floatMiddleY)) {
float amountDown = ((float) view.getBottom() - floatMiddleY) / (float) view.getHeight();
// amountDown *= 0.5f;
if (amountDown > 1)
amountDown = 1;
outRect.top = (int) (floatingItemBounds.height() * amountDown);
outRect.bottom = -(int) (floatingItemBounds.height() * amountDown);
}
}
}
} else {
outRect.top = 0;
outRect.bottom = 0;
//Make view visible incase invisible
view.setVisibility(View.VISIBLE);
}
}
/**
* Find the new position by scanning through the items on
* screen and finding the positional relationship.
* This *seems* to work, another method would be to use
* getItemOffsets, but I think that could miss items?..
*/
private int getNewPostion(RecyclerView rv) {
int itemsOnScreen = rv.getLayoutManager().getChildCount();
float floatMiddleY = floatingItemBounds.top + floatingItemBounds.height() / 2;
int above = 0;
int below = Integer.MAX_VALUE;
for (int n = 0; n < itemsOnScreen; n++) //Scan though items on screen, however they may not
{ // be in order!
View view = rv.getLayoutManager().getChildAt(n);
if (view.getVisibility() != View.VISIBLE)
continue;
int itemPos = rv.getChildLayoutPosition(view);
if (itemPos == selectedDragItemPos) //Don't check against itself!
continue;
float viewMiddleY = view.getTop() + view.getHeight() / 2;
if (floatMiddleY > viewMiddleY) //Is above this item
{
if (itemPos > above)
above = itemPos;
} else if (floatMiddleY <= viewMiddleY) //Is below this item
{
if (itemPos < below)
below = itemPos;
}
}
debugLog("above = " + above + " below = " + below);
if (below != Integer.MAX_VALUE) {
if (below < selectedDragItemPos) //Need to count itself
below++;
return below - 1;
} else {
if (above < selectedDragItemPos)
above++;
return above;
}
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
debugLog("onInterceptTouchEvent");
//if (e.getAction() == MotionEvent.ACTION_DOWN)
{
View itemView = rv.findChildViewUnder(e.getX(), e.getY());
if (itemView == null)
return false;
boolean dragging = false;
if ((dragHandleWidth > 0) && (e.getX() < dragHandleWidth)) {
dragging = true;
} else if (viewHandleId != -1) {
//Find the handle in the list item
View handleView = itemView.findViewById(viewHandleId);
if (handleView == null) {
Log.e(TAG, "The view ID " + viewHandleId + " was not found in the RecycleView item");
return false;
}
//View should be visible to drag
if (handleView.getVisibility() != View.VISIBLE) {
return false;
}
//We need to find the relative position of the handle to the parent view
//Then we can work out if the touch is within the handle
int[] parentItemPos = new int[2];
itemView.getLocationInWindow(parentItemPos);
int[] handlePos = new int[2];
handleView.getLocationInWindow(handlePos);
int xRel = handlePos[0] - parentItemPos[0];
int yRel = handlePos[1] - parentItemPos[1];
Rect touchBounds = new Rect(itemView.getLeft() + xRel, itemView.getTop() + yRel,
itemView.getLeft() + xRel + handleView.getWidth(),
itemView.getTop() + yRel + handleView.getHeight()
);
if (touchBounds.contains((int) e.getX(), (int) e.getY()))
dragging = true;
debugLog("parentItemPos = " + parentItemPos[0] + " " + parentItemPos[1]);
debugLog("handlePos = " + handlePos[0] + " " + handlePos[1]);
}
if (dragging) {
debugLog("Started Drag");
setIsDragging(true);
floatingItem = createFloatingBitmap(itemView);
fingerAnchorY = (int) e.getY();
fingerOffsetInViewY = fingerAnchorY - itemView.getTop();
fingerY = fingerAnchorY;
selectedDragItemPos = rv.getChildLayoutPosition(itemView);
debugLog("selectedDragItemPos = " + selectedDragItemPos);
return true;
}
}
return false;
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean b) {
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
debugLog("onTouchEvent");
if ((e.getAction() == MotionEvent.ACTION_UP) ||
(e.getAction() == MotionEvent.ACTION_CANCEL)) {
if ((e.getAction() == MotionEvent.ACTION_UP) && selectedDragItemPos != -1) {
int newPos = getNewPostion(rv);
if (moveInterface != null)
moveInterface.onItemMoved(selectedDragItemPos, newPos);
}
setIsDragging(false);
selectedDragItemPos = -1;
floatingItem = null;
rv.invalidateItemDecorations();
return;
}
fingerY = (int) e.getY();
if (floatingItem != null) {
floatingItemBounds.top = fingerY - fingerOffsetInViewY;
if (floatingItemBounds.top < -floatingItemStatingBounds.height() / 2) //Allow half the view out the top
floatingItemBounds.top = -floatingItemStatingBounds.height() / 2;
floatingItemBounds.bottom = floatingItemBounds.top + floatingItemStatingBounds.height();
floatingItem.setBounds(floatingItemBounds);
}
//Do auto scrolling at end of list
float scrollAmount = 0;
if (fingerY > (rv.getHeight() * (1 - autoScrollWindow))) {
scrollAmount = (fingerY - (rv.getHeight() * (1 - autoScrollWindow)));
} else if (fingerY < (rv.getHeight() * autoScrollWindow)) {
scrollAmount = (fingerY - (rv.getHeight() * autoScrollWindow));
}
debugLog("Scroll: " + scrollAmount);
scrollAmount *= autoScrollSpeed;
rv.scrollBy(0, (int) scrollAmount);
rv.invalidateItemDecorations();// Redraw
}
private void setIsDragging(final boolean dragging) {
if (dragging != isDragging) {
isDragging = dragging;
if (dragStateChangedListener != null) {
if (isDragging) {
dragStateChangedListener.onDragStart();
} else {
dragStateChangedListener.onDragStop();
}
}
}
}
public void setOnDragStateChangedListener(final OnDragStateChangedListener dragStateChangedListener) {
this.dragStateChangedListener = dragStateChangedListener;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (floatingItem != null) {
floatingItem.setAlpha((int) (255 * floatingItemAlpha));
bgColor.setColor(floatingItemBgColor);
c.drawRect(floatingItemBounds, bgColor);
floatingItem.draw(c);
}
}
/**
* @param position
* @return True if we can drag the item over this position, False if not.
*/
protected boolean canDragOver(int position) {
return true;
}
private BitmapDrawable createFloatingBitmap(View v) {
floatingItemStatingBounds = new Rect(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
floatingItemBounds = new Rect(floatingItemStatingBounds);
Bitmap bitmap = Bitmap.createBitmap(floatingItemStatingBounds.width(),
floatingItemStatingBounds.height(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
BitmapDrawable retDrawable = new BitmapDrawable(v.getResources(), bitmap);
retDrawable.setBounds(floatingItemBounds);
return retDrawable;
}
public interface OnItemMovedListener {
void onItemMoved(int from, int to);
}
public interface OnDragStateChangedListener {
void onDragStart();
void onDragStop();
}
}