1 /* 2 * Copyright (C) 2016 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.statusbar; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.util.ArrayMap; 23 import android.util.ArraySet; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.view.animation.Interpolator; 27 28 import com.android.app.animation.Interpolators; 29 import com.android.systemui.res.R; 30 import com.android.systemui.statusbar.notification.TransformState; 31 import com.android.systemui.statusbar.notification.stack.StackStateAnimator; 32 33 import java.util.Stack; 34 35 /** 36 * A view that can be transformed to and from. 37 */ 38 public class ViewTransformationHelper implements TransformableView, 39 TransformState.TransformInfo { 40 41 private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view; 42 43 private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>(); 44 private ArraySet<Integer> mKeysTransformingToSimilar = new ArraySet<>(); 45 private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>(); 46 private ValueAnimator mViewTransformationAnimation; 47 addTransformedView(int key, View transformedView)48 public void addTransformedView(int key, View transformedView) { 49 mTransformedViews.put(key, transformedView); 50 } 51 addTransformedView(View transformedView)52 public void addTransformedView(View transformedView) { 53 int key = transformedView.getId(); 54 if (key == View.NO_ID) { 55 throw new IllegalArgumentException("View argument does not have a valid id"); 56 } 57 addTransformedView(key, transformedView); 58 } 59 60 /** 61 * Add a view that transforms to a similar sibling, meaning that we should consider any mapping 62 * found treated as the same viewType. This is useful for imageViews, where it's hard to compare 63 * if the source images are the same when they are bitmap based. 64 * 65 * @param key The key how this is added 66 * @param transformedView the view that is added 67 */ addViewTransformingToSimilar(int key, View transformedView)68 public void addViewTransformingToSimilar(int key, View transformedView) { 69 addTransformedView(key, transformedView); 70 mKeysTransformingToSimilar.add(key); 71 } 72 addViewTransformingToSimilar(View transformedView)73 public void addViewTransformingToSimilar(View transformedView) { 74 int key = transformedView.getId(); 75 if (key == View.NO_ID) { 76 throw new IllegalArgumentException("View argument does not have a valid id"); 77 } 78 addViewTransformingToSimilar(key, transformedView); 79 } 80 reset()81 public void reset() { 82 mTransformedViews.clear(); 83 mKeysTransformingToSimilar.clear(); 84 } 85 setCustomTransformation(CustomTransformation transformation, int viewType)86 public void setCustomTransformation(CustomTransformation transformation, int viewType) { 87 mCustomTransformations.put(viewType, transformation); 88 } 89 90 @Override getCurrentState(int fadingView)91 public TransformState getCurrentState(int fadingView) { 92 View view = mTransformedViews.get(fadingView); 93 if (view != null && view.getVisibility() != View.GONE) { 94 TransformState transformState = TransformState.createFrom(view, this); 95 if (mKeysTransformingToSimilar.contains(fadingView)) { 96 transformState.setIsSameAsAnyView(true); 97 } 98 return transformState; 99 } 100 return null; 101 } 102 103 @Override transformTo(final TransformableView notification, final Runnable endRunnable)104 public void transformTo(final TransformableView notification, final Runnable endRunnable) { 105 if (mViewTransformationAnimation != null) { 106 mViewTransformationAnimation.cancel(); 107 } 108 mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); 109 mViewTransformationAnimation.addUpdateListener( 110 animation -> transformTo(notification, animation.getAnimatedFraction())); 111 mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); 112 mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 113 mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { 114 public boolean mCancelled; 115 116 @Override 117 public void onAnimationEnd(Animator animation) { 118 if (!mCancelled) { 119 if (endRunnable != null) { 120 endRunnable.run(); 121 } 122 setVisible(false); 123 mViewTransformationAnimation = null; 124 } else { 125 abortTransformations(); 126 } 127 } 128 129 @Override 130 public void onAnimationCancel(Animator animation) { 131 mCancelled = true; 132 } 133 }); 134 mViewTransformationAnimation.start(); 135 } 136 137 @Override transformTo(TransformableView notification, float transformationAmount)138 public void transformTo(TransformableView notification, float transformationAmount) { 139 for (Integer viewType : mTransformedViews.keySet()) { 140 TransformState ownState = getCurrentState(viewType); 141 if (ownState != null) { 142 CustomTransformation customTransformation = mCustomTransformations.get(viewType); 143 if (customTransformation != null && customTransformation.transformTo( 144 ownState, notification, transformationAmount)) { 145 ownState.recycle(); 146 continue; 147 } 148 TransformState otherState = notification.getCurrentState(viewType); 149 if (otherState != null) { 150 ownState.transformViewTo(otherState, transformationAmount); 151 otherState.recycle(); 152 } else { 153 ownState.disappear(transformationAmount, notification); 154 } 155 ownState.recycle(); 156 } 157 } 158 } 159 160 @Override transformFrom(final TransformableView notification)161 public void transformFrom(final TransformableView notification) { 162 if (mViewTransformationAnimation != null) { 163 mViewTransformationAnimation.cancel(); 164 } 165 mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); 166 mViewTransformationAnimation.addUpdateListener( 167 animation -> transformFrom(notification, animation.getAnimatedFraction())); 168 mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { 169 public boolean mCancelled; 170 171 @Override 172 public void onAnimationEnd(Animator animation) { 173 if (!mCancelled) { 174 setVisible(true); 175 } else { 176 abortTransformations(); 177 } 178 } 179 180 @Override 181 public void onAnimationCancel(Animator animation) { 182 mCancelled = true; 183 } 184 }); 185 mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); 186 mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 187 mViewTransformationAnimation.start(); 188 } 189 190 @Override transformFrom(TransformableView notification, float transformationAmount)191 public void transformFrom(TransformableView notification, float transformationAmount) { 192 for (Integer viewType : mTransformedViews.keySet()) { 193 TransformState ownState = getCurrentState(viewType); 194 if (ownState != null) { 195 CustomTransformation customTransformation = mCustomTransformations.get(viewType); 196 if (customTransformation != null && customTransformation.transformFrom( 197 ownState, notification, transformationAmount)) { 198 ownState.recycle(); 199 continue; 200 } 201 TransformState otherState = notification.getCurrentState(viewType); 202 if (otherState != null) { 203 ownState.transformViewFrom(otherState, transformationAmount); 204 otherState.recycle(); 205 } else { 206 ownState.appear(transformationAmount, notification); 207 } 208 ownState.recycle(); 209 } 210 } 211 } 212 213 @Override setVisible(boolean visible)214 public void setVisible(boolean visible) { 215 if (mViewTransformationAnimation != null) { 216 mViewTransformationAnimation.cancel(); 217 } 218 for (Integer viewType : mTransformedViews.keySet()) { 219 TransformState ownState = getCurrentState(viewType); 220 if (ownState != null) { 221 ownState.setVisible(visible, false /* force */); 222 ownState.recycle(); 223 } 224 } 225 } 226 abortTransformations()227 private void abortTransformations() { 228 for (Integer viewType : mTransformedViews.keySet()) { 229 TransformState ownState = getCurrentState(viewType); 230 if (ownState != null) { 231 ownState.abortTransformation(); 232 ownState.recycle(); 233 } 234 } 235 } 236 237 /** 238 * Add the remaining transformation views such that all views are being transformed correctly 239 * @param viewRoot the root below which all elements need to be transformed 240 */ addRemainingTransformTypes(View viewRoot)241 public void addRemainingTransformTypes(View viewRoot) { 242 // lets now tag the right views 243 int numValues = mTransformedViews.size(); 244 for (int i = 0; i < numValues; i++) { 245 View view = mTransformedViews.valueAt(i); 246 while (view != viewRoot.getParent()) { 247 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true); 248 view = (View) view.getParent(); 249 } 250 } 251 Stack<View> stack = new Stack<>(); 252 // Add the right views now 253 stack.push(viewRoot); 254 while (!stack.isEmpty()) { 255 View child = stack.pop(); 256 Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW); 257 if (containsView == null) { 258 // This one is unhandled, let's add it to our list. 259 int id = child.getId(); 260 if (id != View.NO_ID) { 261 // We only fade views with an id 262 addTransformedView(id, child); 263 continue; 264 } 265 } 266 child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null); 267 if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){ 268 ViewGroup group = (ViewGroup) child; 269 for (int i = 0; i < group.getChildCount(); i++) { 270 stack.push(group.getChildAt(i)); 271 } 272 } 273 } 274 } 275 resetTransformedView(View view)276 public void resetTransformedView(View view) { 277 TransformState state = TransformState.createFrom(view, this); 278 state.setVisible(true /* visible */, true /* force */); 279 state.recycle(); 280 } 281 282 /** 283 * @return a set of all views are being transformed. 284 */ getAllTransformingViews()285 public ArraySet<View> getAllTransformingViews() { 286 return new ArraySet<>(mTransformedViews.values()); 287 } 288 289 @Override isAnimating()290 public boolean isAnimating() { 291 return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning(); 292 } 293 294 public static abstract class CustomTransformation { 295 /** 296 * Transform a state to the given view 297 * @param ownState the state to transform 298 * @param notification the view to transform to 299 * @param transformationAmount how much transformation should be done 300 * @return whether a custom transformation is performed 301 */ transformTo(TransformState ownState, TransformableView notification, float transformationAmount)302 public abstract boolean transformTo(TransformState ownState, 303 TransformableView notification, 304 float transformationAmount); 305 306 /** 307 * Transform to this state from the given view 308 * @param ownState the state to transform to 309 * @param notification the view to transform from 310 * @param transformationAmount how much transformation should be done 311 * @return whether a custom transformation is performed 312 */ transformFrom(TransformState ownState, TransformableView notification, float transformationAmount)313 public abstract boolean transformFrom(TransformState ownState, 314 TransformableView notification, 315 float transformationAmount); 316 317 /** 318 * Perform a custom initialisation before transforming. 319 * 320 * @param ownState our own state 321 * @param otherState the other state 322 * @return whether a custom initialization is done 323 */ initTransformation(TransformState ownState, TransformState otherState)324 public boolean initTransformation(TransformState ownState, 325 TransformState otherState) { 326 return false; 327 } 328 customTransformTarget(TransformState ownState, TransformState otherState)329 public boolean customTransformTarget(TransformState ownState, 330 TransformState otherState) { 331 return false; 332 } 333 334 /** 335 * Get a custom interpolator for this animation 336 * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY 337 * @param isFrom true if this transformation from the other view 338 */ getCustomInterpolator(int interpolationType, boolean isFrom)339 public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) { 340 return null; 341 } 342 } 343 } 344