Home | History | Annotate | Download | only in views
      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 
     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 
     89     protected void setTranslationShift(float translationShift) {
     90         mTranslationShift = translationShift;
     91         mContent.setTranslationY(mTranslationShift * mContent.getHeight());
     92     }
     93 
     94     @Override
     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
    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
    124     public void onDragStart(boolean start) { }
    125 
    126     @Override
    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
    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 
    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 
    179     protected void onCloseComplete() {
    180         mIsOpen = false;
    181         mLauncher.getDragLayer().removeView(this);
    182     }
    183 }
    184