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

Commit d9786b75 authored by Riley Jones's avatar Riley Jones
Browse files

Added support to animate transitions for menu radii

When dragged by the user, FAB has rounded edges on all sides.
When attached to the side of the screen, that side of the menu flattens to lie uniform.
This aims to smooth out the transition.

Adds a RadiiAnimator to use a single ValueAnimator to drive 8 radii.

Bug: 281140482
Test: atest RadiiAnimatorTest
Flag: ACONFIG com.android.systemui.Flags.floating_menu_radii_animation ENABLED
Change-Id: Ia77bc87905f1d863be713f7e51fc91988819bea8
parent e5d62519
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -22,3 +22,10 @@ flag {
    description: "Adjusts bounds to allow the floating menu to render on top of navigation bars."
    bug: "283768342"
}

flag {
    name: "floating_menu_radii_animation"
    namespace: "accessibility"
    description: "Animates the floating menu's transition between curved and jagged edges."
    bug: "281140482"
}
 No newline at end of file
+21 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.accessibility.floatingmenu;

interface IRadiiAnimationListener {
    void onRadiiAnimationUpdate(float[] radii);
}
+11 −1
Original line number Diff line number Diff line
@@ -69,6 +69,7 @@ class MenuAnimationController {
    private static final int FADE_EFFECT_DURATION_MS = 3000;

    private final MenuView mMenuView;
    private final MenuViewAppearance mMenuViewAppearance;
    private final ValueAnimator mFadeOutAnimator;
    private final Handler mHandler;
    private boolean mIsFadeEffectEnabled;
@@ -81,14 +82,19 @@ class MenuAnimationController {
    final HashMap<DynamicAnimation.ViewProperty, DynamicAnimation> mPositionAnimations =
            new HashMap<>();

    MenuAnimationController(MenuView menuView) {
    @VisibleForTesting
    final RadiiAnimator mRadiiAnimator;

    MenuAnimationController(MenuView menuView, MenuViewAppearance menuViewAppearance) {
        mMenuView = menuView;
        mMenuViewAppearance = menuViewAppearance;

        mHandler = createUiHandler();
        mFadeOutAnimator = new ValueAnimator();
        mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
        mFadeOutAnimator.addUpdateListener(
                (animation) -> menuView.setAlpha((float) animation.getAnimatedValue()));
        mRadiiAnimator = new RadiiAnimator(mMenuViewAppearance.getMenuRadii(), mMenuView::setRadii);
    }

    void moveToPosition(PointF position) {
@@ -427,6 +433,10 @@ class MenuAnimationController {
                .start();
    }

    void startRadiiAnimation(float[] endRadii) {
        mRadiiAnimator.startAnimation(endRadii);
    }

    private void onSpringAnimationsEnd(PointF position, boolean writeToPosition) {
        mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
        constrainPositionAndUpdate(position, writeToPosition);
+17 −4
Original line number Diff line number Diff line
@@ -78,7 +78,7 @@ class MenuView extends FrameLayout implements

        mMenuViewModel = menuViewModel;
        mMenuViewAppearance = menuViewAppearance;
        mMenuAnimationController = new MenuAnimationController(this);
        mMenuAnimationController = new MenuAnimationController(this, menuViewAppearance);
        mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
        mTargetFeaturesView = new RecyclerView(context);
        mTargetFeaturesView.setAdapter(mAdapter);
@@ -184,9 +184,17 @@ class MenuView extends FrameLayout implements
                insets[3]);

        final GradientDrawable gradientDrawable = getContainerViewGradient();
        gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuRadii());
        gradientDrawable.setStroke(mMenuViewAppearance.getMenuStrokeWidth(),
                mMenuViewAppearance.getMenuStrokeColor());
        if (Flags.floatingMenuRadiiAnimation()) {
            mMenuAnimationController.startRadiiAnimation(mMenuViewAppearance.getMenuRadii());
        } else {
            gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuRadii());
        }
    }

    void setRadii(float[] radii) {
        getContainerViewGradient().setCornerRadii(radii);
    }

    private void onMoveToTucked(boolean isMoveToTucked) {
@@ -391,9 +399,14 @@ class MenuView extends FrameLayout implements
        getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2],
                insets[3]);

        if (Flags.floatingMenuRadiiAnimation()) {
            mMenuAnimationController.startRadiiAnimation(
                    mMenuViewAppearance.getMenuMovingStateRadii());
        } else {
            final GradientDrawable gradientDrawable = getContainerViewGradient();
            gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuMovingStateRadii());
        }
    }

    void onBoundsInParentChanged(int newLeft, int newTop) {
        mBoundsInParent.offsetTo(newLeft, newTop);
+104 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.accessibility.floatingmenu;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.util.MathUtils;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;

import java.util.Arrays;

/**
 * Manages the animation of the floating menu's radii.
 * <p>
 * There are 8 output values total. There are 4 corners,
 * and each corner has a value for the x and y axes.
 */
class RadiiAnimator {
    static final int RADII_COUNT = 8;

    private float[] mStartValues;
    private float[] mEndValues;
    private final ValueAnimator mAnimationDriver = ValueAnimator.ofFloat(0.0f, 1.0f);

    RadiiAnimator(float[] initialValues, IRadiiAnimationListener animationListener) {
        if (initialValues.length != RADII_COUNT) {
            initialValues = Arrays.copyOf(initialValues, RADII_COUNT);
        }

        mStartValues = initialValues;
        mEndValues = initialValues;

        mAnimationDriver.setRepeatCount(0);
        mAnimationDriver.addUpdateListener(
                animation -> animationListener.onRadiiAnimationUpdate(
                        evaluate(animation.getAnimatedFraction())));
        mAnimationDriver.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(@NonNull Animator animation) {
                animationListener.onRadiiAnimationUpdate(evaluate(/* t = */ 0.0f));
            }

            @Override
            public void onAnimationEnd(@NonNull Animator animation) {}

            @Override
            public void onAnimationCancel(@NonNull Animator animation) {
                animationListener.onRadiiAnimationUpdate(
                        evaluate(mAnimationDriver.getAnimatedFraction()));
            }

            @Override
            public void onAnimationRepeat(@NonNull Animator animation) {}
        });
        mAnimationDriver.setInterpolator(new android.view.animation.BounceInterpolator());
    }

    void startAnimation(float[] endValues) {
        if (mAnimationDriver.isStarted()) {
            mAnimationDriver.cancel();
            mStartValues = evaluate(mAnimationDriver.getAnimatedFraction());
        } else {
            mStartValues = mEndValues;
        }
        mEndValues = endValues;

        mAnimationDriver.start();
    }

    void skipAnimationToEnd() {
        mAnimationDriver.end();
    }

    @VisibleForTesting
    float[] evaluate(float time /* interpolator value between 0.0 and 1.0 */) {
        float[] out = new float[8];
        for (int i = 0; i < RADII_COUNT; i++) {
            out[i] = MathUtils.lerp(mStartValues[i], mEndValues[i], time);
        }
        return out;
    }

    boolean isStarted() {
        return mAnimationDriver.isStarted();
    }
}
Loading