/** * Copyright 2012 * * Nicolas Desjardins * https://github.com/mrKlar * * Facilite solutions * http://www.facilitesolutions.com/ * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package ca.laplanete.mobile.pageddragdropgrid; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.util.AttributeSet; import android.util.SparseArray; import android.util.TypedValue; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.View.OnLongClickListener; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.Animation; import android.view.animation.AnimationSet; import android.view.animation.RotateAnimation; import android.view.animation.ScaleAnimation; import android.view.animation.TranslateAnimation; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Timer; import java.util.TimerTask; // TODO: Auto-generated Javadoc /** * The Class DragDropGrid. */ @SuppressLint("UseSparseArrays") public class DragDropGrid extends ViewGroup implements OnTouchListener, OnLongClickListener { /** The animation duration. */ private static int ANIMATION_DURATION = 250; /** The egde detection margin. */ private static int EGDE_DETECTION_MARGIN = 35; /** The adapter. */ private PagedDragDropGridAdapter adapter; /** The on click listener. */ private OnClickListener onClickListener = null; /** The container. */ private PagedContainer container; /** The new positions. */ private SparseArray<Integer> newPositions = new SparseArray<Integer>(); /** The grid page width. */ private int gridPageWidth = 0; /** The dragged. */ private int dragged = -1; /** The column width size. */ private int columnWidthSize; /** The row height size. */ private int rowHeightSize; /** The biggest child width. */ private int biggestChildWidth; /** The biggest child height. */ private int biggestChildHeight; /** The computed column count. */ private int computedColumnCount; /** The computed row count. */ private int computedRowCount; /** The initial x. */ private int initialX; /** The initial y. */ private int initialY; /** The moving view. */ private boolean movingView; /** The last target. */ private int lastTarget = -1; /** The was on edge just now. */ private boolean wasOnEdgeJustNow = false; /** The edge scroll timer. */ private Timer edgeScrollTimer; /** The edge timer handler. */ final private Handler edgeTimerHandler = new Handler(); /** The last touch x. */ private int lastTouchX; /** The last touch y. */ private int lastTouchY; /** The grid page height. */ private int gridPageHeight; /** The delete zone. */ private DeleteDropZoneView deleteZone; /** * Instantiates a new drag drop grid. * * @param context the context * @param attrs the attrs * @param defStyle the def style */ public DragDropGrid(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } /** * Instantiates a new drag drop grid. * * @param context the context * @param attrs the attrs */ public DragDropGrid(Context context, AttributeSet attrs) { super(context, attrs); init(); } /** * Instantiates a new drag drop grid. * * @param context the context */ public DragDropGrid(Context context) { super(context); init(); } /** * Instantiates a new drag drop grid. * * @param context the context * @param attrs the attrs * @param defStyle the def style * @param adapter the adapter * @param container the container */ public DragDropGrid(Context context, AttributeSet attrs, int defStyle, PagedDragDropGridAdapter adapter, PagedContainer container) { super(context, attrs, defStyle); this.adapter = adapter; this.container = container; init(); } /** * Instantiates a new drag drop grid. * * @param context the context * @param attrs the attrs * @param adapter the adapter * @param container the container */ public DragDropGrid(Context context, AttributeSet attrs, PagedDragDropGridAdapter adapter, PagedContainer container) { super(context, attrs); this.adapter = adapter; this.container = container; init(); } /** * Instantiates a new drag drop grid. * * @param context the context * @param adapter the adapter * @param container the container */ public DragDropGrid(Context context, PagedDragDropGridAdapter adapter, PagedContainer container) { super(context); this.adapter = adapter; this.container = container; init(); } /** * Inits the. */ private void init() { setOnTouchListener(this); setOnLongClickListener(this); createDeleteZone(); } /** * Sets the adapter. * * @param adapter the new adapter */ public void setAdapter(PagedDragDropGridAdapter adapter) { this.adapter = adapter; addChildViews(); } /* (non-Javadoc) * @see android.view.View#setOnClickListener(android.view.View.OnClickListener) */ public void setOnClickListener(OnClickListener l) { onClickListener = l; } /** * Adds the child views. */ private void addChildViews() { for (int page = 0; page < adapter.pageCount(); page++) { for (int item = 0; item < adapter.itemCountInPage(page); item++) { addView(adapter.view(page, item)); } } deleteZone.bringToFront(); } /** * Animate move all items. */ private void animateMoveAllItems() { Animation rotateAnimation = createFastRotateAnimation(); for (int i=0; i < getItemViewCount(); i++) { View child = getChildAt(i); child.startAnimation(rotateAnimation); } } /** * Cancel animations. */ private void cancelAnimations() { for (int i=0; i < getItemViewCount(); i++) { if (i != dragged) { View child = getChildAt(i); child.clearAnimation(); } } } /* (non-Javadoc) * @see android.view.ViewGroup#onInterceptTouchEvent(android.view.MotionEvent) */ public boolean onInterceptTouchEvent(MotionEvent event) { return onTouch(null, event); } /* (non-Javadoc) * @see android.view.View.OnTouchListener#onTouch(android.view.View, android.view.MotionEvent) */ @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: touchDown(event); break; case MotionEvent.ACTION_MOVE: touchMove(event); break; case MotionEvent.ACTION_UP: touchUp(event); break; } if (aViewIsDragged()) return true; return false; } /** * Touch up. * * @param event the event */ private void touchUp(MotionEvent event) { if(!aViewIsDragged()) { if(onClickListener != null) { View clickedView = getChildAt(getTargetAtCoor((int) event.getX(), (int) event.getY())); if(clickedView != null) onClickListener.onClick(clickedView); } } else { manageChildrenReordering(); hideDeleteView(); cancelEdgeTimer(); movingView = false; dragged = -1; lastTarget = -1; container.enableScroll(); cancelAnimations(); } } /** * Manage children reordering. */ private void manageChildrenReordering() { boolean draggedDeleted = touchUpInDeleteZoneDrop(lastTouchX, lastTouchY); if (draggedDeleted) { animateDeleteDragged(); reorderChildrenWhenDraggedIsDeleted(); } else { reorderChildren(); } } /** * Animate delete dragged. */ private void animateDeleteDragged() { ScaleAnimation scale = new ScaleAnimation(1.4f, 0f, 1.4f, 0f, biggestChildWidth / 2 , biggestChildHeight / 2); scale.setDuration(200); scale.setFillAfter(true); scale.setFillEnabled(true); getChildAt(dragged).clearAnimation(); getChildAt(dragged).startAnimation(scale); } /** * Reorder children when dragged is deleted. */ private void reorderChildrenWhenDraggedIsDeleted() { Integer newDraggedPosition = newPositions.get(dragged,dragged); List<View> children = cleanUnorderedChildren(); addReorderedChildrenToParent(children); tellAdapterDraggedIsDeleted(newDraggedPosition); removeViewAt(newDraggedPosition); requestLayout(); } /** * Tell adapter dragged is deleted. * * @param newDraggedPosition the new dragged position */ private void tellAdapterDraggedIsDeleted(Integer newDraggedPosition) { ItemPosition position = itemInformationAtPosition(newDraggedPosition); adapter.deleteItem(position.pageIndex,position.itemIndex); } /** * Touch down. * * @param event the event */ private void touchDown(MotionEvent event) { initialX = (int)event.getRawX(); initialY = (int)event.getRawY(); lastTouchX = (int)event.getRawX() + (currentPage() * gridPageWidth); lastTouchY = (int)event.getRawY(); } /** * Touch move. * * @param event the event */ private void touchMove(MotionEvent event) { if (movingView && aViewIsDragged()) { lastTouchX = (int) event.getX(); lastTouchY = (int) event.getY(); moveDraggedView(lastTouchX, lastTouchY); manageSwapPosition(lastTouchX, lastTouchY); manageEdgeCoordinates(lastTouchX); manageDeleteZoneHover(lastTouchX, lastTouchY); } } /** * Manage delete zone hover. * * @param x the x * @param y the y */ private void manageDeleteZoneHover(int x, int y) { Rect zone = new Rect(); deleteZone.getHitRect(zone); if (zone.intersect(x, y, x+1, y+1)) { deleteZone.highlight(); } else { deleteZone.smother(); } } /** * Touch up in delete zone drop. * * @param x the x * @param y the y * @return true, if successful */ private boolean touchUpInDeleteZoneDrop(int x, int y) { Rect zone = new Rect(); deleteZone.getHitRect(zone); if (zone.intersect(x, y, x+1, y+1)) { deleteZone.smother(); return true; } return false; } /** * Move dragged view. * * @param x the x * @param y the y */ private void moveDraggedView(int x, int y) { View childAt = getChildAt(dragged); int width = childAt.getMeasuredWidth(); int height = childAt.getMeasuredHeight(); int l = x - (1 * width / 2); int t = y - (1 * height / 2); childAt.layout(l, t, l + width, t + height); } /** * Manage swap position. * * @param x the x * @param y the y */ private void manageSwapPosition(int x, int y) { int target = getTargetAtCoor(x, y); if (childHasMoved(target) && target != lastTarget) { animateGap(target); lastTarget = target; } } /** * Manage edge coordinates. * * @param x the x */ private void manageEdgeCoordinates(int x) { final boolean onRightEdge = onRightEdgeOfScreen(x); final boolean onLeftEdge = onLeftEdgeOfScreen(x); if (canScrollToEitherSide(onRightEdge,onLeftEdge)) { if (!wasOnEdgeJustNow) { startEdgeDelayTimer(onRightEdge, onLeftEdge); wasOnEdgeJustNow = true; } } else { if (wasOnEdgeJustNow) { stopAnimateOnTheEdge(); } wasOnEdgeJustNow = false; cancelEdgeTimer(); } } /** * Stop animate on the edge. */ private void stopAnimateOnTheEdge() { View draggedView = getChildAt(dragged); draggedView.clearAnimation(); animateDragged(); } /** * Cancel edge timer. */ private void cancelEdgeTimer() { if (edgeScrollTimer != null) { edgeScrollTimer.cancel(); edgeScrollTimer = null; } } /** * Start edge delay timer. * * @param onRightEdge the on right edge * @param onLeftEdge the on left edge */ private void startEdgeDelayTimer(final boolean onRightEdge, final boolean onLeftEdge) { if (canScrollToEitherSide(onRightEdge, onLeftEdge)) { animateOnTheEdge(); if (edgeScrollTimer == null) { edgeScrollTimer = new Timer(); scheduleScroll(onRightEdge, onLeftEdge); } } } /** * Schedule scroll. * * @param onRightEdge the on right edge * @param onLeftEdge the on left edge */ private void scheduleScroll(final boolean onRightEdge, final boolean onLeftEdge) { edgeScrollTimer.schedule(new TimerTask() { @Override public void run() { if (wasOnEdgeJustNow) { wasOnEdgeJustNow = false; edgeTimerHandler.post(new Runnable() { @Override public void run() { hideDeleteView(); scroll(onRightEdge, onLeftEdge); cancelAnimations(); animateMoveAllItems(); animateDragged(); popDeleteView(); } }); } } }, 1000); } /** * Can scroll to either side. * * @param onRightEdge the on right edge * @param onLeftEdge the on left edge * @return true, if successful */ private boolean canScrollToEitherSide(final boolean onRightEdge, final boolean onLeftEdge) { return (onLeftEdge && container.canScrollToPreviousPage()) || (onRightEdge && container.canScrollToNextPage()); } /** * Scroll. * * @param onRightEdge the on right edge * @param onLeftEdge the on left edge */ private void scroll(boolean onRightEdge, boolean onLeftEdge) { cancelEdgeTimer(); if (onLeftEdge && container.canScrollToPreviousPage()) { scrollToPreviousPage(); } else if (onRightEdge && container.canScrollToNextPage()) { scrollToNextPage(); } wasOnEdgeJustNow = false; } /** * Scroll to next page. */ private void scrollToNextPage() { tellAdapterToMoveItemToNextPage(dragged); moveDraggedToNextPage(); container.scrollRight(); int currentPage = currentPage(); int lastItem = adapter.itemCountInPage(currentPage)-1; dragged = positionOfItem(currentPage, lastItem); requestLayout(); stopAnimateOnTheEdge(); } /** * Scroll to previous page. */ private void scrollToPreviousPage() { tellAdapterToMoveItemToPreviousPage(dragged); moveDraggedToPreviousPage(); container.scrollLeft(); int currentPage = currentPage(); int lastItem = adapter.itemCountInPage(currentPage)-1; dragged = positionOfItem(currentPage, lastItem); requestLayout(); stopAnimateOnTheEdge(); } /** * Move dragged to previous page. */ private void moveDraggedToPreviousPage() { List<View> children = cleanUnorderedChildren(); List<View> reorderedViews = reeorderView(children); int draggedEndPosition = newPositions.get(dragged, dragged); View draggedView = reorderedViews.get(draggedEndPosition); reorderedViews.remove(draggedEndPosition); int currentPage = currentPage(); int indexFirstElementInCurrentPage = 0; for (int i=0;i<currentPage;i++) { indexFirstElementInCurrentPage += adapter.itemCountInPage(i); } int indexOfDraggedOnNewPage = indexFirstElementInCurrentPage-1; reorderAndAddViews(reorderedViews, draggedView, indexOfDraggedOnNewPage); } /** * Removes the item children. * * @param children the children */ private void removeItemChildren(List<View> children) { for (View child : children) { removeView(child); } } /** * Move dragged to next page. */ private void moveDraggedToNextPage() { List<View> children = cleanUnorderedChildren(); List<View> reorderedViews = reeorderView(children); int draggedEndPosition = newPositions.get(dragged, dragged); View draggedView = reorderedViews.get(draggedEndPosition); reorderedViews.remove(draggedEndPosition); int currentPage = currentPage(); int indexLastElementInNextPage = 0; for (int i=0;i<=currentPage+1;i++) { indexLastElementInNextPage += adapter.itemCountInPage(i); } int indexOfDraggedOnNewPage = indexLastElementInNextPage-1; reorderAndAddViews(reorderedViews, draggedView, indexOfDraggedOnNewPage); } /** * Reorder and add views. * * @param reorderedViews the reordered views * @param draggedView the dragged view * @param indexOfDraggedOnNewPage the index of dragged on new page */ private void reorderAndAddViews(List<View> reorderedViews, View draggedView, int indexOfDraggedOnNewPage) { reorderedViews.add(indexOfDraggedOnNewPage,draggedView); newPositions.clear(); for (View view : reorderedViews) { if (view != null) { addView(view); } } deleteZone.bringToFront(); } /** * On left edge of screen. * * @param x the x * @return true, if successful */ private boolean onLeftEdgeOfScreen(int x) { int currentPage = container.currentPage(); int leftEdgeXCoor = currentPage*gridPageWidth; int distanceFromEdge = x - leftEdgeXCoor; return (x > 0 && distanceFromEdge <= EGDE_DETECTION_MARGIN); } /** * On right edge of screen. * * @param x the x * @return true, if successful */ private boolean onRightEdgeOfScreen(int x) { int currentPage = container.currentPage(); int rightEdgeXCoor = (currentPage*gridPageWidth) + gridPageWidth; int distanceFromEdge = rightEdgeXCoor - x; return (x > (rightEdgeXCoor - EGDE_DETECTION_MARGIN)) && (distanceFromEdge < EGDE_DETECTION_MARGIN); } /** * Animate on the edge. */ private void animateOnTheEdge() { View v = getChildAt(dragged); ScaleAnimation scale = new ScaleAnimation(.667f, 1.5f, .667f, 1.5f, v.getMeasuredWidth() * 3 / 4, v.getMeasuredHeight() * 3 / 4); scale.setDuration(200); scale.setRepeatMode(Animation.REVERSE); scale.setRepeatCount(Animation.INFINITE); v.clearAnimation(); v.startAnimation(scale); } /** * Animate gap. * * @param targetLocationInGrid the target location in grid */ private void animateGap(int targetLocationInGrid) { int viewAtPosition = currentViewAtPosition(targetLocationInGrid); if (viewAtPosition == dragged) { return; } View targetView = getChildAt(viewAtPosition); Point oldXY = getCoorForIndex(viewAtPosition); Point newXY = getCoorForIndex(newPositions.get(dragged, dragged)); Point oldOffset = computeTranslationStartDeltaRelativeToRealViewPosition(targetLocationInGrid, viewAtPosition, oldXY); Point newOffset = computeTranslationEndDeltaRelativeToRealViewPosition(oldXY, newXY); animateMoveToNewPosition(targetView, oldOffset, newOffset); saveNewPositions(targetLocationInGrid, viewAtPosition); } /** * Compute translation end delta relative to real view position. * * @param oldXY the old xy * @param newXY the new xy * @return the point */ private Point computeTranslationEndDeltaRelativeToRealViewPosition(Point oldXY, Point newXY) { return new Point(newXY.x - oldXY.x, newXY.y - oldXY.y); } /** * Compute translation start delta relative to real view position. * * @param targetLocation the target location * @param viewAtPosition the view at position * @param oldXY the old xy * @return the point */ private Point computeTranslationStartDeltaRelativeToRealViewPosition(int targetLocation, int viewAtPosition, Point oldXY) { Point oldOffset; if (viewWasAlreadyMoved(targetLocation, viewAtPosition)) { Point targetLocationPoint = getCoorForIndex(targetLocation); oldOffset = computeTranslationEndDeltaRelativeToRealViewPosition(oldXY, targetLocationPoint); } else { oldOffset = new Point(0,0); } return oldOffset; } /** * Save new positions. * * @param targetLocation the target location * @param viewAtPosition the view at position */ private void saveNewPositions(int targetLocation, int viewAtPosition) { newPositions.put(viewAtPosition, newPositions.get(dragged, dragged)); newPositions.put(dragged, targetLocation); tellAdapterToSwapDraggedWithTarget(newPositions.get(dragged, dragged), newPositions.get(viewAtPosition, viewAtPosition)); } /** * View was already moved. * * @param targetLocation the target location * @param viewAtPosition the view at position * @return true, if successful */ private boolean viewWasAlreadyMoved(int targetLocation, int viewAtPosition) { return viewAtPosition != targetLocation; } /** * Animate move to new position. * * @param targetView the target view * @param oldOffset the old offset * @param newOffset the new offset */ private void animateMoveToNewPosition(View targetView, Point oldOffset, Point newOffset) { AnimationSet set = new AnimationSet(true); Animation rotate = createFastRotateAnimation(); Animation translate = createTranslateAnimation(oldOffset, newOffset); set.addAnimation(rotate); set.addAnimation(translate); targetView.clearAnimation(); targetView.startAnimation(set); } /** * Creates the translate animation. * * @param oldOffset the old offset * @param newOffset the new offset * @return the translate animation */ private TranslateAnimation createTranslateAnimation(Point oldOffset, Point newOffset) { TranslateAnimation translate = new TranslateAnimation(Animation.ABSOLUTE, oldOffset.x, Animation.ABSOLUTE, newOffset.x, Animation.ABSOLUTE, oldOffset.y, Animation.ABSOLUTE, newOffset.y); translate.setDuration(ANIMATION_DURATION); translate.setFillEnabled(true); translate.setFillAfter(true); translate.setInterpolator(new AccelerateDecelerateInterpolator()); return translate; } /** * Creates the fast rotate animation. * * @return the animation */ private Animation createFastRotateAnimation() { Animation rotate = new RotateAnimation(-2.0f, 2.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); rotate.setRepeatMode(Animation.REVERSE); rotate.setRepeatCount(Animation.INFINITE); rotate.setDuration(60); rotate.setInterpolator(new AccelerateDecelerateInterpolator()); return rotate; } /** * Current view at position. * * @param targetLocation the target location * @return the int */ private int currentViewAtPosition(int targetLocation) { int viewAtPosition = targetLocation; for (int i = 0; i < newPositions.size(); i++) { int value = newPositions.valueAt(i); if (value == targetLocation) { viewAtPosition = newPositions.keyAt(i); break; } } return viewAtPosition; } /** * Gets the coor for index. * * @param index the index * @return the coor for index */ private Point getCoorForIndex(int index) { ItemPosition page = itemInformationAtPosition(index); int row = page.itemIndex / computedColumnCount; int col = page.itemIndex - (row * computedColumnCount); int x = (currentPage() * gridPageWidth) + (columnWidthSize * col); int y = rowHeightSize * row; return new Point(x, y); } /** * Gets the target at coor. * * @param x the x * @param y the y * @return the target at coor */ private int getTargetAtCoor(int x, int y) { int page = currentPage(); int col = getColumnOfCoordinate(x, page); int row = getRowOfCoordinate(y); int positionInPage = col + (row * computedColumnCount); return positionOfItem(page, positionInPage); } /** * Gets the column of coordinate. * * @param x the x * @param page the page * @return the column of coordinate */ private int getColumnOfCoordinate(int x, int page) { int col = 0; int pageLeftBorder = (page) * gridPageWidth; for (int i = 1; i <= computedColumnCount; i++) { int colRightBorder = (i * columnWidthSize) + pageLeftBorder; if (x < colRightBorder) { break; } col++; } return col; } /** * Gets the row of coordinate. * * @param y the y * @return the row of coordinate */ private int getRowOfCoordinate(int y) { int row = 0; for (int i = 1; i <= computedRowCount; i++) { if (y < i * rowHeightSize) { break; } row++; } return row; } /** * Current page. * * @return the int */ private int currentPage() { return container.currentPage(); } /** * Reorder children. */ private void reorderChildren() { List<View> children = cleanUnorderedChildren(); addReorderedChildrenToParent(children); requestLayout(); } /** * Clean unordered children. * * @return the list */ private List<View> cleanUnorderedChildren() { List<View> children = saveChildren(); removeItemChildren(children); return children; } /** * Adds the reordered children to parent. * * @param children the children */ private void addReorderedChildrenToParent(List<View> children) { List<View> reorderedViews = reeorderView(children); newPositions.clear(); for (View view : reorderedViews) { if (view != null) addView(view); } deleteZone.bringToFront(); } /** * Save children. * * @return the list */ private List<View> saveChildren() { List<View> children = new ArrayList<View>(); for (int i = 0; i < getItemViewCount(); i++) { View child = getChildAt(i); child.clearAnimation(); children.add(child); } return children; } /** * Reeorder view. * * @param children the children * @return the list */ private List<View> reeorderView(List<View> children) { View[] views = new View[children.size()]; for (int i = 0; i < children.size(); i++) { int position = newPositions.get(i, -1); if (childHasMoved(position)) { views[position] = children.get(i); } else { views[i] = children.get(i); } } return new ArrayList<View>(Arrays.asList(views)); } /** * Child has moved. * * @param position the position * @return true, if successful */ private boolean childHasMoved(int position) { return position != -1; } /* (non-Javadoc) * @see android.view.View#onMeasure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); Display display = wm.getDefaultDisplay(); widthSize = acknowledgeWidthSize(widthMode, widthSize, display); heightSize = acknowledgeHeightSize(heightMode, heightSize, display); adaptChildrenMeasuresToViewSize(widthSize, heightSize); searchBiggestChildMeasures(); computeGridMatrixSize(widthSize, heightSize); computeColumnsAndRowsSizes(widthSize, heightSize); measureChild(deleteZone, MeasureSpec.makeMeasureSpec(gridPageWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec((int)getPixelFromDip(40), MeasureSpec.EXACTLY)); setMeasuredDimension(widthSize * adapter.pageCount(), heightSize); } /** * Gets the pixel from dip. * * @param size the size * @return the pixel from dip */ private float getPixelFromDip(int size) { Resources r = getResources(); float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, size, r.getDisplayMetrics()); return px; } /** * Compute columns and rows sizes. * * @param widthSize the width size * @param heightSize the height size */ private void computeColumnsAndRowsSizes(int widthSize, int heightSize) { columnWidthSize = widthSize / computedColumnCount; rowHeightSize = heightSize / computedRowCount; } /** * Compute grid matrix size. * * @param widthSize the width size * @param heightSize the height size */ private void computeGridMatrixSize(int widthSize, int heightSize) { if (adapter.columnCount() != -1 && adapter.rowCount() != -1) { computedColumnCount = adapter.columnCount(); computedRowCount = adapter.rowCount(); } else { if (biggestChildWidth > 0 && biggestChildHeight > 0) { computedColumnCount = widthSize / biggestChildWidth; computedRowCount = heightSize / biggestChildHeight; } } if (computedColumnCount == 0) { computedColumnCount = 1; } if (computedRowCount == 0) { computedRowCount = 1; } } /** * Search biggest child measures. */ private void searchBiggestChildMeasures() { biggestChildWidth = 0; biggestChildHeight = 0; for (int index = 0; index < getItemViewCount(); index++) { View child = getChildAt(index); if (biggestChildHeight < child.getMeasuredHeight()) { biggestChildHeight = child.getMeasuredHeight(); } if (biggestChildWidth < child.getMeasuredWidth()) { biggestChildWidth = child.getMeasuredWidth(); } } } /** * Gets the item view count. * * @return the item view count */ private int getItemViewCount() { // -1 to remove the DeleteZone from the loop return getChildCount()-1; } /** * Adapt children measures to view size. * * @param widthSize the width size * @param heightSize the height size */ private void adaptChildrenMeasuresToViewSize(int widthSize, int heightSize) { if (adapter.columnCount() != PagedDragDropGridAdapter.AUTOMATIC && adapter.rowCount() != PagedDragDropGridAdapter.AUTOMATIC) { int desiredGridItemWidth = widthSize / adapter.columnCount(); int desiredGridItemHeight = heightSize / adapter.rowCount(); measureChildren(MeasureSpec.makeMeasureSpec(desiredGridItemWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(desiredGridItemHeight, MeasureSpec.AT_MOST)); } else { measureChildren(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); } } /** * Acknowledge height size. * * @param heightMode the height mode * @param heightSize the height size * @param display the display * @return the int */ @SuppressWarnings("deprecation") private int acknowledgeHeightSize(int heightMode, int heightSize, Display display) { if (heightMode == MeasureSpec.UNSPECIFIED) { heightSize = display.getHeight(); } gridPageHeight = heightSize; return heightSize; } /** * Acknowledge width size. * * @param widthMode the width mode * @param widthSize the width size * @param display the display * @return the int */ @SuppressWarnings("deprecation") private int acknowledgeWidthSize(int widthMode, int widthSize, Display display) { if (widthMode == MeasureSpec.UNSPECIFIED) { widthSize = display.getWidth(); } gridPageWidth = widthSize; return widthSize; } /* (non-Javadoc) * @see android.view.ViewGroup#onLayout(boolean, int, int, int, int) */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int pageWidth = (l + r) / adapter.pageCount(); for (int page = 0; page < adapter.pageCount(); page++) { layoutPage(pageWidth, page); } } /** * Layout page. * * @param pageWidth the page width * @param page the page */ private void layoutPage(int pageWidth, int page) { int col = 0; int row = 0; for (int childIndex = 0; childIndex < adapter.itemCountInPage(page); childIndex++) { layoutAChild(pageWidth, page, col, row, childIndex); col++; if (col == computedColumnCount) { col = 0; row++; } } } /** * Layout a child. * * @param pageWidth the page width * @param page the page * @param col the col * @param row the row * @param childIndex the child index */ private void layoutAChild(int pageWidth, int page, int col, int row, int childIndex) { int position = positionOfItem(page, childIndex); View child = getChildAt(position); int left = 0; int top = 0; if (position == dragged && lastTouchOnEdge()) { left = computePageEdgeXCoor(child); top = lastTouchY - (child.getMeasuredHeight() / 2); } else { left = (page * pageWidth) + (col * columnWidthSize) + ((columnWidthSize - child.getMeasuredWidth()) / 2); top = (row * rowHeightSize) + ((rowHeightSize - child.getMeasuredHeight()) / 2); } child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight()); } /** * Last touch on edge. * * @return true, if successful */ private boolean lastTouchOnEdge() { return onRightEdgeOfScreen(lastTouchX) || onLeftEdgeOfScreen(lastTouchX); } /** * Compute page edge x coor. * * @param child the child * @return the int */ private int computePageEdgeXCoor(View child) { int left; left = lastTouchX - (child.getMeasuredWidth() / 2); if (onRightEdgeOfScreen(lastTouchX)) { left = left - gridPageWidth; } else if (onLeftEdgeOfScreen(lastTouchX)) { left = left + gridPageWidth; } return left; } /* (non-Javadoc) * @see android.view.View.OnLongClickListener#onLongClick(android.view.View) */ @Override public boolean onLongClick(View v) { if(positionForView(v) != -1) { container.disableScroll(); movingView = true; dragged = positionForView(v); animateMoveAllItems(); animateDragged(); popDeleteView(); return true; } return false; } /** * Animate dragged. */ private void animateDragged() { ScaleAnimation scale = new ScaleAnimation(1f, 1.4f, 1f, 1.4f, biggestChildWidth / 2 , biggestChildHeight / 2); scale.setDuration(200); scale.setFillAfter(true); scale.setFillEnabled(true); if (aViewIsDragged()) { getChildAt(dragged).clearAnimation(); getChildAt(dragged).startAnimation(scale); } } /** * A view is dragged. * * @return true, if successful */ private boolean aViewIsDragged() { return dragged != -1; } /** * Pop delete view. */ private void popDeleteView() { deleteZone.setVisibility(View.VISIBLE); int l = currentPage() * deleteZone.getMeasuredWidth(); int t = gridPageHeight - deleteZone.getMeasuredHeight(); deleteZone.layout(l, t, l + gridPageWidth, t + gridPageHeight); } /** * Creates the delete zone. */ private void createDeleteZone() { deleteZone = new DeleteDropZoneView(getContext()); addView(deleteZone); } /** * Hide delete view. */ private void hideDeleteView() { deleteZone.setVisibility(View.GONE); } /** * Position for view. * * @param v the v * @return the int */ private int positionForView(View v) { for (int index = 0; index < getItemViewCount(); index++) { View child = getChildAt(index); if (isPointInsideView(initialX, initialY, child)) { return index; } } return -1; } /** * Checks if is point inside view. * * @param x the x * @param y the y * @param view the view * @return true, if is point inside view */ private boolean isPointInsideView(float x, float y, View view) { int location[] = new int[2]; view.getLocationOnScreen(location); int viewX = location[0]; int viewY = location[1]; if (pointIsInsideViewBounds(x, y, view, viewX, viewY)) { return true; } else { return false; } } /** * Point is inside view bounds. * * @param x the x * @param y the y * @param view the view * @param viewX the view x * @param viewY the view y * @return true, if successful */ private boolean pointIsInsideViewBounds(float x, float y, View view, int viewX, int viewY) { return (x > viewX && x < (viewX + view.getWidth())) && (y > viewY && y < (viewY + view.getHeight())); } /** * Sets the container. * * @param container the new container */ public void setContainer(PagedDragDropGrid container) { this.container = container; } /** * Position of item. * * @param pageIndex the page index * @param childIndex the child index * @return the int */ private int positionOfItem(int pageIndex, int childIndex) { int currentGlobalIndex = 0; for (int currentPageIndex = 0; currentPageIndex < adapter.pageCount(); currentPageIndex++) { int itemCount = adapter.itemCountInPage(currentPageIndex); for (int currentItemIndex = 0; currentItemIndex < itemCount; currentItemIndex++) { if (pageIndex == currentPageIndex && childIndex == currentItemIndex) { return currentGlobalIndex; } currentGlobalIndex++; } } return -1; } /** * Item information at position. * * @param position the position * @return the item position */ private ItemPosition itemInformationAtPosition(int position) { int currentGlobalIndex = 0; for (int currentPageIndex = 0; currentPageIndex < adapter.pageCount(); currentPageIndex++) { int itemCount = adapter.itemCountInPage(currentPageIndex); for (int currentItemIndex = 0; currentItemIndex < itemCount; currentItemIndex++) { if (currentGlobalIndex == position) { return new ItemPosition(currentPageIndex, currentItemIndex); } currentGlobalIndex++; } } return null; } /** * Tell adapter to swap dragged with target. * * @param dragged the dragged * @param target the target */ private void tellAdapterToSwapDraggedWithTarget(int dragged, int target) { ItemPosition draggedItemPositionInPage = itemInformationAtPosition(dragged); ItemPosition targetItemPositionInPage = itemInformationAtPosition(target); if (draggedItemPositionInPage != null && targetItemPositionInPage != null) { adapter.swapItems(draggedItemPositionInPage.pageIndex,draggedItemPositionInPage.itemIndex, targetItemPositionInPage.itemIndex); } } /** * Tell adapter to move item to previous page. * * @param itemIndex the item index */ private void tellAdapterToMoveItemToPreviousPage(int itemIndex) { ItemPosition itemPosition = itemInformationAtPosition(itemIndex); adapter.moveItemToPreviousPage(itemPosition.pageIndex,itemPosition.itemIndex); } /** * Tell adapter to move item to next page. * * @param itemIndex the item index */ private void tellAdapterToMoveItemToNextPage(int itemIndex) { ItemPosition itemPosition = itemInformationAtPosition(itemIndex); adapter.moveItemToNextPage(itemPosition.pageIndex,itemPosition.itemIndex); } /** * The Class ItemPosition. */ private class ItemPosition { /** The page index. */ public int pageIndex; /** The item index. */ public int itemIndex; /** * Instantiates a new item position. * * @param pageIndex the page index * @param itemIndex the item index */ public ItemPosition(int pageIndex, int itemIndex) { super(); this.pageIndex = pageIndex; this.itemIndex = itemIndex; } } }