/* * Copyright (C) 2022 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.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.view.MotionEvent; import androidx.annotation.NonNull; import androidx.dynamicanimation.animation.DynamicAnimation; import com.android.systemui.R; import com.android.wm.shell.common.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; /** * Controls the interaction between {@link MagnetizedObject} and * {@link MagnetizedObject.MagneticTarget}. */ class DismissAnimationController { private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false; private static final float COMPLETELY_OPAQUE = 1.0f; private static final float COMPLETELY_TRANSPARENT = 0.0f; private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f; private static final float ANIMATING_MAX_ALPHA = 0.7f; private final DismissView mDismissView; private final MenuView mMenuView; private final ValueAnimator mDismissAnimator; private final MagnetizedObject mMagnetizedObject; private float mMinDismissSize; private float mSizePercent; DismissAnimationController(DismissView dismissView, MenuView menuView) { mDismissView = dismissView; mDismissView.setPivotX(dismissView.getWidth() / 2.0f); mDismissView.setPivotY(dismissView.getHeight() / 2.0f); mMenuView = menuView; updateResources(); mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT); mDismissAnimator.addUpdateListener(dismissAnimation -> { final float animatedValue = (float) dismissAnimation.getAnimatedValue(); final float scaleValue = Math.max(animatedValue, mSizePercent); dismissView.getCircle().setScaleX(scaleValue); dismissView.getCircle().setScaleY(scaleValue); menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA)); }); mDismissAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) { super.onAnimationEnd(animation, isReverse); if (isReverse) { mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE); mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE); mMenuView.setAlpha(COMPLETELY_OPAQUE); } } }); mMagnetizedObject = new MagnetizedObject(mMenuView.getContext(), mMenuView, new MenuAnimationController.MenuPositionProperty( DynamicAnimation.TRANSLATION_X), new MenuAnimationController.MenuPositionProperty( DynamicAnimation.TRANSLATION_Y)) { @Override public void getLocationOnScreen(MenuView underlyingObject, int[] loc) { underlyingObject.getLocationOnScreen(loc); } @Override public float getHeight(MenuView underlyingObject) { return underlyingObject.getHeight(); } @Override public float getWidth(MenuView underlyingObject) { return underlyingObject.getWidth(); } }; final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget( dismissView.getCircle(), (int) mMinDismissSize); mMagnetizedObject.addTarget(magneticTarget); mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU); } void showDismissView(boolean show) { if (show) { mDismissView.show(); } else { mDismissView.hide(); } } void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) { mMagnetizedObject.setMagnetListener(magnetListener); } void maybeConsumeDownMotionEvent(MotionEvent event) { mMagnetizedObject.maybeConsumeMotionEvent(event); } /** * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. * * @param event that move the magnetized object which is also the menu list view. * @return true if the location of the motion events moves within the magnetic field of a * target, but false if didn't set * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. */ boolean maybeConsumeMoveMotionEvent(MotionEvent event) { return mMagnetizedObject.maybeConsumeMotionEvent(event); } /** * This used to pass {@link MotionEvent#ACTION_UP} to the magnetized object to check if it was * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. * * @param event that move the magnetized object which is also the menu list view. * @return true if the location of the motion events moves within the magnetic field of a * target, but false if didn't set * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. */ boolean maybeConsumeUpMotionEvent(MotionEvent event) { return mMagnetizedObject.maybeConsumeMotionEvent(event); } void animateDismissMenu(boolean scaleUp) { if (scaleUp) { mDismissAnimator.start(); } else { mDismissAnimator.reverse(); } } void updateResources() { final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize( R.dimen.dismiss_circle_size); mMinDismissSize = mDismissView.getResources().getDimensionPixelSize( R.dimen.dismiss_circle_small); mSizePercent = mMinDismissSize / maxDismissSize; } interface DismissCallback { void onDismiss(); } }