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 com.android.launcher2;
18 
19 import android.content.Context;
20 import android.util.AttributeSet;
21 import android.view.MotionEvent;
22 import android.view.View;
23 
24 
25 /* Class that does most of the work of enabling dragging items out of a PagedView by performing a
26  * vertical drag. Used by both CustomizePagedView and AllAppsPagedView.
27  * Subclasses must do the following:
28  *   * call setDragSlopeThreshold after making an instance of the PagedViewWithDraggableItems
29  *   * call child.setOnLongClickListener(this) and child.setOnTouchListener(this) on all children
30  *       (good place to do it is in syncPageItems)
31  *   * override beginDragging(View) (but be careful to call super.beginDragging(View)
32  *
33  */
34 public abstract class PagedViewWithDraggableItems extends PagedView
35     implements View.OnLongClickListener, View.OnTouchListener {
36     private View mLastTouchedItem;
37     private boolean mIsDragging;
38     private boolean mIsDragEnabled;
39     private float mDragSlopeThreshold;
40     private Launcher mLauncher;
41 
PagedViewWithDraggableItems(Context context)42     public PagedViewWithDraggableItems(Context context) {
43         this(context, null);
44     }
45 
PagedViewWithDraggableItems(Context context, AttributeSet attrs)46     public PagedViewWithDraggableItems(Context context, AttributeSet attrs) {
47         this(context, attrs, 0);
48     }
49 
PagedViewWithDraggableItems(Context context, AttributeSet attrs, int defStyle)50     public PagedViewWithDraggableItems(Context context, AttributeSet attrs, int defStyle) {
51         super(context, attrs, defStyle);
52         mLauncher = (Launcher) context;
53     }
54 
beginDragging(View v)55     protected boolean beginDragging(View v) {
56         boolean wasDragging = mIsDragging;
57         mIsDragging = true;
58         return !wasDragging;
59     }
60 
cancelDragging()61     protected void cancelDragging() {
62         mIsDragging = false;
63         mLastTouchedItem = null;
64         mIsDragEnabled = false;
65     }
66 
handleTouchEvent(MotionEvent ev)67     private void handleTouchEvent(MotionEvent ev) {
68         final int action = ev.getAction();
69         switch (action & MotionEvent.ACTION_MASK) {
70             case MotionEvent.ACTION_DOWN:
71                 cancelDragging();
72                 mIsDragEnabled = true;
73                 break;
74             case MotionEvent.ACTION_MOVE:
75                 if (mTouchState != TOUCH_STATE_SCROLLING && !mIsDragging && mIsDragEnabled) {
76                     determineDraggingStart(ev);
77                 }
78                 break;
79         }
80     }
81 
82     @Override
onInterceptTouchEvent(MotionEvent ev)83     public boolean onInterceptTouchEvent(MotionEvent ev) {
84         handleTouchEvent(ev);
85         return super.onInterceptTouchEvent(ev);
86     }
87 
88     @Override
onTouchEvent(MotionEvent ev)89     public boolean onTouchEvent(MotionEvent ev) {
90         handleTouchEvent(ev);
91         return super.onTouchEvent(ev);
92     }
93 
94     @Override
onTouch(View v, MotionEvent event)95     public boolean onTouch(View v, MotionEvent event) {
96         mLastTouchedItem = v;
97         mIsDragEnabled = true;
98         return false;
99     }
100 
101     @Override
onLongClick(View v)102     public boolean onLongClick(View v) {
103         // Return early if this is not initiated from a touch
104         if (!v.isInTouchMode()) return false;
105         // Return early if we are still animating the pages
106         if (mNextPage != INVALID_PAGE) return false;
107         // When we have exited all apps or are in transition, disregard long clicks
108         if (!mLauncher.isAllAppsVisible() ||
109                 mLauncher.getWorkspace().isSwitchingState()) return false;
110         // Return if global dragging is not enabled
111         if (!mLauncher.isDraggingEnabled()) return false;
112 
113         return beginDragging(v);
114     }
115 
116     /*
117      * Determines if we should change the touch state to start scrolling after the
118      * user moves their touch point too far.
119      */
determineScrollingStart(MotionEvent ev)120     protected void determineScrollingStart(MotionEvent ev) {
121         if (!mIsDragging) super.determineScrollingStart(ev);
122     }
123 
124     /*
125      * Determines if we should change the touch state to start dragging after the
126      * user moves their touch point far enough.
127      */
determineDraggingStart(MotionEvent ev)128     protected void determineDraggingStart(MotionEvent ev) {
129         /*
130          * Locally do absolute value. mLastMotionX is set to the y value
131          * of the down event.
132          */
133         final int pointerIndex = ev.findPointerIndex(mActivePointerId);
134         final float x = ev.getX(pointerIndex);
135         final float y = ev.getY(pointerIndex);
136         final int xDiff = (int) Math.abs(x - mLastMotionX);
137         final int yDiff = (int) Math.abs(y - mLastMotionY);
138 
139         final int touchSlop = mTouchSlop;
140         boolean yMoved = yDiff > touchSlop;
141         boolean isUpwardMotion = (yDiff / (float) xDiff) > mDragSlopeThreshold;
142 
143         if (isUpwardMotion && yMoved && mLastTouchedItem != null) {
144             // Drag if the user moved far enough along the Y axis
145             beginDragging(mLastTouchedItem);
146 
147             // Cancel any pending long press
148             if (mAllowLongPress) {
149                 mAllowLongPress = false;
150                 // Try canceling the long press. It could also have been scheduled
151                 // by a distant descendant, so use the mAllowLongPress flag to block
152                 // everything
153                 final View currentPage = getPageAt(mCurrentPage);
154                 if (currentPage != null) {
155                     currentPage.cancelLongPress();
156                 }
157             }
158         }
159     }
160 
setDragSlopeThreshold(float dragSlopeThreshold)161     public void setDragSlopeThreshold(float dragSlopeThreshold) {
162         mDragSlopeThreshold = dragSlopeThreshold;
163     }
164 
165     @Override
onDetachedFromWindow()166     protected void onDetachedFromWindow() {
167         cancelDragging();
168         super.onDetachedFromWindow();
169     }
170 
171     /** Show the scrolling indicators when we move the page */
onPageBeginMoving()172     protected void onPageBeginMoving() {
173         showScrollingIndicator(false);
174     }
onPageEndMoving()175     protected void onPageEndMoving() {
176         hideScrollingIndicator(false);
177     }
178 }
179