| /* |
| * Copyright (C) 2022 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.window; |
| |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.util.FloatProperty; |
| |
| import com.android.internal.dynamicanimation.animation.DynamicAnimation; |
| import com.android.internal.dynamicanimation.animation.SpringAnimation; |
| import com.android.internal.dynamicanimation.animation.SpringForce; |
| |
| /** |
| * An animator that drives the predictive back progress with a spring. |
| * |
| * The back gesture's latest touch point and committal state determines the final position of |
| * the spring. The continuous movement of the spring is used to produce {@link BackEvent}s with |
| * smoothly transitioning progress values. |
| * |
| * @hide |
| */ |
| public class BackProgressAnimator { |
| /** |
| * A factor to scale the input progress by, so that it works better with the spring. |
| * We divide the output progress by this value before sending it to apps, so that apps |
| * always receive progress values in [0, 1]. |
| */ |
| private static final float SCALE_FACTOR = 100f; |
| private final SpringAnimation mSpring; |
| private ProgressCallback mCallback; |
| private float mProgress = 0; |
| private BackMotionEvent mLastBackEvent; |
| private boolean mBackAnimationInProgress = false; |
| @Nullable |
| private Runnable mBackCancelledFinishRunnable; |
| private final DynamicAnimation.OnAnimationEndListener mOnAnimationEndListener = |
| (animation, canceled, value, velocity) -> { |
| invokeBackCancelledRunnable(); |
| reset(); |
| }; |
| |
| |
| private void setProgress(float progress) { |
| mProgress = progress; |
| } |
| |
| private float getProgress() { |
| return mProgress; |
| } |
| |
| private static final FloatProperty<BackProgressAnimator> PROGRESS_PROP = |
| new FloatProperty<BackProgressAnimator>("progress") { |
| @Override |
| public void setValue(BackProgressAnimator animator, float value) { |
| animator.setProgress(value); |
| animator.updateProgressValue(value); |
| } |
| |
| @Override |
| public Float get(BackProgressAnimator object) { |
| return object.getProgress(); |
| } |
| }; |
| |
| |
| /** A callback to be invoked when there's a progress value update from the animator. */ |
| public interface ProgressCallback { |
| /** Called when there's a progress value update. */ |
| void onProgressUpdate(BackEvent event); |
| } |
| |
| public BackProgressAnimator() { |
| mSpring = new SpringAnimation(this, PROGRESS_PROP); |
| mSpring.setSpring(new SpringForce() |
| .setStiffness(SpringForce.STIFFNESS_MEDIUM) |
| .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)); |
| } |
| |
| /** |
| * Sets a new target position for the back progress. |
| * |
| * @param event the {@link BackMotionEvent} containing the latest target progress. |
| */ |
| public void onBackProgressed(BackMotionEvent event) { |
| if (!mBackAnimationInProgress) { |
| return; |
| } |
| mLastBackEvent = event; |
| if (mSpring == null) { |
| return; |
| } |
| mSpring.animateToFinalPosition(event.getProgress() * SCALE_FACTOR); |
| } |
| |
| /** |
| * Starts the back progress animation. |
| * |
| * @param event the {@link BackMotionEvent} that started the gesture. |
| * @param callback the back callback to invoke for the gesture. It will receive back progress |
| * dispatches as the progress animation updates. |
| */ |
| public void onBackStarted(BackMotionEvent event, ProgressCallback callback) { |
| reset(); |
| mLastBackEvent = event; |
| mCallback = callback; |
| mBackAnimationInProgress = true; |
| updateProgressValue(0); |
| } |
| |
| /** |
| * Resets the back progress animation. This should be called when back is invoked or cancelled. |
| */ |
| public void reset() { |
| if (mBackCancelledFinishRunnable != null) { |
| // Ensure that last progress value that apps see is 0 |
| updateProgressValue(0); |
| invokeBackCancelledRunnable(); |
| } |
| mSpring.animateToFinalPosition(0); |
| if (mSpring.canSkipToEnd()) { |
| mSpring.skipToEnd(); |
| } else { |
| // Should never happen. |
| mSpring.cancel(); |
| } |
| mBackAnimationInProgress = false; |
| mLastBackEvent = null; |
| mCallback = null; |
| mProgress = 0; |
| } |
| |
| /** |
| * Animate the back progress animation from current progress to start position. |
| * This should be called when back is cancelled. |
| * |
| * @param finishCallback the callback to be invoked when the progress is reach to 0. |
| */ |
| public void onBackCancelled(@NonNull Runnable finishCallback) { |
| mBackCancelledFinishRunnable = finishCallback; |
| mSpring.addEndListener(mOnAnimationEndListener); |
| mSpring.animateToFinalPosition(0); |
| } |
| |
| /** |
| * Removes the finishCallback passed into {@link #onBackCancelled} |
| */ |
| public void removeOnBackCancelledFinishCallback() { |
| mSpring.removeEndListener(mOnAnimationEndListener); |
| mBackCancelledFinishRunnable = null; |
| } |
| |
| /** Returns true if the back animation is in progress. */ |
| boolean isBackAnimationInProgress() { |
| return mBackAnimationInProgress; |
| } |
| |
| private void updateProgressValue(float progress) { |
| if (mLastBackEvent == null || mCallback == null || !mBackAnimationInProgress) { |
| return; |
| } |
| mCallback.onProgressUpdate( |
| new BackEvent(mLastBackEvent.getTouchX(), mLastBackEvent.getTouchY(), |
| progress / SCALE_FACTOR, mLastBackEvent.getSwipeEdge())); |
| } |
| |
| private void invokeBackCancelledRunnable() { |
| mSpring.removeEndListener(mOnAnimationEndListener); |
| mBackCancelledFinishRunnable.run(); |
| mBackCancelledFinishRunnable = null; |
| } |
| |
| } |