Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f90122a5 authored by Lyn Han's avatar Lyn Han Committed by Android (Google) Code Review
Browse files

Merge "Move dismiss target from stack to subview"

parents 3ffe12b6 d271cdff
Loading
Loading
Loading
Loading
+0 −148
Original line number Diff line number Diff line
/*
 * 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;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;

import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;

import com.android.systemui.R;

/** Dismiss view that contains a scrim gradient, as well as a dismiss icon, text, and circle. */
public class BubbleDismissView extends FrameLayout {
    /** Duration for animations involving the dismiss target text/icon. */
    private static final int DISMISS_TARGET_ANIMATION_BASE_DURATION = 150;
    private static final float SCALE_FOR_POP = 1.2f;
    private static final float SCALE_FOR_DISMISS = 0.9f;

    private LinearLayout mDismissTarget;
    private ImageView mDismissIcon;
    private View mDismissCircle;

    private SpringAnimation mDismissTargetAlphaSpring;
    private SpringAnimation mDismissTargetVerticalSpring;

    public BubbleDismissView(Context context) {
        super(context);
        setVisibility(GONE);

        LayoutInflater.from(context).inflate(R.layout.bubble_dismiss_target, this, true);
        mDismissTarget = findViewById(R.id.bubble_dismiss_icon_container);
        mDismissIcon = findViewById(R.id.bubble_dismiss_close_icon);
        mDismissCircle = findViewById(R.id.bubble_dismiss_circle);

        // Set up the basic target area animations. These are very simple animations that don't need
        // fancy interpolators.
        final AccelerateDecelerateInterpolator interpolator =
                new AccelerateDecelerateInterpolator();
        mDismissIcon.animate()
                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION)
                .setInterpolator(interpolator);
        mDismissCircle.animate()
                .setDuration(DISMISS_TARGET_ANIMATION_BASE_DURATION / 2)
                .setInterpolator(interpolator);

        mDismissTargetAlphaSpring =
                new SpringAnimation(mDismissTarget, DynamicAnimation.ALPHA)
                        .setSpring(new SpringForce()
                                .setStiffness(SpringForce.STIFFNESS_LOW)
                                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
        mDismissTargetVerticalSpring =
                new SpringAnimation(mDismissTarget, DynamicAnimation.TRANSLATION_Y)
                        .setSpring(new SpringForce()
                                .setStiffness(SpringForce.STIFFNESS_MEDIUM)
                                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));

        mDismissTargetAlphaSpring.addEndListener((anim, canceled, alpha, velocity) -> {
            // Since DynamicAnimations end when they're 'nearly' done, we can't rely on alpha being
            // exactly zero when this listener is triggered. However, if it's less than 50% we can
            // safely assume it was animating out rather than in.
            if (alpha < 0.5f) {
                // If the alpha spring was animating the view out, set it to GONE when it's done.
                setVisibility(INVISIBLE);
            }
        });
    }

    /** Springs in the dismiss target. */
    void springIn() {
        setVisibility(View.VISIBLE);

        // Fade in the dismiss target icon.
        mDismissIcon.animate()
                .setDuration(50)
                .scaleX(1f)
                .scaleY(1f)
                .alpha(1f);
        mDismissTarget.setAlpha(0f);
        mDismissTargetAlphaSpring.animateToFinalPosition(1f);

        // Spring up the dismiss target.
        mDismissTarget.setTranslationY(mDismissTarget.getHeight() / 2f);
        mDismissTargetVerticalSpring.animateToFinalPosition(0);

        mDismissCircle.setAlpha(0f);
        mDismissCircle.setScaleX(SCALE_FOR_POP);
        mDismissCircle.setScaleY(SCALE_FOR_POP);

        // Fade in circle and reduce size.
        mDismissCircle.animate()
                .alpha(1f)
                .scaleX(1f)
                .scaleY(1f);
    }

    /** Springs out the dismiss target. */
    void springOut() {
        // Fade out the target icon.
        mDismissIcon.animate()
                .setDuration(50)
                .scaleX(SCALE_FOR_DISMISS)
                .scaleY(SCALE_FOR_DISMISS)
                .alpha(0f);

        // Fade out the target.
        mDismissTargetAlphaSpring.animateToFinalPosition(0f);

        // Spring the target down a bit.
        mDismissTargetVerticalSpring.animateToFinalPosition(mDismissTarget.getHeight() / 2f);

        // Pop out the circle.
        mDismissCircle.animate()
                .scaleX(SCALE_FOR_DISMISS)
                .scaleY(SCALE_FOR_DISMISS)
                .alpha(0f);
    }

    /** Returns the Y value of the center of the dismiss target. */
    float getDismissTargetCenterY() {
        return getTop() + mDismissTarget.getTop() + mDismissTarget.getHeight() / 2f;
    }

    /** Returns the dismiss target, which contains the text/icon and any added padding. */
    View getDismissTarget() {
        return mDismissTarget;
    }
}
+15 −98
Original line number Diff line number Diff line
@@ -48,7 +48,6 @@ import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.TransitionDrawable;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
@@ -95,7 +94,6 @@ import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
import com.android.systemui.util.RelativeTouchListener;
import com.android.systemui.util.animation.PhysicsAnimator;
@@ -139,9 +137,6 @@ public class BubbleStackView extends FrameLayout
    /** Percent to darken the bubbles when they're in the dismiss target. */
    private static final float DARKEN_PERCENT = 0.3f;

    /** Duration of the dismiss scrim fading in/out. */
    private static final int DISMISS_TRANSITION_DURATION_MS = 200;

    /** How long to wait, in milliseconds, before hiding the flyout. */
    @VisibleForTesting
    static final int FLYOUT_HIDE_AFTER = 5000;
