Files
Flexera-Launcher/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleDragAnimator.java
T
Ivan Tkachenko 90a769d1d3 Bubble bar dismiss interaction
A single bubble and the bubble stack dismiss functionality is implemented using `BubbleDragController`. It attaches a touch listener to the corresponding views and handles gesture interactions.
* When the view is long clicked the dragging to dismiss interaction starts. It scales up the dragged view and presents the dismiss view in the bottom of the window.
* When the bubble or the bubble stack is dragged close to the dismiss target area, it gets pulled towards it and sticks to it. The `MagnetizedObject` and `MagneticTarget` classes are used for it.
* When the dragged view is released outside of the dismiss area, it moves back to the initial position with animation.
* When the dragged bubble is released inside of the dismiss area, it will dismiss the bubble with animation and remove it from the stack.
* When the dragged bubble bar stack is released inside the dismiss area, all the bubbles will get dismissed and the bubble bar will dissapear.

Bug: 271466616
Test: manual, TBD
Flag: WM_BUBBLE_BAR
Change-Id: I83393898be61ec522db92688ac2e111ef7d72fe6
2023-07-28 18:17:09 +00:00

223 lines
8.9 KiB
Java

/*
* Copyright (C) 2023 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 com.android.launcher3.taskbar.bubbles;
import static androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW;
import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
import android.content.res.Resources;
import android.graphics.PointF;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.FloatPropertyCompat;
import com.android.launcher3.R;
import com.android.wm.shell.animation.PhysicsAnimator;
import com.android.wm.shell.common.bubbles.DismissCircleView;
import com.android.wm.shell.common.bubbles.DismissView;
/**
* The animator performs the bubble animations while dragging and coordinates bubble and dismiss
* view animations when it gets magnetized, released or dismissed.
*/
public class BubbleDragAnimator {
private static final float SCALE_BUBBLE_FOCUSED = 1.2f;
private static final float SCALE_BUBBLE_CAPTURED = 0.9f;
private static final float SCALE_BUBBLE_BAR_FOCUSED = 1.1f;
private final PhysicsAnimator.SpringConfig mDefaultConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
private final PhysicsAnimator.SpringConfig mTranslationConfig =
new PhysicsAnimator.SpringConfig(STIFFNESS_MEDIUM, DAMPING_RATIO_LOW_BOUNCY);
@NonNull
private final View mView;
@NonNull
private final PhysicsAnimator<View> mBubbleAnimator;
@Nullable
private DismissView mDismissView;
@Nullable
private PhysicsAnimator<DismissCircleView> mDismissAnimator;
private final float mBubbleFocusedScale;
private final float mBubbleCapturedScale;
private final float mDismissCapturedScale;
/**
* Should be initialised for each dragged view
*
* @param view the dragged view to animate
*/
public BubbleDragAnimator(@NonNull View view) {
mView = view;
mBubbleAnimator = PhysicsAnimator.getInstance(view);
mBubbleAnimator.setDefaultSpringConfig(mDefaultConfig);
Resources resources = view.getResources();
final int collapsedSize = resources.getDimensionPixelSize(
R.dimen.bubblebar_dismiss_target_small_size);
final int expandedSize = resources.getDimensionPixelSize(
R.dimen.bubblebar_dismiss_target_size);
mDismissCapturedScale = (float) collapsedSize / expandedSize;
if (view instanceof BubbleBarView) {
mBubbleFocusedScale = SCALE_BUBBLE_BAR_FOCUSED;
mBubbleCapturedScale = mDismissCapturedScale;
} else {
mBubbleFocusedScale = SCALE_BUBBLE_FOCUSED;
mBubbleCapturedScale = SCALE_BUBBLE_CAPTURED;
}
}
/**
* Sets dismiss view to be animated alongside the dragged bubble
*/
public void setDismissView(@NonNull DismissView dismissView) {
mDismissView = dismissView;
mDismissAnimator = PhysicsAnimator.getInstance(dismissView.getCircle());
mDismissAnimator.setDefaultSpringConfig(mDefaultConfig);
}
/**
* Animates the focused state of the bubble when the dragging starts
*/
public void animateFocused() {
mBubbleAnimator.cancel();
mBubbleAnimator
.spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
.spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
.start();
}
/**
* Animates the dragged bubble movement back to the initial position.
*
* @param initialPosition the position to animate to
* @param velocity the initial velocity to use for the spring animation
* @param endActions gets called when the animation completes or gets cancelled
*/
public void animateToInitialState(@NonNull PointF initialPosition, @NonNull PointF velocity,
@Nullable Runnable endActions) {
mBubbleAnimator.cancel();
mBubbleAnimator
.spring(DynamicAnimation.SCALE_X, 1f)
.spring(DynamicAnimation.SCALE_Y, 1f)
.spring(DynamicAnimation.TRANSLATION_X, initialPosition.x, velocity.x,
mTranslationConfig)
.spring(DynamicAnimation.TRANSLATION_Y, initialPosition.y, velocity.y,
mTranslationConfig)
.addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
boolean allRelevantPropertyAnimationsEnded) -> {
if (canceled || allRelevantPropertyAnimationsEnded) {
resetAnimatedViews(initialPosition);
if (endActions != null) {
endActions.run();
}
}
})
.start();
}
/**
* Animates the dragged view alongside the dismiss view when it gets captured in the dismiss
* target area.
*/
public void animateDismissCaptured() {
mBubbleAnimator.cancel();
mBubbleAnimator
.spring(DynamicAnimation.SCALE_X, mBubbleCapturedScale)
.spring(DynamicAnimation.SCALE_Y, mBubbleCapturedScale)
.spring(DynamicAnimation.ALPHA, mDismissCapturedScale)
.start();
if (mDismissAnimator != null) {
mDismissAnimator.cancel();
mDismissAnimator
.spring(DynamicAnimation.SCALE_X, mDismissCapturedScale)
.spring(DynamicAnimation.SCALE_Y, mDismissCapturedScale)
.start();
}
}
/**
* Animates the dragged view alongside the dismiss view when it gets released from the dismiss
* target area.
*/
public void animateDismissReleased() {
mBubbleAnimator.cancel();
mBubbleAnimator
.spring(DynamicAnimation.SCALE_X, mBubbleFocusedScale)
.spring(DynamicAnimation.SCALE_Y, mBubbleFocusedScale)
.spring(DynamicAnimation.ALPHA, 1f)
.start();
if (mDismissAnimator != null) {
mDismissAnimator.cancel();
mDismissAnimator
.spring(DynamicAnimation.SCALE_X, 1f)
.spring(DynamicAnimation.SCALE_Y, 1f)
.start();
}
}
/**
* Animates the dragged bubble dismiss when it's released in the dismiss target area.
*
* @param initialPosition the initial position to move the bubble too after animation finishes
* @param endActions gets called when the animation completes or gets cancelled
*/
public void animateDismiss(@NonNull PointF initialPosition, @Nullable Runnable endActions) {
float dismissHeight = mDismissView != null ? mDismissView.getHeight() : 0f;
float translationY = mView.getTranslationY() + dismissHeight;
mBubbleAnimator
.spring(DynamicAnimation.TRANSLATION_Y, translationY)
.spring(DynamicAnimation.SCALE_X, 0f)
.spring(DynamicAnimation.SCALE_Y, 0f)
.spring(DynamicAnimation.ALPHA, 0f)
.addEndListener((View target, @NonNull FloatPropertyCompat<? super View> property,
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
boolean allRelevantPropertyAnimationsEnded) -> {
if (canceled || allRelevantPropertyAnimationsEnded) {
resetAnimatedViews(initialPosition);
if (endActions != null) endActions.run();
}
})
.start();
}
/**
* Reset the animated views to the initial state
*
* @param initialPosition position of the bubble
*/
private void resetAnimatedViews(@NonNull PointF initialPosition) {
mView.setScaleX(1f);
mView.setScaleY(1f);
mView.setAlpha(1f);
mView.setTranslationX(initialPosition.x);
mView.setTranslationY(initialPosition.y);
if (mDismissView != null) {
mDismissView.getCircle().setScaleX(1f);
mDismissView.getCircle().setScaleY(1f);
}
}
}