1 /* 2 * Copyright (C) 2013 Google Inc. All Rights Reserved. 3 */ 4 5 package com.android.deskclock.widget.sgv; 6 7 import android.graphics.Point; 8 import android.util.Log; 9 import android.view.View; 10 11 import com.android.deskclock.widget.sgv.StaggeredGridView.LayoutParams; 12 import com.android.deskclock.widget.sgv.StaggeredGridView.ReorderListener; 13 14 15 /** 16 * Helper class for doing reorder animations. Works out the logical position of 17 * where an item would be placed as the user drags it around. 18 */ 19 public final class ReorderHelper { 20 21 private static final String TAG = "DeskClock"; 22 23 /** 24 * Constant to indicate an unsupported reordering position. 25 */ 26 public static final int INVALID_REORDER_POS = -2; 27 28 private final ReorderListener mReorderListener; 29 30 // Current {@link ReorderView} that is currently being dragged over. If drag is released here, 31 // and this child supports reordering, the dragged view will be reordered to be next 32 // to this child. 33 private ReorderView mCurrentDraggedOverChild; 34 35 // The current child that is being dragged for reordering. 36 private ReorderView mDraggedChild; 37 38 // The id of mDraggedChild. 39 private long mDraggedChildId = -1; 40 41 // The parent view group that dragged children are attached to. 42 private final StaggeredGridView mParentView; 43 44 private boolean mEnableUpdatesOnDrag = true; 45 ReorderHelper(ReorderListener listener, StaggeredGridView parentView)46 public ReorderHelper(ReorderListener listener, StaggeredGridView parentView) { 47 mReorderListener = listener; 48 mParentView = parentView; 49 if (listener == null) { 50 throw new IllegalArgumentException("ReorderListener cannot be null"); 51 } 52 53 if (parentView == null) { 54 throw new IllegalArgumentException("ParentView cannot be null"); 55 } 56 } 57 58 /** 59 * Handle dropping the dragged child. 60 * @return true if the drop results in a reordering, false otherwise. 61 */ handleDrop(Point p)62 public boolean handleDrop(Point p) { 63 View reorderTarget = null; 64 if (mCurrentDraggedOverChild != null) { 65 reorderTarget = getReorderableChildAtCoordinate(p); 66 } else { 67 Log.w(TAG, "Current dragged over child does not exist"); 68 } 69 70 // If reorder target is null, the drag coordinate is not over any 71 // reordering areas. Don't update dragged over child if its the same as 72 // it was before or is the same as the child's original item. 73 if (reorderTarget != null) { 74 final LayoutParams lp = (LayoutParams) reorderTarget.getLayoutParams(); 75 // Ensure that target position is not the same as the original, 76 // since that's a no-op. 77 if (lp.position != mCurrentDraggedOverChild.position) { 78 updateDraggedOverChild(reorderTarget); 79 } 80 } 81 82 if (mCurrentDraggedOverChild != null && 83 mDraggedChild.position != mCurrentDraggedOverChild.position) { 84 return mReorderListener.onReorder(mDraggedChild.target, mDraggedChild.id, 85 mDraggedChild.position, 86 mCurrentDraggedOverChild.position); 87 } else { 88 // Even if the dragged child is not dropped in a reorder area, we 89 // would still need to notify the listener of the drop event. 90 mReorderListener.onDrop(mDraggedChild.target, mDraggedChild.position, 91 mCurrentDraggedOverChild.position); 92 return false; 93 } 94 } 95 handleDragCancelled(View draggedView)96 public void handleDragCancelled(View draggedView) { 97 mReorderListener.onCancelDrag(draggedView); 98 } 99 handleDragStart(View view, int pos, long id, Point p)100 public void handleDragStart(View view, int pos, long id, Point p) { 101 mDraggedChild = new ReorderView(view, pos, id); 102 mDraggedChildId = id; 103 mCurrentDraggedOverChild = new ReorderView(view, pos, id); 104 mReorderListener.onPickedUp(mDraggedChild.target); 105 } 106 107 /** 108 * Handles determining which child views should be moved out of the way to 109 * make space for a reordered item and updates the ReorderListener when a 110 * new child view's space is entered by the dragging view. 111 */ handleDrag(Point p)112 public void handleDrag(Point p) { 113 if (p == null || p.y < 0 && p.y > mParentView.getHeight()) { 114 // If the user drags off screen, DragEvent.ACTION_DRAG_ENDED, would be called, so we'll 115 // treat it as though the user has released drag. 116 handleDrop(p); 117 return; 118 } 119 120 if (!mEnableUpdatesOnDrag) { 121 return; 122 } 123 124 View reorderTarget = null; 125 if (mCurrentDraggedOverChild != null) { 126 reorderTarget = getReorderableChildAtCoordinate(p); 127 } else { 128 Log.w(TAG, "Current dragged over child does not exist"); 129 } 130 131 // If reorder target is null, the drag coordinate is not over any 132 // reordering areas. Don't update dragged over child if its the same as 133 // it was before or is the same as the child's original item. 134 if (reorderTarget != null) { 135 final LayoutParams lp = (LayoutParams) reorderTarget.getLayoutParams(); 136 if (lp.position != mCurrentDraggedOverChild.position) { 137 updateDraggedOverChild(reorderTarget); 138 // Ensure that target position is not the same as the original, 139 // since that's a no-op. 140 mReorderListener.onEnterReorderArea(reorderTarget, lp.position); 141 } 142 } 143 } 144 145 /** 146 * Enable updates on drag events. If set to false, handleDrag will not update place holder 147 */ enableUpdatesOnDrag(boolean enabled)148 public void enableUpdatesOnDrag(boolean enabled) { 149 mEnableUpdatesOnDrag = enabled; 150 } 151 152 /** 153 * Clear dragged over child info 154 */ clearDraggedOverChild()155 public void clearDraggedOverChild() { 156 mCurrentDraggedOverChild = null; 157 } 158 159 /** 160 * Return if the currently dragged view is over a valid reordering area. 161 */ isOverReorderingArea()162 public boolean isOverReorderingArea() { 163 return mCurrentDraggedOverChild != null; 164 } 165 166 /** 167 * Get the position of the child that is being dragged over. If there isn't one, returns 168 * {@link #INVALID_REORDER_POS} 169 */ getCurrentDraggedOverChildPosition()170 public int getCurrentDraggedOverChildPosition() { 171 if (mCurrentDraggedOverChild != null) { 172 return mCurrentDraggedOverChild.position; 173 } 174 175 return INVALID_REORDER_POS; 176 } 177 178 /** 179 * Get the id of the child that is being dragged. If there isn't one, returns -1 180 */ getDraggedChildId()181 public long getDraggedChildId() { 182 return mDraggedChildId; 183 } 184 185 /** 186 * Get the original view of the child that is being dragged. If there isn't 187 * one, returns null 188 */ getDraggedChild()189 public View getDraggedChild() { 190 return mDraggedChild != null ? mDraggedChild.target : null; 191 } 192 193 /** 194 * Clear original dragged child info 195 */ clearDraggedChild()196 public void clearDraggedChild() { 197 mDraggedChild = null; 198 } 199 200 // TODO: Consolidate clearDraggedChild() and clearDraggedChildId(). clearDraggedChildId()201 public void clearDraggedChildId() { 202 mDraggedChildId = -1; 203 } 204 205 /** 206 * Get the original position of the child that is being dragged. If there isn't one, returns 207 * {@link #INVALID_REORDER_POS}; 208 */ getDraggedChildPosition()209 public int getDraggedChildPosition() { 210 return mDraggedChild != null ? mDraggedChild.position : INVALID_REORDER_POS; 211 } 212 updateDraggedChildView(View v)213 public void updateDraggedChildView(View v) { 214 if (mDraggedChild != null && v != mDraggedChild.target) { 215 mDraggedChild.target = v; 216 } 217 } 218 updateDraggedOverChildView(View v)219 public void updateDraggedOverChildView(View v) { 220 if (mCurrentDraggedOverChild != null && v != mCurrentDraggedOverChild.target) { 221 mCurrentDraggedOverChild.target = v; 222 } 223 } 224 225 /** 226 * Update the current view that is being dragged over, and clean up all drag and hover 227 * UI states from other sibling views. 228 * @param child The new child that is being dragged over. 229 */ updateDraggedOverChild(View child)230 private void updateDraggedOverChild(View child) { 231 final LayoutParams childLayoutParam = (LayoutParams) child.getLayoutParams(); 232 mCurrentDraggedOverChild = new ReorderView( 233 child, childLayoutParam.position, childLayoutParam.id); 234 } 235 236 /** 237 * Return the child view specified by the coordinates if 238 * there exists a child there. 239 * 240 * @return the child in this StaggeredGridView at the coordinates, null otherwise. 241 */ getReorderableChildAtCoordinate(Point p)242 public View getReorderableChildAtCoordinate(Point p) { 243 if (p == null || p.y < 0) { 244 // TODO: If we've dragged off the screen, return null for now until we know what 245 // we'd like the experience to be like. 246 return null; 247 } 248 249 final int count = mParentView.getChildCount(); 250 for (int i = 0; i < count; i++) { 251 if (!mParentView.isChildReorderable(i)) { 252 continue; 253 } 254 final View childView = mParentView.getChildAt(i); 255 if (p.x >= childView.getLeft() && p.x < childView.getRight() 256 && p.y >= childView.getTop() && p.y < childView.getBottom()) { 257 return childView; 258 } 259 } 260 261 return null; 262 } 263 hasReorderListener()264 public boolean hasReorderListener() { 265 return mReorderListener != null; 266 } 267 268 private class ReorderView { 269 final long id; 270 final int position; 271 View target; ReorderView(View v, int pos, long i)272 public ReorderView(View v, int pos, long i) { 273 target = v; 274 position = pos; 275 id = i; 276 } 277 } 278 }