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.animation.TimeInterpolator;
     23 import android.animation.ValueAnimator;
     24 import android.content.Context;
     25 import android.util.FloatProperty;
     26 import android.util.Log;
     27 import android.util.Property;
     28 import android.view.ViewConfiguration;
     29 import android.view.ViewDebug;
     30 import android.widget.OverScroller;
     31 
     32 import com.android.systemui.Interpolators;
     33 import com.android.systemui.R;
     34 import com.android.systemui.recents.Recents;
     35 import com.android.systemui.shared.recents.utilities.AnimationProps;
     36 import com.android.systemui.shared.recents.utilities.Utilities;
     37 import com.android.systemui.recents.views.lowram.TaskStackLowRamLayoutAlgorithm;
     38 import com.android.systemui.statusbar.FlingAnimationUtils;
     39 
     40 import java.io.PrintWriter;
     41 
     42 /* The scrolling logic for a TaskStackView */
     43 public class TaskStackViewScroller {
     44 
     45     private static final String TAG = "TaskStackViewScroller";
     46     private static final boolean DEBUG = false;
     47 
     48     public interface TaskStackViewScrollerCallbacks {
     49         void onStackScrollChanged(float prevScroll, float curScroll, AnimationProps animation);
     50     }
     51 
     52     /**
     53      * A Property wrapper around the <code>stackScroll</code> functionality handled by the
     54      * {@link #setStackScroll(float)} and
     55      * {@link #getStackScroll()} methods.
     56      */
     57     private static final Property<TaskStackViewScroller, Float> STACK_SCROLL =
     58             new FloatProperty<TaskStackViewScroller>("stackScroll") {
     59                 @Override
     60                 public void setValue(TaskStackViewScroller object, float value) {
     61                     object.setStackScroll(value);
     62                 }
     63 
     64                 @Override
     65                 public Float get(TaskStackViewScroller object) {
     66                     return object.getStackScroll();
     67                 }
     68             };
     69 
     70     Context mContext;
     71     TaskStackLayoutAlgorithm mLayoutAlgorithm;
     72     TaskStackViewScrollerCallbacks mCb;
     73 
     74     @ViewDebug.ExportedProperty(category="recents")
     75     float mStackScrollP;
     76     @ViewDebug.ExportedProperty(category="recents")
     77     float mLastDeltaP = 0f;
     78     float mFlingDownScrollP;
     79     int mFlingDownY;
     80 
     81     OverScroller mScroller;
     82     ObjectAnimator mScrollAnimator;
     83     float mFinalAnimatedScroll;
     84 
     85     final FlingAnimationUtils mFlingAnimationUtils;
     86 
     87     public TaskStackViewScroller(Context context, TaskStackViewScrollerCallbacks cb,
     88             TaskStackLayoutAlgorithm layoutAlgorithm) {
     89         mContext = context;
     90         mCb = cb;
     91         mScroller = new OverScroller(context);
     92         if (Recents.getConfiguration().isLowRamDevice) {
     93             mScroller.setFriction(0.06f);
     94         }
     95         mLayoutAlgorithm = layoutAlgorithm;
     96         mFlingAnimationUtils = new FlingAnimationUtils(context, 0.3f);
     97     }
     98 
     99     /** Resets the task scroller. */
    100     void reset() {
    101         mStackScrollP = 0f;
    102         mLastDeltaP = 0f;
    103     }
    104 
    105     void resetDeltaScroll() {
    106         mLastDeltaP = 0f;
    107     }
    108 
    109     /** Gets the current stack scroll */
    110     public float getStackScroll() {
    111         return mStackScrollP;
    112     }
    113 
    114     /**
    115      * Sets the current stack scroll immediately.
    116      */
    117     public void setStackScroll(float s) {
    118         setStackScroll(s, AnimationProps.IMMEDIATE);
    119     }
    120 
    121     /**
    122      * Sets the current stack scroll immediately, and returns the difference between the target
    123      * scroll and the actual scroll after accounting for the effect on the focus state.
    124      */
    125     public float setDeltaStackScroll(float downP, float deltaP) {
    126         float targetScroll = downP + deltaP;
    127         float newScroll = mLayoutAlgorithm.updateFocusStateOnScroll(downP + mLastDeltaP, targetScroll,
    128                 mStackScrollP);
    129         setStackScroll(newScroll, AnimationProps.IMMEDIATE);
    130         mLastDeltaP = deltaP;
    131         return newScroll - targetScroll;
    132     }
    133 
    134     /**
    135      * Sets the current stack scroll, but indicates to the callback the preferred animation to
    136      * update to this new scroll.
    137      */
    138     public void setStackScroll(float newScroll, AnimationProps animation) {
    139         float prevScroll = mStackScrollP;
    140         mStackScrollP = newScroll;
    141         if (mCb != null) {
    142             mCb.onStackScrollChanged(prevScroll, mStackScrollP, animation);
    143         }
    144     }
    145 
    146     /**
    147      * Sets the current stack scroll to the initial state when you first enter recents.
    148      * @return whether the stack progress changed.
    149      */
    150     public boolean setStackScrollToInitialState() {
    151         float prevScroll = mStackScrollP;
    152         setStackScroll(mLayoutAlgorithm.mInitialScrollP);
    153         return Float.compare(prevScroll, mStackScrollP) != 0;
    154     }
    155 
    156     /**
    157      * Starts a fling that is coordinated with the {@link TaskStackViewTouchHandler}.
    158      */
    159     public void fling(float downScrollP, int downY, int y, int velY, int minY, int maxY,
    160             int overscroll) {
    161         if (DEBUG) {
    162             Log.d(TAG, "fling: " + downScrollP + ", downY: " + downY + ", y: " + y +
    163                     ", velY: " + velY + ", minY: " + minY + ", maxY: " + maxY);
    164         }
    165         mFlingDownScrollP = downScrollP;
    166         mFlingDownY = downY;
    167         mScroller.fling(0, y, 0, velY, 0, 0, minY, maxY, 0, overscroll);
    168     }
    169 
    170     /** Bounds the current scroll if necessary */
    171     public boolean boundScroll() {
    172         float curScroll = getStackScroll();
    173         float newScroll = getBoundedStackScroll(curScroll);
    174         if (Float.compare(newScroll, curScroll) != 0) {
    175             setStackScroll(newScroll);
    176             return true;
    177         }
    178         return false;
    179     }
    180 
    181     /** Returns the bounded stack scroll */
    182     float getBoundedStackScroll(float scroll) {
    183         return Utilities.clamp(scroll, mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP);
    184     }
    185 
    186     /** Returns the amount that the absolute value of how much the scroll is out of bounds. */
    187     float getScrollAmountOutOfBounds(float scroll) {
    188         if (scroll < mLayoutAlgorithm.mMinScrollP) {
    189             return Math.abs(scroll - mLayoutAlgorithm.mMinScrollP);
    190         } else if (scroll > mLayoutAlgorithm.mMaxScrollP) {
    191             return Math.abs(scroll - mLayoutAlgorithm.mMaxScrollP);
    192         }
    193         return 0f;
    194     }
    195 
    196     /** Returns whether the specified scroll is out of bounds */
    197     boolean isScrollOutOfBounds() {
    198         return Float.compare(getScrollAmountOutOfBounds(mStackScrollP), 0f) != 0;
    199     }
    200 
    201     /**
    202      * Scrolls the closest task and snaps into place. Only used in recents for low ram devices.
    203      * @param velocity of scroll
    204      */
    205     void scrollToClosestTask(int velocity) {
    206         float stackScroll = getStackScroll();
    207 
    208         // Skip if not in low ram layout and if the scroll is out of min and max bounds
    209         if (!Recents.getConfiguration().isLowRamDevice || stackScroll < mLayoutAlgorithm.mMinScrollP
    210                 || stackScroll > mLayoutAlgorithm.mMaxScrollP) {
    211             return;
    212         }
    213         TaskStackLowRamLayoutAlgorithm algorithm = mLayoutAlgorithm.mTaskStackLowRamLayoutAlgorithm;
    214 
    215         float flingThreshold = ViewConfiguration.get(mContext).getScaledMinimumFlingVelocity();
    216         if (Math.abs(velocity) > flingThreshold) {
    217             int minY = algorithm.percentageToScroll(mLayoutAlgorithm.mMinScrollP);
    218             int maxY = algorithm.percentageToScroll(mLayoutAlgorithm.mMaxScrollP);
    219 
    220             // Calculate the fling and snap to closest task from final y position, computeScroll()
    221             // never runs when cancelled with animateScroll() and the overscroll is not calculated
    222             // here
    223             fling(0 /* downScrollP */, 0 /* downY */, algorithm.percentageToScroll(stackScroll),
    224                     -velocity, minY, maxY, 0 /* overscroll */);
    225             float pos = algorithm.scrollToPercentage(mScroller.getFinalY());
    226 
    227             float newScrollP = algorithm.getClosestTaskP(pos, mLayoutAlgorithm.mNumStackTasks,
    228                     velocity);
    229             ValueAnimator animator = ObjectAnimator.ofFloat(stackScroll, newScrollP);
    230             mFlingAnimationUtils.apply(animator, algorithm.percentageToScroll(stackScroll),
    231                     algorithm.percentageToScroll(newScrollP), velocity);
    232             animateScroll(newScrollP, (int) animator.getDuration(), animator.getInterpolator(),
    233                     null /* postRunnable */);
    234         } else {
    235             float newScrollP = algorithm.getClosestTaskP(stackScroll,
    236                     mLayoutAlgorithm.mNumStackTasks, velocity);
    237             animateScroll(newScrollP, 300, Interpolators.ACCELERATE_DECELERATE,
    238                     null /* postRunnable */);
    239         }
    240     }
    241 
    242     /** Animates the stack scroll into bounds */
    243     ObjectAnimator animateBoundScroll() {
    244         // TODO: Take duration for snap back
    245         float curScroll = getStackScroll();
    246         float newScroll = getBoundedStackScroll(curScroll);
    247         if (Float.compare(newScroll, curScroll) != 0) {
    248             // Start a new scroll animation
    249             animateScroll(newScroll, null /* postScrollRunnable */);
    250         }
    251         return mScrollAnimator;
    252     }
    253 
    254     /** Animates the stack scroll */
    255     void animateScroll(float newScroll, final Runnable postRunnable) {
    256         int duration = mContext.getResources().getInteger(
    257                 R.integer.recents_animate_task_stack_scroll_duration);
    258         animateScroll(newScroll, duration, postRunnable);
    259     }
    260 
    261     /** Animates the stack scroll */
    262     void animateScroll(float newScroll, int duration, final Runnable postRunnable) {
    263         animateScroll(newScroll, duration, Interpolators.LINEAR_OUT_SLOW_IN, postRunnable);
    264     }
    265 
    266     /** Animates the stack scroll with time interpolator */
    267     void animateScroll(float newScroll, int duration, TimeInterpolator interpolator,
    268             final Runnable postRunnable) {
    269         ObjectAnimator an = ObjectAnimator.ofFloat(this, STACK_SCROLL, getStackScroll(), newScroll);
    270         an.setDuration(duration);
    271         an.setInterpolator(interpolator);
    272         animateScroll(newScroll, an, postRunnable);
    273     }
    274 
    275     /** Animates the stack scroll with animator */
    276     private void animateScroll(float newScroll, ObjectAnimator animator,
    277             final Runnable postRunnable) {
    278         // Finish any current scrolling animations
    279         if (mScrollAnimator != null && mScrollAnimator.isRunning()) {
    280             setStackScroll(mFinalAnimatedScroll);
    281             mScroller.forceFinished(true);
    282         }
    283         stopScroller();
    284         stopBoundScrollAnimation();
    285 
    286         if (Float.compare(mStackScrollP, newScroll) != 0) {
    287             mFinalAnimatedScroll = newScroll;
    288             mScrollAnimator = animator;
    289             mScrollAnimator.addListener(new AnimatorListenerAdapter() {
    290                 @Override
    291                 public void onAnimationEnd(Animator animation) {
    292                     if (postRunnable != null) {
    293                         postRunnable.run();
    294                     }
    295                     mScrollAnimator.removeAllListeners();
    296                 }
    297             });
    298             mScrollAnimator.start();
    299         } else {
    300             if (postRunnable != null) {
    301                 postRunnable.run();
    302             }
    303         }
    304     }
    305 
    306     /** Aborts any current stack scrolls */
    307     void stopBoundScrollAnimation() {
    308         Utilities.cancelAnimationWithoutCallbacks(mScrollAnimator);
    309     }
    310 
    311     /**** OverScroller ****/
    312 
    313     /** Called from the view draw, computes the next scroll. */
    314     boolean computeScroll() {
    315         if (mScroller.computeScrollOffset()) {
    316             float deltaP = mLayoutAlgorithm.getDeltaPForY(mFlingDownY, mScroller.getCurrY());
    317             mFlingDownScrollP += setDeltaStackScroll(mFlingDownScrollP, deltaP);
    318             if (DEBUG) {
    319                 Log.d(TAG, "computeScroll: " + (mFlingDownScrollP + deltaP));
    320             }
    321             return true;
    322         }
    323         return false;
    324     }
    325 
    326     /** Returns whether the overscroller is scrolling. */
    327     boolean isScrolling() {
    328         return !mScroller.isFinished();
    329     }
    330 
    331     float getScrollVelocity() {
    332         return mScroller.getCurrVelocity();
    333     }
    334 
    335     /** Stops the scroller and any current fling. */
    336     void stopScroller() {
    337         if (!mScroller.isFinished()) {
    338             mScroller.abortAnimation();
    339         }
    340     }
    341 
    342     public void dump(String prefix, PrintWriter writer) {
    343         writer.print(prefix); writer.print(TAG);
    344         writer.print(" stackScroll:"); writer.print(mStackScrollP);
    345         writer.println();
    346     }
    347 }