1 /* 2 * Copyright (C) 2022 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 android.window; 18 19 import android.annotation.NonNull; 20 import android.util.FloatProperty; 21 22 import com.android.internal.dynamicanimation.animation.DynamicAnimation; 23 import com.android.internal.dynamicanimation.animation.SpringAnimation; 24 import com.android.internal.dynamicanimation.animation.SpringForce; 25 26 /** 27 * An animator that drives the predictive back progress with a spring. 28 * 29 * The back gesture's latest touch point and committal state determines the final position of 30 * the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with 31 * smoothly transitioning progress values. 32 * 33 * @hide 34 */ 35 public class BackProgressAnimator { 36 /** 37 * A factor to scale the input progress by, so that it works better with the spring. 38 * We divide the output progress by this value before sending it to apps, so that apps 39 * always receive progress values in [0, 1]. 40 */ 41 private static final float SCALE_FACTOR = 100f; 42 private final SpringAnimation mSpring; 43 private ProgressCallback mCallback; 44 private float mProgress = 0; 45 private BackMotionEvent mLastBackEvent; 46 private boolean mBackAnimationInProgress = false; 47 setProgress(float progress)48 private void setProgress(float progress) { 49 mProgress = progress; 50 } 51 getProgress()52 private float getProgress() { 53 return mProgress; 54 } 55 56 private static final FloatProperty<BackProgressAnimator> PROGRESS_PROP = 57 new FloatProperty<BackProgressAnimator>("progress") { 58 @Override 59 public void setValue(BackProgressAnimator animator, float value) { 60 animator.setProgress(value); 61 animator.updateProgressValue(value); 62 } 63 64 @Override 65 public Float get(BackProgressAnimator object) { 66 return object.getProgress(); 67 } 68 }; 69 70 71 /** A callback to be invoked when there's a progress value update from the animator. */ 72 public interface ProgressCallback { 73 /** Called when there's a progress value update. */ onProgressUpdate(BackEvent event)74 void onProgressUpdate(BackEvent event); 75 } 76 BackProgressAnimator()77 public BackProgressAnimator() { 78 mSpring = new SpringAnimation(this, PROGRESS_PROP); 79 mSpring.setSpring(new SpringForce() 80 .setStiffness(SpringForce.STIFFNESS_MEDIUM) 81 .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); 82 } 83 84 /** 85 * Sets a new target position for the back progress. 86 * 87 * @param event the {@link BackMotionEvent} containing the latest target progress. 88 */ onBackProgressed(BackMotionEvent event)89 public void onBackProgressed(BackMotionEvent event) { 90 if (!mBackAnimationInProgress) { 91 return; 92 } 93 mLastBackEvent = event; 94 if (mSpring == null) { 95 return; 96 } 97 mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR); 98 } 99 100 /** 101 * Starts the back progress animation. 102 * 103 * @param event the {@link BackMotionEvent} that started the gesture. 104 * @param callback the back callback to invoke for the gesture. It will receive back progress 105 * dispatches as the progress animation updates. 106 */ onBackStarted(BackMotionEvent event, ProgressCallback callback)107 public void onBackStarted(BackMotionEvent event, ProgressCallback callback) { 108 reset(); 109 mLastBackEvent = event; 110 mCallback = callback; 111 mBackAnimationInProgress = true; 112 } 113 114 /** 115 * Resets the back progress animation. This should be called when back is invoked or cancelled. 116 */ reset()117 public void reset() { 118 mSpring.animateToFinalPosition(0); 119 if (mSpring.canSkipToEnd()) { 120 mSpring.skipToEnd(); 121 } else { 122 // Should never happen. 123 mSpring.cancel(); 124 } 125 mBackAnimationInProgress = false; 126 mLastBackEvent = null; 127 mCallback = null; 128 mProgress = 0; 129 } 130 131 /** 132 * Animate the back progress animation from current progress to start position. 133 * This should be called when back is cancelled. 134 * 135 * @param finishCallback the callback to be invoked when the progress is reach to 0. 136 */ onBackCancelled(@onNull Runnable finishCallback)137 public void onBackCancelled(@NonNull Runnable finishCallback) { 138 final DynamicAnimation.OnAnimationEndListener listener = 139 new DynamicAnimation.OnAnimationEndListener() { 140 @Override 141 public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value, 142 float velocity) { 143 mSpring.removeEndListener(this); 144 finishCallback.run(); 145 reset(); 146 } 147 }; 148 mSpring.addEndListener(listener); 149 mSpring.animateToFinalPosition(0); 150 } 151 152 /** Returns true if the back animation is in progress. */ isBackAnimationInProgress()153 boolean isBackAnimationInProgress() { 154 return mBackAnimationInProgress; 155 } 156 updateProgressValue(float progress)157 private void updateProgressValue(float progress) { 158 if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) { 159 return; 160 } 161 mCallback.onProgressUpdate( 162 new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(), 163 progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge())); 164 } 165 166 } 167