Home | History | Annotate | Download | only in views
      1 /*
      2  * Copyright (C) 2014 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.systemui.recents.views;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.content.Context;
     23 import android.util.FloatProperty;
     24 import android.util.Log;
     25 import android.util.MutableFloat;
     26 import android.util.Property;
     27 import android.view.ViewDebug;
     28 import android.widget.OverScroller;
     29 
     30 import com.android.systemui.Interpolators;
     31 import com.android.systemui.R;
     32 import com.android.systemui.recents.misc.Utilities;
     33 
     34 import java.io.PrintWriter;
     35 
     36 /* The scrolling logic for a TaskStackView */
     37 public class TaskStackViewScroller {
     38 
     39     private static final String TAG = "TaskStackViewScroller";
     40     private static final boolean DEBUG = false;
     41 
     42     public interface TaskStackViewScrollerCallbacks {
     43         void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
     44     }
     45 
     46     /**
     47      * A Property wrapper around the <code>stackScroll</code> functionality handled by the
     48      * {@link #setStackScroll(float)} and
     49      * {@link #getStackScroll()} methods.
     50      */
     51     private static final Property<TaskStackViewScroller, Float> STACK_SCROLL =
     52             new FloatProperty<TaskStackViewScroller>("stackScroll") {
     53                 @Override
     54                 public void setValue(TaskStackViewScroller object, float value) {
     55                     object.setStackScroll(value);
     56                 }
     57 
     58                 @Override
     59                 public Float get(TaskStackViewScroller object) {
     60                     return object.getStackScroll();
     61                 }
     62             };
     63 
     64     Context mContext;
     65     TaskStackLayoutAlgorithm mLayoutAlgorithm;
     66     TaskStackViewScrollerCallbacks mCb;
     67 
     68     @ViewDebug.ExportedProperty(category="recents")
     69     float mStackScrollP;
     70     @ViewDebug.ExportedProperty(category="recents")
     71     float mLastDeltaP = 0f;
     72     float mFlingDownScrollP;
     73     int mFlingDownY;
     74 
     75     OverScroller mScroller;
     76     ObjectAnimator mScrollAnimator;
     77     float mFinalAnimatedScroll;
     78 
     79     public TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb,
     80             TaskStackLayoutAlgorithm layoutAlgorithm) {
     81         mContext = context;
     82         mCb = cb;
     83         mScroller = new OverScroller(context);
     84         mLayoutAlgorithm = layoutAlgorithm;
     85     }
     86 
     87     /** Resets the task scroller. */
     88     void reset() {
     89         mStackScrollP = 0f;
     90         mLastDeltaP = 0f;
     91     }
     92 
     93     void resetDeltaScroll() {
     94         mLastDeltaP = 0f;
     95     }
     96 
     97     /** Gets the current stack scroll */
     98     public float getStackScroll() {
     99         return mStackScrollP;
    100     }
    101 
    102     /**
    103      * Sets the current stack scroll immediately.
    104      */
    105     public void setStackScroll(float s) {
    106         setStackScroll(s, AnimationProps.IMMEDIATE);
    107     }
    108 
    109     /**
    110      * Sets the current stack scroll immediately, and returns the difference between the target
    111      * scroll and the actual scroll after accounting for the effect on the focus state.
    112      */
    113     public float setDeltaStackScroll(float downP, float deltaP) {
    114         float targetScroll = downP + deltaP;
    115         float newScroll = mLayoutAlgorithm.updateFocusStateOnScroll(downP + mLastDeltaP, targetScroll,
    116                 mStackScrollP);
    117         setStackScroll(newScroll, AnimationProps.IMMEDIATE);
    118         mLastDeltaP = deltaP;
    119         return newScroll - targetScroll;
    120     }
    121 
    122     /**
    123      * Sets the current stack scroll, but indicates to the callback the preferred animation to
    124      * update to this new scroll.
    125      */
    126     public void setStackScroll(float newScroll, AnimationProps animation) {
    127         float prevScroll = mStackScrollP;
    128         mStackScrollP = newScroll;
    129         if (mCb != null) {
    130             mCb.onStackScrollChanged(prevScroll, mStackScrollP, animation);
    131         }
    132     }
    133 
    134     /**
    135      * Sets the current stack scroll to the initial state when you first enter recents.
    136      * @return whether the stack progress changed.
    137      */
    138     public boolean setStackScrollToInitialState() {
    139         float prevScroll = mStackScrollP;
    140         setStackScroll(mLayoutAlgorithm.mInitialScrollP);
    141         return Float.compare(prevScroll, mStackScrollP) != 0;
    142     }
    143 
    144     /**
    145      * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}.
    146      */
    147     public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY,
    148             int overscroll) {
    149         if (DEBUG) {
    150             Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y +
    151                     ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY);
    152         }
    153         mFlingDownScrollP = downScrollP;
    154         mFlingDownY = downY;
    155         mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll);
    156     }
    157 
    158     /** Bounds the current scroll if necessary */
    159     public boolean boundScroll() {
    160         float curScroll = getStackScroll();
    161         float newScroll = getBoundedStackScroll(curScroll);
    162         if (Float.compare(newScroll, curScroll) != 0) {
    163             setStackScroll(newScroll);
    164             return true;
    165         }
    166         return false;
    167     }
    168 
    169     /** Returns the bounded stack scroll */
    170     float getBoundedStackScroll(float scroll) {
    171         return Utilities.clamp(scroll, mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP);
    172     }
    173 
    174     /** Returns the amount that the absolute value of how much the scroll is out of bounds. */
    175     float getScrollAmountOutOfBounds(float scroll) {
    176         if (scroll < mLayoutAlgorithm.mMinScrollP) {
    177             return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);
    178         } else if (scroll > mLayoutAlgorithm.mMaxScrollP) {
    179             return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP);
    180         }
    181         return 0f;
    182     }
    183 
    184     /** Returns whether the specified scroll is out of bounds */
    185     boolean isScrollOutOfBounds() {
    186         return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0;
    187     }
    188 
    189     /** Animates the stack scroll into bounds */
    190     ObjectAnimator animateBoundScroll() {
    191         // TODO: Take duration for snap back
    192         float curScroll = getStackScroll();
    193         float newScroll = getBoundedStackScroll(curScroll);
    194         if (Float.compare(newScroll, curScroll) != 0) {
    195             // Start a new scroll animation
    196             animateScroll(newScroll, null /* postScrollRunnable */);
    197         }
    198         return mScrollAnimator;
    199     }
    200 
    201     /** Animates the stack scroll */
    202     void animateScroll(float newScroll, final Runnable postRunnable) {
    203         int duration = mContext.getResources().getInteger(
    204                 R.integer.recents_animate_task_stack_scroll_duration);
    205         animateScroll(newScroll, duration, postRunnable);
    206     }
    207 
    208     /** Animates the stack scroll */
    209     void animateScroll(float newScroll, int duration, final Runnable postRunnable) {
    210         // Finish any current scrolling animations
    211         if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
    212             setStackScroll(mFinalAnimatedScroll);
    213             mScroller.forceFinished(true);
    214         }
    215         stopScroller();
    216         stopBoundScrollAnimation();
    217 
    218         if (Float.compare(mStackScrollP, newScroll) != 0) {
    219             mFinalAnimatedScroll = newScroll;
    220             mScrollAnimator = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
    221             mScrollAnimator.setDuration(duration);
    222             mScrollAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
    223             mScrollAnimator.addListener(new AnimatorListenerAdapter() {
    224                 @Override
    225                 public void onAnimationEnd(Animator animation) {
    226                     if (postRunnable != null) {
    227                         postRunnable.run();
    228                     }
    229                     mScrollAnimator.removeAllListeners();
    230                 }
    231             });
    232             mScrollAnimator.start();
    233         } else {
    234             if (postRunnable != null) {
    235                 postRunnable.run();
    236             }
    237         }
    238     }
    239 
    240     /** Aborts any current stack scrolls */
    241     void stopBoundScrollAnimation() {
    242         Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator);
    243     }
    244 
    245     /**** OverScroller ****/
    246 
    247     /** Called from the view draw, computes the next scroll. */
    248     boolean computeScroll() {
    249         if (mScroller.computeScrollOffset()) {
    250             float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY());
    251             mFlingDownScrollP += setDeltaStackScroll(mFlingDownScrollP, deltaP);
    252             if (DEBUG) {
    253                 Log.d(TAG, "computeScroll: " + (mFlingDownScrollP + deltaP));
    254             }
    255             return true;
    256         }
    257         return false;
    258     }
    259 
    260     /** Returns whether the overscroller is scrolling. */
    261     boolean isScrolling() {
    262         return !mScroller.isFinished();
    263     }
    264 
    265     float getScrollVelocity() {
    266         return mScroller.getCurrVelocity();
    267     }
    268 
    269     /** Stops the scroller and any current fling. */
    270     void stopScroller() {
    271         if (!mScroller.isFinished()) {
    272             mScroller.abortAnimation();
    273         }
    274     }
    275 
    276     public void dump(String prefix, PrintWriter writer) {
    277         writer.print(prefix); writer.print(TAG);
    278         writer.print(" stackScroll:"); writer.print(mStackScrollP);
    279         writer.println();
    280     }
    281 }