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.systemui.Interpolators; 29 import com.android.systemui.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(new ValueAnimator.AnimatorUpdateListener() { 110 @Override 111 public void onAnimationUpdate(ValueAnimator animation) { 112 transformTo(notification, animation.getAnimatedFraction()); 113 } 114 }); 115 mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); 116 mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 117 mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { 118 public boolean mCancelled; 119 120 @Override 121 public void onAnimationEnd(Animator animation) { 122 if (!mCancelled) { 123 if (endRunnable != null) { 124 endRunnable.run(); 125 } 126 setVisible(false); 127 mViewTransformationAnimation = null; 128 } else { 129 abortTransformations(); 130 } 131 } 132 133 @Override 134 public void onAnimationCancel(Animator animation) { 135 mCancelled = true; 136 } 137 }); 138 mViewTransformationAnimation.start(); 139 } 140 141 @Override transformTo(TransformableView notification, float transformationAmount)142 public void transformTo(TransformableView notification, float transformationAmount) { 143 for (Integer viewType : mTransformedViews.keySet()) { 144 TransformState ownState = getCurrentState(viewType); 145 if (ownState != null) { 146 CustomTransformation customTransformation = mCustomTransformations.get(viewType); 147 if (customTransformation != null && customTransformation.transformTo( 148 ownState, notification, transformationAmount)) { 149 ownState.recycle(); 150 continue; 151 } 152 TransformState otherState = notification.getCurrentState(viewType); 153 if (otherState != null) { 154 ownState.transformViewTo(otherState, transformationAmount); 155 otherState.recycle(); 156 } else { 157 ownState.disappear(transformationAmount, notification); 158 } 159 ownState.recycle(); 160 } 161 } 162 } 163 164 @Override transformFrom(final TransformableView notification)165 public void transformFrom(final TransformableView notification) { 166 if (mViewTransformationAnimation != null) { 167 mViewTransformationAnimation.cancel(); 168 } 169 mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f); 170 mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 171 @Override 172 public void onAnimationUpdate(ValueAnimator animation) { 173 transformFrom(notification, animation.getAnimatedFraction()); 174 } 175 }); 176 mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() { 177 public boolean mCancelled; 178 179 @Override 180 public void onAnimationEnd(Animator animation) { 181 if (!mCancelled) { 182 setVisible(true); 183 } else { 184 abortTransformations(); 185 } 186 } 187 188 @Override 189 public void onAnimationCancel(Animator animation) { 190 mCancelled = true; 191 } 192 }); 193 mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR); 194 mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); 195 mViewTransformationAnimation.start(); 196 } 197 198 @Override transformFrom(TransformableView notification, float transformationAmount)199 public void transformFrom(TransformableView notification, float transformationAmount) { 200 for (Integer viewType : mTransformedViews.keySet()) { 201 TransformState ownState = getCurrentState(viewType); 202 if (ownState != null) { 203 CustomTransformation customTransformation = mCustomTransformations.get(viewType); 204 if (customTransformation != null && customTransformation.transformFrom( 205 ownState, notification, transformationAmount)) { 206 ownState.recycle(); 207 continue; 208 } 209 TransformState otherState = notification.getCurrentState(viewType); 210 if (otherState != null) { 211 ownState.transformViewFrom(otherState, transformationAmount); 212 otherState.recycle(); 213 } else { 214 ownState.appear(transformationAmount, notification); 215 } 216 ownState.recycle(); 217 } 218 } 219 } 220 221 @Override setVisible(boolean visible)222 public void setVisible(boolean visible) { 223 if (mViewTransformationAnimation != null) { 224 mViewTransformationAnimation.cancel(); 225 } 226 for (Integer viewType : mTransformedViews.keySet()) { 227 TransformState ownState = getCurrentState(viewType); 228 if (ownState != null) { 229 ownState.setVisible(visible, false /* force */); 230 ownState.recycle(); 231 } 232 } 233 } 234 abortTransformations()235 private void abortTransformations() { 236 for (Integer viewType : mTransformedViews.keySet()) { 237 TransformState ownState = getCurrentState(viewType); 238 if (ownState != null) { 239 ownState.abortTransformation(); 240 ownState.recycle(); 241 } 242 } 243 } 244 245 /** 246 * Add the remaining transformation views such that all views are being transformed correctly 247 * @param viewRoot the root below which all elements need to be transformed 248 */ addRemainingTransformTypes(View viewRoot)249 public void addRemainingTransformTypes(View viewRoot) { 250 // lets now tag the right views 251 int numValues = mTransformedViews.size(); 252 for (int i = 0; i < numValues; i++) { 253 View view = mTransformedViews.valueAt(i); 254 while (view != viewRoot.getParent()) { 255 view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true); 256 view = (View) view.getParent(); 257 } 258 } 259 Stack<View> stack = new Stack<>(); 260 // Add the right views now 261 stack.push(viewRoot); 262 while (!stack.isEmpty()) { 263 View child = stack.pop(); 264 Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW); 265 if (containsView == null) { 266 // This one is unhandled, let's add it to our list. 267 int id = child.getId(); 268 if (id != View.NO_ID) { 269 // We only fade views with an id 270 addTransformedView(id, child); 271 continue; 272 } 273 } 274 child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null); 275 if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){ 276 ViewGroup group = (ViewGroup) child; 277 for (int i = 0; i < group.getChildCount(); i++) { 278 stack.push(group.getChildAt(i)); 279 } 280 } 281 } 282 } 283 resetTransformedView(View view)284 public void resetTransformedView(View view) { 285 TransformState state = TransformState.createFrom(view, this); 286 state.setVisible(true /* visible */, true /* force */); 287 state.recycle(); 288 } 289 290 /** 291 * @return a set of all views are being transformed. 292 */ getAllTransformingViews()293 public ArraySet<View> getAllTransformingViews() { 294 return new ArraySet<>(mTransformedViews.values()); 295 } 296 297 @Override isAnimating()298 public boolean isAnimating() { 299 return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning(); 300 } 301 302 public static abstract class CustomTransformation { 303 /** 304 * Transform a state to the given view 305 * @param ownState the state to transform 306 * @param notification the view to transform to 307 * @param transformationAmount how much transformation should be done 308 * @return whether a custom transformation is performed 309 */ transformTo(TransformState ownState, TransformableView notification, float transformationAmount)310 public abstract boolean transformTo(TransformState ownState, 311 TransformableView notification, 312 float transformationAmount); 313 314 /** 315 * Transform to this state from the given view 316 * @param ownState the state to transform to 317 * @param notification the view to transform from 318 * @param transformationAmount how much transformation should be done 319 * @return whether a custom transformation is performed 320 */ transformFrom(TransformState ownState, TransformableView notification, float transformationAmount)321 public abstract boolean transformFrom(TransformState ownState, 322 TransformableView notification, 323 float transformationAmount); 324 325 /** 326 * Perform a custom initialisation before transforming. 327 * 328 * @param ownState our own state 329 * @param otherState the other state 330 * @return whether a custom initialization is done 331 */ initTransformation(TransformState ownState, TransformState otherState)332 public boolean initTransformation(TransformState ownState, 333 TransformState otherState) { 334 return false; 335 } 336 customTransformTarget(TransformState ownState, TransformState otherState)337 public boolean customTransformTarget(TransformState ownState, 338 TransformState otherState) { 339 return false; 340 } 341 342 /** 343 * Get a custom interpolator for this animation 344 * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY 345 * @param isFrom true if this transformation from the other view 346 */ getCustomInterpolator(int interpolationType, boolean isFrom)347 public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) { 348 return null; 349 } 350 } 351 } 352