1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.accessibility.floatingmenu; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.view.MotionEvent; 23 24 import androidx.annotation.NonNull; 25 import androidx.dynamicanimation.animation.DynamicAnimation; 26 27 import com.android.systemui.R; 28 import com.android.wm.shell.common.bubbles.DismissView; 29 import com.android.wm.shell.common.magnetictarget.MagnetizedObject; 30 31 /** 32 * Controls the interaction between {@link MagnetizedObject} and 33 * {@link MagnetizedObject.MagneticTarget}. 34 */ 35 class DismissAnimationController { 36 private static final boolean ENABLE_FLING_TO_DISMISS_MENU = false; 37 private static final float COMPLETELY_OPAQUE = 1.0f; 38 private static final float COMPLETELY_TRANSPARENT = 0.0f; 39 private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f; 40 private static final float ANIMATING_MAX_ALPHA = 0.7f; 41 42 private final DismissView mDismissView; 43 private final MenuView mMenuView; 44 private final ValueAnimator mDismissAnimator; 45 private final MagnetizedObject<?> mMagnetizedObject; 46 private float mMinDismissSize; 47 private float mSizePercent; 48 DismissAnimationController(DismissView dismissView, MenuView menuView)49 DismissAnimationController(DismissView dismissView, MenuView menuView) { 50 mDismissView = dismissView; 51 mDismissView.setPivotX(dismissView.getWidth() / 2.0f); 52 mDismissView.setPivotY(dismissView.getHeight() / 2.0f); 53 mMenuView = menuView; 54 55 updateResources(); 56 57 mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT); 58 mDismissAnimator.addUpdateListener(dismissAnimation -> { 59 final float animatedValue = (float) dismissAnimation.getAnimatedValue(); 60 final float scaleValue = Math.max(animatedValue, mSizePercent); 61 dismissView.getCircle().setScaleX(scaleValue); 62 dismissView.getCircle().setScaleY(scaleValue); 63 64 menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA)); 65 }); 66 67 mDismissAnimator.addListener(new AnimatorListenerAdapter() { 68 @Override 69 public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) { 70 super.onAnimationEnd(animation, isReverse); 71 72 if (isReverse) { 73 mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE); 74 mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE); 75 mMenuView.setAlpha(COMPLETELY_OPAQUE); 76 } 77 } 78 }); 79 80 mMagnetizedObject = 81 new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView, 82 new MenuAnimationController.MenuPositionProperty( 83 DynamicAnimation.TRANSLATION_X), 84 new MenuAnimationController.MenuPositionProperty( 85 DynamicAnimation.TRANSLATION_Y)) { 86 @Override 87 public void getLocationOnScreen(MenuView underlyingObject, int[] loc) { 88 underlyingObject.getLocationOnScreen(loc); 89 } 90 91 @Override 92 public float getHeight(MenuView underlyingObject) { 93 return underlyingObject.getHeight(); 94 } 95 96 @Override 97 public float getWidth(MenuView underlyingObject) { 98 return underlyingObject.getWidth(); 99 } 100 }; 101 102 final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget( 103 dismissView.getCircle(), (int) mMinDismissSize); 104 mMagnetizedObject.addTarget(magneticTarget); 105 mMagnetizedObject.setFlingToTargetEnabled(ENABLE_FLING_TO_DISMISS_MENU); 106 } 107 showDismissView(boolean show)108 void showDismissView(boolean show) { 109 if (show) { 110 mDismissView.show(); 111 } else { 112 mDismissView.hide(); 113 } 114 } 115 setMagnetListener(MagnetizedObject.MagnetListener magnetListener)116 void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) { 117 mMagnetizedObject.setMagnetListener(magnetListener); 118 } 119 maybeConsumeDownMotionEvent(MotionEvent event)120 void maybeConsumeDownMotionEvent(MotionEvent event) { 121 mMagnetizedObject.maybeConsumeMotionEvent(event); 122 } 123 124 /** 125 * This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was 126 * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. 127 * 128 * @param event that move the magnetized object which is also the menu list view. 129 * @return true if the location of the motion events moves within the magnetic field of a 130 * target, but false if didn't set 131 * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. 132 */ maybeConsumeMoveMotionEvent(MotionEvent event)133 boolean maybeConsumeMoveMotionEvent(MotionEvent event) { 134 return mMagnetizedObject.maybeConsumeMotionEvent(event); 135 } 136 137 /** 138 * This used to pass {@link MotionEvent#ACTION_UP} to the magnetized object to check if it was 139 * within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}. 140 * 141 * @param event that move the magnetized object which is also the menu list view. 142 * @return true if the location of the motion events moves within the magnetic field of a 143 * target, but false if didn't set 144 * {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}. 145 */ maybeConsumeUpMotionEvent(MotionEvent event)146 boolean maybeConsumeUpMotionEvent(MotionEvent event) { 147 return mMagnetizedObject.maybeConsumeMotionEvent(event); 148 } 149 animateDismissMenu(boolean scaleUp)150 void animateDismissMenu(boolean scaleUp) { 151 if (scaleUp) { 152 mDismissAnimator.start(); 153 } else { 154 mDismissAnimator.reverse(); 155 } 156 } 157 updateResources()158 void updateResources() { 159 final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize( 160 R.dimen.dismiss_circle_size); 161 mMinDismissSize = mDismissView.getResources().getDimensionPixelSize( 162 R.dimen.dismiss_circle_small); 163 mSizePercent = mMinDismissSize / maxDismissSize; 164 } 165 166 interface DismissCallback { onDismiss()167 void onDismiss(); 168 } 169 } 170