chromium/chrome/browser/ui/android/appmenu/java/src/org/chromium/chrome/browser/ui/appmenu/AppMenuUtil.java

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.ui.appmenu;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;

import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.interpolators.Interpolators;
import org.chromium.ui.modelutil.PropertyKey;
import org.chromium.ui.modelutil.PropertyModel;

/** This is a helper class for app menu. */
public class AppMenuUtil {
    /** MenuItem Animation Constants */
    static final int ENTER_ITEM_DURATION_MS = 350;

    static final int ENTER_ITEM_BASE_DELAY_MS = 80;
    static final int ENTER_ITEM_ADDL_DELAY_MS = 30;
    static final float ENTER_STANDARD_ITEM_OFFSET_Y_DP = -10.f;
    static final float ENTER_STANDARD_ITEM_OFFSET_X_DP = 10.f;

    /**
     * This builds an {@link Animator} for the enter animation of a standard menu item.  This means
     * it will animate the alpha from 0 to 1 and translate the view from -10dp to 0dp on the y axis.
     *
     * @param view     The menu item {@link View} to be animated.
     * @param position The position in the menu.  This impacts the start delay of the animation.
     * @return         The {@link Animator}.
     */
    public static Animator buildStandardItemEnterAnimator(final View view, int position) {
        float dpToPx = view.getContext().getResources().getDisplayMetrics().density;
        final int startDelay = ENTER_ITEM_BASE_DELAY_MS + ENTER_ITEM_ADDL_DELAY_MS * position;

        AnimatorSet animation = new AnimatorSet();
        final float offsetYPx = ENTER_STANDARD_ITEM_OFFSET_Y_DP * dpToPx;
        animation.playTogether(
                ObjectAnimator.ofFloat(view, View.ALPHA, 0.f, 1.f),
                ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, offsetYPx, 0.f));
        animation.setStartDelay(startDelay);
        animation.setDuration(ENTER_ITEM_DURATION_MS);
        animation.setInterpolator(Interpolators.EMPHASIZED);

        animation.addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        view.setAlpha(0.f);
                    }
                });
        return animation;
    }

    /**
     * This builds an {@link Animator} for the enter animation of icon row menu items.  This means
     * it will animate the alpha from 0 to 1 and translate the views from 10dp to 0dp on the x axis.
     *
     * @param buttons The list of icons in the menu item that should be animated.
     * @param isMenuIconAtStart Whether the menu was triggered from a menu icon positioned at start.
     * @return        The {@link Animator}.
     */
    public static Animator buildIconItemEnterAnimator(
            final ImageView[] buttons, boolean isMenuIconAtStart) {
        if (buttons.length < 1) return new AnimatorSet();
        float dpToPx = buttons[0].getContext().getResources().getDisplayMetrics().density;
        final boolean rtl = LocalizationUtils.isLayoutRtl();
        float offsetSign = (rtl == /* XNOR */ isMenuIconAtStart) ? 1.f : -1.f;
        final float offsetXPx = ENTER_STANDARD_ITEM_OFFSET_X_DP * dpToPx * offsetSign;
        final int maxViewsToAnimate = buttons.length;

        AnimatorSet animation = new AnimatorSet();
        AnimatorSet.Builder builder = null;
        for (int i = 0; i < maxViewsToAnimate; i++) {
            final int startDelay = ENTER_ITEM_ADDL_DELAY_MS * i;

            ImageView view = buttons[i];
            Animator alpha = ObjectAnimator.ofFloat(view, View.ALPHA, 0.f, 1.f);
            Animator translate = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, offsetXPx, 0);
            alpha.setStartDelay(startDelay);
            translate.setStartDelay(startDelay);
            alpha.setDuration(ENTER_ITEM_DURATION_MS);
            translate.setDuration(ENTER_ITEM_DURATION_MS);

            if (builder == null) {
                builder = animation.play(alpha);
            } else {
                builder.with(alpha);
            }
            builder.with(translate);
        }
        animation.setStartDelay(ENTER_ITEM_BASE_DELAY_MS);
        animation.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN_INTERPOLATOR);

        animation.addListener(
                new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        for (int i = 0; i < maxViewsToAnimate; i++) {
                            buttons[i].setAlpha(0.f);
                        }
                    }
                });
        return animation;
    }

    /**
     * Create a {@link PropertyModel} from a {@link MenuItem}.
     *
     * @param menuItem The MenuItem which need to be transferred to the {@link PropertyModel}.
     * @return         The {@link PropertyModel}.
     */
    public static PropertyModel menuItemToPropertyModel(MenuItem menuItem) {
        return new PropertyModel.Builder(AppMenuItemProperties.ALL_KEYS)
                .with(AppMenuItemProperties.MENU_ITEM_ID, menuItem.getItemId())
                .with(AppMenuItemProperties.TITLE, menuItem.getTitle())
                .with(AppMenuItemProperties.TITLE_CONDENSED, menuItem.getTitleCondensed())
                .with(AppMenuItemProperties.ICON, menuItem.getIcon())
                .with(AppMenuItemProperties.CHECKABLE, menuItem.isCheckable())
                .with(AppMenuItemProperties.CHECKED, menuItem.isChecked())
                .with(AppMenuItemProperties.ENABLED, menuItem.isEnabled())
                .build();
    }

    /**
     * builds a enter animation of a standard menu item.
     *
     * @param model The model containing the data for the view.
     * @param view The view to be animated.
     * @param key The key of the property to be bound.
     */
    public static void bindStandardItemEnterAnimation(
            PropertyModel model, View view, PropertyKey key) {
        if (key == AppMenuItemProperties.SUPPORT_ENTER_ANIMATION) {
            if (model.get(AppMenuItemProperties.SUPPORT_ENTER_ANIMATION)) {
                int position = model.get(AppMenuItemProperties.POSITION);
                view.setTag(
                        R.id.menu_item_enter_anim_id,
                        buildStandardItemEnterAnimator(view, position));
            } else {
                view.setTag(R.id.menu_item_enter_anim_id, null);
            }
        }
    }
}