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 com.android.launcher3.dragndrop; 18 19 import android.graphics.PointF; 20 import android.os.SystemClock; 21 import android.view.DragEvent; 22 import android.view.MotionEvent; 23 import android.view.VelocityTracker; 24 import android.view.ViewConfiguration; 25 26 import com.android.launcher3.ButtonDropTarget; 27 import com.android.launcher3.DropTarget; 28 import com.android.launcher3.Launcher; 29 import com.android.launcher3.R; 30 import com.android.launcher3.util.FlingAnimation; 31 32 /** 33 * Utility class to manage fling to delete action during drag and drop. 34 */ 35 public class FlingToDeleteHelper { 36 37 private static final float MAX_FLING_DEGREES = 35f; 38 39 private final Launcher mLauncher; 40 private final int mFlingToDeleteThresholdVelocity; 41 42 private ButtonDropTarget mDropTarget; 43 private VelocityTracker mVelocityTracker; 44 FlingToDeleteHelper(Launcher launcher)45 public FlingToDeleteHelper(Launcher launcher) { 46 mLauncher = launcher; 47 mFlingToDeleteThresholdVelocity = launcher.getResources() 48 .getDimensionPixelSize(R.dimen.drag_flingToDeleteMinVelocity); 49 } 50 recordMotionEvent(MotionEvent ev)51 public void recordMotionEvent(MotionEvent ev) { 52 if (mVelocityTracker == null) { 53 mVelocityTracker = VelocityTracker.obtain(); 54 } 55 mVelocityTracker.addMovement(ev); 56 } 57 58 /** 59 * Same as {@link #recordMotionEvent}. It creates a temporary {@link MotionEvent} object 60 * using {@param event} for tracking velocity. 61 */ recordDragEvent(long dragStartTime, DragEvent event)62 public void recordDragEvent(long dragStartTime, DragEvent event) { 63 final int motionAction; 64 switch (event.getAction()) { 65 case DragEvent.ACTION_DRAG_STARTED: 66 motionAction = MotionEvent.ACTION_DOWN; 67 break; 68 case DragEvent.ACTION_DRAG_LOCATION: 69 motionAction = MotionEvent.ACTION_MOVE; 70 break; 71 case DragEvent.ACTION_DRAG_ENDED: 72 motionAction = MotionEvent.ACTION_UP; 73 break; 74 default: 75 return; 76 } 77 MotionEvent emulatedEvent = MotionEvent.obtain(dragStartTime, SystemClock.uptimeMillis(), 78 motionAction, event.getX(), event.getY(), 0); 79 recordMotionEvent(emulatedEvent); 80 emulatedEvent.recycle(); 81 } 82 releaseVelocityTracker()83 public void releaseVelocityTracker() { 84 if (mVelocityTracker != null) { 85 mVelocityTracker.recycle(); 86 mVelocityTracker = null; 87 } 88 } 89 getDropTarget()90 public DropTarget getDropTarget() { 91 return mDropTarget; 92 } 93 getFlingAnimation(DropTarget.DragObject dragObject, DragOptions options)94 public Runnable getFlingAnimation(DropTarget.DragObject dragObject, DragOptions options) { 95 PointF vel = isFlingingToDelete(); 96 options.isFlingToDelete = vel != null; 97 if (!options.isFlingToDelete) { 98 return null; 99 } 100 return new FlingAnimation(dragObject, vel, mDropTarget, mLauncher, options); 101 } 102 103 /** 104 * Determines whether the user flung the current item to delete it. 105 * 106 * @return the vector at which the item was flung, or null if no fling was detected. 107 */ isFlingingToDelete()108 private PointF isFlingingToDelete() { 109 if (mVelocityTracker == null) return null; 110 if (mDropTarget == null) { 111 mDropTarget = (ButtonDropTarget) mLauncher.findViewById(R.id.delete_target_text); 112 } 113 if (mDropTarget == null || !mDropTarget.isDropEnabled()) return null; 114 ViewConfiguration config = ViewConfiguration.get(mLauncher); 115 mVelocityTracker.computeCurrentVelocity(1000, config.getScaledMaximumFlingVelocity()); 116 PointF vel = new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); 117 float theta = MAX_FLING_DEGREES + 1; 118 if (mVelocityTracker.getYVelocity() < mFlingToDeleteThresholdVelocity) { 119 // Do a quick dot product test to ensure that we are flinging upwards 120 PointF upVec = new PointF(0f, -1f); 121 theta = getAngleBetweenVectors(vel, upVec); 122 } else if (mLauncher.getDeviceProfile().isVerticalBarLayout() && 123 mVelocityTracker.getXVelocity() < mFlingToDeleteThresholdVelocity) { 124 // Remove icon is on left side instead of top, so check if we are flinging to the left. 125 PointF leftVec = new PointF(-1f, 0f); 126 theta = getAngleBetweenVectors(vel, leftVec); 127 } 128 if (theta <= Math.toRadians(MAX_FLING_DEGREES)) { 129 return vel; 130 } 131 return null; 132 } 133 getAngleBetweenVectors(PointF vec1, PointF vec2)134 private float getAngleBetweenVectors(PointF vec1, PointF vec2) { 135 return (float) Math.acos(((vec1.x * vec2.x) + (vec1.y * vec2.y)) / 136 (vec1.length() * vec2.length())); 137 } 138 } 139