afdad57bdf
After dragging the bubble bar to dismiss, don't reset its alpha
value.
Flag: com.android.wm.shell.enable_bubble_bar
Fixes: 359804931
Test: manual
- Create a bubble in the bubble bar
- Drag bubble bar to dismiss
- Create another bubble
- Observe the new bubble does not flicker
Change-Id: I8d56f71d38ec33acda8efade50735b59254d77d4
228 lines
9.1 KiB
Java
228 lines
9.1 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 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.common.bubbles.DismissCircleView;
|
|
import com.android.wm.shell.common.bubbles.DismissView;
|
|
import com.android.wm.shell.shared.animation.PhysicsAnimator;
|
|
|
|
/**
|
|
* 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;
|
|
// 400f matches to MEDIUM_LOW spring stiffness
|
|
private static final float TRANSLATION_SPRING_STIFFNESS = 400f;
|
|
|
|
private final PhysicsAnimator.SpringConfig mDefaultConfig =
|
|
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
|
|
private final PhysicsAnimator.SpringConfig mTranslationConfig =
|
|
new PhysicsAnimator.SpringConfig(TRANSLATION_SPRING_STIFFNESS,
|
|
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 restingPosition 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 animateToRestingState(@NonNull PointF restingPosition, @NonNull PointF velocity,
|
|
@Nullable Runnable endActions) {
|
|
mBubbleAnimator.cancel();
|
|
mBubbleAnimator
|
|
.spring(DynamicAnimation.SCALE_X, 1f)
|
|
.spring(DynamicAnimation.SCALE_Y, 1f)
|
|
.spring(BubbleDragController.DRAG_TRANSLATION_X, restingPosition.x, velocity.x,
|
|
mTranslationConfig)
|
|
.spring(DynamicAnimation.TRANSLATION_Y, restingPosition.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(restingPosition, /* dismissed= */ false);
|
|
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, /* dismissed= */ true);
|
|
if (endActions != null) endActions.run();
|
|
}
|
|
})
|
|
.start();
|
|
}
|
|
|
|
/**
|
|
* Reset the animated views to the initial state
|
|
*
|
|
* @param initialPosition position of the bubble
|
|
* @param dismissed whether the animated view was dismissed
|
|
*/
|
|
private void resetAnimatedViews(@NonNull PointF initialPosition, boolean dismissed) {
|
|
mView.setScaleX(1f);
|
|
mView.setScaleY(1f);
|
|
if (!dismissed) {
|
|
mView.setAlpha(1f);
|
|
}
|
|
mView.setTranslationX(initialPosition.x);
|
|
mView.setTranslationY(initialPosition.y);
|
|
|
|
if (mDismissView != null) {
|
|
mDismissView.getCircle().setScaleX(1f);
|
|
mDismissView.getCircle().setScaleY(1f);
|
|
}
|
|
}
|
|
}
|