• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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.views;
17 
18 import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
19 
20 import android.animation.Animator;
21 import android.animation.AnimatorListenerAdapter;
22 import android.animation.ObjectAnimator;
23 import android.animation.PropertyValuesHolder;
24 import android.content.Context;
25 import android.util.AttributeSet;
26 import android.util.Property;
27 import android.view.MotionEvent;
28 import android.view.View;
29 import android.view.animation.Interpolator;
30 
31 import com.android.launcher3.AbstractFloatingView;
32 import com.android.launcher3.Launcher;
33 import com.android.launcher3.Utilities;
34 import com.android.launcher3.anim.Interpolators;
35 import com.android.launcher3.touch.SwipeDetector;
36 
37 /**
38  * Extension of AbstractFloatingView with common methods for sliding in from bottom
39  */
40 public abstract class AbstractSlideInView extends AbstractFloatingView
41         implements SwipeDetector.Listener {
42 
43     protected static Property<AbstractSlideInView, Float> TRANSLATION_SHIFT =
44             new Property<AbstractSlideInView, Float>(Float.class, "translationShift") {
45 
46                 @Override
47                 public Float get(AbstractSlideInView view) {
48                     return view.mTranslationShift;
49                 }
50 
51                 @Override
52                 public void set(AbstractSlideInView view, Float value) {
53                     view.setTranslationShift(value);
54                 }
55             };
56     protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
57     protected static final float TRANSLATION_SHIFT_OPENED = 0f;
58 
59     protected final Launcher mLauncher;
60     protected final SwipeDetector mSwipeDetector;
61     protected final ObjectAnimator mOpenCloseAnimator;
62 
63     protected View mContent;
64     protected Interpolator mScrollInterpolator;
65 
66     // range [0, 1], 0=> completely open, 1=> completely closed
67     protected float mTranslationShift = TRANSLATION_SHIFT_CLOSED;
68 
69     protected boolean mNoIntercept;
70 
AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr)71     public AbstractSlideInView(Context context, AttributeSet attrs, int defStyleAttr) {
72         super(context, attrs, defStyleAttr);
73         mLauncher = Launcher.getLauncher(context);
74 
75         mScrollInterpolator = Interpolators.SCROLL_CUBIC;
76         mSwipeDetector = new SwipeDetector(context, this, SwipeDetector.VERTICAL);
77 
78         mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
79         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
80             @Override
81             public void onAnimationEnd(Animator animation) {
82                 mSwipeDetector.finishedScrolling();
83                 announceAccessibilityChanges();
84             }
85         });
86     }
87 
setTranslationShift(float translationShift)88     protected void setTranslationShift(float translationShift) {
89         mTranslationShift = translationShift;
90         mContent.setTranslationY(mTranslationShift * mContent.getHeight());
91     }
92 
93     @Override
onControllerInterceptTouchEvent(MotionEvent ev)94     public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
95         if (mNoIntercept) {
96             return false;
97         }
98 
99         int directionsToDetectScroll = mSwipeDetector.isIdleState() ?
100                 SwipeDetector.DIRECTION_NEGATIVE : 0;
101         mSwipeDetector.setDetectableScrollConditions(
102                 directionsToDetectScroll, false);
103         mSwipeDetector.onTouchEvent(ev);
104         return mSwipeDetector.isDraggingOrSettling()
105                 || !getPopupContainer().isEventOverView(mContent, ev);
106     }
107 
108     @Override
onControllerTouchEvent(MotionEvent ev)109     public boolean onControllerTouchEvent(MotionEvent ev) {
110         mSwipeDetector.onTouchEvent(ev);
111         if (ev.getAction() == MotionEvent.ACTION_UP && mSwipeDetector.isIdleState()
112                 && !isOpeningAnimationRunning()) {
113             // If we got ACTION_UP without ever starting swipe, close the panel.
114             if (!getPopupContainer().isEventOverView(mContent, ev)) {
115                 close(true);
116             }
117         }
118         return true;
119     }
120 
isOpeningAnimationRunning()121     private boolean isOpeningAnimationRunning() {
122         return mIsOpen && mOpenCloseAnimator.isRunning();
123     }
124 
125     /* SwipeDetector.Listener */
126 
127     @Override
onDragStart(boolean start)128     public void onDragStart(boolean start) { }
129 
130     @Override
onDrag(float displacement)131     public boolean onDrag(float displacement) {
132         float range = mContent.getHeight();
133         displacement = Utilities.boundToRange(displacement, 0, range);
134         setTranslationShift(displacement / range);
135         return true;
136     }
137 
138     @Override
onDragEnd(float velocity, boolean fling)139     public void onDragEnd(float velocity, boolean fling) {
140         if ((fling && velocity > 0) || mTranslationShift > 0.5f) {
141             mScrollInterpolator = scrollInterpolatorForVelocity(velocity);
142             mOpenCloseAnimator.setDuration(SwipeDetector.calculateDuration(
143                     velocity, TRANSLATION_SHIFT_CLOSED - mTranslationShift));
144             close(true);
145         } else {
146             mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(
147                     TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
148             mOpenCloseAnimator.setDuration(
149                     SwipeDetector.calculateDuration(velocity, mTranslationShift))
150                     .setInterpolator(Interpolators.DEACCEL);
151             mOpenCloseAnimator.start();
152         }
153     }
154 
handleClose(boolean animate, long defaultDuration)155     protected void handleClose(boolean animate, long defaultDuration) {
156         if (mIsOpen && !animate) {
157             mOpenCloseAnimator.cancel();
158             setTranslationShift(TRANSLATION_SHIFT_CLOSED);
159             onCloseComplete();
160             return;
161         }
162         if (!mIsOpen) {
163             return;
164         }
165         mOpenCloseAnimator.setValues(
166                 PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_CLOSED));
167         mOpenCloseAnimator.addListener(new AnimatorListenerAdapter() {
168             @Override
169             public void onAnimationEnd(Animator animation) {
170                 onCloseComplete();
171             }
172         });
173         if (mSwipeDetector.isIdleState()) {
174             mOpenCloseAnimator
175                     .setDuration(defaultDuration)
176                     .setInterpolator(Interpolators.ACCEL);
177         } else {
178             mOpenCloseAnimator.setInterpolator(mScrollInterpolator);
179         }
180         mOpenCloseAnimator.start();
181     }
182 
onCloseComplete()183     protected void onCloseComplete() {
184         mIsOpen = false;
185         getPopupContainer().removeView(this);
186     }
187 
getPopupContainer()188     protected BaseDragLayer getPopupContainer() {
189         return mLauncher.getDragLayer();
190     }
191 }
192