Skip to content

Instantly share code, notes, and snippets.

@iamutkarshtiwari
Created December 21, 2018 06:51
Show Gist options
  • Save iamutkarshtiwari/4b130d0d26a77896b8dfc8cadcee3fc2 to your computer and use it in GitHub Desktop.
Save iamutkarshtiwari/4b130d0d26a77896b8dfc8cadcee3fc2 to your computer and use it in GitHub Desktop.
DynamicGridView.java
package com.project.ui.dynamicgridview;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.GridView;
import android.widget.ListAdapter;
import com.project.R;
import com.project.util.DeviceUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Stack;
/**
* Author: alex askerov
* Date: 9/6/13
* Time: 12:31 PM
*/
public class DynamicGridView extends GridView {
private static final int INVALID_ID = -1;
private static final int MOVE_DURATION = 300;
private static final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 8;
private BitmapDrawable hoverCell;
private Rect hoverCellCurrentBounds;
private Rect hoverCellOriginalBounds;
private int totalOffsetY = 0;
private int totalOffsetX = 0;
private int downX = -1;
private int downY = -1;
private int lastEventY = -1;
private int lastEventX = -1;
//used to distinguish straight line and diagonal switching
private int overlapIfSwitchStraightLine;
private List<Long> idList = new ArrayList<>();
private long mobileItemId = INVALID_ID;
private boolean cellIsMobile = false;
private int activePointerId = INVALID_ID;
private boolean isMobileScrolling;
private int smoothScrollAmountAtEdge = 0;
private boolean isWaitingForScrollFinish = false;
private int scrollState = OnScrollListener.SCROLL_STATE_IDLE;
private boolean isEditMode = false;
private boolean hoverAnimation;
private boolean reorderAnimation;
private boolean isEditModeEnabled = true;
private OnScrollListener userScrollListener;
private OnDropListener dropListener;
private OnDragListener dragListener;
private OnEditModeChangeListener editModeChangeListener;
private OnSelectedItemDraggableListener selectedItemDraggableListener;
private OnItemClickListener userItemClickListener;
private OnItemClickListener localItemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (!isEditMode() && isEnabled() && userItemClickListener != null) {
userItemClickListener.onItemClick(parent, view, position, id);
}
}
};
private boolean undoSupportEnabled;
private Stack<DynamicGridModification> modificationStack;
private DynamicGridModification currentModification;
private OnSelectedItemBitmapCreationListener selectedItemBitmapCreationListener;
private View mobileView;
public DynamicGridView(Context context) {
super(context);
init(context);
}
public DynamicGridView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public DynamicGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
@Override
public void setOnScrollListener(OnScrollListener scrollListener) {
this.userScrollListener = scrollListener;
}
public void setOnDropListener(OnDropListener dropListener) {
this.dropListener = dropListener;
}
public void setOnDragListener(OnDragListener dragListener) {
this.dragListener = dragListener;
}
public void setOnSelectedItemDraggableListener(OnSelectedItemDraggableListener listener){
this.selectedItemDraggableListener = listener;
}
/**
* Start edit mode without starting drag;
*/
public void startEditMode() {
startEditMode(-1);
}
/**
* Start edit mode with position. Useful for start edit mode in
* {@link android.widget.AdapterView.OnItemClickListener}
* or {@link android.widget.AdapterView.OnItemLongClickListener}
*/
public void startEditMode(int position) {
if (!isEditModeEnabled)
return;
requestDisallowInterceptTouchEvent(true);
if (position != -1) {
startDragAtPosition(position);
}
isEditMode = true;
if (editModeChangeListener != null)
editModeChangeListener.onEditModeChanged(true);
}
public void stopEditMode() {
isEditMode = false;
requestDisallowInterceptTouchEvent(false);
if (editModeChangeListener != null)
editModeChangeListener.onEditModeChanged(false);
}
public boolean isEditModeEnabled() {
return isEditModeEnabled;
}
public void setEditModeEnabled(boolean enabled) {
this.isEditModeEnabled = enabled;
}
public void setOnEditModeChangeListener(OnEditModeChangeListener editModeChangeListener) {
this.editModeChangeListener = editModeChangeListener;
}
public boolean isEditMode() {
return isEditMode;
}
@Override
public void setOnItemClickListener(OnItemClickListener listener) {
this.userItemClickListener = listener;
super.setOnItemClickListener(localItemClickListener);
}
public boolean isUndoSupportEnabled() {
return undoSupportEnabled;
}
public void setUndoSupportEnabled(boolean undoSupportEnabled) {
if (this.undoSupportEnabled != undoSupportEnabled) {
if (undoSupportEnabled) {
this.modificationStack = new Stack<>();
} else {
this.modificationStack = null;
}
}
this.undoSupportEnabled = undoSupportEnabled;
}
public void undoLastModification() {
if (undoSupportEnabled) {
if (modificationStack != null && !modificationStack.isEmpty()) {
DynamicGridModification modification = modificationStack.pop();
undoModification(modification);
}
}
}
public void undoAllModifications() {
if (undoSupportEnabled) {
if (modificationStack != null && !modificationStack.isEmpty()) {
while (!modificationStack.isEmpty()) {
DynamicGridModification modification = modificationStack.pop();
undoModification(modification);
}
}
}
}
public boolean hasModificationHistory() {
if (undoSupportEnabled) {
if (modificationStack != null && !modificationStack.isEmpty()) {
return true;
}
}
return false;
}
public void clearModificationHistory() {
modificationStack.clear();
}
public void setOnSelectedItemBitmapCreationListener(OnSelectedItemBitmapCreationListener selectedItemBitmapCreationListener) {
this.selectedItemBitmapCreationListener = selectedItemBitmapCreationListener;
}
private void undoModification(DynamicGridModification modification) {
for (Pair<Integer, Integer> transition : modification.getTransitions()) {
reorderElements(transition.second, transition.first);
}
}
public void init(Context context) {
super.setOnScrollListener(scrollListener);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
smoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE * metrics.density + 0.5f);
overlapIfSwitchStraightLine = getResources().getDimensionPixelSize(R.dimen.overlap_if_switch_straight_line);
}
private void reorderElements(int originalPosition, int targetPosition) {
if (dragListener != null)
dragListener.onDragPositionsChanged(originalPosition, targetPosition);
getAdapterInterface().reorderItems(originalPosition, targetPosition);
}
private int getColumnCount() {
return getAdapterInterface().getColumnCount();
}
private DynamicGridAdapterInterface getAdapterInterface() {
return ((DynamicGridAdapterInterface) getAdapter());
}
/**
* Creates the hover cell with the appropriate bitmap and of appropriate
* size. The hover cell's BitmapDrawable is drawn on top of the bitmap every
* single time an invalidate call is made.
*/
private BitmapDrawable getAndAddHoverView(View v) {
int w = v.getWidth();
int h = v.getHeight();
int top = v.getTop();
int left = v.getLeft();
//Bitmap b = getBitmapFromView(v);
Bitmap b = createShadowBitmap(v);
BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
hoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
hoverCellCurrentBounds = new Rect(hoverCellOriginalBounds);
drawable.setBounds(hoverCellCurrentBounds);
return drawable;
}
/**
* Returns a bitmap showing a screenshot of the view passed in.
*/
private Bitmap getBitmapFromView(View v) {
Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
return bitmap;
}
/**
* Returns a bitmap showing a screenshot with shadow of the view passed in.
*/
private Bitmap createShadowBitmap(View v) {
Bitmap bitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
v.draw(canvas);
BlurMaskFilter blurFilter = new BlurMaskFilter(7, BlurMaskFilter.Blur.NORMAL);
Paint shadowPaint = new Paint();
shadowPaint.setMaskFilter(blurFilter);
int[] offsetXY = new int[2];
Bitmap shadowImage = bitmap.extractAlpha(shadowPaint, offsetXY);
/* Need to convert shadowImage from 8-bit to ARGB here. */
Bitmap shadowImage32 = shadowImage.copy(Bitmap.Config.ARGB_8888, true);
// http://stackoverflow.com/questions/21837671/new-warning-in-android-4-4
// Fix the non pre-multiplied exception for API 19+.
// ギャラリーの画像はアルファチャンネル(プリマルチプライド)を設定されていない場合がある。
// そのような画像をv19以降でキャンバスに設定するとエラーが発生する
// そのため v19以降では明示的にsetPremultipliedを呼んであげる必要がある
if ( android.os.Build.VERSION.SDK_INT >= 19 && !shadowImage32.isPremultiplied() )
{
shadowImage32.setPremultiplied( true );
}
Canvas c = new Canvas(shadowImage32);
c.drawBitmap(bitmap, -offsetXY[0]-3, -offsetXY[1]-3, null);
return shadowImage32;
}
private void updateNeighborViewsForId(long itemId) {
idList.clear();
int draggedPos = getPositionForID(itemId);
for (int pos = getFirstVisiblePosition(); pos <= getLastVisiblePosition(); pos++) {
if (draggedPos != pos && getAdapterInterface().canReorder(pos)) {
idList.add(getId(pos));
}
}
}
/**
* Retrieves the position in the grid corresponding to <code>itemId</code>
*/
public int getPositionForID(long itemId) {
View v = getViewForId(itemId);
if (v == null) {
return -1;
} else {
return getPositionForView(v);
}
}
public View getViewForId(long itemId) {
int firstVisiblePosition = getFirstVisiblePosition();
ListAdapter adapter = getAdapter();
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
int position = firstVisiblePosition + i;
long id = adapter.getItemId(position);
if (id == itemId) {
return v;
}
}
return null;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
downX = (int) event.getX();
downY = (int) event.getY();
activePointerId = event.getPointerId(0);
if (isEditMode && isEnabled()) {
layoutChildren();
int position = pointToPosition(downX, downY);
startDragAtPosition(position);
} else if (!isEnabled()) {
return false;
}
break;
case MotionEvent.ACTION_MOVE:
if (activePointerId == INVALID_ID) {
break;
}
int pointerIndex = event.findPointerIndex(activePointerId);
lastEventY = (int) event.getY(pointerIndex);
lastEventX = (int) event.getX(pointerIndex);
int deltaY = lastEventY - downY;
int deltaX = lastEventX - downX;
if (cellIsMobile) {
hoverCellCurrentBounds.offsetTo(hoverCellOriginalBounds.left + deltaX + totalOffsetX,
hoverCellOriginalBounds.top + deltaY + totalOffsetY);
hoverCell.setBounds(hoverCellCurrentBounds);
invalidate();
handleCellSwitch();
isMobileScrolling = false;
handleMobileCellScroll();
return false;
}
break;
case MotionEvent.ACTION_UP:
touchEventsEnded();
if (undoSupportEnabled) {
if (currentModification != null && !currentModification.getTransitions().isEmpty()) {
modificationStack.push(currentModification);
currentModification = new DynamicGridModification();
}
}
if (hoverCell != null) {
if (dropListener != null) {
dropListener.onActionDrop();
}
}
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
if (hoverCell != null) {
if (dropListener != null) {
dropListener.onActionDrop();
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
/* If a multitouch event took place and the original touch dictating
* the movement of the hover cell has ended, then the dragging event
* ends and the hover cell is animated to its corresponding position
* in the listview. */
pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == activePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
private void startDragAtPosition(int position) {
totalOffsetY = 0;
totalOffsetX = 0;
int itemNum = position - getFirstVisiblePosition();
View selectedView = getChildAt(itemNum);
if (selectedView != null) {
mobileItemId = getAdapter().getItemId(position);
if (selectedItemBitmapCreationListener != null)
selectedItemBitmapCreationListener.onPreSelectedItemBitmapCreation(selectedView, position, mobileItemId);
hoverCell = getAndAddHoverView(selectedView);
if (selectedItemBitmapCreationListener != null)
selectedItemBitmapCreationListener.onPostSelectedItemBitmapCreation(selectedView, position, mobileItemId);
selectedView.setVisibility(View.INVISIBLE);
cellIsMobile = true;
updateNeighborViewsForId(mobileItemId);
if (dragListener != null) {
dragListener.onDragStarted(position);
}
}
}
private void handleMobileCellScroll() {
isMobileScrolling = handleMobileCellVerticalScroll(hoverCellCurrentBounds);
isMobileScrolling = handleMobileCellHorizontalScroll(hoverCellCurrentBounds);
}
public boolean handleMobileCellVerticalScroll(Rect r) {
int offset = computeVerticalScrollOffset();
int height = getHeight();
int extent = computeVerticalScrollExtent();
int range = computeVerticalScrollRange();
int hoverViewTop = r.top;
int hoverHeight = r.height();
if (hoverViewTop <= 0 && offset > 0) {
smoothScrollBy(-smoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
smoothScrollBy(smoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
public boolean handleMobileCellHorizontalScroll(Rect r) {
int offset = computeHorizontalScrollOffset();
int width = getWidth();
int extent = computeHorizontalScrollExtent();
int range = computeHorizontalScrollRange();
int hoverViewLeft = r.left;
int hoverWidth = r.width();
if (hoverViewLeft <= 0 && offset > 0) {
smoothScrollBy(-smoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewLeft + hoverWidth >= width && (offset + extent) < range) {
smoothScrollBy(smoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
}
private void touchEventsEnded() {
final View mobileView = getViewForId(mobileItemId);
if (mobileView != null && (cellIsMobile || isWaitingForScrollFinish)) {
cellIsMobile = false;
isWaitingForScrollFinish = false;
isMobileScrolling = false;
activePointerId = INVALID_ID;
// If the autoscroller has not completed scrolling, we need to wait for it to
// finish in order to determine the final location of where the hover cell
// should be animated to.
if (scrollState != OnScrollListener.SCROLL_STATE_IDLE) {
isWaitingForScrollFinish = true;
return;
}
hoverCellCurrentBounds.offsetTo(mobileView.getLeft(), mobileView.getTop());
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
animateBounds(mobileView);
} else {
hoverCell.setBounds(hoverCellCurrentBounds);
invalidate();
reset(mobileView);
}
} else {
touchEventsCancelled();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void animateBounds(final View mobileView) {
TypeEvaluator<Rect> boundEvaluator = new TypeEvaluator<Rect>() {
public Rect evaluate(float fraction, Rect startValue, Rect endValue) {
return new Rect(interpolate(startValue.left, endValue.left, fraction),
interpolate(startValue.top, endValue.top, fraction),
interpolate(startValue.right, endValue.right, fraction),
interpolate(startValue.bottom, endValue.bottom, fraction));
}
public int interpolate(int start, int end, float fraction) {
return (int) (start + fraction * (end - start));
}
};
ObjectAnimator hoverViewAnimator = ObjectAnimator.ofObject(hoverCell, "bounds",
boundEvaluator, hoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(valueAnimator -> invalidate());
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
hoverAnimation = true;
updateEnableState();
}
@Override
public void onAnimationEnd(Animator animation) {
hoverAnimation = false;
updateEnableState();
reset(mobileView);
}
});
hoverViewAnimator.start();
}
private void reset(View mobileView) {
idList.clear();
mobileItemId = INVALID_ID;
mobileView.setVisibility(View.VISIBLE);
hoverCell = null;
//ugly fix for unclear disappearing items after reorder
for (int i = 0; i < getLastVisiblePosition() - getFirstVisiblePosition(); i++) {
View child = getChildAt(i);
if (child != null) {
child.setVisibility(View.VISIBLE);
}
}
invalidate();
}
private void updateEnableState() {
setEnabled(!hoverAnimation && !reorderAnimation);
}
private void touchEventsCancelled() {
View mobileView = getViewForId(mobileItemId);
if (cellIsMobile) {
reset(mobileView);
}
cellIsMobile = false;
isMobileScrolling = false;
activePointerId = INVALID_ID;
}
private void handleCellSwitch() {
final int deltaY = lastEventY - downY;
final int deltaX = lastEventX - downX;
final int deltaYTotal = hoverCellOriginalBounds.centerY() + totalOffsetY + deltaY;
final int deltaXTotal = hoverCellOriginalBounds.centerX() + totalOffsetX + deltaX;
mobileView = getViewForId(mobileItemId);
View targetView = null;
float vX = 0;
float vY = 0;
Point mobileColumnRowPair = getColumnAndRowForView(mobileView);
for (Long id : idList) {
View view = getViewForId(id);
if (view != null) {
Point targetColumnRowPair = getColumnAndRowForView(view);
if ((aboveRight(targetColumnRowPair, mobileColumnRowPair)
&& deltaYTotal < view.getBottom() && deltaXTotal > view.getLeft()
|| aboveLeft(targetColumnRowPair, mobileColumnRowPair)
&& deltaYTotal < view.getBottom() && deltaXTotal < view.getRight()
|| belowRight(targetColumnRowPair, mobileColumnRowPair)
&& deltaYTotal > view.getTop() && deltaXTotal > view.getLeft()
|| belowLeft(targetColumnRowPair, mobileColumnRowPair)
&& deltaYTotal > view.getTop() && deltaXTotal < view.getRight()
|| above(targetColumnRowPair, mobileColumnRowPair)
&& deltaYTotal < view.getBottom() - overlapIfSwitchStraightLine
|| below(targetColumnRowPair, mobileColumnRowPair)
&& deltaYTotal > view.getTop() + overlapIfSwitchStraightLine
|| right(targetColumnRowPair, mobileColumnRowPair)
&& deltaXTotal > view.getLeft() + overlapIfSwitchStraightLine
|| left(targetColumnRowPair, mobileColumnRowPair)
&& deltaXTotal < view.getRight() - overlapIfSwitchStraightLine)) {
float xDiff = Math.abs(DynamicGridUtils.getViewX(view) - DynamicGridUtils.getViewX(mobileView));
float yDiff = Math.abs(DynamicGridUtils.getViewY(view) - DynamicGridUtils.getViewY(mobileView));
if (xDiff >= vX && yDiff >= vY) {
vX = xDiff;
vY = yDiff;
targetView = view;
}
}
}
}
if (targetView != null) {
final int originalPosition = getPositionForView(mobileView);
int targetPosition = getPositionForView(targetView);
// if targetPosition is not draggable then do nothing
if(!selectedItemDraggableListener.isDraggable(targetPosition)) return;
final DynamicGridAdapterInterface adapter = getAdapterInterface();
if (targetPosition == INVALID_POSITION || !adapter.canReorder(originalPosition) || !adapter.canReorder(targetPosition)) {
updateNeighborViewsForId(mobileItemId);
return;
}
reorderElements(originalPosition, targetPosition);
if (undoSupportEnabled) {
currentModification.addTransition(originalPosition, targetPosition);
}
downY = lastEventY;
downX = lastEventX;
SwitchCellAnimator switchCellAnimator;
if (!DeviceUtils.isAtLeastLollipop()) //below Android L
switchCellAnimator = new KitKatSwitchCellAnimator(deltaX, deltaY);
else //Android L
switchCellAnimator = new LSwitchCellAnimator(deltaX, deltaY);
updateNeighborViewsForId(mobileItemId);
switchCellAnimator.animateSwitchCell(originalPosition, targetPosition);
}
}
private interface SwitchCellAnimator {
void animateSwitchCell(final int originalPosition, final int targetPosition);
}
/**
* for versions KitKat and below.
*/
private class KitKatSwitchCellAnimator implements SwitchCellAnimator {
private int deltaY;
private int deltaX;
public KitKatSwitchCellAnimator(int deltaX, int deltaY) {
this.deltaX = deltaX;
this.deltaY = deltaY;
}
@Override
public void animateSwitchCell(final int originalPosition, final int targetPosition) {
assert mobileView != null;
getViewTreeObserver().addOnPreDrawListener(new AnimateSwitchViewOnPreDrawListener(mobileView, originalPosition, targetPosition));
mobileView = getViewForId(mobileItemId);
}
private class AnimateSwitchViewOnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
private final View previousMobileView;
private final int originalPosition;
private final int targetPosition;
AnimateSwitchViewOnPreDrawListener(final View previousMobileView, final int originalPosition, final int targetPosition) {
this.previousMobileView = previousMobileView;
this.originalPosition = originalPosition;
this.targetPosition = targetPosition;
}
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
totalOffsetY += deltaY;
totalOffsetX += deltaX;
animateReorder(originalPosition, targetPosition);
previousMobileView.setVisibility(View.VISIBLE);
if (mobileView != null) {
mobileView.setVisibility(View.INVISIBLE);
}
return true;
}
}
}
/**
* for versions L and above.
*/
private class LSwitchCellAnimator implements SwitchCellAnimator {
private int deltaY;
private int deltaX;
public LSwitchCellAnimator(int deltaX, int deltaY) {
this.deltaX = deltaX;
this.deltaY = deltaY;
}
@Override
public void animateSwitchCell(final int originalPosition, final int targetPosition) {
getViewTreeObserver().addOnPreDrawListener(new AnimateSwitchViewOnPreDrawListener(originalPosition, targetPosition));
}
private class AnimateSwitchViewOnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
private final int originalPosition;
private final int targetPosition;
AnimateSwitchViewOnPreDrawListener(final int originalPosition, final int targetPosition) {
this.originalPosition = originalPosition;
this.targetPosition = targetPosition;
}
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
totalOffsetY += deltaY;
totalOffsetX += deltaX;
animateReorder(originalPosition, targetPosition);
assert mobileView != null;
mobileView.setVisibility(View.VISIBLE);
mobileView = getViewForId(mobileItemId);
assert mobileView != null;
mobileView.setVisibility(View.INVISIBLE);
return true;
}
}
}
private boolean belowLeft(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
}
private boolean belowRight(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
}
private boolean aboveLeft(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
}
private boolean aboveRight(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
}
private boolean above(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y < mobileColumnRowPair.y && targetColumnRowPair.x == mobileColumnRowPair.x;
}
private boolean below(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y > mobileColumnRowPair.y && targetColumnRowPair.x == mobileColumnRowPair.x;
}
private boolean right(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y == mobileColumnRowPair.y && targetColumnRowPair.x > mobileColumnRowPair.x;
}
private boolean left(Point targetColumnRowPair, Point mobileColumnRowPair) {
return targetColumnRowPair.y == mobileColumnRowPair.y && targetColumnRowPair.x < mobileColumnRowPair.x;
}
private Point getColumnAndRowForView(View view) {
int pos = getPositionForView(view);
int columns = getColumnCount();
int column = pos % columns;
int row = pos / columns;
return new Point(column, row);
}
private long getId(int position) {
return getAdapter().getItemId(position);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void animateReorder(final int oldPosition, final int newPosition) {
boolean isForward = newPosition > oldPosition;
List<Animator> resultList = new LinkedList<>();
if (isForward) {
for (int pos = Math.min(oldPosition, newPosition); pos < Math.max(oldPosition, newPosition); pos++) {
View view = getViewForId(getId(pos));
if ((pos + 1) % getColumnCount() == 0) {
resultList.add(createTranslationAnimations(view, -view.getWidth() * (getColumnCount() - 1), 0,
view.getHeight(), 0));
} else {
resultList.add(createTranslationAnimations(view, view.getWidth(), 0, 0, 0));
}
}
} else {
for (int pos = Math.max(oldPosition, newPosition); pos > Math.min(oldPosition, newPosition); pos--) {
View view = getViewForId(getId(pos));
if ((pos + getColumnCount()) % getColumnCount() == 0) {
resultList.add(createTranslationAnimations(view, view.getWidth() * (getColumnCount() - 1), 0,
-view.getHeight(), 0));
} else {
resultList.add(createTranslationAnimations(view, -view.getWidth(), 0, 0, 0));
}
}
}
AnimatorSet resultSet = new AnimatorSet();
resultSet.playTogether(resultList);
resultSet.setDuration(MOVE_DURATION);
resultSet.setInterpolator(new AccelerateDecelerateInterpolator());
resultSet.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
reorderAnimation = true;
updateEnableState();
}
@Override
public void onAnimationEnd(Animator animation) {
reorderAnimation = false;
updateEnableState();
}
});
resultSet.start();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private AnimatorSet createTranslationAnimations(View view, float startX, float endX, float startY, float endY) {
ObjectAnimator animX = ObjectAnimator.ofFloat(view, "translationX", startX, endX);
ObjectAnimator animY = ObjectAnimator.ofFloat(view, "translationY", startY, endY);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
return animSetXY;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (hoverCell != null) {
hoverCell.draw(canvas);
}
}
public interface OnDropListener {
void onActionDrop();
}
public interface OnDragListener {
public void onDragStarted(int position);
public void onDragPositionsChanged(int oldPosition, int newPosition);
}
public interface OnEditModeChangeListener {
public void onEditModeChanged(boolean inEditMode);
}
public interface OnSelectedItemBitmapCreationListener {
public void onPreSelectedItemBitmapCreation(View selectedView, int position, long itemId);
public void onPostSelectedItemBitmapCreation(View selectedView, int position, long itemId);
}
public interface OnSelectedItemDraggableListener {
boolean isDraggable(int position);
}
/**
* This scroll listener is added to the gridview in order to handle cell swapping
* when the cell is either at the top or bottom edge of the gridview. If the hover
* cell is at either edge of the gridview, the gridview will begin scrolling. As
* scrolling takes place, the gridview continuously checks if new cells became visible
* and determines whether they are potential candidates for a cell swap.
*/
private OnScrollListener scrollListener = new OnScrollListener() {
private int previousFirstVisibleItem = -1;
private int previousVisibleItemCount = -1;
private int currentFirstVisibleItem;
private int currentVisibleItemCount;
private int currentScrollState;
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
currentFirstVisibleItem = firstVisibleItem;
currentVisibleItemCount = visibleItemCount;
previousFirstVisibleItem = (previousFirstVisibleItem == -1) ? currentFirstVisibleItem
: previousFirstVisibleItem;
previousVisibleItemCount = (previousVisibleItemCount == -1) ? currentVisibleItemCount
: previousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
previousFirstVisibleItem = currentFirstVisibleItem;
previousVisibleItemCount = currentVisibleItemCount;
if (userScrollListener != null) {
userScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
currentScrollState = scrollState;
DynamicGridView.this.scrollState = scrollState;
isScrollCompleted();
if (userScrollListener != null) {
userScrollListener.onScrollStateChanged(view, scrollState);
}
}
/**
* This method is in charge of invoking 1 of 2 actions. Firstly, if the gridview
* is in a state of scrolling invoked by the hover cell being outside the bounds
* of the gridview, then this scrolling event is continued. Secondly, if the hover
* cell has already been released, this invokes the animation for the hover cell
* to return to its correct position after the gridview has entered an idle scroll
* state.
*/
private void isScrollCompleted() {
if (currentVisibleItemCount > 0 && currentScrollState == SCROLL_STATE_IDLE) {
if (cellIsMobile && isMobileScrolling) {
handleMobileCellScroll();
} else if (isWaitingForScrollFinish) {
touchEventsEnded();
}
}
}
/**
* Determines if the gridview scrolled up enough to reveal a new cell at the
* top of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleFirstVisibleCellChange() {
if (currentFirstVisibleItem != previousFirstVisibleItem) {
if (cellIsMobile && mobileItemId != INVALID_ID) {
updateNeighborViewsForId(mobileItemId);
handleCellSwitch();
}
}
}
/**
* Determines if the gridview scrolled down enough to reveal a new cell at the
* bottom of the list. If so, then the appropriate parameters are updated.
*/
public void checkAndHandleLastVisibleCellChange() {
int currentLastVisibleItem = currentFirstVisibleItem + currentVisibleItemCount;
int previousLastVisibleItem = previousFirstVisibleItem + previousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (cellIsMobile && mobileItemId != INVALID_ID) {
updateNeighborViewsForId(mobileItemId);
handleCellSwitch();
}
}
}
};
private static class DynamicGridModification {
private List<Pair<Integer, Integer>> transitions;
DynamicGridModification() {
super();
this.transitions = new Stack<>();
}
public boolean hasTransitions() {
return !transitions.isEmpty();
}
public void addTransition(int oldPosition, int newPosition) {
transitions.add(new Pair<>(oldPosition, newPosition));
}
public List<Pair<Integer, Integer>> getTransitions() {
Collections.reverse(transitions);
return transitions;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment