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 17 package com.android.launcher3.anim; 18 19 import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs; 20 21 import android.content.Context; 22 import android.graphics.Path; 23 import android.view.animation.AccelerateDecelerateInterpolator; 24 import android.view.animation.AccelerateInterpolator; 25 import android.view.animation.DecelerateInterpolator; 26 import android.view.animation.Interpolator; 27 import android.view.animation.LinearInterpolator; 28 import android.view.animation.OvershootInterpolator; 29 import android.view.animation.PathInterpolator; 30 31 import com.android.launcher3.Utilities; 32 33 34 /** 35 * Common interpolators used in Launcher 36 */ 37 public class Interpolators { 38 39 public static final Interpolator LINEAR = new LinearInterpolator(); 40 41 public static final Interpolator ACCEL = new AccelerateInterpolator(); 42 public static final Interpolator ACCEL_0_75 = new AccelerateInterpolator(0.75f); 43 public static final Interpolator ACCEL_1_5 = new AccelerateInterpolator(1.5f); 44 public static final Interpolator ACCEL_2 = new AccelerateInterpolator(2); 45 46 public static final Interpolator DEACCEL = new DecelerateInterpolator(); 47 public static final Interpolator DEACCEL_1_5 = new DecelerateInterpolator(1.5f); 48 public static final Interpolator DEACCEL_1_7 = new DecelerateInterpolator(1.7f); 49 public static final Interpolator DEACCEL_2 = new DecelerateInterpolator(2); 50 public static final Interpolator DEACCEL_2_5 = new DecelerateInterpolator(2.5f); 51 public static final Interpolator DEACCEL_3 = new DecelerateInterpolator(3f); 52 public static final Interpolator DEACCEL_5 = new DecelerateInterpolator(5f); 53 54 public static final Interpolator ACCEL_DEACCEL = new AccelerateDecelerateInterpolator(); 55 56 public static final Interpolator FAST_OUT_SLOW_IN = new PathInterpolator(0.4f, 0f, 0.2f, 1f); 57 58 public static final Interpolator AGGRESSIVE_EASE = new PathInterpolator(0.2f, 0f, 0f, 1f); 59 public static final Interpolator AGGRESSIVE_EASE_IN_OUT = new PathInterpolator(0.6f,0, 0.4f, 1); 60 61 public static final Interpolator EXAGGERATED_EASE; 62 63 public static final Interpolator INSTANT = t -> 1; 64 65 private static final int MIN_SETTLE_DURATION = 200; 66 private static final float OVERSHOOT_FACTOR = 0.9f; 67 68 static { 69 Path exaggeratedEase = new Path(); 70 exaggeratedEase.moveTo(0, 0); 71 exaggeratedEase.cubicTo(0.05f, 0f, 0.133333f, 0.08f, 0.166666f, 0.4f); 72 exaggeratedEase.cubicTo(0.225f, 0.94f, 0.5f, 1f, 1f, 1f); 73 EXAGGERATED_EASE = new PathInterpolator(exaggeratedEase); 74 } 75 76 public static final Interpolator OVERSHOOT_1_2 = new OvershootInterpolator(1.2f); 77 public static final Interpolator OVERSHOOT_1_7 = new OvershootInterpolator(1.7f); 78 79 public static final Interpolator TOUCH_RESPONSE_INTERPOLATOR = 80 new PathInterpolator(0.3f, 0f, 0.1f, 1f); 81 82 /** 83 * Inversion of ZOOM_OUT, compounded with an ease-out. 84 */ 85 public static final Interpolator ZOOM_IN = new Interpolator() { 86 @Override 87 public float getInterpolation(float v) { 88 return DEACCEL_3.getInterpolation(1 - ZOOM_OUT.getInterpolation(1 - v)); 89 } 90 }; 91 92 public static final Interpolator ZOOM_OUT = new Interpolator() { 93 94 private static final float FOCAL_LENGTH = 0.35f; 95 96 @Override 97 public float getInterpolation(float v) { 98 return zInterpolate(v); 99 } 100 101 /** 102 * This interpolator emulates the rate at which the perceived scale of an object changes 103 * as its distance from a camera increases. When this interpolator is applied to a scale 104 * animation on a view, it evokes the sense that the object is shrinking due to moving away 105 * from the camera. 106 */ 107 private float zInterpolate(float input) { 108 return (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + input)) / 109 (1.0f - FOCAL_LENGTH / (FOCAL_LENGTH + 1.0f)); 110 } 111 }; 112 113 public static final Interpolator SCROLL = new Interpolator() { 114 @Override 115 public float getInterpolation(float t) { 116 t -= 1.0f; 117 return t*t*t*t*t + 1; 118 } 119 }; 120 121 public static final Interpolator SCROLL_CUBIC = new Interpolator() { 122 @Override 123 public float getInterpolation(float t) { 124 t -= 1.0f; 125 return t*t*t + 1; 126 } 127 }; 128 129 private static final float FAST_FLING_PX_MS = 10; 130 scrollInterpolatorForVelocity(float velocity)131 public static Interpolator scrollInterpolatorForVelocity(float velocity) { 132 return Math.abs(velocity) > FAST_FLING_PX_MS ? SCROLL : SCROLL_CUBIC; 133 } 134 135 /** 136 * Create an OvershootInterpolator with tension directly related to the velocity (in px/ms). 137 * @param velocity The start velocity of the animation we want to overshoot. 138 */ overshootInterpolatorForVelocity(float velocity)139 public static Interpolator overshootInterpolatorForVelocity(float velocity) { 140 return new OvershootInterpolator(Math.min(Math.abs(velocity), 3f)); 141 } 142 143 /** 144 * Runs the given interpolator such that the entire progress is set between the given bounds. 145 * That is, we set the interpolation to 0 until lowerBound and reach 1 by upperBound. 146 */ clampToProgress(Interpolator interpolator, float lowerBound, float upperBound)147 public static Interpolator clampToProgress(Interpolator interpolator, float lowerBound, 148 float upperBound) { 149 if (upperBound <= lowerBound) { 150 throw new IllegalArgumentException(String.format( 151 "lowerBound (%f) must be less than upperBound (%f)", lowerBound, upperBound)); 152 } 153 return t -> { 154 if (t < lowerBound) { 155 return 0; 156 } 157 if (t > upperBound) { 158 return 1; 159 } 160 return interpolator.getInterpolation((t - lowerBound) / (upperBound - lowerBound)); 161 }; 162 } 163 164 /** 165 * Runs the given interpolator such that the interpolated value is mapped to the given range. 166 * This is useful, for example, if we only use this interpolator for part of the animation, 167 * such as to take over a user-controlled animation when they let go. 168 */ 169 public static Interpolator mapToProgress(Interpolator interpolator, float lowerBound, 170 float upperBound) { 171 return t -> Utilities.mapRange(interpolator.getInterpolation(t), lowerBound, upperBound); 172 } 173 174 /** 175 * Computes parameters necessary for an overshoot effect. 176 */ 177 public static class OvershootParams { 178 public Interpolator interpolator; 179 public float start; 180 public float end; 181 public long duration; 182 183 /** 184 * Given the input params, sets OvershootParams variables to be used by the caller. 185 * @param startProgress The progress from 0 to 1 that the overshoot starts from. 186 * @param overshootPastProgress The progress from 0 to 1 where we overshoot past (should 187 * either be equal to startProgress or endProgress, depending on if we want to 188 * overshoot immediately or only once we reach the end). 189 * @param endProgress The final progress from 0 to 1 that we will settle to. 190 * @param velocityPxPerMs The initial velocity that causes this overshoot. 191 * @param totalDistancePx The distance against which progress is calculated. 192 */ 193 public OvershootParams(float startProgress, float overshootPastProgress, 194 float endProgress, float velocityPxPerMs, int totalDistancePx, Context context) { 195 velocityPxPerMs = Math.abs(velocityPxPerMs); 196 start = startProgress; 197 int startPx = (int) (start * totalDistancePx); 198 // Overshoot by about half a frame. 199 float overshootBy = OVERSHOOT_FACTOR * velocityPxPerMs * 200 getSingleFrameMs(context) / totalDistancePx / 2; 201 overshootBy = Utilities.boundToRange(overshootBy, 0.02f, 0.15f); 202 end = overshootPastProgress + overshootBy; 203 int endPx = (int) (end * totalDistancePx); 204 int overshootDistance = endPx - startPx; 205 // Calculate deceleration necessary to reach overshoot distance. 206 // Formula: velocityFinal^2 = velocityInitial^2 + 2 * acceleration * distance 207 // 0 = v^2 + 2ad (velocityFinal == 0) 208 // a = v^2 / -2d 209 float decelerationPxPerMs = velocityPxPerMs * velocityPxPerMs / (2 * overshootDistance); 210 // Calculate time necessary to reach peak of overshoot. 211 // Formula: acceleration = velocity / time 212 // time = velocity / acceleration 213 duration = (long) (velocityPxPerMs / decelerationPxPerMs); 214 215 // Now that we're at the top of the overshoot, need to settle back to endProgress. 216 float settleDistance = end - endProgress; 217 int settleDistancePx = (int) (settleDistance * totalDistancePx); 218 // Calculate time necessary for the settle. 219 // Formula: distance = velocityInitial * time + 1/2 * acceleration * time^2 220 // d = 1/2at^2 (velocityInitial = 0, since we just stopped at the top) 221 // t = sqrt(2d/a) 222 // Above formula assumes constant acceleration. Since we use ACCEL_DEACCEL, we actually 223 // have acceleration to halfway then deceleration the rest. So the formula becomes: 224 // t = sqrt(d/a) * 2 (half the distance for accel, half for deaccel) 225 long settleDuration = (long) Math.sqrt(settleDistancePx / decelerationPxPerMs) * 4; 226 227 settleDuration = Math.max(MIN_SETTLE_DURATION, settleDuration); 228 // How much of the animation to devote to playing the overshoot (the rest is for settle). 229 float overshootFraction = (float) duration / (duration + settleDuration); 230 duration += settleDuration; 231 // Finally, create the interpolator, composed of two interpolators: an overshoot, which 232 // reaches end > 1, and then a settle to endProgress. 233 Interpolator overshoot = Interpolators.clampToProgress(DEACCEL, 0, overshootFraction); 234 // The settle starts at 1, where 1 is the top of the overshoot, and maps to a fraction 235 // such that final progress is endProgress. For example, if we overshot to 1.1 but want 236 // to end at 1, we need to map to 1/1.1. 237 Interpolator settle = Interpolators.clampToProgress(Interpolators.mapToProgress( 238 ACCEL_DEACCEL, 1, (endProgress - start) / (end - start)), overshootFraction, 1); 239 interpolator = t -> t <= overshootFraction 240 ? overshoot.getInterpolation(t) 241 : settle.getInterpolation(t); 242 } 243 } 244 } 245