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

Commit 1bc33421 authored by Liran Binyamin's avatar Liran Binyamin Committed by Android (Google) Code Review
Browse files

Merge "Initial animation of new bubble bar bubble" into main

parents 599ed3ba 074a3a66
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@
 */
package com.android.launcher3.taskbar.bubbles

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.ColorFilter
@@ -27,12 +28,10 @@ import com.android.launcher3.R
import com.android.launcher3.Utilities
import com.android.launcher3.Utilities.mapToRange
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
import com.android.launcher3.taskbar.TaskbarActivityContext
import com.android.wm.shell.common.TriangleShape

/** Drawable for the background of the bubble bar. */
class BubbleBarBackground(context: TaskbarActivityContext, private val backgroundHeight: Float) :
    Drawable() {
class BubbleBarBackground(context: Context, private val backgroundHeight: Float) : Drawable() {

    private val DARK_THEME_SHADOW_ALPHA = 51f
    private val LIGHT_THEME_SHADOW_ALPHA = 25f
@@ -46,6 +45,7 @@ class BubbleBarBackground(context: TaskbarActivityContext, private val backgroun

    var arrowPositionX: Float = 0f
        private set

    private var showingArrow: Boolean = false
    private var arrowDrawable: ShapeDrawable

+31 −5
Original line number Diff line number Diff line
@@ -39,8 +39,6 @@ import androidx.dynamicanimation.animation.SpringForce;

import com.android.launcher3.R;
import com.android.launcher3.anim.SpringAnimationBuilder;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.views.ActivityContext;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;

import java.util.List;
@@ -159,8 +157,6 @@ public class BubbleBarView extends FrameLayout {

    public BubbleBarView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        TaskbarActivityContext activityContext = ActivityContext.lookupContext(context);

        setAlpha(0);
        setVisibility(INVISIBLE);
        mIconOverlapAmount = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_overlap);
@@ -171,7 +167,7 @@ public class BubbleBarView extends FrameLayout {

        setClipToPadding(false);

        mBubbleBarBackground = new BubbleBarBackground(activityContext,
        mBubbleBarBackground = new BubbleBarBackground(context,
                getResources().getDimensionPixelSize(R.dimen.bubblebar_size));
        setBackgroundDrawable(mBubbleBarBackground);

@@ -379,6 +375,36 @@ public class BubbleBarView extends FrameLayout {
        return mRelativePivotY;
    }

    /** Prepares for animating a bubble while being stashed. */
    public void prepareForAnimatingBubbleWhileStashed(String bubbleKey) {
        // we're about to animate the new bubble in. the new bubble has already been added to this
        // view, but we're currently stashed, so before we can start the animation we need make
        // everything else in the bubble bar invisible, except for the bubble that's being animated.
        setBackground(null);
        for (int i = 0; i < getChildCount(); i++) {
            final BubbleView view = (BubbleView) getChildAt(i);
            final String key = view.getBubble().getKey();
            if (!bubbleKey.equals(key)) {
                view.setVisibility(INVISIBLE);
            }
        }
        setVisibility(VISIBLE);
        setAlpha(1);
        setTranslationY(0);
        setScaleX(1);
        setScaleY(1);
    }

    /** Resets the state after the bubble animation completed. */
    public void onAnimatingBubbleCompleted() {
        setBackground(mBubbleBarBackground);
        for (int i = 0; i < getChildCount(); i++) {
            final BubbleView view = (BubbleView) getChildAt(i);
            view.setVisibility(VISIBLE);
            view.setAlpha(1f);
        }
    }

    // TODO: (b/280605790) animate it
    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
+11 −0
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.launcher3.taskbar.TaskbarInsetsController;
import com.android.launcher3.taskbar.TaskbarStashController;
import com.android.launcher3.taskbar.bubbles.animation.BubbleBarViewAnimator;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.MultiValueAlpha;
import com.android.quickstep.SystemUiProxy;
@@ -81,6 +82,8 @@ public class BubbleBarViewController {
    private boolean mHiddenForNoBubbles = true;
    private boolean mShouldShowEducation;

    private BubbleBarViewAnimator mBubbleBarViewAnimator;

    public BubbleBarViewController(TaskbarActivityContext activity, BubbleBarView barView) {
        mActivity = activity;
        mBarView = barView;
@@ -113,6 +116,8 @@ public class BubbleBarViewController {
        mBarView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
                mTaskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
        );

        mBubbleBarViewAnimator = new BubbleBarViewAnimator(mBarView, mBubbleStashController);
    }

    private void onBubbleClicked(View v) {
@@ -316,6 +321,12 @@ public class BubbleBarViewController {
                    new FrameLayout.LayoutParams(mIconSize, mIconSize, Gravity.LEFT));
            b.getView().setOnClickListener(mBubbleClickListener);
            mBubbleDragController.setupBubbleView(b.getView());

            boolean isStashedOrGone =
                    mBubbleStashController.isStashed() || mBarView.getVisibility() != VISIBLE;
            if (b instanceof BubbleBarBubble && isStashedOrGone) {
                mBubbleBarViewAnimator.animateBubbleInForStashed((BubbleBarBubble) b);
            }
        } else {
            Log.w(TAG, "addBubble, bubble was null!");
        }
+3 −3
Original line number Diff line number Diff line
@@ -145,7 +145,7 @@ public class BubbleView extends ConstraintLayout {
    }

    /** Sets the bubble being rendered in this view. */
    void setBubble(BubbleBarBubble bubble) {
    public void setBubble(BubbleBarBubble bubble) {
        mBubble = bubble;
        mBubbleIcon.setImageBitmap(bubble.getIcon());
        mAppIcon.setImageBitmap(bubble.getBadge());
@@ -159,7 +159,7 @@ public class BubbleView extends ConstraintLayout {
     * the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
     * come from an app.
     */
    void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
    public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
        mBubble = overflow;
        mBubbleIcon.setImageBitmap(bitmap);
        mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
@@ -168,7 +168,7 @@ public class BubbleView extends ConstraintLayout {

    /** Returns the bubble being rendered in this view. */
    @Nullable
    BubbleBarItem getBubble() {
    public BubbleBarItem getBubble() {
        return mBubble;
    }

+118 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.animation

import android.view.View
import android.view.View.VISIBLE
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.SpringForce
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
import com.android.launcher3.taskbar.bubbles.BubbleBarView
import com.android.launcher3.taskbar.bubbles.BubbleStashController
import com.android.launcher3.taskbar.bubbles.BubbleView
import com.android.wm.shell.shared.animation.PhysicsAnimator

/** Handles animations for bubble bar bubbles. */
class BubbleBarViewAnimator
@JvmOverloads
constructor(
    private val bubbleBarView: BubbleBarView,
    private val bubbleStashController: BubbleStashController,
    private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
) {

    private companion object {
        /** The time to show the flyout. */
        const val FLYOUT_DELAY_MS: Long = 2500
        /** The translation Y the new bubble will animate to. */
        const val BUBBLE_ANIMATION_TRANSLATION_Y = -50f
    }

    /** An interface for scheduling jobs. */
    interface Scheduler {

        /** Schedule the given [block] to run. */
        fun post(block: () -> Unit)

        /** Schedule the given [block] to start with a delay of [delayMillis]. */
        fun postDelayed(delayMillis: Long, block: () -> Unit)
    }

    /** A [Scheduler] that uses a Handler to run jobs. */
    private class HandlerScheduler(private val view: View) : Scheduler {

        override fun post(block: () -> Unit) {
            view.post(block)
        }

        override fun postDelayed(delayMillis: Long, block: () -> Unit) {
            view.postDelayed(block, delayMillis)
        }
    }

    private val springConfig =
        PhysicsAnimator.SpringConfig(
            stiffness = SpringForce.STIFFNESS_LOW,
            dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
        )

    /** Animates a bubble for the state where the bubble bar is stashed. */
    fun animateBubbleInForStashed(b: BubbleBarBubble) {
        val bubbleView = b.view
        val animator = PhysicsAnimator.getInstance(bubbleView)
        if (animator.isRunning()) animator.cancel()
        // the animation of a new bubble is divided into 2 parts. The first part shows the bubble
        // and the second part hides it after a delay.
        val showAnimation = buildShowAnimation(bubbleView, b.key, animator)
        val hideAnimation = buildHideAnimation(animator)
        scheduler.post(showAnimation)
        scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
    }

    /** Returns a lambda that starts the animation that shows the new bubble. */
    private fun buildShowAnimation(
        bubbleView: BubbleView,
        key: String,
        animator: PhysicsAnimator<BubbleView>
    ): () -> Unit = {
        bubbleBarView.prepareForAnimatingBubbleWhileStashed(key)
        animator.setDefaultSpringConfig(springConfig)
        animator
            .spring(DynamicAnimation.ALPHA, 1f)
            .spring(DynamicAnimation.TRANSLATION_Y, BUBBLE_ANIMATION_TRANSLATION_Y)
        bubbleView.alpha = 0f
        bubbleView.visibility = VISIBLE
        animator.start()
    }

    /** Returns a lambda that starts the animation that hides the new bubble. */
    private fun buildHideAnimation(animator: PhysicsAnimator<BubbleView>): () -> Unit = {
        animator.setDefaultSpringConfig(springConfig)
        animator
            .spring(DynamicAnimation.ALPHA, 0f)
            .spring(DynamicAnimation.TRANSLATION_Y, 0f)
            .addEndListener { _, _, _, canceled, _, _, allRelevantPropertyAnimsEnded ->
                if (!canceled && allRelevantPropertyAnimsEnded) {
                    if (bubbleStashController.isStashed) {
                        bubbleBarView.alpha = 0f
                    }
                    bubbleBarView.onAnimatingBubbleCompleted()
                }
            }
        animator.start()
    }
}
Loading