blob: 4f870f6ceffccc17d8aa44c6c58d28dc5bf4f15a [file] [log] [blame]
/*
* Copyright (C) 2019 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.systemui.bubbles.animation;
import android.graphics.PointF;
import android.view.View;
import android.view.WindowInsets;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.systemui.R;
import com.google.android.collect.Sets;
import java.util.Set;
/**
* Animation controller for bubbles when they're in their expanded state, or animating to/from the
* expanded state. This controls the expansion animation as well as bubbles 'dragging out' to be
* dismissed.
*/
public class ExpandedAnimationController
extends PhysicsAnimationLayout.PhysicsAnimationController {
/**
* The stack position from which the bubbles were expanded. Saved in {@link #expandFromStack}
* and used to return to stack form in {@link #collapseBackToStack}.
*/
private PointF mExpandedFrom;
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
/** Spacing between bubbles in the expanded state. */
private float mBubblePaddingPx;
/** Size of each bubble. */
private float mBubbleSizePx;
@Override
protected void setLayout(PhysicsAnimationLayout layout) {
super.setLayout(layout);
mStackOffsetPx = layout.getResources().getDimensionPixelSize(
R.dimen.bubble_stack_offset);
mBubblePaddingPx = layout.getResources().getDimensionPixelSize(
R.dimen.bubble_padding);
mBubbleSizePx = layout.getResources().getDimensionPixelSize(
R.dimen.individual_bubble_size);
}
/**
* Animates expanding the bubbles into a row along the top of the screen.
*
* @return The y-value to which the bubbles were expanded, in case that's useful.
*/
public float expandFromStack(PointF expandedFrom, Runnable after) {
mExpandedFrom = expandedFrom;
// How much to translate the next bubble, so that it is not overlapping the previous one.
float translateNextBubbleXBy = mBubblePaddingPx;
for (int i = 0; i < mLayout.getChildCount(); i++) {
mLayout.animatePositionForChildAtIndex(i, translateNextBubbleXBy, getExpandedY());
translateNextBubbleXBy += mBubbleSizePx + mBubblePaddingPx;
}
runAfterTranslationsEnd(after);
return getExpandedY();
}
/** Animate collapsing the bubbles back to their stacked position. */
public void collapseBackToStack(Runnable after) {
// Stack to the left if we're going to the left, or right if not.
final float sideMultiplier = mLayout.isFirstChildXLeftOfCenter(mExpandedFrom.x) ? -1 : 1;
for (int i = 0; i < mLayout.getChildCount(); i++) {
mLayout.animatePositionForChildAtIndex(
i, mExpandedFrom.x + (sideMultiplier * i * mStackOffsetPx), mExpandedFrom.y);
}
runAfterTranslationsEnd(after);
}
/** The Y value of the row of expanded bubbles. */
private float getExpandedY() {
final WindowInsets insets = mLayout.getRootWindowInsets();
if (insets != null) {
return mBubblePaddingPx + Math.max(
insets.getSystemWindowInsetTop(),
insets.getDisplayCutout() != null
? insets.getDisplayCutout().getSafeInsetTop()
: 0);
}
return mBubblePaddingPx;
}
/** Runs the given Runnable after all translation-related animations have ended. */
private void runAfterTranslationsEnd(Runnable after) {
DynamicAnimation.OnAnimationEndListener allEndedListener =
(animation, canceled, value, velocity) -> {
if (!mLayout.arePropertiesAnimating(
DynamicAnimation.TRANSLATION_X,
DynamicAnimation.TRANSLATION_Y)) {
after.run();
}
};
mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_X);
mLayout.setEndListenerForProperty(allEndedListener, DynamicAnimation.TRANSLATION_Y);
}
@Override
Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
return Sets.newHashSet(
DynamicAnimation.TRANSLATION_X,
DynamicAnimation.TRANSLATION_Y);
}
@Override
int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index) {
return NONE;
}
@Override
float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property) {
return 0;
}
@Override
SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view) {
return new SpringForce()
.setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY);
}
@Override
void onChildAdded(View child, int index) {
// TODO: Animate the new bubble into the row, and push the other bubbles out of the way.
child.setTranslationY(getExpandedY());
}
@Override
void onChildToBeRemoved(View child, int index, Runnable actuallyRemove) {
// TODO: Animate the bubble out, and pull the other bubbles into its position.
actuallyRemove.run();
}
}