Merge "ActivityView animations!" into rvc-dev am: ab0ffd1d48
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/11793537 Change-Id: I73010d1cdfe621de7a30022a0febee23df00dbf6
This commit is contained in:
@@ -1222,10 +1222,6 @@
|
|||||||
<dimen name="bubble_dismiss_slop">16dp</dimen>
|
<dimen name="bubble_dismiss_slop">16dp</dimen>
|
||||||
<!-- Height of button allowing users to adjust settings for bubbles. -->
|
<!-- Height of button allowing users to adjust settings for bubbles. -->
|
||||||
<dimen name="bubble_manage_button_height">48dp</dimen>
|
<dimen name="bubble_manage_button_height">48dp</dimen>
|
||||||
<!-- How far, horizontally, to animate the expanded view over when animating in/out. -->
|
|
||||||
<dimen name="bubble_expanded_animate_x_distance">100dp</dimen>
|
|
||||||
<!-- How far, vertically, to animate the expanded view over when animating in/out. -->
|
|
||||||
<dimen name="bubble_expanded_animate_y_distance">500dp</dimen>
|
|
||||||
<!-- Max width of the message bubble-->
|
<!-- Max width of the message bubble-->
|
||||||
<dimen name="bubble_message_max_width">144dp</dimen>
|
<dimen name="bubble_message_max_width">144dp</dimen>
|
||||||
<!-- Min width of the message bubble -->
|
<!-- Min width of the message bubble -->
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
|
|||||||
import static android.graphics.PixelFormat.TRANSPARENT;
|
import static android.graphics.PixelFormat.TRANSPARENT;
|
||||||
import static android.view.Display.INVALID_DISPLAY;
|
import static android.view.Display.INVALID_DISPLAY;
|
||||||
import static android.view.InsetsState.ITYPE_IME;
|
import static android.view.InsetsState.ITYPE_IME;
|
||||||
|
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
|
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
|
||||||
import static android.view.ViewRootImpl.sNewInsetsMode;
|
import static android.view.ViewRootImpl.sNewInsetsMode;
|
||||||
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
|
||||||
@@ -33,7 +34,6 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPAND
|
|||||||
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||||
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
||||||
|
|
||||||
import android.annotation.Nullable;
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.ActivityOptions;
|
import android.app.ActivityOptions;
|
||||||
import android.app.ActivityTaskManager;
|
import android.app.ActivityTaskManager;
|
||||||
@@ -46,6 +46,7 @@ import android.content.res.Resources;
|
|||||||
import android.content.res.TypedArray;
|
import android.content.res.TypedArray;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.Insets;
|
import android.graphics.Insets;
|
||||||
|
import android.graphics.Outline;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.drawable.ShapeDrawable;
|
import android.graphics.drawable.ShapeDrawable;
|
||||||
@@ -55,12 +56,19 @@ import android.os.RemoteException;
|
|||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
|
import android.view.SurfaceControl;
|
||||||
|
import android.view.SurfaceView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewOutlineProvider;
|
||||||
import android.view.WindowInsets;
|
import android.view.WindowInsets;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.accessibility.AccessibilityNodeInfo;
|
import android.view.accessibility.AccessibilityNodeInfo;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import com.android.internal.policy.ScreenDecorationsUtils;
|
import com.android.internal.policy.ScreenDecorationsUtils;
|
||||||
import com.android.systemui.Dependency;
|
import com.android.systemui.Dependency;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
@@ -88,6 +96,7 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
// The triangle pointing to the expanded view
|
// The triangle pointing to the expanded view
|
||||||
private View mPointerView;
|
private View mPointerView;
|
||||||
private int mPointerMargin;
|
private int mPointerMargin;
|
||||||
|
@Nullable private int[] mExpandedViewContainerLocation;
|
||||||
|
|
||||||
private AlphaOptimizedButton mSettingsIcon;
|
private AlphaOptimizedButton mSettingsIcon;
|
||||||
|
|
||||||
@@ -121,6 +130,16 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
private View mVirtualImeView;
|
private View mVirtualImeView;
|
||||||
private WindowManager mVirtualDisplayWindowManager;
|
private WindowManager mVirtualDisplayWindowManager;
|
||||||
private boolean mImeShowing = false;
|
private boolean mImeShowing = false;
|
||||||
|
private float mCornerRadius = 0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for the ActivityView that has a solid, round-rect background that shows if the
|
||||||
|
* ActivityView hasn't loaded.
|
||||||
|
*/
|
||||||
|
private FrameLayout mActivityViewContainer = new FrameLayout(getContext());
|
||||||
|
|
||||||
|
/** The SurfaceView that the ActivityView draws to. */
|
||||||
|
@Nullable private SurfaceView mActivitySurface;
|
||||||
|
|
||||||
private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
|
private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() {
|
||||||
@Override
|
@Override
|
||||||
@@ -269,7 +288,28 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
|
|
||||||
// Set ActivityView's alpha value as zero, since there is no view content to be shown.
|
// Set ActivityView's alpha value as zero, since there is no view content to be shown.
|
||||||
setContentVisibility(false);
|
setContentVisibility(false);
|
||||||
addView(mActivityView);
|
|
||||||
|
mActivityViewContainer.setBackgroundColor(Color.WHITE);
|
||||||
|
mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() {
|
||||||
|
@Override
|
||||||
|
public void getOutline(View view, Outline outline) {
|
||||||
|
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mActivityViewContainer.setClipToOutline(true);
|
||||||
|
mActivityViewContainer.addView(mActivityView);
|
||||||
|
mActivityViewContainer.setLayoutParams(
|
||||||
|
new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
||||||
|
addView(mActivityViewContainer);
|
||||||
|
|
||||||
|
if (mActivityView != null
|
||||||
|
&& mActivityView.getChildCount() > 0
|
||||||
|
&& mActivityView.getChildAt(0) instanceof SurfaceView) {
|
||||||
|
// Retrieve the surface from the ActivityView so we can screenshot it and change its
|
||||||
|
// z-ordering. This should always be possible, since ActivityView's constructor adds the
|
||||||
|
// SurfaceView as its first child.
|
||||||
|
mActivitySurface = (SurfaceView) mActivityView.getChildAt(0);
|
||||||
|
}
|
||||||
|
|
||||||
// Expanded stack layout, top to bottom:
|
// Expanded stack layout, top to bottom:
|
||||||
// Expanded view container
|
// Expanded view container
|
||||||
@@ -327,6 +367,39 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
return mBubble != null ? mBubble.getKey() : "null";
|
return mBubble != null ? mBubble.getKey() : "null";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asks the ActivityView's surface to draw on top of all other views in the window. This is
|
||||||
|
* useful for ordering surfaces during animations, but should otherwise be set to false so that
|
||||||
|
* bubbles and menus can draw over the ActivityView.
|
||||||
|
*/
|
||||||
|
void setSurfaceZOrderedOnTop(boolean onTop) {
|
||||||
|
if (mActivitySurface == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mActivitySurface.setZOrderedOnTop(onTop, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */
|
||||||
|
@Nullable SurfaceControl.ScreenshotGraphicBuffer snapshotActivitySurface() {
|
||||||
|
if (mActivitySurface == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SurfaceControl.captureLayers(
|
||||||
|
mActivitySurface.getSurfaceControl(),
|
||||||
|
new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()),
|
||||||
|
1 /* scale */);
|
||||||
|
}
|
||||||
|
|
||||||
|
int[] getActivityViewLocationOnScreen() {
|
||||||
|
if (mActivityView != null) {
|
||||||
|
return mActivityView.getLocationOnScreen();
|
||||||
|
} else {
|
||||||
|
return new int[]{0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void setManageClickListener(OnClickListener manageClickListener) {
|
void setManageClickListener(OnClickListener manageClickListener) {
|
||||||
findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
|
findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
|
||||||
}
|
}
|
||||||
@@ -345,12 +418,12 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
void applyThemeAttrs() {
|
void applyThemeAttrs() {
|
||||||
final TypedArray ta = mContext.obtainStyledAttributes(
|
final TypedArray ta = mContext.obtainStyledAttributes(
|
||||||
new int[] {android.R.attr.dialogCornerRadius});
|
new int[] {android.R.attr.dialogCornerRadius});
|
||||||
float cornerRadius = ta.getDimensionPixelSize(0, 0);
|
mCornerRadius = ta.getDimensionPixelSize(0, 0);
|
||||||
ta.recycle();
|
ta.recycle();
|
||||||
|
|
||||||
if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
|
if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
|
||||||
mContext.getResources())) {
|
mContext.getResources())) {
|
||||||
mActivityView.setCornerRadius(cornerRadius);
|
mActivityView.setCornerRadius(mCornerRadius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,6 +471,7 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
mPointerView.setAlpha(alpha);
|
mPointerView.setAlpha(alpha);
|
||||||
if (mActivityView != null) {
|
if (mActivityView != null) {
|
||||||
mActivityView.setAlpha(alpha);
|
mActivityView.setAlpha(alpha);
|
||||||
|
mActivityView.bringToFront();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,6 +631,11 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
||||||
Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
|
Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mExpandedViewContainerLocation == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (usingActivityView()) {
|
if (usingActivityView()) {
|
||||||
float desiredHeight = mOverflowHeight;
|
float desiredHeight = mOverflowHeight;
|
||||||
if (!mIsOverflow) {
|
if (!mIsOverflow) {
|
||||||
@@ -564,7 +643,7 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
}
|
}
|
||||||
float height = Math.min(desiredHeight, getMaxExpandedHeight());
|
float height = Math.min(desiredHeight, getMaxExpandedHeight());
|
||||||
height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight);
|
height = Math.max(height, mIsOverflow? mOverflowHeight : mMinHeight);
|
||||||
LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
|
ViewGroup.LayoutParams lp = mActivityView.getLayoutParams();
|
||||||
mNeedsNewHeight = lp.height != height;
|
mNeedsNewHeight = lp.height != height;
|
||||||
if (!mKeyboardVisible) {
|
if (!mKeyboardVisible) {
|
||||||
// If the keyboard is visible... don't adjust the height because that will cause
|
// If the keyboard is visible... don't adjust the height because that will cause
|
||||||
@@ -574,7 +653,8 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
mNeedsNewHeight = false;
|
mNeedsNewHeight = false;
|
||||||
}
|
}
|
||||||
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
||||||
Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height
|
Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()
|
||||||
|
+ " height=" + height
|
||||||
+ " mNeedsNewHeight=" + mNeedsNewHeight);
|
+ " mNeedsNewHeight=" + mNeedsNewHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -582,28 +662,40 @@ public class BubbleExpandedView extends LinearLayout {
|
|||||||
|
|
||||||
private int getMaxExpandedHeight() {
|
private int getMaxExpandedHeight() {
|
||||||
mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
|
mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize);
|
||||||
int[] windowLocation = mActivityView.getLocationOnScreen();
|
|
||||||
int bottomInset = getRootWindowInsets() != null
|
int bottomInset = getRootWindowInsets() != null
|
||||||
? getRootWindowInsets().getStableInsetBottom()
|
? getRootWindowInsets().getStableInsetBottom()
|
||||||
: 0;
|
: 0;
|
||||||
return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
|
|
||||||
|
return mDisplaySize.y
|
||||||
|
- mExpandedViewContainerLocation[1]
|
||||||
|
- getPaddingTop()
|
||||||
|
- getPaddingBottom()
|
||||||
|
- mSettingsIconHeight
|
||||||
|
- mPointerHeight
|
||||||
- mPointerMargin - bottomInset;
|
- mPointerMargin - bottomInset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update appearance of the expanded view being displayed.
|
* Update appearance of the expanded view being displayed.
|
||||||
|
*
|
||||||
|
* @param containerLocationOnScreen The location on-screen of the container the expanded view is
|
||||||
|
* added to. This allows us to calculate max height without
|
||||||
|
* waiting for layout.
|
||||||
*/
|
*/
|
||||||
public void updateView() {
|
public void updateView(int[] containerLocationOnScreen) {
|
||||||
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
||||||
Log.d(TAG, "updateView: bubble="
|
Log.d(TAG, "updateView: bubble="
|
||||||
+ getBubbleKey());
|
+ getBubbleKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mExpandedViewContainerLocation = containerLocationOnScreen;
|
||||||
|
|
||||||
if (usingActivityView()
|
if (usingActivityView()
|
||||||
&& mActivityView.getVisibility() == VISIBLE
|
&& mActivityView.getVisibility() == VISIBLE
|
||||||
&& mActivityView.isAttachedToWindow()) {
|
&& mActivityView.isAttachedToWindow()) {
|
||||||
mActivityView.onLocationChanged();
|
mActivityView.onLocationChanged();
|
||||||
|
updateHeight();
|
||||||
}
|
}
|
||||||
updateHeight();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ import android.view.DisplayCutout;
|
|||||||
import android.view.Gravity;
|
import android.view.Gravity;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.SurfaceControl;
|
||||||
|
import android.view.SurfaceView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.ViewOutlineProvider;
|
import android.view.ViewOutlineProvider;
|
||||||
@@ -82,6 +84,7 @@ import com.android.internal.util.ContrastColorUtil;
|
|||||||
import com.android.systemui.Interpolators;
|
import com.android.systemui.Interpolators;
|
||||||
import com.android.systemui.Prefs;
|
import com.android.systemui.Prefs;
|
||||||
import com.android.systemui.R;
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.bubbles.animation.AnimatableScaleMatrix;
|
||||||
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
|
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
|
||||||
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
|
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
|
||||||
import com.android.systemui.bubbles.animation.StackAnimationController;
|
import com.android.systemui.bubbles.animation.StackAnimationController;
|
||||||
@@ -147,6 +150,16 @@ public class BubbleStackView extends FrameLayout
|
|||||||
StackAnimationController.IME_ANIMATION_STIFFNESS,
|
StackAnimationController.IME_ANIMATION_STIFFNESS,
|
||||||
StackAnimationController.DEFAULT_BOUNCINESS);
|
StackAnimationController.DEFAULT_BOUNCINESS);
|
||||||
|
|
||||||
|
private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
|
||||||
|
new PhysicsAnimator.SpringConfig(300f, 0.9f);
|
||||||
|
|
||||||
|
private final PhysicsAnimator.SpringConfig mScaleOutSpringConfig =
|
||||||
|
new PhysicsAnimator.SpringConfig(900f, 1f);
|
||||||
|
|
||||||
|
private final PhysicsAnimator.SpringConfig mTranslateSpringConfig =
|
||||||
|
new PhysicsAnimator.SpringConfig(
|
||||||
|
SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_NO_BOUNCY);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface to synchronize {@link View} state and the screen.
|
* Interface to synchronize {@link View} state and the screen.
|
||||||
*
|
*
|
||||||
@@ -186,8 +199,6 @@ public class BubbleStackView extends FrameLayout
|
|||||||
|
|
||||||
private Point mDisplaySize;
|
private Point mDisplaySize;
|
||||||
|
|
||||||
private final SpringAnimation mExpandedViewXAnim;
|
|
||||||
private final SpringAnimation mExpandedViewYAnim;
|
|
||||||
private final BubbleData mBubbleData;
|
private final BubbleData mBubbleData;
|
||||||
|
|
||||||
private final ValueAnimator mDesaturateAndDarkenAnimator;
|
private final ValueAnimator mDesaturateAndDarkenAnimator;
|
||||||
@@ -199,6 +210,24 @@ public class BubbleStackView extends FrameLayout
|
|||||||
|
|
||||||
private FrameLayout mExpandedViewContainer;
|
private FrameLayout mExpandedViewContainer;
|
||||||
|
|
||||||
|
/** Matrix used to scale the expanded view container with a given pivot point. */
|
||||||
|
private final AnimatableScaleMatrix mExpandedViewContainerMatrix = new AnimatableScaleMatrix();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SurfaceView that we draw screenshots of animating-out bubbles into. This allows us to animate
|
||||||
|
* between bubble activities without needing both to be alive at the same time.
|
||||||
|
*/
|
||||||
|
private SurfaceView mAnimatingOutSurfaceView;
|
||||||
|
|
||||||
|
/** Container for the animating-out SurfaceView. */
|
||||||
|
private FrameLayout mAnimatingOutSurfaceContainer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Buffer containing a screenshot of the animating-out bubble. This is drawn into the
|
||||||
|
* SurfaceView during animations.
|
||||||
|
*/
|
||||||
|
private SurfaceControl.ScreenshotGraphicBuffer mAnimatingOutBubbleBuffer;
|
||||||
|
|
||||||
private BubbleFlyoutView mFlyout;
|
private BubbleFlyoutView mFlyout;
|
||||||
/** Runnable that fades out the flyout and then sets it to GONE. */
|
/** Runnable that fades out the flyout and then sets it to GONE. */
|
||||||
private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
|
private Runnable mHideFlyout = () -> animateFlyoutCollapsed(true, 0 /* velX */);
|
||||||
@@ -230,8 +259,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
private int mBubblePaddingTop;
|
private int mBubblePaddingTop;
|
||||||
private int mBubbleTouchPadding;
|
private int mBubbleTouchPadding;
|
||||||
private int mExpandedViewPadding;
|
private int mExpandedViewPadding;
|
||||||
private int mExpandedAnimateXDistance;
|
private int mCornerRadius;
|
||||||
private int mExpandedAnimateYDistance;
|
|
||||||
private int mPointerHeight;
|
private int mPointerHeight;
|
||||||
private int mStatusBarHeight;
|
private int mStatusBarHeight;
|
||||||
private int mImeOffset;
|
private int mImeOffset;
|
||||||
@@ -680,10 +708,6 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
mBubbleElevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
||||||
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
||||||
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
|
mBubbleTouchPadding = res.getDimensionPixelSize(R.dimen.bubble_touch_padding);
|
||||||
mExpandedAnimateXDistance =
|
|
||||||
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_x_distance);
|
|
||||||
mExpandedAnimateYDistance =
|
|
||||||
res.getDimensionPixelSize(R.dimen.bubble_expanded_animate_y_distance);
|
|
||||||
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
|
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
|
||||||
|
|
||||||
mStatusBarHeight =
|
mStatusBarHeight =
|
||||||
@@ -698,6 +722,11 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
|
mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding);
|
||||||
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
int elevation = res.getDimensionPixelSize(R.dimen.bubble_elevation);
|
||||||
|
|
||||||
|
final TypedArray ta = mContext.obtainStyledAttributes(
|
||||||
|
new int[] {android.R.attr.dialogCornerRadius});
|
||||||
|
mCornerRadius = ta.getDimensionPixelSize(0, 0);
|
||||||
|
ta.recycle();
|
||||||
|
|
||||||
final Runnable onBubbleAnimatedOut = () -> {
|
final Runnable onBubbleAnimatedOut = () -> {
|
||||||
if (getBubbleCount() == 0) {
|
if (getBubbleCount() == 0) {
|
||||||
allBubblesAnimatedOutAction.run();
|
allBubblesAnimatedOutAction.run();
|
||||||
@@ -731,6 +760,24 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mExpandedViewContainer.setClipChildren(false);
|
mExpandedViewContainer.setClipChildren(false);
|
||||||
addView(mExpandedViewContainer);
|
addView(mExpandedViewContainer);
|
||||||
|
|
||||||
|
mAnimatingOutSurfaceContainer = new FrameLayout(getContext());
|
||||||
|
mAnimatingOutSurfaceContainer.setLayoutParams(
|
||||||
|
new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
||||||
|
addView(mAnimatingOutSurfaceContainer);
|
||||||
|
|
||||||
|
mAnimatingOutSurfaceView = new SurfaceView(getContext());
|
||||||
|
mAnimatingOutSurfaceView.setUseAlpha();
|
||||||
|
mAnimatingOutSurfaceView.setZOrderOnTop(true);
|
||||||
|
mAnimatingOutSurfaceView.setCornerRadius(mCornerRadius);
|
||||||
|
mAnimatingOutSurfaceView.setLayoutParams(new ViewGroup.LayoutParams(0, 0));
|
||||||
|
mAnimatingOutSurfaceContainer.addView(mAnimatingOutSurfaceView);
|
||||||
|
|
||||||
|
mAnimatingOutSurfaceContainer.setPadding(
|
||||||
|
mExpandedViewPadding,
|
||||||
|
mExpandedViewPadding,
|
||||||
|
mExpandedViewPadding,
|
||||||
|
mExpandedViewPadding);
|
||||||
|
|
||||||
setUpManageMenu();
|
setUpManageMenu();
|
||||||
|
|
||||||
setUpFlyout();
|
setUpFlyout();
|
||||||
@@ -776,26 +823,6 @@ public class BubbleStackView extends FrameLayout
|
|||||||
// MagnetizedObjects.
|
// MagnetizedObjects.
|
||||||
mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius);
|
mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius);
|
||||||
|
|
||||||
mExpandedViewXAnim =
|
|
||||||
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_X);
|
|
||||||
mExpandedViewXAnim.setSpring(
|
|
||||||
new SpringForce()
|
|
||||||
.setStiffness(SpringForce.STIFFNESS_LOW)
|
|
||||||
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
|
|
||||||
|
|
||||||
mExpandedViewYAnim =
|
|
||||||
new SpringAnimation(mExpandedViewContainer, DynamicAnimation.TRANSLATION_Y);
|
|
||||||
mExpandedViewYAnim.setSpring(
|
|
||||||
new SpringForce()
|
|
||||||
.setStiffness(SpringForce.STIFFNESS_LOW)
|
|
||||||
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
|
|
||||||
mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> {
|
|
||||||
if (mIsExpanded && mExpandedBubble != null
|
|
||||||
&& mExpandedBubble.getExpandedView() != null) {
|
|
||||||
mExpandedBubble.getExpandedView().updateView();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setClipChildren(false);
|
setClipChildren(false);
|
||||||
setFocusable(true);
|
setFocusable(true);
|
||||||
mBubbleContainer.bringToFront();
|
mBubbleContainer.bringToFront();
|
||||||
@@ -830,7 +857,7 @@ public class BubbleStackView extends FrameLayout
|
|||||||
if (mIsExpanded) {
|
if (mIsExpanded) {
|
||||||
mExpandedViewContainer.setTranslationY(getExpandedViewY());
|
mExpandedViewContainer.setTranslationY(getExpandedViewY());
|
||||||
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
mExpandedBubble.getExpandedView().updateView();
|
mExpandedBubble.getExpandedView().updateView(getLocationOnScreen());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -954,15 +981,10 @@ public class BubbleStackView extends FrameLayout
|
|||||||
|
|
||||||
PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
|
PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
|
||||||
|
|
||||||
final TypedArray ta = mContext.obtainStyledAttributes(
|
|
||||||
new int[] {android.R.attr.dialogCornerRadius});
|
|
||||||
final int menuCornerRadius = ta.getDimensionPixelSize(0, 0);
|
|
||||||
ta.recycle();
|
|
||||||
|
|
||||||
mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
|
mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
|
||||||
@Override
|
@Override
|
||||||
public void getOutline(View view, Outline outline) {
|
public void getOutline(View view, Outline outline) {
|
||||||
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius);
|
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
mManageMenu.setClipToOutline(true);
|
mManageMenu.setClipToOutline(true);
|
||||||
@@ -1447,6 +1469,31 @@ public class BubbleStackView extends FrameLayout
|
|||||||
mBubbleData.setShowingOverflow(true);
|
mBubbleData.setShowingOverflow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we're expanded, screenshot the currently expanded bubble (before expanding the newly
|
||||||
|
// selected bubble) so we can animate it out.
|
||||||
|
if (mIsExpanded && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
// Before screenshotting, have the real ActivityView show on top of other surfaces
|
||||||
|
// so that the screenshot doesn't flicker on top of it.
|
||||||
|
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
screenshotAnimatingOutBubbleIntoSurface((success) -> {
|
||||||
|
mAnimatingOutSurfaceContainer.setVisibility(
|
||||||
|
success ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
showNewlySelectedBubble(bubbleToSelect);
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
showNewlySelectedBubble(bubbleToSelect);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showNewlySelectedBubble(bubbleToSelect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
|
||||||
final BubbleViewProvider previouslySelected = mExpandedBubble;
|
final BubbleViewProvider previouslySelected = mExpandedBubble;
|
||||||
mExpandedBubble = bubbleToSelect;
|
mExpandedBubble = bubbleToSelect;
|
||||||
updatePointerPosition();
|
updatePointerPosition();
|
||||||
@@ -1637,50 +1684,18 @@ public class BubbleStackView extends FrameLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void beforeExpandedViewAnimation() {
|
private void beforeExpandedViewAnimation() {
|
||||||
|
mIsExpansionAnimating = true;
|
||||||
hideFlyoutImmediate();
|
hideFlyoutImmediate();
|
||||||
updateExpandedBubble();
|
updateExpandedBubble();
|
||||||
updateExpandedView();
|
updateExpandedView();
|
||||||
mIsExpansionAnimating = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void afterExpandedViewAnimation() {
|
private void afterExpandedViewAnimation() {
|
||||||
updateExpandedView();
|
|
||||||
mIsExpansionAnimating = false;
|
mIsExpansionAnimating = false;
|
||||||
|
updateExpandedView();
|
||||||
requestUpdate();
|
requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void animateCollapse() {
|
|
||||||
// Hide the menu if it's visible.
|
|
||||||
showManageMenu(false);
|
|
||||||
|
|
||||||
mIsExpanded = false;
|
|
||||||
final BubbleViewProvider previouslySelected = mExpandedBubble;
|
|
||||||
beforeExpandedViewAnimation();
|
|
||||||
maybeShowManageEducation(false);
|
|
||||||
|
|
||||||
if (DEBUG_BUBBLE_STACK_VIEW) {
|
|
||||||
Log.d(TAG, "animateCollapse");
|
|
||||||
Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
|
|
||||||
mExpandedBubble));
|
|
||||||
}
|
|
||||||
updateOverflowVisibility();
|
|
||||||
mBubbleContainer.cancelAllAnimations();
|
|
||||||
mExpandedAnimationController.collapseBackToStack(
|
|
||||||
mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
|
|
||||||
/* collapseTo */,
|
|
||||||
() -> {
|
|
||||||
mBubbleContainer.setActiveController(mStackAnimationController);
|
|
||||||
afterExpandedViewAnimation();
|
|
||||||
previouslySelected.setContentVisibility(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
|
|
||||||
mExpandedViewYAnim.animateToFinalPosition(getCollapsedY());
|
|
||||||
mExpandedViewContainer.animate()
|
|
||||||
.setDuration(100)
|
|
||||||
.alpha(0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void animateExpansion() {
|
private void animateExpansion() {
|
||||||
mIsExpanded = true;
|
mIsExpanded = true;
|
||||||
hideStackUserEducation(true /* fromExpansion */);
|
hideStackUserEducation(true /* fromExpansion */);
|
||||||
@@ -1688,32 +1703,196 @@ public class BubbleStackView extends FrameLayout
|
|||||||
|
|
||||||
mBubbleContainer.setActiveController(mExpandedAnimationController);
|
mBubbleContainer.setActiveController(mExpandedAnimationController);
|
||||||
updateOverflowVisibility();
|
updateOverflowVisibility();
|
||||||
|
updatePointerPosition();
|
||||||
mExpandedAnimationController.expandFromStack(() -> {
|
mExpandedAnimationController.expandFromStack(() -> {
|
||||||
updatePointerPosition();
|
|
||||||
afterExpandedViewAnimation();
|
afterExpandedViewAnimation();
|
||||||
maybeShowManageEducation(true);
|
maybeShowManageEducation(true);
|
||||||
} /* after */);
|
} /* after */);
|
||||||
|
|
||||||
mExpandedViewContainer.setTranslationX(getCollapsedX());
|
mExpandedViewContainer.setTranslationX(0);
|
||||||
mExpandedViewContainer.setTranslationY(getCollapsedY());
|
mExpandedViewContainer.setTranslationY(getExpandedViewY());
|
||||||
mExpandedViewContainer.setAlpha(0f);
|
mExpandedViewContainer.setAlpha(1f);
|
||||||
|
|
||||||
|
// X-value of the bubble we're expanding, once it's settled in its row.
|
||||||
|
final float bubbleWillBeAtX =
|
||||||
|
mExpandedAnimationController.getBubbleLeft(
|
||||||
|
mBubbleData.getBubbles().indexOf(mExpandedBubble));
|
||||||
|
|
||||||
|
// How far horizontally the bubble will be animating. We'll wait a bit longer for bubbles
|
||||||
|
// that are animating farther, so that the expanded view doesn't move as much.
|
||||||
|
final float horizontalDistanceAnimated =
|
||||||
|
Math.abs(bubbleWillBeAtX
|
||||||
|
- mStackAnimationController.getStackPosition().x);
|
||||||
|
|
||||||
|
// Wait for the path animation target to reach its end, and add a small amount of extra time
|
||||||
|
// if the bubble is moving a lot horizontally.
|
||||||
|
long startDelay = 0L;
|
||||||
|
|
||||||
|
// Should not happen since we lay out before expanding, but just in case...
|
||||||
|
if (getWidth() > 0) {
|
||||||
|
startDelay = (long)
|
||||||
|
(ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION
|
||||||
|
+ (horizontalDistanceAnimated / getWidth()) * 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the pivot point for the scale, so the expanded view animates out from the bubble.
|
||||||
|
mExpandedViewContainerMatrix.setScale(
|
||||||
|
0f, 0f,
|
||||||
|
bubbleWillBeAtX + mBubbleSize / 2f, getExpandedViewY());
|
||||||
|
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
||||||
|
|
||||||
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
postDelayed(() -> PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
|
||||||
|
.spring(AnimatableScaleMatrix.SCALE_X,
|
||||||
|
AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
|
||||||
|
mScaleInSpringConfig)
|
||||||
|
.spring(AnimatableScaleMatrix.SCALE_Y,
|
||||||
|
AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
|
||||||
|
mScaleInSpringConfig)
|
||||||
|
.addUpdateListener((target, values) -> {
|
||||||
|
mExpandedViewContainerMatrix.postTranslate(
|
||||||
|
mExpandedBubble.getIconView().getTranslationX()
|
||||||
|
- bubbleWillBeAtX,
|
||||||
|
0);
|
||||||
|
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
||||||
|
})
|
||||||
|
.withEndActions(() -> {
|
||||||
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.start(), startDelay);
|
||||||
|
|
||||||
mExpandedViewXAnim.animateToFinalPosition(0f);
|
|
||||||
mExpandedViewYAnim.animateToFinalPosition(getExpandedViewY());
|
|
||||||
mExpandedViewContainer.animate()
|
|
||||||
.setDuration(100)
|
|
||||||
.alpha(1f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getCollapsedX() {
|
private void animateCollapse() {
|
||||||
return mStackAnimationController.getStackPosition().x < getWidth() / 2
|
// Hide the menu if it's visible.
|
||||||
? -mExpandedAnimateXDistance
|
showManageMenu(false);
|
||||||
: mExpandedAnimateXDistance;
|
|
||||||
|
mIsExpanded = false;
|
||||||
|
|
||||||
|
mBubbleContainer.cancelAllAnimations();
|
||||||
|
|
||||||
|
// If we were in the middle of swapping, the animating-out surface would have been scaling
|
||||||
|
// to zero - finish it off.
|
||||||
|
PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
|
||||||
|
mAnimatingOutSurfaceContainer.setScaleX(0f);
|
||||||
|
mAnimatingOutSurfaceContainer.setScaleY(0f);
|
||||||
|
|
||||||
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
mExpandedBubble.getExpandedView().hideImeIfVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
final long startDelay =
|
||||||
|
(long) (ExpandedAnimationController.EXPAND_COLLAPSE_TARGET_ANIM_DURATION * 0.6f);
|
||||||
|
postDelayed(() -> mExpandedAnimationController.collapseBackToStack(
|
||||||
|
mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
|
||||||
|
/* collapseTo */,
|
||||||
|
() -> {
|
||||||
|
mBubbleContainer.setActiveController(mStackAnimationController);
|
||||||
|
}), startDelay);
|
||||||
|
|
||||||
|
// We want to visually collapse into this bubble during the animation.
|
||||||
|
final View expandingFromBubble = mExpandedBubble.getIconView();
|
||||||
|
|
||||||
|
// X-value the bubble is animating from (back into the stack).
|
||||||
|
final float expandingFromBubbleAtX =
|
||||||
|
mExpandedAnimationController.getBubbleLeft(
|
||||||
|
mBubbleData.getBubbles().indexOf(mExpandedBubble));
|
||||||
|
|
||||||
|
// Set the pivot point.
|
||||||
|
mExpandedViewContainerMatrix.setScale(
|
||||||
|
1f, 1f,
|
||||||
|
expandingFromBubbleAtX + mBubbleSize / 2f,
|
||||||
|
getExpandedViewY());
|
||||||
|
|
||||||
|
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix).cancel();
|
||||||
|
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
|
||||||
|
.spring(AnimatableScaleMatrix.SCALE_X, 0f, mScaleOutSpringConfig)
|
||||||
|
.spring(AnimatableScaleMatrix.SCALE_Y, 0f, mScaleOutSpringConfig)
|
||||||
|
.addUpdateListener((target, values) -> {
|
||||||
|
if (expandingFromBubble != null) {
|
||||||
|
// Follow the bubble as it translates!
|
||||||
|
mExpandedViewContainerMatrix.postTranslate(
|
||||||
|
expandingFromBubble.getTranslationX()
|
||||||
|
- expandingFromBubbleAtX, 0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
||||||
|
|
||||||
|
// Hide early so we don't have a tiny little expanded view still visible at the
|
||||||
|
// end of the scale animation.
|
||||||
|
if (mExpandedViewContainerMatrix.getScaleX() < 0.05f) {
|
||||||
|
mExpandedViewContainer.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.withEndActions(() -> {
|
||||||
|
final BubbleViewProvider previouslySelected = mExpandedBubble;
|
||||||
|
beforeExpandedViewAnimation();
|
||||||
|
maybeShowManageEducation(false);
|
||||||
|
|
||||||
|
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||||
|
Log.d(TAG, "animateCollapse");
|
||||||
|
Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
|
||||||
|
mExpandedBubble));
|
||||||
|
}
|
||||||
|
updateOverflowVisibility();
|
||||||
|
|
||||||
|
afterExpandedViewAnimation();
|
||||||
|
previouslySelected.setContentVisibility(false);
|
||||||
|
})
|
||||||
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getCollapsedY() {
|
private void animateSwitchBubbles() {
|
||||||
return Math.min(mStackAnimationController.getStackPosition().y,
|
// The surface contains a screenshot of the animating out bubble, so we just need to animate
|
||||||
mExpandedAnimateYDistance);
|
// it out (and then release the GraphicBuffer).
|
||||||
|
PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
|
||||||
|
PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer)
|
||||||
|
.spring(DynamicAnimation.SCALE_X, 0f, mScaleOutSpringConfig)
|
||||||
|
.spring(DynamicAnimation.SCALE_Y, 0f, mScaleOutSpringConfig)
|
||||||
|
.spring(DynamicAnimation.TRANSLATION_Y,
|
||||||
|
mAnimatingOutSurfaceContainer.getTranslationY() - mBubbleSize * 2,
|
||||||
|
mTranslateSpringConfig)
|
||||||
|
.withEndActions(this::releaseAnimatingOutBubbleBuffer)
|
||||||
|
.start();
|
||||||
|
|
||||||
|
float expandingFromBubbleDestinationX =
|
||||||
|
mExpandedAnimationController.getBubbleLeft(
|
||||||
|
mBubbleData.getBubbles().indexOf(mExpandedBubble));
|
||||||
|
|
||||||
|
mExpandedViewContainer.setAlpha(1f);
|
||||||
|
mExpandedViewContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
mExpandedViewContainerMatrix.setScale(
|
||||||
|
0f, 0f, expandingFromBubbleDestinationX + mBubbleSize / 2f, getExpandedViewY());
|
||||||
|
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
||||||
|
|
||||||
|
mExpandedViewContainer.postDelayed(() -> {
|
||||||
|
if (!mIsExpanded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicsAnimator.getInstance(mExpandedViewContainerMatrix)
|
||||||
|
.spring(AnimatableScaleMatrix.SCALE_X,
|
||||||
|
AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
|
||||||
|
mScaleInSpringConfig)
|
||||||
|
.spring(AnimatableScaleMatrix.SCALE_Y,
|
||||||
|
AnimatableScaleMatrix.getAnimatableValueForScaleFactor(1f),
|
||||||
|
mScaleInSpringConfig)
|
||||||
|
.addUpdateListener((target, values) -> {
|
||||||
|
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
|
||||||
|
})
|
||||||
|
.withEndActions(() -> {
|
||||||
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
|
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.start();
|
||||||
|
}, 25);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) {
|
private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) {
|
||||||
@@ -2228,11 +2407,114 @@ public class BubbleStackView extends FrameLayout
|
|||||||
if (mIsExpanded && mExpandedBubble != null
|
if (mIsExpanded && mExpandedBubble != null
|
||||||
&& mExpandedBubble.getExpandedView() != null) {
|
&& mExpandedBubble.getExpandedView() != null) {
|
||||||
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
|
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
|
||||||
|
bev.setContentVisibility(false);
|
||||||
|
mExpandedViewContainerMatrix.setScaleX(0f);
|
||||||
|
mExpandedViewContainerMatrix.setScaleY(0f);
|
||||||
|
mExpandedViewContainer.setVisibility(View.INVISIBLE);
|
||||||
|
mExpandedViewContainer.setAlpha(0f);
|
||||||
mExpandedViewContainer.addView(bev);
|
mExpandedViewContainer.addView(bev);
|
||||||
bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
|
bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
|
||||||
bev.populateExpandedView();
|
bev.populateExpandedView();
|
||||||
mExpandedViewContainer.setVisibility(VISIBLE);
|
|
||||||
mExpandedViewContainer.setAlpha(1.0f);
|
if (!mIsExpansionAnimating) {
|
||||||
|
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
|
||||||
|
post(this::animateSwitchBubbles);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests a snapshot from the currently expanded bubble's ActivityView and displays it in a
|
||||||
|
* SurfaceView. This allows us to load a newly expanded bubble's Activity into the ActivityView,
|
||||||
|
* while animating the (screenshot of the) previously selected bubble's content away.
|
||||||
|
*
|
||||||
|
* @param onComplete Callback to run once we're done here - called with 'false' if something
|
||||||
|
* went wrong, or 'true' if the SurfaceView is now showing a screenshot of the
|
||||||
|
* expanded bubble.
|
||||||
|
*/
|
||||||
|
private void screenshotAnimatingOutBubbleIntoSurface(Consumer<Boolean> onComplete) {
|
||||||
|
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
|
||||||
|
// You can't animate null.
|
||||||
|
onComplete.accept(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final BubbleExpandedView animatingOutExpandedView = mExpandedBubble.getExpandedView();
|
||||||
|
|
||||||
|
// Release the previous screenshot if it hasn't been released already.
|
||||||
|
if (mAnimatingOutBubbleBuffer != null) {
|
||||||
|
releaseAnimatingOutBubbleBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
mAnimatingOutBubbleBuffer = animatingOutExpandedView.snapshotActivitySurface();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// If we fail for any reason, print the stack trace and then notify the callback of our
|
||||||
|
// failure. This is not expected to occur, but it's not worth crashing over.
|
||||||
|
Log.wtf(TAG, e);
|
||||||
|
onComplete.accept(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAnimatingOutBubbleBuffer == null
|
||||||
|
|| mAnimatingOutBubbleBuffer.getGraphicBuffer() == null) {
|
||||||
|
// While no exception was thrown, we were unable to get a snapshot.
|
||||||
|
onComplete.accept(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the surface container's properties have been reset.
|
||||||
|
PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
|
||||||
|
mAnimatingOutSurfaceContainer.setScaleX(1f);
|
||||||
|
mAnimatingOutSurfaceContainer.setScaleY(1f);
|
||||||
|
mAnimatingOutSurfaceContainer.setTranslationX(0);
|
||||||
|
mAnimatingOutSurfaceContainer.setTranslationY(0);
|
||||||
|
|
||||||
|
final int[] activityViewLocation =
|
||||||
|
mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen();
|
||||||
|
final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen();
|
||||||
|
|
||||||
|
// Translate the surface to overlap the real ActivityView.
|
||||||
|
mAnimatingOutSurfaceContainer.setTranslationY(
|
||||||
|
activityViewLocation[1] - surfaceViewLocation[1]);
|
||||||
|
|
||||||
|
// Set the width/height of the SurfaceView to match the snapshot.
|
||||||
|
mAnimatingOutSurfaceView.getLayoutParams().width =
|
||||||
|
mAnimatingOutBubbleBuffer.getGraphicBuffer().getWidth();
|
||||||
|
mAnimatingOutSurfaceView.getLayoutParams().height =
|
||||||
|
mAnimatingOutBubbleBuffer.getGraphicBuffer().getHeight();
|
||||||
|
mAnimatingOutSurfaceView.requestLayout();
|
||||||
|
|
||||||
|
// Post to wait for layout.
|
||||||
|
post(() -> {
|
||||||
|
// The buffer might have been destroyed if the user is mashing on bubbles, that's okay.
|
||||||
|
if (mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) {
|
||||||
|
onComplete.accept(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mIsExpanded) {
|
||||||
|
onComplete.accept(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach the buffer! We're now displaying the snapshot.
|
||||||
|
mAnimatingOutSurfaceView.getHolder().getSurface().attachAndQueueBufferWithColorSpace(
|
||||||
|
mAnimatingOutBubbleBuffer.getGraphicBuffer(),
|
||||||
|
mAnimatingOutBubbleBuffer.getColorSpace());
|
||||||
|
|
||||||
|
mSurfaceSynchronizer.syncSurfaceAndRun(() -> post(() -> onComplete.accept(true)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases the buffer containing the screenshot of the animating-out bubble, if it exists and
|
||||||
|
* isn't yet destroyed.
|
||||||
|
*/
|
||||||
|
private void releaseAnimatingOutBubbleBuffer() {
|
||||||
|
if (mAnimatingOutBubbleBuffer != null
|
||||||
|
&& !mAnimatingOutBubbleBuffer.getGraphicBuffer().isDestroyed()) {
|
||||||
|
mAnimatingOutBubbleBuffer.getGraphicBuffer().destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2242,19 +2524,10 @@ public class BubbleStackView extends FrameLayout
|
|||||||
}
|
}
|
||||||
|
|
||||||
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
|
mExpandedViewContainer.setVisibility(mIsExpanded ? VISIBLE : GONE);
|
||||||
if (mIsExpanded) {
|
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
||||||
final float y = getExpandedViewY();
|
mExpandedViewContainer.setTranslationY(getExpandedViewY());
|
||||||
if (!mExpandedViewYAnim.isRunning()) {
|
mExpandedBubble.getExpandedView().updateView(
|
||||||
// We're not animating so set the value
|
mExpandedViewContainer.getLocationOnScreen());
|
||||||
mExpandedViewContainer.setTranslationY(y);
|
|
||||||
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
|
|
||||||
mExpandedBubble.getExpandedView().updateView();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// We are animating so update the value; there is an end listener on the animator
|
|
||||||
// that will ensure expandedeView.updateView gets called.
|
|
||||||
mExpandedViewYAnim.animateToFinalPosition(y);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
|
mStackOnLeftOrWillBe = mStackAnimationController.isStackOnLeftSide();
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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.Matrix;
|
||||||
|
|
||||||
|
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||||
|
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matrix whose scale properties can be animated using physics animations, via the {@link #SCALE_X}
|
||||||
|
* and {@link #SCALE_Y} FloatProperties.
|
||||||
|
*
|
||||||
|
* This is useful when you need to perform a scale animation with a pivot point, since pivot points
|
||||||
|
* are not supported by standard View scale operations but are supported by matrices.
|
||||||
|
*
|
||||||
|
* NOTE: DynamicAnimation assumes that all custom properties are denominated in pixels, and thus
|
||||||
|
* considers 1 to be the smallest user-visible change for custom properties. This means that if you
|
||||||
|
* animate {@link #SCALE_X} and {@link #SCALE_Y} to 3f, for example, the animation would have only
|
||||||
|
* three frames.
|
||||||
|
*
|
||||||
|
* To work around this, whenever animating to a desired scale value, animate to the value returned
|
||||||
|
* by {@link #getAnimatableValueForScaleFactor} instead. The SCALE_X and SCALE_Y properties will
|
||||||
|
* convert that (larger) value into the appropriate scale factor when scaling the matrix.
|
||||||
|
*/
|
||||||
|
public class AnimatableScaleMatrix extends Matrix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The X value of the scale.
|
||||||
|
*
|
||||||
|
* NOTE: This must be set or animated to the value returned by
|
||||||
|
* {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
|
||||||
|
*/
|
||||||
|
public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_X =
|
||||||
|
new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleX") {
|
||||||
|
@Override
|
||||||
|
public float getValue(AnimatableScaleMatrix object) {
|
||||||
|
return getAnimatableValueForScaleFactor(object.mScaleX);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(AnimatableScaleMatrix object, float value) {
|
||||||
|
object.setScaleX(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Y value of the scale.
|
||||||
|
*
|
||||||
|
* NOTE: This must be set or animated to the value returned by
|
||||||
|
* {@link #getAnimatableValueForScaleFactor}, not the desired scale factor itself.
|
||||||
|
*/
|
||||||
|
public static final FloatPropertyCompat<AnimatableScaleMatrix> SCALE_Y =
|
||||||
|
new FloatPropertyCompat<AnimatableScaleMatrix>("matrixScaleY") {
|
||||||
|
@Override
|
||||||
|
public float getValue(AnimatableScaleMatrix object) {
|
||||||
|
return getAnimatableValueForScaleFactor(object.mScaleY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setValue(AnimatableScaleMatrix object, float value) {
|
||||||
|
object.setScaleY(value * DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private float mScaleX = 1f;
|
||||||
|
private float mScaleY = 1f;
|
||||||
|
|
||||||
|
private float mPivotX = 0f;
|
||||||
|
private float mPivotY = 0f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the value to animate SCALE_X or SCALE_Y to in order to achieve the desired scale
|
||||||
|
* factor.
|
||||||
|
*/
|
||||||
|
public static float getAnimatableValueForScaleFactor(float scale) {
|
||||||
|
return scale * (1f / DynamicAnimation.MIN_VISIBLE_CHANGE_SCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScale(float sx, float sy, float px, float py) {
|
||||||
|
mScaleX = sx;
|
||||||
|
mScaleY = sy;
|
||||||
|
mPivotX = px;
|
||||||
|
mPivotY = py;
|
||||||
|
super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleX(float scaleX) {
|
||||||
|
mScaleX = scaleX;
|
||||||
|
super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScaleY(float scaleY) {
|
||||||
|
mScaleY = scaleY;
|
||||||
|
super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPivotX(float pivotX) {
|
||||||
|
mPivotX = pivotX;
|
||||||
|
super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPivotY(float pivotY) {
|
||||||
|
mPivotY = pivotY;
|
||||||
|
super.setScale(mScaleX, mScaleY, mPivotX, mPivotY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScaleX() {
|
||||||
|
return mScaleX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getScaleY() {
|
||||||
|
return mScaleY;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPivotX() {
|
||||||
|
return mPivotX;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPivotY() {
|
||||||
|
return mPivotY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ public class ExpandedAnimationController
|
|||||||
private static final int ANIMATE_TRANSLATION_FACTOR = 4;
|
private static final int ANIMATE_TRANSLATION_FACTOR = 4;
|
||||||
|
|
||||||
/** Duration of the expand/collapse target path animation. */
|
/** Duration of the expand/collapse target path animation. */
|
||||||
private static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
|
public static final int EXPAND_COLLAPSE_TARGET_ANIM_DURATION = 175;
|
||||||
|
|
||||||
/** Stiffness for the expand/collapse path-following animation. */
|
/** Stiffness for the expand/collapse path-following animation. */
|
||||||
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
|
private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
|
||||||
|
|||||||
@@ -425,7 +425,8 @@ public class BubbleControllerTest extends SysuiTestCase {
|
|||||||
BubbleStackView stackView = mBubbleController.getStackView();
|
BubbleStackView stackView = mBubbleController.getStackView();
|
||||||
mBubbleData.setExpanded(true);
|
mBubbleData.setExpanded(true);
|
||||||
assertTrue(mBubbleController.isStackExpanded());
|
assertTrue(mBubbleController.isStackExpanded());
|
||||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
|
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
|
||||||
|
true, mRow2.getEntry().getKey());
|
||||||
|
|
||||||
assertTrue(mSysUiStateBubblesExpanded);
|
assertTrue(mSysUiStateBubblesExpanded);
|
||||||
|
|
||||||
@@ -443,9 +444,11 @@ public class BubbleControllerTest extends SysuiTestCase {
|
|||||||
mRow.getEntry()));
|
mRow.getEntry()));
|
||||||
|
|
||||||
// collapse for previous bubble
|
// collapse for previous bubble
|
||||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
|
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
|
||||||
|
false, mRow2.getEntry().getKey());
|
||||||
// expand for selected bubble
|
// expand for selected bubble
|
||||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
|
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
|
||||||
|
true, mRow.getEntry().getKey());
|
||||||
|
|
||||||
// Collapse
|
// Collapse
|
||||||
mBubbleController.collapseStack();
|
mBubbleController.collapseStack();
|
||||||
|
|||||||
@@ -382,7 +382,8 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
|
|||||||
BubbleStackView stackView = mBubbleController.getStackView();
|
BubbleStackView stackView = mBubbleController.getStackView();
|
||||||
mBubbleData.setExpanded(true);
|
mBubbleData.setExpanded(true);
|
||||||
assertTrue(mBubbleController.isStackExpanded());
|
assertTrue(mBubbleController.isStackExpanded());
|
||||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getEntry().getKey());
|
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
|
||||||
|
true, mRow.getEntry().getKey());
|
||||||
|
|
||||||
// Last added is the one that is expanded
|
// Last added is the one that is expanded
|
||||||
assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
|
assertEquals(mRow2.getEntry(), mBubbleData.getSelectedBubble().getEntry());
|
||||||
@@ -397,9 +398,11 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase {
|
|||||||
mRow.getEntry()));
|
mRow.getEntry()));
|
||||||
|
|
||||||
// collapse for previous bubble
|
// collapse for previous bubble
|
||||||
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
|
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
|
||||||
|
false, mRow2.getEntry().getKey());
|
||||||
// expand for selected bubble
|
// expand for selected bubble
|
||||||
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getEntry().getKey());
|
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
|
||||||
|
true, mRow.getEntry().getKey());
|
||||||
|
|
||||||
// Collapse
|
// Collapse
|
||||||
mBubbleController.collapseStack();
|
mBubbleController.collapseStack();
|
||||||
|
|||||||
Reference in New Issue
Block a user