@@ -300,7 +295,7 @@ public class BubbleStackView extends FrameLayout
    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        pw.println("Stack view state:");
        pw.print("  gestureInProgress:       "); pw.println(mIsGestureInProgress);
        pw.print("  showingDismiss:          "); pw.println(mShowingDismiss);
        pw.print("  showingDismiss:          "); pw.println(mDismissView.isShowing());
        pw.print("  isExpansionAnimating:    "); pw.println(mIsExpansionAnimating);
        pw.print("  expandedContainerVis:    "); pw.println(mExpandedViewContainer.getVisibility());
        pw.print("  expandedContainerAlpha:  "); pw.println(mExpandedViewContainer.getAlpha());
@@ -347,7 +342,6 @@ public class BubbleStackView extends FrameLayout
    private boolean mViewUpdatedRequested = false;
    private boolean mIsExpansionAnimating = false;
    private boolean mIsBubbleSwitchAnimating = false;
    private boolean mShowingDismiss = false;

    /** The view to desaturate/darken when magneted to the dismiss target. */
    @Nullable private View mDesaturateAndDarkenTargetView;
@@ -465,7 +459,7 @@ public class BubbleStackView extends FrameLayout
                    if (wasFlungOut) {
                        mExpandedAnimationController.snapBubbleBack(
                                mExpandedAnimationController.getDraggedOutBubble(), velX, velY);
                        hideDismissTarget();
                        mDismissView.hide();
                    } else {
                        mExpandedAnimationController.onUnstuckFromTarget();
                    }
@@ -479,9 +473,9 @@ public class BubbleStackView extends FrameLayout

                    mExpandedAnimationController.dismissDraggedOutBubble(
                            mExpandedAnimationController.getDraggedOutBubble() /* bubble */,
                            mDismissTargetContainer.getHeight() /* translationYBy */,
                            mDismissView.getHeight() /* translationYBy */,
                            BubbleStackView.this::dismissMagnetizedObject /* after */);
                    hideDismissTarget();
                    mDismissView.hide();
                }
            };

@@ -502,7 +496,7 @@ public class BubbleStackView extends FrameLayout
                    if (wasFlungOut) {
                        mStackAnimationController.flingStackThenSpringToEdge(
                                mStackAnimationController.getStackPosition().x, velX, velY);
                        hideDismissTarget();
                        mDismissView.hide();
                    } else {
                        mStackAnimationController.onUnstuckFromTarget();
                    }
@@ -511,14 +505,14 @@ public class BubbleStackView extends FrameLayout
                @Override
                public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
                    mStackAnimationController.animateStackDismissal(
                            mDismissTargetContainer.getHeight() /* translationYBy */,
                            mDismissView.getHeight() /* translationYBy */,
                            () -> {
                                resetDesaturationAndDarken();
                                dismissMagnetizedObject();
                            }
                    );

                    hideDismissTarget();
                    mDismissView.hide();
                }
            };

