Home | History | Annotate | Download | only in view
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.view;
     18 
     19 import android.content.ClipData;
     20 import android.content.ClipDescription;
     21 import android.os.Parcel;
     22 import android.os.Parcelable;
     23 
     24 //TODO: Improve Javadoc
     25 /**
     26  * Represents an event that is sent out by the system at various times during a drag and drop
     27  * operation. It is a complex data structure that contains several important pieces of data about
     28  * the operation and the underlying data.
     29  * <p>
     30  *  View objects that receive a DragEvent call {@link #getAction()}, which returns
     31  *  an action type that indicates the state of the drag and drop operation. This allows a View
     32  *  object to react to a change in state by changing its appearance or performing other actions.
     33  *  For example, a View can react to the {@link #ACTION_DRAG_ENTERED} action type by
     34  *  by changing one or more colors in its displayed image.
     35  * </p>
     36  * <p>
     37  *  During a drag and drop operation, the system displays an image that the user drags. This image
     38  *  is called a drag shadow. Several action types reflect the position of the drag shadow relative
     39  *  to the View receiving the event.
     40  * </p>
     41  * <p>
     42  *  Most methods return valid data only for certain event actions. This is summarized in the
     43  *  following table. Each possible {@link #getAction()} value is listed in the first column. The
     44  *  other columns indicate which method or methods return valid data for that getAction() value:
     45  * </p>
     46  * <table>
     47  *  <tr>
     48  *      <th scope="col">getAction() Value</th>
     49  *      <th scope="col">getClipDescription()</th>
     50  *      <th scope="col">getLocalState()</th>
     51  *      <th scope="col">getX()</th>
     52  *      <th scope="col">getY()</th>
     53  *      <th scope="col">getClipData()</th>
     54  *      <th scope="col">getResult()</th>
     55  *  </tr>
     56  *  <tr>
     57  *      <td>ACTION_DRAG_STARTED</td>
     58  *      <td style="text-align: center;">X</td>
     59  *      <td style="text-align: center;">X</td>
     60  *      <td style="text-align: center;">X</td>
     61  *      <td style="text-align: center;">X</td>
     62  *      <td style="text-align: center;">&nbsp;</td>
     63  *      <td style="text-align: center;">&nbsp;</td>
     64  *  </tr>
     65  *  <tr>
     66  *      <td>ACTION_DRAG_ENTERED</td>
     67  *      <td style="text-align: center;">X</td>
     68  *      <td style="text-align: center;">X</td>
     69  *      <td style="text-align: center;">&nbsp;</td>
     70  *      <td style="text-align: center;">&nbsp;</td>
     71  *      <td style="text-align: center;">&nbsp;</td>
     72  *      <td style="text-align: center;">&nbsp;</td>
     73  *  </tr>
     74  *  <tr>
     75  *      <td>ACTION_DRAG_LOCATION</td>
     76  *      <td style="text-align: center;">X</td>
     77  *      <td style="text-align: center;">X</td>
     78  *      <td style="text-align: center;">X</td>
     79  *      <td style="text-align: center;">X</td>
     80  *      <td style="text-align: center;">&nbsp;</td>
     81  *      <td style="text-align: center;">&nbsp;</td>
     82  *  </tr>
     83  *  <tr>
     84  *      <td>ACTION_DRAG_EXITED</td>
     85  *      <td style="text-align: center;">X</td>
     86  *      <td style="text-align: center;">X</td>
     87  *      <td style="text-align: center;">&nbsp;</td>
     88  *      <td style="text-align: center;">&nbsp;</td>
     89  *      <td style="text-align: center;">&nbsp;</td>
     90  *      <td style="text-align: center;">&nbsp;</td>
     91  *  </tr>
     92  *  <tr>
     93  *      <td>ACTION_DROP</td>
     94  *      <td style="text-align: center;">X</td>
     95  *      <td style="text-align: center;">X</td>
     96  *      <td style="text-align: center;">X</td>
     97  *      <td style="text-align: center;">X</td>
     98  *      <td style="text-align: center;">X</td>
     99  *      <td style="text-align: center;">&nbsp;</td>
    100  *  </tr>
    101  *  <tr>
    102  *      <td>ACTION_DRAG_ENDED</td>
    103  *      <td style="text-align: center;">X</td>
    104  *      <td style="text-align: center;">X</td>
    105  *      <td style="text-align: center;">&nbsp;</td>
    106  *      <td style="text-align: center;">&nbsp;</td>
    107  *      <td style="text-align: center;">&nbsp;</td>
    108  *      <td style="text-align: center;">X</td>
    109  *  </tr>
    110  * </table>
    111  * <p>
    112  *  The {@link android.view.DragEvent#getAction()},
    113  *  {@link android.view.DragEvent#describeContents()},
    114  *  {@link android.view.DragEvent#writeToParcel(Parcel,int)}, and
    115  *  {@link android.view.DragEvent#toString()} methods always return valid data.
    116  * </p>
    117  *
    118  * <div class="special reference">
    119  * <h3>Developer Guides</h3>
    120  * <p>For a guide to implementing drag and drop features, read the
    121  * <a href="{@docRoot}guide/topics/ui/drag-drop.html">Drag and Drop</a> developer guide.</p>
    122  * </div>
    123  */
    124 public class DragEvent implements Parcelable {
    125     private static final boolean TRACK_RECYCLED_LOCATION = false;
    126 
    127     int mAction;
    128     float mX, mY;
    129     ClipDescription mClipDescription;
    130     ClipData mClipData;
    131     Object mLocalState;
    132     boolean mDragResult;
    133 
    134     private DragEvent mNext;
    135     private RuntimeException mRecycledLocation;
    136     private boolean mRecycled;
    137 
    138     private static final int MAX_RECYCLED = 10;
    139     private static final Object gRecyclerLock = new Object();
    140     private static int gRecyclerUsed = 0;
    141     private static DragEvent gRecyclerTop = null;
    142 
    143     /**
    144      * Action constant returned by {@link #getAction()}: Signals the start of a
    145      * drag and drop operation. The View should return {@code true} from its
    146      * {@link View#onDragEvent(DragEvent) onDragEvent()} handler method or
    147      * {@link View.View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()} listener
    148      * if it can accept a drop. The onDragEvent() or onDrag() methods usually inspect the metadata
    149      * from {@link #getClipDescription()} to determine if they can accept the data contained in
    150      * this drag. For an operation that doesn't represent data transfer, these methods may
    151      * perform other actions to determine whether or not the View should accept the drag.
    152      * If the View wants to indicate that it is a valid drop target, it can also react by
    153      * changing its appearance.
    154      * <p>
    155      * A View only receives further drag events if it returns {@code true} in response to
    156      * ACTION_DRAG_STARTED.
    157      * </p>
    158      * @see #ACTION_DRAG_ENDED
    159      */
    160     public static final int ACTION_DRAG_STARTED = 1;
    161 
    162     /**
    163      * Action constant returned by {@link #getAction()}: Sent to a View after
    164      * {@link #ACTION_DRAG_ENTERED} if the drag shadow is still within the View object's bounding
    165      * box. The {@link #getX()} and {@link #getY()} methods supply
    166      * the X and Y position of of the drag point within the View object's bounding box.
    167      * <p>
    168      * A View receives an {@link #ACTION_DRAG_ENTERED} event before receiving any
    169      * ACTION_DRAG_LOCATION events.
    170      * </p>
    171      * <p>
    172      * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the
    173      * drag shadow out of the View object's bounding box. If the user moves the drag shadow back
    174      * into the View object's bounding box, the View receives an ACTION_DRAG_ENTERED again before
    175      * receiving any more ACTION_DRAG_LOCATION events.
    176      * </p>
    177      * @see #ACTION_DRAG_ENTERED
    178      * @see #getX()
    179      * @see #getY()
    180      */
    181     public static final int ACTION_DRAG_LOCATION = 2;
    182 
    183     /**
    184      * Action constant returned by {@link #getAction()}: Signals to a View that the user
    185      * has released the drag shadow, and the drag point is within the bounding box of the View.
    186      * The View should retrieve the data from the DragEvent by calling {@link #getClipData()}.
    187      * The methods {@link #getX()} and {@link #getY()} return the X and Y position of the drop point
    188      * within the View object's bounding box.
    189      * <p>
    190      * The View should return {@code true} from its {@link View#onDragEvent(DragEvent)}
    191      * handler or {@link View.View.OnDragListener#onDrag(View,DragEvent) OnDragListener.onDrag()}
    192      * listener if it accepted the drop, and {@code false} if it ignored the drop.
    193      * </p>
    194      * <p>
    195      * The View can also react to this action by changing its appearance.
    196      * </p>
    197      * @see #getClipData()
    198      * @see #getX()
    199      * @see #getY()
    200      */
    201     public static final int ACTION_DROP = 3;
    202 
    203     /**
    204      * Action constant returned by {@link #getAction()}:  Signals to a View that the drag and drop
    205      * operation has concluded.  A View that changed its appearance during the operation should
    206      * return to its usual drawing state in response to this event.
    207      * <p>
    208      * All views that received an ACTION_DRAG_STARTED event will receive the
    209      * ACTION_DRAG_ENDED event even if they are not currently visible when the drag ends.
    210      * </p>
    211      * <p>
    212      *  The View object can call {@link #getResult()} to see the result of the operation.
    213      *  If a View returned {@code true} in response to {@link #ACTION_DROP}, then
    214      *  getResult() returns {@code true}, otherwise it returns {@code false}.
    215      * </p>
    216      * @see #ACTION_DRAG_STARTED
    217      * @see #getResult()
    218      */
    219     public static final int ACTION_DRAG_ENDED = 4;
    220 
    221     /**
    222      * Action constant returned by {@link #getAction()}: Signals to a View that the drag point has
    223      * entered the bounding box of the View.
    224      * <p>
    225      *  If the View can accept a drop, it can react to ACTION_DRAG_ENTERED
    226      *  by changing its appearance in a way that tells the user that the View is the current
    227      *  drop target.
    228      * </p>
    229      * The system stops sending ACTION_DRAG_LOCATION events to a View once the user moves the
    230      * drag shadow out of the View object's bounding box. If the user moves the drag shadow back
    231      * into the View object's bounding box, the View receives an ACTION_DRAG_ENTERED again before
    232      * receiving any more ACTION_DRAG_LOCATION events.
    233      * </p>
    234      * @see #ACTION_DRAG_ENTERED
    235      * @see #ACTION_DRAG_LOCATION
    236      */
    237     public static final int ACTION_DRAG_ENTERED = 5;
    238 
    239     /**
    240      * Action constant returned by {@link #getAction()}: Signals that the user has moved the
    241      * drag shadow outside the bounding box of the View.
    242      * The View can react by changing its appearance in a way that tells the user that
    243      * View is no longer the immediate drop target.
    244      * <p>
    245      *  After the system sends an ACTION_DRAG_EXITED event to the View, the View receives no more
    246      *  ACTION_DRAG_LOCATION events until the user drags the drag shadow back over the View.
    247      * </p>
    248      *
    249      */
    250      public static final int ACTION_DRAG_EXITED = 6;
    251 
    252     private DragEvent() {
    253     }
    254 
    255     private void init(int action, float x, float y, ClipDescription description, ClipData data,
    256             Object localState, boolean result) {
    257         mAction = action;
    258         mX = x;
    259         mY = y;
    260         mClipDescription = description;
    261         mClipData = data;
    262         mLocalState = localState;
    263         mDragResult = result;
    264     }
    265 
    266     static DragEvent obtain() {
    267         return DragEvent.obtain(0, 0f, 0f, null, null, null, false);
    268     }
    269 
    270     /** @hide */
    271     public static DragEvent obtain(int action, float x, float y, Object localState,
    272             ClipDescription description, ClipData data, boolean result) {
    273         final DragEvent ev;
    274         synchronized (gRecyclerLock) {
    275             if (gRecyclerTop == null) {
    276                 ev = new DragEvent();
    277                 ev.init(action, x, y, description, data, localState, result);
    278                 return ev;
    279             }
    280             ev = gRecyclerTop;
    281             gRecyclerTop = ev.mNext;
    282             gRecyclerUsed -= 1;
    283         }
    284         ev.mRecycledLocation = null;
    285         ev.mRecycled = false;
    286         ev.mNext = null;
    287 
    288         ev.init(action, x, y, description, data, localState, result);
    289 
    290         return ev;
    291     }
    292 
    293     /** @hide */
    294     public static DragEvent obtain(DragEvent source) {
    295         return obtain(source.mAction, source.mX, source.mY, source.mLocalState,
    296                 source.mClipDescription, source.mClipData, source.mDragResult);
    297     }
    298 
    299     /**
    300      * Inspect the action value of this event.
    301      * @return One of the following action constants, in the order in which they usually occur
    302      * during a drag and drop operation:
    303      * <ul>
    304      *  <li>{@link #ACTION_DRAG_STARTED}</li>
    305      *  <li>{@link #ACTION_DRAG_ENTERED}</li>
    306      *  <li>{@link #ACTION_DRAG_LOCATION}</li>
    307      *  <li>{@link #ACTION_DROP}</li>
    308      *  <li>{@link #ACTION_DRAG_EXITED}</li>
    309      *  <li>{@link #ACTION_DRAG_ENDED}</li>
    310      * </ul>
    311      */
    312     public int getAction() {
    313         return mAction;
    314     }
    315 
    316     /**
    317      * Gets the X coordinate of the drag point. The value is only valid if the event action is
    318      * {@link #ACTION_DRAG_LOCATION} or {@link #ACTION_DROP}.
    319      * @return The current drag point's Y coordinate
    320      */
    321     public float getX() {
    322         return mX;
    323     }
    324 
    325     /**
    326      * Gets the Y coordinate of the drag point. The value is valid if the
    327      * event action is {@link #ACTION_DRAG_ENTERED}, {@link #ACTION_DRAG_LOCATION},
    328      * {@link #ACTION_DROP}, or {@link #ACTION_DRAG_EXITED}.
    329      * @return The current drag point's Y coordinate
    330      */
    331     public float getY() {
    332         return mY;
    333     }
    334 
    335     /**
    336      * Returns the {@link android.content.ClipData} object sent to the system as part of the call
    337      * to
    338      * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
    339      * This method only returns valid data if the event action is {@link #ACTION_DROP}.
    340      * @return The ClipData sent to the system by startDrag().
    341      */
    342     public ClipData getClipData() {
    343         return mClipData;
    344     }
    345 
    346     /**
    347      * Returns the {@link android.content.ClipDescription} object contained in the
    348      * {@link android.content.ClipData} object sent to the system as part of the call to
    349      * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
    350      * The drag handler or listener for a View can use the metadata in this object to decide if the
    351      * View can accept the dragged View object's data.
    352      * <p>
    353      * This method returns valid data for all event actions.
    354      * @return The ClipDescription that was part of the ClipData sent to the system by startDrag().
    355      */
    356     public ClipDescription getClipDescription() {
    357         return mClipDescription;
    358     }
    359 
    360     /**
    361      * Returns the local state object sent to the system as part of the call to
    362      * {@link android.view.View#startDrag(ClipData,View.DragShadowBuilder,Object,int) startDrag()}.
    363      * The object is intended to provide local information about the drag and drop operation. For
    364      * example, it can indicate whether the drag and drop operation is a copy or a move.
    365      * <p>
    366      *  This method returns valid data for all event actions.
    367      * </p>
    368      * @return The local state object sent to the system by startDrag().
    369      */
    370     public Object getLocalState() {
    371         return mLocalState;
    372     }
    373 
    374     /**
    375      * <p>
    376      * Returns an indication of the result of the drag and drop operation.
    377      * This method only returns valid data if the action type is {@link #ACTION_DRAG_ENDED}.
    378      * The return value depends on what happens after the user releases the drag shadow.
    379      * </p>
    380      * <p>
    381      * If the user releases the drag shadow on a View that can accept a drop, the system sends an
    382      * {@link #ACTION_DROP} event to the View object's drag event listener. If the listener
    383      * returns {@code true}, then getResult() will return {@code true}.
    384      * If the listener returns {@code false}, then getResult() returns {@code false}.
    385      * </p>
    386      * <p>
    387      * Notice that getResult() also returns {@code false} if no {@link #ACTION_DROP} is sent. This
    388      * happens, for example, when the user releases the drag shadow over an area outside of the
    389      * application. In this case, the system sends out {@link #ACTION_DRAG_ENDED} for the current
    390      * operation, but never sends out {@link #ACTION_DROP}.
    391      * </p>
    392      * @return {@code true} if a drag event listener returned {@code true} in response to
    393      * {@link #ACTION_DROP}. If the system did not send {@link #ACTION_DROP} before
    394      * {@link #ACTION_DRAG_ENDED}, or if the listener returned {@code false} in response to
    395      * {@link #ACTION_DROP}, then {@code false} is returned.
    396      */
    397     public boolean getResult() {
    398         return mDragResult;
    399     }
    400 
    401     /**
    402      * Recycle the DragEvent, to be re-used by a later caller.  After calling
    403      * this function you must never touch the event again.
    404      *
    405      * @hide
    406      */
    407     public final void recycle() {
    408         // Ensure recycle is only called once!
    409         if (TRACK_RECYCLED_LOCATION) {
    410             if (mRecycledLocation != null) {
    411                 throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation);
    412             }
    413             mRecycledLocation = new RuntimeException("Last recycled here");
    414         } else {
    415             if (mRecycled) {
    416                 throw new RuntimeException(toString() + " recycled twice!");
    417             }
    418             mRecycled = true;
    419         }
    420 
    421         mClipData = null;
    422         mClipDescription = null;
    423         mLocalState = null;
    424 
    425         synchronized (gRecyclerLock) {
    426             if (gRecyclerUsed < MAX_RECYCLED) {
    427                 gRecyclerUsed++;
    428                 mNext = gRecyclerTop;
    429                 gRecyclerTop = this;
    430             }
    431         }
    432     }
    433 
    434     /**
    435      * Returns a string containing a concise, human-readable representation of this DragEvent
    436      * object.
    437      * @return A string representation of the DragEvent object.
    438      */
    439     @Override
    440     public String toString() {
    441         return "DragEvent{" + Integer.toHexString(System.identityHashCode(this))
    442         + " action=" + mAction + " @ (" + mX + ", " + mY + ") desc=" + mClipDescription
    443         + " data=" + mClipData + " local=" + mLocalState + " result=" + mDragResult
    444         + "}";
    445     }
    446 
    447     /* Parcelable interface */
    448 
    449     /**
    450      * Returns information about the {@link android.os.Parcel} representation of this DragEvent
    451      * object.
    452      * @return Information about the {@link android.os.Parcel} representation.
    453      */
    454     public int describeContents() {
    455         return 0;
    456     }
    457 
    458     /**
    459      * Creates a {@link android.os.Parcel} object from this DragEvent object.
    460      * @param dest A {@link android.os.Parcel} object in which to put the DragEvent object.
    461      * @param flags Flags to store in the Parcel.
    462      */
    463     public void writeToParcel(Parcel dest, int flags) {
    464         dest.writeInt(mAction);
    465         dest.writeFloat(mX);
    466         dest.writeFloat(mY);
    467         dest.writeInt(mDragResult ? 1 : 0);
    468         if (mClipData == null) {
    469             dest.writeInt(0);
    470         } else {
    471             dest.writeInt(1);
    472             mClipData.writeToParcel(dest, flags);
    473         }
    474         if (mClipDescription == null) {
    475             dest.writeInt(0);
    476         } else {
    477             dest.writeInt(1);
    478             mClipDescription.writeToParcel(dest, flags);
    479         }
    480     }
    481 
    482     /**
    483      * A container for creating a DragEvent from a Parcel.
    484      */
    485     public static final Parcelable.Creator<DragEvent> CREATOR =
    486         new Parcelable.Creator<DragEvent>() {
    487         public DragEvent createFromParcel(Parcel in) {
    488             DragEvent event = DragEvent.obtain();
    489             event.mAction = in.readInt();
    490             event.mX = in.readFloat();
    491             event.mY = in.readFloat();
    492             event.mDragResult = (in.readInt() != 0);
    493             if (in.readInt() != 0) {
    494                 event.mClipData = ClipData.CREATOR.createFromParcel(in);
    495             }
    496             if (in.readInt() != 0) {
    497                 event.mClipDescription = ClipDescription.CREATOR.createFromParcel(in);
    498             }
    499             return event;
    500         }
    501 
    502         public DragEvent[] newArray(int size) {
    503             return new DragEvent[size];
    504         }
    505     };
    506 }
    507