package com.marshalchen.common.uimodule.dynamicgrid;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
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.ViewGroup;
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.marshalchen.ultimateandroiduicomponent.R;
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 = AbstractDynamicGridAdapter.INVALID_ID;
private static final int MOVE_DURATION = 300;
private static final int SMOOTH_SCROLL_AMOUNT_AT_EDGE = 8;
private BitmapDrawable mHoverCell;
private Rect mHoverCellCurrentBounds;
private Rect mHoverCellOriginalBounds;
private int mTotalOffsetY = 0;
private int mTotalOffsetX = 0;
private int mDownX = -1;
private int mDownY = -1;
private int mLastEventY = -1;
private int mLastEventX = -1;
private List<Long> idList = new ArrayList<Long>();
private long mMobileItemId = INVALID_ID;
private boolean mCellIsMobile = false;
private int mActivePointerId = INVALID_ID;
private boolean mIsMobileScrolling;
private int mSmoothScrollAmountAtEdge = 0;
private boolean mIsWaitingForScrollFinish = false;
private int mScrollState = OnScrollListener.SCROLL_STATE_IDLE;
private boolean mIsEditMode = false;
private List<ObjectAnimator> mWobbleAnimators = new LinkedList<ObjectAnimator>();
private OnDropListener mDropListener;
private boolean mHoverAnimation;
private boolean mReorderAnimation;
private boolean mWobbleInEditMode = true;
private OnItemLongClickListener mUserLongClickListener;
private OnItemLongClickListener mLocalLongClickListener = new OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> arg0, View arg1, int pos, long id) {
if (!isEnabled() || isEditMode())
return false;
mTotalOffsetY = 0;
mTotalOffsetX = 0;
int position = pointToPosition(mDownX, mDownY);
int itemNum = position - getFirstVisiblePosition();
View selectedView = getChildAt(itemNum);
mMobileItemId = getAdapter().getItemId(position);
if (mSelectedItemBitmapCreationListener != null) {
mSelectedItemBitmapCreationListener.OnPreSelectedItemBitmapCreation(selectedView, position, mMobileItemId);
}
mHoverCell = getAndAddHoverView(selectedView);
if (isPostHoneycomb() && selectedView != null)
selectedView.setVisibility(View.INVISIBLE);
mCellIsMobile = true;
if (mSelectedItemBitmapCreationListener != null) {
mSelectedItemBitmapCreationListener.OnPostSelectedItemBitmapCreation(selectedView, position, mMobileItemId);
}
updateNeighborViewsForId(mMobileItemId);
currentModification = new DynamicGridModification();
if (isPostHoneycomb() && mWobbleInEditMode)
startWobbleAnimation();
if (mUserLongClickListener != null)
mUserLongClickListener.onItemLongClick(arg0, arg1, pos, id);
mIsEditMode = true;
return true;
}
};
private OnItemClickListener mUserItemClickListener;
private OnItemClickListener mLocalItemClickListener = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (!isEditMode() && isEnabled() && mUserItemClickListener != null) {
mUserItemClickListener.onItemClick(parent, view, position, id);
}
}
};
private boolean undoSupportEnabled;
private Stack<DynamicGridModification> modificationStack;
private DynamicGridModification currentModification;
private OnSelectedItemBitmapCreationListener mSelectedItemBitmapCreationListener;
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);
}
public void setOnDropListener(OnDropListener dropListener) {
this.mDropListener = dropListener;
}
public void startEditMode() {
mIsEditMode = true;
if (isPostHoneycomb() && mWobbleInEditMode)
startWobbleAnimation();
}
public void stopEditMode() {
mIsEditMode = false;
if (isPostHoneycomb() && mWobbleInEditMode)
stopWobble(true);
}
public boolean isEditMode() {
return mIsEditMode;
}
public boolean isWobbleInEditMode() {
return mWobbleInEditMode;
}
public void setWobbleInEditMode(boolean wobbleInEditMode) {
this.mWobbleInEditMode = wobbleInEditMode;
}
@Override
public void setOnItemLongClickListener(final OnItemLongClickListener listener) {
mUserLongClickListener = listener;
super.setOnItemLongClickListener(mLocalLongClickListener);
}
@Override
public void setOnItemClickListener(OnItemClickListener listener) {
this.mUserItemClickListener = listener;
super.setOnItemClickListener(mLocalItemClickListener);
}
public boolean isUndoSupportEnabled() {
return undoSupportEnabled;
}
public void setUndoSupportEnabled(boolean undoSupportEnabled) {
if (this.undoSupportEnabled != undoSupportEnabled) {
if (undoSupportEnabled) {
this.modificationStack = new Stack<DynamicGridModification>();
} 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.mSelectedItemBitmapCreationListener = selectedItemBitmapCreationListener;
}
private void undoModification(DynamicGridModification modification) {
for (Pair<Integer, Integer> transition : modification.getTransitions()) {
reorderElements(transition.second, transition.first);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void startWobbleAnimation() {
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (v != null && Boolean.TRUE != v.getTag(R.id.dynamic_grid_wobble_tag)) {
if (i % 2 == 0)
animateWobble(v);
else
animateWobbleInverse(v);
v.setTag(R.id.dynamic_grid_wobble_tag, true);
}
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void stopWobble(boolean resetRotation) {
for (Animator wobbleAnimator : mWobbleAnimators) {
wobbleAnimator.cancel();
}
mWobbleAnimators.clear();
for (int i = 0; i < getChildCount(); i++) {
View v = getChildAt(i);
if (v != null) {
if (resetRotation) v.setRotation(0);
v.setTag(R.id.dynamic_grid_wobble_tag, false);
}
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void restartWobble() {
stopWobble(false);
startWobbleAnimation();
}
public void init(Context context) {
setOnScrollListener(mScrollListener);
DisplayMetrics metrics = context.getResources().getDisplayMetrics();
mSmoothScrollAmountAtEdge = (int) (SMOOTH_SCROLL_AMOUNT_AT_EDGE * metrics.density + 0.5f);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void animateWobble(View v) {
ObjectAnimator animator = createBaseWobble(v);
animator.setFloatValues(-2, 2);
animator.start();
mWobbleAnimators.add(animator);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void animateWobbleInverse(View v) {
ObjectAnimator animator = createBaseWobble(v);
animator.setFloatValues(2, -2);
animator.start();
mWobbleAnimators.add(animator);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private ObjectAnimator createBaseWobble(View v) {
ObjectAnimator animator = new ObjectAnimator();
animator.setDuration(180);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setPropertyName("rotation");
animator.setTarget(v);
return animator;
}
private void reorderElements(int originalPosition, int targetPosition) {
getAdapterInterface().reorderItems(originalPosition, targetPosition);
}
private int getColumnCount() {
return getAdapterInterface().getColumnCount();
}
private AbstractDynamicGridAdapter getAdapterInterface() {
return ((AbstractDynamicGridAdapter) 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);
BitmapDrawable drawable = new BitmapDrawable(getResources(), b);
mHoverCellOriginalBounds = new Rect(left, top, left + w, top + h);
mHoverCellCurrentBounds = new Rect(mHoverCellOriginalBounds);
drawable.setBounds(mHoverCellCurrentBounds);
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;
}
private void updateNeighborViewsForId(long itemId) {
int draggedPos = getPositionForID(itemId);
for (int pos = getFirstVisiblePosition(); pos <= getLastVisiblePosition(); pos++) {
if (draggedPos != 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();
AbstractDynamicGridAdapter adapter = ((AbstractDynamicGridAdapter) 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:
mDownX = (int) event.getX();
mDownY = (int) event.getY();
mActivePointerId = event.getPointerId(0);
if (mIsEditMode && isEnabled()) {
layoutChildren();
mTotalOffsetY = 0;
mTotalOffsetX = 0;
int position = pointToPosition(mDownX, mDownY);
int itemNum = position - getFirstVisiblePosition();
View selectedView = getChildAt(itemNum);
if (selectedView == null) {
return false;
} else {
mMobileItemId = getAdapter().getItemId(position);
mHoverCell = getAndAddHoverView(selectedView);
if (isPostHoneycomb())
selectedView.setVisibility(View.INVISIBLE);
mCellIsMobile = true;
updateNeighborViewsForId(mMobileItemId);
}
} else if (!isEnabled()) {
return false;
}
break;
case MotionEvent.ACTION_MOVE:
if (mActivePointerId == INVALID_ID) {
break;
}
int pointerIndex = event.findPointerIndex(mActivePointerId);
mLastEventY = (int) event.getY(pointerIndex);
mLastEventX = (int) event.getX(pointerIndex);
int deltaY = mLastEventY - mDownY;
int deltaX = mLastEventX - mDownX;
if (mCellIsMobile) {
mHoverCellCurrentBounds.offsetTo(mHoverCellOriginalBounds.left + deltaX + mTotalOffsetX,
mHoverCellOriginalBounds.top + deltaY + mTotalOffsetY);
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
handleCellSwitch();
mIsMobileScrolling = 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 (mHoverCell != null) {
if (mDropListener != null) {
mDropListener.onActionDrop();
}
}
break;
case MotionEvent.ACTION_CANCEL:
touchEventsCancelled();
if (mHoverCell != null) {
if (mDropListener != null) {
mDropListener.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 == mActivePointerId) {
touchEventsEnded();
}
break;
default:
break;
}
return super.onTouchEvent(event);
}
private void handleMobileCellScroll() {
mIsMobileScrolling = handleMobileCellScroll(mHoverCellCurrentBounds);
}
public boolean handleMobileCellScroll(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(-mSmoothScrollAmountAtEdge, 0);
return true;
}
if (hoverViewTop + hoverHeight >= height && (offset + extent) < range) {
smoothScrollBy(mSmoothScrollAmountAtEdge, 0);
return true;
}
return false;
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
}
private void touchEventsEnded() {
final View mobileView = getViewForId(mMobileItemId);
if (mCellIsMobile || mIsWaitingForScrollFinish) {
mCellIsMobile = false;
mIsWaitingForScrollFinish = false;
mIsMobileScrolling = false;
mActivePointerId = 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 (mScrollState != OnScrollListener.SCROLL_STATE_IDLE) {
mIsWaitingForScrollFinish = true;
return;
}
mHoverCellCurrentBounds.offsetTo(mobileView.getLeft(), mobileView.getTop());
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
animateBounds(mobileView);
} else {
mHoverCell.setBounds(mHoverCellCurrentBounds);
invalidate();
reset(mobileView);
}
} else {
touchEventsCancelled();
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void animateBounds(final View mobileView) {
TypeEvaluator<Rect> sBoundEvaluator = 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(mHoverCell, "bounds",
sBoundEvaluator, mHoverCellCurrentBounds);
hoverViewAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
invalidate();
}
});
hoverViewAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mHoverAnimation = true;
updateEnableState();
}
@Override
public void onAnimationEnd(Animator animation) {
mHoverAnimation = false;
updateEnableState();
reset(mobileView);
}
});
hoverViewAnimator.start();
}
private void reset(View mobileView) {
idList.clear();
mMobileItemId = INVALID_ID;
mobileView.setVisibility(View.VISIBLE);
mHoverCell = null;
if (!mIsEditMode && isPostHoneycomb() && mWobbleInEditMode)
stopWobble(true);
if (mIsEditMode && isPostHoneycomb() && mWobbleInEditMode)
restartWobble();
invalidate();
}
private void updateEnableState() {
setEnabled(!mHoverAnimation && !mReorderAnimation);
}
/**
* Seems that GridView before HONEYCOMB not support stable id in proper way.
* That cause bugs on view recycle if we will animate or change visibility state for items.
*
* @return
*/
private boolean isPostHoneycomb() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
}
private void touchEventsCancelled() {
View mobileView = getViewForId(mMobileItemId);
if (mCellIsMobile) {
reset(mobileView);
}
mCellIsMobile = false;
mIsMobileScrolling = false;
mActivePointerId = INVALID_ID;
}
private void handleCellSwitch() {
final int deltaY = mLastEventY - mDownY;
final int deltaX = mLastEventX - mDownX;
final int deltaYTotal = mHoverCellOriginalBounds.centerY() + mTotalOffsetY + deltaY;
final int deltaXTotal = mHoverCellOriginalBounds.centerX() + mTotalOffsetX + deltaX;
View mobileView = getViewForId(mMobileItemId);
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())
|| below(targetColumnRowPair, mobileColumnRowPair) && deltaYTotal > view.getTop()
|| right(targetColumnRowPair, mobileColumnRowPair) && deltaXTotal > view.getLeft()
|| left(targetColumnRowPair, mobileColumnRowPair) && deltaXTotal < view.getRight()) {
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 == INVALID_POSITION) {
updateNeighborViewsForId(mMobileItemId);
return;
}
reorderElements(originalPosition, targetPosition);
if (undoSupportEnabled) {
currentModification.addTransition(originalPosition, targetPosition);
}
mDownY = mLastEventY;
mDownX = mLastEventX;
mobileView.setVisibility(View.VISIBLE);
if (isPostHoneycomb()) {
targetView.setVisibility(View.INVISIBLE);
}
updateNeighborViewsForId(mMobileItemId);
final ViewTreeObserver observer = getViewTreeObserver();
final int finalTargetPosition = targetPosition;
if (isPostHoneycomb() && observer != null) {
observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
observer.removeOnPreDrawListener(this);
mTotalOffsetY += deltaY;
mTotalOffsetX += deltaX;
animateReorder(originalPosition, finalTargetPosition);
return true;
}
});
} else {
mTotalOffsetY += deltaY;
mTotalOffsetX += deltaX;
}
}
}
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<Animator>();
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) {
mReorderAnimation = true;
updateEnableState();
}
@Override
public void onAnimationEnd(Animator animation) {
mReorderAnimation = 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 (mHoverCell != null) {
mHoverCell.draw(canvas);
}
}
/**
* Interface provide callback for end of drag'n'drop event
*/
public interface OnDropListener {
/**
* called when view been dropped
*/
void onActionDrop();
}
/**
* 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 mScrollListener = new OnScrollListener() {
private int mPreviousFirstVisibleItem = -1;
private int mPreviousVisibleItemCount = -1;
private int mCurrentFirstVisibleItem;
private int mCurrentVisibleItemCount;
private int mCurrentScrollState;
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
mCurrentFirstVisibleItem = firstVisibleItem;
mCurrentVisibleItemCount = visibleItemCount;
mPreviousFirstVisibleItem = (mPreviousFirstVisibleItem == -1) ? mCurrentFirstVisibleItem
: mPreviousFirstVisibleItem;
mPreviousVisibleItemCount = (mPreviousVisibleItemCount == -1) ? mCurrentVisibleItemCount
: mPreviousVisibleItemCount;
checkAndHandleFirstVisibleCellChange();
checkAndHandleLastVisibleCellChange();
mPreviousFirstVisibleItem = mCurrentFirstVisibleItem;
mPreviousVisibleItemCount = mCurrentVisibleItemCount;
if (isPostHoneycomb() && mWobbleInEditMode) {
updateWobbleState(visibleItemCount);
}
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
private void updateWobbleState(int visibleItemCount) {
for (int i = 0; i < visibleItemCount; i++) {
View child = getChildAt(i);
if (child != null) {
if (mMobileItemId != INVALID_ID && Boolean.TRUE != child.getTag(R.id.dynamic_grid_wobble_tag)) {
if (i % 2 == 0)
animateWobble(child);
else
animateWobbleInverse(child);
child.setTag(R.id.dynamic_grid_wobble_tag, true);
} else if (mMobileItemId == INVALID_ID && child.getRotation() != 0) {
child.setRotation(0);
child.setTag(R.id.dynamic_grid_wobble_tag, false);
}
}
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
mCurrentScrollState = scrollState;
mScrollState = scrollState;
isScrollCompleted();
}
/**
* 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 (mCurrentVisibleItemCount > 0 && mCurrentScrollState == SCROLL_STATE_IDLE) {
if (mCellIsMobile && mIsMobileScrolling) {
handleMobileCellScroll();
} else if (mIsWaitingForScrollFinish) {
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 (mCurrentFirstVisibleItem != mPreviousFirstVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForId(mMobileItemId);
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 = mCurrentFirstVisibleItem + mCurrentVisibleItemCount;
int previousLastVisibleItem = mPreviousFirstVisibleItem + mPreviousVisibleItemCount;
if (currentLastVisibleItem != previousLastVisibleItem) {
if (mCellIsMobile && mMobileItemId != INVALID_ID) {
updateNeighborViewsForId(mMobileItemId);
handleCellSwitch();
}
}
}
};
public interface OnSelectedItemBitmapCreationListener {
public void OnPreSelectedItemBitmapCreation(View selectedView, int position, long itemId);
public void OnPostSelectedItemBitmapCreation(View selectedView, int position, long itemId);
}
private boolean haveScrollbar = false;
public boolean isHaveScrollbar() {
return haveScrollbar;
}
public void setHaveScrollbar(boolean haveScrollbar) {
this.haveScrollbar = haveScrollbar;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (haveScrollbar == false) {
// int expandSpec = View.MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, View.MeasureSpec.AT_MOST);
// int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
// MeasureSpec.AT_MOST);
// super.onMeasure(widthMeasureSpec, expandSpec);
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
class DynamicGridModification {
private List<Pair<Integer, Integer>> transitions;
DynamicGridModification() {
super();
this.transitions = new Stack<Pair<Integer, Integer>>();
}
public boolean hasTransitions() {
return !transitions.isEmpty();
}
public void addTransition(int oldPosition, int newPosition) {
transitions.add(new Pair<Integer, Integer>(oldPosition, newPosition));
}
public List<Pair<Integer, Integer>> getTransitions() {
Collections.reverse(transitions);
return transitions;
}
}