@@ -639,7 +633,7 @@ public class BubbleStackView extends FrameLayout
            }

            // Show the dismiss target, if we haven't already.
            springInDismissTargetMaybe();
            mDismissView.show();

            // First, see if the magnetized object consumes the event - if so, we shouldn't move the
            // bubble since it's stuck to the target.
@@ -681,7 +675,7 @@ public class BubbleStackView extends FrameLayout
                            SysUiStatsLog.BUBBLE_UICHANGED__ACTION__STACK_MOVED);
                }

                hideDismissTarget();
                mDismissView.hide();
            }

            mIsDraggingStack = false;
@@ -743,12 +737,7 @@ public class BubbleStackView extends FrameLayout
        }
    };

    private View mDismissTargetCircle;
    private ViewGroup mDismissTargetContainer;
    private PhysicsAnimator<View> mDismissTargetAnimator;
    private PhysicsAnimator.SpringConfig mDismissTargetSpring = new PhysicsAnimator.SpringConfig(
            SpringForce.STIFFNESS_LOW, SpringForce.DAMPING_RATIO_LOW_BOUNCY);

    private DismissView mDismissView;
    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;

    @Nullable
@@ -866,34 +855,8 @@ public class BubbleStackView extends FrameLayout
                .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY));
        mFlyoutTransitionSpring.addEndListener(mAfterFlyoutTransitionSpring);

        final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
        mDismissTargetCircle = new DismissCircleView(context);
        final FrameLayout.LayoutParams newParams =
                new FrameLayout.LayoutParams(targetSize, targetSize);
        newParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
        mDismissTargetCircle.setLayoutParams(newParams);
        mDismissTargetAnimator = PhysicsAnimator.getInstance(mDismissTargetCircle);

        mDismissTargetContainer = new FrameLayout(context);
        mDismissTargetContainer.setLayoutParams(new FrameLayout.LayoutParams(
                MATCH_PARENT,
                getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
                Gravity.BOTTOM));

        final int bottomMargin =
                getResources().getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin);
        mDismissTargetContainer.setPadding(0, 0, 0, bottomMargin);
        mDismissTargetContainer.setClipToPadding(false);
        mDismissTargetContainer.setClipChildren(false);
        mDismissTargetContainer.addView(mDismissTargetCircle);
        mDismissTargetContainer.setVisibility(View.INVISIBLE);
        mDismissTargetContainer.setBackgroundResource(
                R.drawable.floating_dismiss_gradient_transition);
        addView(mDismissTargetContainer);

        // Start translated down so the target springs up.
        mDismissTargetCircle.setTranslationY(
                getResources().getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height));
        mDismissView = new DismissView(context);
        addView(mDismissView);

        final ContentResolver contentResolver = getContext().getContentResolver();
        final int dismissRadius = Settings.Secure.getInt(
@@ -901,7 +864,8 @@ public class BubbleStackView extends FrameLayout

        // Save the MagneticTarget instance for the newly set up view - we'll add this to the
        // MagnetizedObjects.
        mMagneticTarget = new MagnetizedObject.MagneticTarget(mDismissTargetCircle, dismissRadius);
        mMagneticTarget = new MagnetizedObject.MagneticTarget(
                mDismissView.getCircle(), dismissRadius);

        setClipChildren(false);
        setFocusable(true);
@@ -1277,12 +1241,7 @@ public class BubbleStackView extends FrameLayout
        }
        mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
        mStackAnimationController.updateResources(mOrientation);

        final int targetSize = res.getDimensionPixelSize(R.dimen.dismiss_circle_size);
        mDismissTargetCircle.getLayoutParams().width = targetSize;
        mDismissTargetCircle.getLayoutParams().height = targetSize;
        mDismissTargetCircle.requestLayout();

        mDismissView.updateResources();
        mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2);
    }

