First snapshot (beta 0.9)

- playlists working (not all features added yet)
- minor fixes and improvements
This commit is contained in:
Karim Abou Zeid 2015-03-18 15:32:35 +01:00
commit 11ee05ce64
30 changed files with 1055 additions and 126 deletions

View file

@ -0,0 +1,469 @@
/*
* DragSortRecycler
*
* Added drag and drop functionality to your RecyclerView
*
*
* Copyright 2014 Emile Belanger.
*
* 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.kabouzeid.gramophone.misc;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.support.annotation.Nullable;
import java.lang.reflect.Modifier;
public class DragSortRecycler extends RecyclerView.ItemDecoration implements RecyclerView.OnItemTouchListener {
final String TAG = "DragSortRecycler";
final boolean DEBUG = false;
private int dragHandleWidth = 0;
private int selectedDragItemPos = -1;
private int fingerAnchorY;
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;
OnItemMovedListener moveInterface;
private boolean isDragging;
@Nullable
OnDragStateChangedListener dragStateChangedListener;
public interface OnItemMovedListener
{
public void onItemMoved(int from, int to);
}
public interface OnDragStateChangedListener {
public void onDragStart();
public void onDragStop();
}
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.getChildPosition(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.getChildPosition(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.getChildPosition(itemView);
debugLog("selectedDragItemPos = " + selectedDragItemPos);
return true;
}
}
return false;
}
@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;
}
Paint bgColor = new Paint();
@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);
}
}
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;
}
};
/**
*
*
* @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;
}
}