1 /* 2 * Copyright (C) 2016 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 androidx.core.view; 18 19 20 import android.graphics.Point; 21 import android.view.MotionEvent; 22 import android.view.View; 23 24 /** 25 * DragStartHelper is a utility class for implementing drag and drop support. 26 * <p> 27 * It detects gestures commonly used to start drag (long click for any input source, 28 * click and drag for mouse). 29 * <p> 30 * It also keeps track of the screen location where the drag started, and helps determining 31 * the hot spot position for a drag shadow. 32 * <p> 33 * Implement {@link DragStartHelper.OnDragStartListener} to start the drag operation: 34 * <pre> 35 * DragStartHelper.OnDragStartListener listener = new DragStartHelper.OnDragStartListener { 36 * protected void onDragStart(View view, DragStartHelper helper) { 37 * View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) { 38 * public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { 39 * super.onProvideShadowMetrics(shadowSize, shadowTouchPoint); 40 * helper.getTouchPosition(shadowTouchPoint); 41 * } 42 * }; 43 * view.startDrag(mClipData, shadowBuilder, mLocalState, mDragFlags); 44 * } 45 * }; 46 * mDragStartHelper = new DragStartHelper(mDraggableView, listener); 47 * </pre> 48 * Once created, DragStartHelper can be attached to a view (this will replace existing long click 49 * and touch listeners): 50 * <pre> 51 * mDragStartHelper.attach(); 52 * </pre> 53 * It may also be used in combination with existing listeners: 54 * <pre> 55 * public boolean onTouch(View view, MotionEvent event) { 56 * if (mDragStartHelper.onTouch(view, event)) { 57 * return true; 58 * } 59 * return handleTouchEvent(view, event); 60 * } 61 * public boolean onLongClick(View view) { 62 * if (mDragStartHelper.onLongClick(view)) { 63 * return true; 64 * } 65 * return handleLongClickEvent(view); 66 * } 67 * </pre> 68 */ 69 public class DragStartHelper { 70 private final View mView; 71 private final OnDragStartListener mListener; 72 73 private int mLastTouchX, mLastTouchY; 74 private boolean mDragging; 75 76 /** 77 * Interface definition for a callback to be invoked when a drag start gesture is detected. 78 */ 79 public interface OnDragStartListener { 80 /** 81 * Called when a drag start gesture has been detected. 82 * 83 * @param v The view over which the drag start gesture has been detected. 84 * @param helper The DragStartHelper object which detected the gesture. 85 * @return True if the listener has started the drag operation, false otherwise. 86 */ 87 boolean onDragStart(View v, DragStartHelper helper); 88 } 89 90 /** 91 * Create a DragStartHelper associated with the specified view. 92 * The newly created helper is not initially attached to the view, {@link #attach} must be 93 * called explicitly. 94 * @param view A View 95 */ 96 public DragStartHelper(View view, OnDragStartListener listener) { 97 mView = view; 98 mListener = listener; 99 } 100 101 /** 102 * Attach the helper to the view. 103 * <p> 104 * This will replace previously existing touch and long click listeners. 105 */ 106 public void attach() { 107 mView.setOnLongClickListener(mLongClickListener); 108 mView.setOnTouchListener(mTouchListener); 109 } 110 111 /** 112 * Detach the helper from the view. 113 * <p> 114 * This will reset touch and long click listeners to {@code null}. 115 */ 116 public void detach() { 117 mView.setOnLongClickListener(null); 118 mView.setOnTouchListener(null); 119 } 120 121 /** 122 * Handle a touch event. 123 * @param v The view the touch event has been dispatched to. 124 * @param event The MotionEvent object containing full information about 125 * the event. 126 * @return True if the listener has consumed the event, false otherwise. 127 */ 128 public boolean onTouch(View v, MotionEvent event) { 129 final int x = (int) event.getX(); 130 final int y = (int) event.getY(); 131 switch (event.getAction()) { 132 case MotionEvent.ACTION_DOWN: 133 mLastTouchX = x; 134 mLastTouchY = y; 135 break; 136 137 case MotionEvent.ACTION_MOVE: 138 if (!MotionEventCompat.isFromSource(event, InputDeviceCompat.SOURCE_MOUSE) 139 || (event.getButtonState() 140 & MotionEvent.BUTTON_PRIMARY) == 0) { 141 break; 142 } 143 if (mDragging) { 144 // Ignore ACTION_MOVE events once the drag operation is in progress. 145 break; 146 } 147 if (mLastTouchX == x && mLastTouchY == y) { 148 // Do not call the listener unless the pointer position has actually changed. 149 break; 150 } 151 mLastTouchX = x; 152 mLastTouchY = y; 153 mDragging = mListener.onDragStart(v, this); 154 return mDragging; 155 156 case MotionEvent.ACTION_UP: 157 case MotionEvent.ACTION_CANCEL: 158 mDragging = false; 159 break; 160 } 161 return false; 162 } 163 164 /** 165 * Handle a long click event. 166 * @param v The view that was clicked and held. 167 * @return true if the callback consumed the long click, false otherwise. 168 */ 169 public boolean onLongClick(View v) { 170 return mListener.onDragStart(v, this); 171 } 172 173 /** 174 * Compute the position of the touch event that started the drag operation. 175 * @param point The position of the touch event that started the drag operation. 176 */ 177 public void getTouchPosition(Point point) { 178 point.set(mLastTouchX, mLastTouchY); 179 } 180 181 private final View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() { 182 @Override 183 public boolean onLongClick(View v) { 184 return DragStartHelper.this.onLongClick(v); 185 } 186 }; 187 188 private final View.OnTouchListener mTouchListener = new View.OnTouchListener() { 189 @Override 190 public boolean onTouch(View v, MotionEvent event) { 191 return DragStartHelper.this.onTouch(v, event); 192 } 193 }; 194 } 195 196