Home | History | Annotate | Download | only in sgv
      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 
     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      */
     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 
     96     public void handleDragCancelled(View draggedView) {
     97         mReorderListener.onCancelDrag(draggedView);
     98     }
     99 
    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      */
    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      */
    148     public void enableUpdatesOnDrag(boolean enabled) {
    149         mEnableUpdatesOnDrag = enabled;
    150     }
    151 
    152     /**
    153      * Clear dragged over child info
    154      */
    155     public void clearDraggedOverChild() {
    156         mCurrentDraggedOverChild = null;
    157     }
    158 
    159     /**
    160      * Return if the currently dragged view is over a valid reordering area.
    161      */
    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      */
    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      */
    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      */
    189     public View getDraggedChild() {
    190         return mDraggedChild != null ? mDraggedChild.target : null;
    191     }
    192 
    193     /**
    194      * Clear original dragged child info
    195      */
    196     public void clearDraggedChild() {
    197         mDraggedChild = null;
    198     }
    199 
    200     // TODO: Consolidate clearDraggedChild() and 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      */
    209     public int getDraggedChildPosition() {
    210         return mDraggedChild != null ? mDraggedChild.position : INVALID_REORDER_POS;
    211     }
    212 
    213     public void updateDraggedChildView(View v) {
    214         if (mDraggedChild != null && v != mDraggedChild.target) {
    215             mDraggedChild.target = v;
    216         }
    217     }
    218 
    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      */
    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      */
    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 
    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;
    272         public ReorderView(View v, int pos, long i) {
    273             target = v;
    274             position = pos;
    275             id = i;
    276         }
    277     }
    278 }