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