@@ -2360,48 +2319,6 @@ public class BubbleStackView extends FrameLayout
        }
    }

    /** Animates in the dismiss target. */
    private void springInDismissTargetMaybe() {
        if (mShowingDismiss) {
            return;
        }

        mShowingDismiss = true;

        mDismissTargetContainer.bringToFront();
        mDismissTargetContainer.setZ(Short.MAX_VALUE - 1);
        mDismissTargetContainer.setVisibility(VISIBLE);

        ((TransitionDrawable) mDismissTargetContainer.getBackground()).startTransition(
                DISMISS_TRANSITION_DURATION_MS);

        mDismissTargetAnimator.cancel();
        mDismissTargetAnimator
                .spring(DynamicAnimation.TRANSLATION_Y, 0f, mDismissTargetSpring)
                .start();
    }

    /**
     * Animates the dismiss target out, as well as the circle that encircles the bubbles, if they
     * were dragged into the target and encircled.
     */
    private void hideDismissTarget() {
        if (!mShowingDismiss) {
            return;
        }

        mShowingDismiss = false;

        ((TransitionDrawable) mDismissTargetContainer.getBackground()).reverseTransition(
                DISMISS_TRANSITION_DURATION_MS);

        mDismissTargetAnimator
                .spring(DynamicAnimation.TRANSLATION_Y, mDismissTargetContainer.getHeight(),
                        mDismissTargetSpring)
                .withEndActions(() -> mDismissTargetContainer.setVisibility(View.INVISIBLE))
                .start();
    }

    /** Animates the flyout collapsed (to dot), or the reverse, starting with the given velocity. */
    private void animateFlyoutCollapsed(boolean collapsed, float velX) {
        final boolean onLeft = mStackAnimationController.isStackOnLeftSide();
+85 −0
Original line number Diff line number Diff line
package com.android.systemui.bubbles

import android.content.Context
import android.graphics.drawable.TransitionDrawable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce.DAMPING_RATIO_LOW_BOUNCY
import androidx.dynamicanimation.animation.SpringForce.STIFFNESS_LOW
import com.android.systemui.R
import com.android.systemui.util.DismissCircleView
import com.android.systemui.util.animation.PhysicsAnimator

/*
 * View that handles interactions between DismissCircleView and BubbleStackView.
 */
class DismissView(context: Context) : FrameLayout(context) {

    var circle = DismissCircleView(context).apply {
        val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
        val newParams = LayoutParams(targetSize, targetSize)
        newParams.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
        setLayoutParams(newParams)
        setTranslationY(
            resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height).toFloat())
    }

    var isShowing = false
    private val animator = PhysicsAnimator.getInstance(circle)
    private val spring = PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
    private val DISMISS_SCRIM_FADE_MS = 200
    init {
        setLayoutParams(LayoutParams(
            ViewGroup.LayoutParams.MATCH_PARENT,
            resources.getDimensionPixelSize(R.dimen.floating_dismiss_gradient_height),
            Gravity.BOTTOM))
        setPadding(0, 0, 0, resources.getDimensionPixelSize(R.dimen.floating_dismiss_bottom_margin))
        setClipToPadding(false)
        setClipChildren(false)
        setVisibility(View.INVISIBLE)
        setBackgroundResource(
            R.drawable.floating_dismiss_gradient_transition)
        addView(circle)
    }

    /**
     * Animates this view in.
     */
    fun show() {
        if (isShowing) return
        isShowing = true
        bringToFront()
        setZ(Short.MAX_VALUE - 1f)
        setVisibility(View.VISIBLE)
        (getBackground() as TransitionDrawable).startTransition(DISMISS_SCRIM_FADE_MS)
        animator.cancel()
        animator
            .spring(DynamicAnimation.TRANSLATION_Y, 0f, spring)
            .start()
    }

    /**
     * Animates this view out, as well as the circle that encircles the bubbles, if they
     * were dragged into the target and encircled.
     */
    fun hide() {
        if (!isShowing) return
        isShowing = false
        (getBackground() as TransitionDrawable).reverseTransition(DISMISS_SCRIM_FADE_MS)
        animator
            .spring(DynamicAnimation.TRANSLATION_Y, height.toFloat(),
                spring)
            .withEndActions({ setVisibility(View.INVISIBLE) })
            .start()
    }

    fun updateResources() {
        val targetSize: Int = context.resources.getDimensionPixelSize(R.dimen.dismiss_circle_size)
        circle.layoutParams.width = targetSize
        circle.layoutParams.height = targetSize
        circle.requestLayout()
    }
}
 No newline at end of file