• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.launcher3.touch;
17 
18 import android.content.Context;
19 import android.graphics.PointF;
20 import android.view.MotionEvent;
21 import android.view.ViewConfiguration;
22 
23 import androidx.annotation.NonNull;
24 import androidx.annotation.VisibleForTesting;
25 
26 import com.android.launcher3.Utilities;
27 
28 /**
29  * One dimensional scroll/drag/swipe gesture detector (either HORIZONTAL or VERTICAL).
30  */
31 public class SingleAxisSwipeDetector extends BaseSwipeDetector {
32 
33     public static final int DIRECTION_POSITIVE = 1 << 0;
34     public static final int DIRECTION_NEGATIVE = 1 << 1;
35     public static final int DIRECTION_BOTH = DIRECTION_NEGATIVE | DIRECTION_POSITIVE;
36 
37     public static final Direction VERTICAL = new Direction() {
38 
39         @Override
40         boolean isPositive(float displacement) {
41             // Up
42             return displacement < 0;
43         }
44 
45         @Override
46         boolean isNegative(float displacement) {
47             // Down
48             return displacement > 0;
49         }
50 
51         @Override
52         float extractDirection(PointF direction) {
53             return direction.y;
54         }
55 
56         @Override
57         float extractOrthogonalDirection(PointF direction) {
58             return direction.x;
59         }
60 
61         @NonNull
62         @Override
63         public String toString() {
64             return "VERTICAL";
65         }
66     };
67 
68     public static final Direction HORIZONTAL = new Direction() {
69 
70         @Override
71         boolean isPositive(float displacement) {
72             // Right
73             return displacement > 0;
74         }
75 
76         @Override
77         boolean isNegative(float displacement) {
78             // Left
79             return displacement < 0;
80         }
81 
82         @Override
83         float extractDirection(PointF direction) {
84             return direction.x;
85         }
86 
87         @Override
88         float extractOrthogonalDirection(PointF direction) {
89             return direction.y;
90         }
91 
92         @NonNull
93         @Override
94         public String toString() {
95             return "HORIZONTAL";
96         }
97     };
98 
99     private final Direction mDir;
100     /* Client of this gesture detector can register a callback. */
101     private final Listener mListener;
102 
103     private int mScrollDirections;
104 
105     private float mTouchSlopMultiplier = 1f;
106 
SingleAxisSwipeDetector(@onNull Context context, @NonNull Listener l, @NonNull Direction dir)107     public SingleAxisSwipeDetector(@NonNull Context context, @NonNull Listener l,
108             @NonNull Direction dir) {
109         this(ViewConfiguration.get(context), l, dir, Utilities.isRtl(context.getResources()));
110     }
111 
112     @VisibleForTesting
SingleAxisSwipeDetector(@onNull ViewConfiguration config, @NonNull Listener l, @NonNull Direction dir, boolean isRtl)113     protected SingleAxisSwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l,
114             @NonNull Direction dir, boolean isRtl) {
115         super(config, isRtl);
116         mListener = l;
117         mDir = dir;
118     }
119 
120     /**
121      * Provides feasibility to adjust touch slop when visible window size changed. When visible
122      * bounds translate become smaller, multiply a larger multiplier could ensure the UX
123      * more consistent.
124      *
125      * @see #shouldScrollStart(PointF)
126      *
127      * @param touchSlopMultiplier the value to multiply original touch slop.
128      */
setTouchSlopMultiplier(float touchSlopMultiplier)129     public void setTouchSlopMultiplier(float touchSlopMultiplier) {
130         mTouchSlopMultiplier = touchSlopMultiplier;
131     }
132 
setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop)133     public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) {
134         mScrollDirections = scrollDirectionFlags;
135         mIgnoreSlopWhenSettling = ignoreSlop;
136     }
137 
138     /**
139      * Returns if the start drag was towards the positive direction or negative.
140      *
141      * @see #setDetectableScrollConditions(int, boolean)
142      * @see #DIRECTION_BOTH
143      */
wasInitialTouchPositive()144     public boolean wasInitialTouchPositive() {
145         return mDir.isPositive(mDir.extractDirection(mSubtractDisplacement));
146     }
147 
148     @Override
shouldScrollStart(PointF displacement)149     protected boolean shouldScrollStart(PointF displacement) {
150         // Reject cases where the angle or slop condition is not met.
151         float minDisplacement = Math.max(mTouchSlop * mTouchSlopMultiplier,
152                 Math.abs(mDir.extractOrthogonalDirection(displacement)));
153         if (Math.abs(mDir.extractDirection(displacement)) < minDisplacement) {
154             return false;
155         }
156 
157         // Check if the client is interested in scroll in current direction.
158         float displacementComponent = mDir.extractDirection(displacement);
159         return canScrollNegative(displacementComponent) || canScrollPositive(displacementComponent);
160     }
161 
canScrollNegative(float displacement)162     private boolean canScrollNegative(float displacement) {
163         return (mScrollDirections & DIRECTION_NEGATIVE) > 0 && mDir.isNegative(displacement);
164     }
165 
canScrollPositive(float displacement)166     private boolean canScrollPositive(float displacement) {
167         return (mScrollDirections & DIRECTION_POSITIVE) > 0 && mDir.isPositive(displacement);
168     }
169 
170     @Override
reportDragStartInternal(boolean recatch)171     protected void reportDragStartInternal(boolean recatch) {
172         float startDisplacement = mDir.extractDirection(mSubtractDisplacement);
173         mListener.onDragStart(!recatch, startDisplacement);
174     }
175 
176     @Override
reportDraggingInternal(PointF displacement, MotionEvent event)177     protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
178         mListener.onDrag(mDir.extractDirection(displacement),
179                 mDir.extractOrthogonalDirection(displacement), event);
180     }
181 
182     @Override
reportDragEndInternal(PointF velocity)183     protected void reportDragEndInternal(PointF velocity) {
184         float velocityComponent = mDir.extractDirection(velocity);
185         mListener.onDragEnd(velocityComponent);
186     }
187 
188     /** Listener to receive updates on the swipe. */
189     public interface Listener {
190         /**
191          * TODO(b/150256055) consolidate all the different onDrag() methods into one
192          * @param start whether this was the original drag start, as opposed to a recatch.
193          * @param startDisplacement the initial touch displacement for the primary direction as
194          *        given by by {@link Direction#extractDirection(PointF)}
195          */
onDragStart(boolean start, float startDisplacement)196         void onDragStart(boolean start, float startDisplacement);
197 
onDrag(float displacement)198         boolean onDrag(float displacement);
199 
onDrag(float displacement, MotionEvent event)200         default boolean onDrag(float displacement, MotionEvent event) {
201             return onDrag(displacement);
202         }
203 
onDrag(float displacement, float orthogonalDisplacement, MotionEvent ev)204         default boolean onDrag(float displacement, float orthogonalDisplacement, MotionEvent ev) {
205             return onDrag(displacement, ev);
206         }
207 
onDragEnd(float velocity)208         void onDragEnd(float velocity);
209     }
210 
211     public abstract static class Direction {
212 
isPositive(float displacement)213         abstract boolean isPositive(float displacement);
214 
isNegative(float displacement)215         abstract boolean isNegative(float displacement);
216 
217         /** Returns the part of the given {@link PointF} that is relevant to this direction. */
extractDirection(PointF point)218         abstract float extractDirection(PointF point);
219 
extractOrthogonalDirection(PointF point)220         abstract float extractOrthogonalDirection(PointF point);
221 
222     }
223 }
224