1 /* 2 * Copyright (C) 2019 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.quickstep.util; 17 18 import android.animation.Animator; 19 import android.content.res.Resources; 20 import android.graphics.PointF; 21 import android.graphics.RectF; 22 23 import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener; 24 import androidx.dynamicanimation.animation.FloatPropertyCompat; 25 import androidx.dynamicanimation.animation.SpringAnimation; 26 import androidx.dynamicanimation.animation.SpringForce; 27 28 import com.android.launcher3.R; 29 import com.android.launcher3.Utilities; 30 import com.android.launcher3.anim.FlingSpringAnim; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 35 36 /** 37 * Applies spring forces to animate from a starting rect to a target rect, 38 * while providing update callbacks to the caller. 39 */ 40 public class RectFSpringAnim { 41 42 private static final FloatPropertyCompat<RectFSpringAnim> RECT_CENTER_X = 43 new FloatPropertyCompat<RectFSpringAnim>("rectCenterXSpring") { 44 @Override 45 public float getValue(RectFSpringAnim anim) { 46 return anim.mCurrentCenterX; 47 } 48 49 @Override 50 public void setValue(RectFSpringAnim anim, float currentCenterX) { 51 anim.mCurrentCenterX = currentCenterX; 52 anim.onUpdate(); 53 } 54 }; 55 56 private static final FloatPropertyCompat<RectFSpringAnim> RECT_Y = 57 new FloatPropertyCompat<RectFSpringAnim>("rectYSpring") { 58 @Override 59 public float getValue(RectFSpringAnim anim) { 60 return anim.mCurrentY; 61 } 62 63 @Override 64 public void setValue(RectFSpringAnim anim, float y) { 65 anim.mCurrentY = y; 66 anim.onUpdate(); 67 } 68 }; 69 70 private static final FloatPropertyCompat<RectFSpringAnim> RECT_SCALE_PROGRESS = 71 new FloatPropertyCompat<RectFSpringAnim>("rectScaleProgress") { 72 @Override 73 public float getValue(RectFSpringAnim object) { 74 return object.mCurrentScaleProgress; 75 } 76 77 @Override 78 public void setValue(RectFSpringAnim object, float value) { 79 object.mCurrentScaleProgress = value; 80 object.onUpdate(); 81 } 82 }; 83 84 private final RectF mStartRect; 85 private final RectF mTargetRect; 86 private final RectF mCurrentRect = new RectF(); 87 private final List<OnUpdateListener> mOnUpdateListeners = new ArrayList<>(); 88 private final List<Animator.AnimatorListener> mAnimatorListeners = new ArrayList<>(); 89 90 private float mCurrentCenterX; 91 private float mCurrentY; 92 // If true, tracking the bottom of the rects, else tracking the top. 93 private boolean mTrackingBottomY; 94 private float mCurrentScaleProgress; 95 private FlingSpringAnim mRectXAnim; 96 private FlingSpringAnim mRectYAnim; 97 private SpringAnimation mRectScaleAnim; 98 private boolean mAnimsStarted; 99 private boolean mRectXAnimEnded; 100 private boolean mRectYAnimEnded; 101 private boolean mRectScaleAnimEnded; 102 103 private float mMinVisChange; 104 private float mYOvershoot; 105 RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources)106 public RectFSpringAnim(RectF startRect, RectF targetRect, Resources resources) { 107 mStartRect = startRect; 108 mTargetRect = targetRect; 109 mCurrentCenterX = mStartRect.centerX(); 110 111 mTrackingBottomY = startRect.bottom < targetRect.bottom; 112 mCurrentY = mTrackingBottomY ? mStartRect.bottom : mStartRect.top; 113 114 mMinVisChange = resources.getDimensionPixelSize(R.dimen.swipe_up_fling_min_visible_change); 115 mYOvershoot = resources.getDimensionPixelSize(R.dimen.swipe_up_y_overshoot); 116 } 117 118 public void onTargetPositionChanged() { 119 if (mRectXAnim != null && mRectXAnim.getTargetPosition() != mTargetRect.centerX()) { 120 mRectXAnim.updatePosition(mCurrentCenterX, mTargetRect.centerX()); 121 } 122 123 if (mRectYAnim != null) { 124 if (mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.bottom) { 125 mRectYAnim.updatePosition(mCurrentY, mTargetRect.bottom); 126 } else if (!mTrackingBottomY && mRectYAnim.getTargetPosition() != mTargetRect.top) { 127 mRectYAnim.updatePosition(mCurrentY, mTargetRect.top); 128 } 129 } 130 } 131 132 public void addOnUpdateListener(OnUpdateListener onUpdateListener) { 133 mOnUpdateListeners.add(onUpdateListener); 134 } 135 136 public void addAnimatorListener(Animator.AnimatorListener animatorListener) { 137 mAnimatorListeners.add(animatorListener); 138 } 139 140 public void start(PointF velocityPxPerMs) { 141 // Only tell caller that we ended if both x and y animations have ended. 142 OnAnimationEndListener onXEndListener = ((animation, canceled, centerX, velocityX) -> { 143 mRectXAnimEnded = true; 144 maybeOnEnd(); 145 }); 146 OnAnimationEndListener onYEndListener = ((animation, canceled, centerY, velocityY) -> { 147 mRectYAnimEnded = true; 148 maybeOnEnd(); 149 }); 150 151 float startX = mCurrentCenterX; 152 float endX = mTargetRect.centerX(); 153 float minXValue = Math.min(startX, endX); 154 float maxXValue = Math.max(startX, endX); 155 mRectXAnim = new FlingSpringAnim(this, RECT_CENTER_X, startX, endX, 156 velocityPxPerMs.x * 1000, mMinVisChange, minXValue, maxXValue, 1f, onXEndListener); 157 158 float startVelocityY = velocityPxPerMs.y * 1000; 159 // Scale the Y velocity based on the initial velocity to tune the curves. 160 float springVelocityFactor = 0.1f + 0.9f * Math.abs(startVelocityY) / 20000.0f; 161 float startY = mCurrentY; 162 float endY = mTrackingBottomY ? mTargetRect.bottom : mTargetRect.top; 163 float minYValue = Math.min(startY, endY - mYOvershoot); 164 float maxYValue = Math.max(startY, endY); 165 mRectYAnim = new FlingSpringAnim(this, RECT_Y, startY, endY, startVelocityY, 166 mMinVisChange, minYValue, maxYValue, springVelocityFactor, onYEndListener); 167 168 float minVisibleChange = 1f / mStartRect.height(); 169 mRectScaleAnim = new SpringAnimation(this, RECT_SCALE_PROGRESS) 170 .setSpring(new SpringForce(1f) 171 .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY) 172 .setStiffness(SpringForce.STIFFNESS_LOW)) 173 .setStartVelocity(velocityPxPerMs.y * minVisibleChange) 174 .setMaxValue(1f) 175 .setMinimumVisibleChange(minVisibleChange) 176 .addEndListener((animation, canceled, value, velocity) -> { 177 mRectScaleAnimEnded = true; 178 maybeOnEnd(); 179 }); 180 181 mRectXAnim.start(); 182 mRectYAnim.start(); 183 mRectScaleAnim.start(); 184 mAnimsStarted = true; 185 for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { 186 animatorListener.onAnimationStart(null); 187 } 188 } 189 end()190 public void end() { 191 if (mAnimsStarted) { 192 mRectXAnim.end(); 193 mRectYAnim.end(); 194 if (mRectScaleAnim.canSkipToEnd()) { 195 mRectScaleAnim.skipToEnd(); 196 } 197 } 198 } 199 onUpdate()200 private void onUpdate() { 201 if (!mOnUpdateListeners.isEmpty()) { 202 float currentWidth = Utilities.mapRange(mCurrentScaleProgress, mStartRect.width(), 203 mTargetRect.width()); 204 float currentHeight = Utilities.mapRange(mCurrentScaleProgress, mStartRect.height(), 205 mTargetRect.height()); 206 if (mTrackingBottomY) { 207 mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY - currentHeight, 208 mCurrentCenterX + currentWidth / 2, mCurrentY); 209 } else { 210 mCurrentRect.set(mCurrentCenterX - currentWidth / 2, mCurrentY, 211 mCurrentCenterX + currentWidth / 2, mCurrentY + currentHeight); 212 } 213 for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { 214 onUpdateListener.onUpdate(mCurrentRect, mCurrentScaleProgress); 215 } 216 } 217 } 218 maybeOnEnd()219 private void maybeOnEnd() { 220 if (mAnimsStarted && mRectXAnimEnded && mRectYAnimEnded && mRectScaleAnimEnded) { 221 mAnimsStarted = false; 222 for (Animator.AnimatorListener animatorListener : mAnimatorListeners) { 223 animatorListener.onAnimationEnd(null); 224 } 225 } 226 } 227 cancel()228 public void cancel() { 229 if (mAnimsStarted) { 230 for (OnUpdateListener onUpdateListener : mOnUpdateListeners) { 231 onUpdateListener.onCancel(); 232 } 233 } 234 end(); 235 } 236 237 public interface OnUpdateListener { onUpdate(RectF currentRect, float progress)238 void onUpdate(RectF currentRect, float progress); 239 onCancel()240 default void onCancel() { } 241 } 242 } 243