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.notification; 18 19 import android.util.ArraySet; 20 import android.util.Pools; 21 import android.view.View; 22 import android.view.ViewGroup; 23 import android.view.ViewParent; 24 import android.view.animation.Interpolator; 25 import android.widget.ImageView; 26 import android.widget.ProgressBar; 27 import android.widget.TextView; 28 29 import com.android.systemui.Interpolators; 30 import com.android.systemui.R; 31 import com.android.systemui.statusbar.CrossFadeHelper; 32 import com.android.systemui.statusbar.ExpandableNotificationRow; 33 import com.android.systemui.statusbar.TransformableView; 34 import com.android.systemui.statusbar.ViewTransformationHelper; 35 36 /** 37 * A transform state of a view. 38 */ 39 public class TransformState { 40 41 public static final int TRANSFORM_X = 0x1; 42 public static final int TRANSFORM_Y = 0x10; 43 public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y; 44 45 private static final float UNDEFINED = -1f; 46 private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag; 47 private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag; 48 private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag; 49 private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag; 50 private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag; 51 private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag; 52 private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag; 53 private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40); 54 55 protected View mTransformedView; 56 private int[] mOwnPosition = new int[2]; 57 private float mTransformationEndY = UNDEFINED; 58 private float mTransformationEndX = UNDEFINED; 59 initFrom(View view)60 public void initFrom(View view) { 61 mTransformedView = view; 62 } 63 64 /** 65 * Transforms the {@link #mTransformedView} from the given transformviewstate 66 * @param otherState the state to transform from 67 * @param transformationAmount how much to transform 68 */ transformViewFrom(TransformState otherState, float transformationAmount)69 public void transformViewFrom(TransformState otherState, float transformationAmount) { 70 mTransformedView.animate().cancel(); 71 if (sameAs(otherState)) { 72 if (mTransformedView.getVisibility() == View.INVISIBLE 73 || mTransformedView.getAlpha() != 1.0f) { 74 // We have the same content, lets show ourselves 75 mTransformedView.setAlpha(1.0f); 76 mTransformedView.setVisibility(View.VISIBLE); 77 } 78 } else { 79 CrossFadeHelper.fadeIn(mTransformedView, transformationAmount); 80 } 81 transformViewFullyFrom(otherState, transformationAmount); 82 } 83 transformViewFullyFrom(TransformState otherState, float transformationAmount)84 public void transformViewFullyFrom(TransformState otherState, float transformationAmount) { 85 transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount); 86 } 87 transformViewFullyFrom(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)88 public void transformViewFullyFrom(TransformState otherState, 89 ViewTransformationHelper.CustomTransformation customTransformation, 90 float transformationAmount) { 91 transformViewFrom(otherState, TRANSFORM_ALL, customTransformation, transformationAmount); 92 } 93 transformViewVerticalFrom(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)94 public void transformViewVerticalFrom(TransformState otherState, 95 ViewTransformationHelper.CustomTransformation customTransformation, 96 float transformationAmount) { 97 transformViewFrom(otherState, TRANSFORM_Y, customTransformation, transformationAmount); 98 } 99 transformViewVerticalFrom(TransformState otherState, float transformationAmount)100 public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) { 101 transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount); 102 } 103 transformViewFrom(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)104 private void transformViewFrom(TransformState otherState, int transformationFlags, 105 ViewTransformationHelper.CustomTransformation customTransformation, 106 float transformationAmount) { 107 final View transformedView = mTransformedView; 108 boolean transformX = (transformationFlags & TRANSFORM_X) != 0; 109 boolean transformY = (transformationFlags & TRANSFORM_Y) != 0; 110 boolean transformScale = transformScale(); 111 // lets animate the positions correctly 112 if (transformationAmount == 0.0f 113 || transformX && getTransformationStartX() == UNDEFINED 114 || transformY && getTransformationStartY() == UNDEFINED 115 || transformScale && getTransformationStartScaleX() == UNDEFINED 116 || transformScale && getTransformationStartScaleY() == UNDEFINED) { 117 int[] otherPosition; 118 if (transformationAmount != 0.0f) { 119 otherPosition = otherState.getLaidOutLocationOnScreen(); 120 } else { 121 otherPosition = otherState.getLocationOnScreen(); 122 } 123 int[] ownStablePosition = getLaidOutLocationOnScreen(); 124 if (customTransformation == null 125 || !customTransformation.initTransformation(this, otherState)) { 126 if (transformX) { 127 setTransformationStartX(otherPosition[0] - ownStablePosition[0]); 128 } 129 if (transformY) { 130 setTransformationStartY(otherPosition[1] - ownStablePosition[1]); 131 } 132 // we also want to animate the scale if we're the same 133 View otherView = otherState.getTransformedView(); 134 if (transformScale && otherView.getWidth() != transformedView.getWidth()) { 135 setTransformationStartScaleX(otherView.getWidth() * otherView.getScaleX() 136 / (float) transformedView.getWidth()); 137 transformedView.setPivotX(0); 138 } else { 139 setTransformationStartScaleX(UNDEFINED); 140 } 141 if (transformScale && otherView.getHeight() != transformedView.getHeight()) { 142 setTransformationStartScaleY(otherView.getHeight() * otherView.getScaleY() 143 / (float) transformedView.getHeight()); 144 transformedView.setPivotY(0); 145 } else { 146 setTransformationStartScaleY(UNDEFINED); 147 } 148 } 149 if (!transformX) { 150 setTransformationStartX(UNDEFINED); 151 } 152 if (!transformY) { 153 setTransformationStartY(UNDEFINED); 154 } 155 if (!transformScale) { 156 setTransformationStartScaleX(UNDEFINED); 157 setTransformationStartScaleY(UNDEFINED); 158 } 159 setClippingDeactivated(transformedView, true); 160 } 161 float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 162 transformationAmount); 163 if (transformX) { 164 float interpolation = interpolatedValue; 165 if (customTransformation != null) { 166 Interpolator customInterpolator = 167 customTransformation.getCustomInterpolator(TRANSFORM_X, true /* isFrom */); 168 if (customInterpolator != null) { 169 interpolation = customInterpolator.getInterpolation(transformationAmount); 170 } 171 } 172 transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(), 173 0.0f, 174 interpolation)); 175 } 176 if (transformY) { 177 float interpolation = interpolatedValue; 178 if (customTransformation != null) { 179 Interpolator customInterpolator = 180 customTransformation.getCustomInterpolator(TRANSFORM_Y, true /* isFrom */); 181 if (customInterpolator != null) { 182 interpolation = customInterpolator.getInterpolation(transformationAmount); 183 } 184 } 185 transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(), 186 0.0f, 187 interpolation)); 188 } 189 if (transformScale) { 190 float transformationStartScaleX = getTransformationStartScaleX(); 191 if (transformationStartScaleX != UNDEFINED) { 192 transformedView.setScaleX( 193 NotificationUtils.interpolate(transformationStartScaleX, 194 1.0f, 195 interpolatedValue)); 196 } 197 float transformationStartScaleY = getTransformationStartScaleY(); 198 if (transformationStartScaleY != UNDEFINED) { 199 transformedView.setScaleY( 200 NotificationUtils.interpolate(transformationStartScaleY, 201 1.0f, 202 interpolatedValue)); 203 } 204 } 205 } 206 transformScale()207 protected boolean transformScale() { 208 return false; 209 } 210 211 /** 212 * Transforms the {@link #mTransformedView} to the given transformviewstate 213 * @param otherState the state to transform from 214 * @param transformationAmount how much to transform 215 * @return whether an animation was started 216 */ transformViewTo(TransformState otherState, float transformationAmount)217 public boolean transformViewTo(TransformState otherState, float transformationAmount) { 218 mTransformedView.animate().cancel(); 219 if (sameAs(otherState)) { 220 // We have the same text, lets show ourselfs 221 if (mTransformedView.getVisibility() == View.VISIBLE) { 222 mTransformedView.setAlpha(0.0f); 223 mTransformedView.setVisibility(View.INVISIBLE); 224 } 225 return false; 226 } else { 227 CrossFadeHelper.fadeOut(mTransformedView, transformationAmount); 228 } 229 transformViewFullyTo(otherState, transformationAmount); 230 return true; 231 } 232 transformViewFullyTo(TransformState otherState, float transformationAmount)233 public void transformViewFullyTo(TransformState otherState, float transformationAmount) { 234 transformViewTo(otherState, TRANSFORM_ALL, null, transformationAmount); 235 } 236 transformViewFullyTo(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)237 public void transformViewFullyTo(TransformState otherState, 238 ViewTransformationHelper.CustomTransformation customTransformation, 239 float transformationAmount) { 240 transformViewTo(otherState, TRANSFORM_ALL, customTransformation, transformationAmount); 241 } 242 transformViewVerticalTo(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)243 public void transformViewVerticalTo(TransformState otherState, 244 ViewTransformationHelper.CustomTransformation customTransformation, 245 float transformationAmount) { 246 transformViewTo(otherState, TRANSFORM_Y, customTransformation, transformationAmount); 247 } 248 transformViewVerticalTo(TransformState otherState, float transformationAmount)249 public void transformViewVerticalTo(TransformState otherState, float transformationAmount) { 250 transformViewTo(otherState, TRANSFORM_Y, null, transformationAmount); 251 } 252 transformViewTo(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)253 private void transformViewTo(TransformState otherState, int transformationFlags, 254 ViewTransformationHelper.CustomTransformation customTransformation, 255 float transformationAmount) { 256 // lets animate the positions correctly 257 258 final View transformedView = mTransformedView; 259 boolean transformX = (transformationFlags & TRANSFORM_X) != 0; 260 boolean transformY = (transformationFlags & TRANSFORM_Y) != 0; 261 boolean transformScale = transformScale(); 262 // lets animate the positions correctly 263 if (transformationAmount == 0.0f) { 264 if (transformX) { 265 float transformationStartX = getTransformationStartX(); 266 float start = transformationStartX != UNDEFINED ? transformationStartX 267 : transformedView.getTranslationX(); 268 setTransformationStartX(start); 269 } 270 if (transformY) { 271 float transformationStartY = getTransformationStartY(); 272 float start = transformationStartY != UNDEFINED ? transformationStartY 273 : transformedView.getTranslationY(); 274 setTransformationStartY(start); 275 } 276 View otherView = otherState.getTransformedView(); 277 if (transformScale && otherView.getWidth() != transformedView.getWidth()) { 278 setTransformationStartScaleX(transformedView.getScaleX()); 279 transformedView.setPivotX(0); 280 } else { 281 setTransformationStartScaleX(UNDEFINED); 282 } 283 if (transformScale && otherView.getHeight() != transformedView.getHeight()) { 284 setTransformationStartScaleY(transformedView.getScaleY()); 285 transformedView.setPivotY(0); 286 } else { 287 setTransformationStartScaleY(UNDEFINED); 288 } 289 setClippingDeactivated(transformedView, true); 290 } 291 float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation( 292 transformationAmount); 293 int[] otherStablePosition = otherState.getLaidOutLocationOnScreen(); 294 int[] ownPosition = getLaidOutLocationOnScreen(); 295 if (transformX) { 296 float endX = otherStablePosition[0] - ownPosition[0]; 297 float interpolation = interpolatedValue; 298 if (customTransformation != null) { 299 if (customTransformation.customTransformTarget(this, otherState)) { 300 endX = mTransformationEndX; 301 } 302 Interpolator customInterpolator = 303 customTransformation.getCustomInterpolator(TRANSFORM_X, false /* isFrom */); 304 if (customInterpolator != null) { 305 interpolation = customInterpolator.getInterpolation(transformationAmount); 306 } 307 } 308 transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(), 309 endX, 310 interpolation)); 311 } 312 if (transformY) { 313 float endY = otherStablePosition[1] - ownPosition[1]; 314 float interpolation = interpolatedValue; 315 if (customTransformation != null) { 316 if (customTransformation.customTransformTarget(this, otherState)) { 317 endY = mTransformationEndY; 318 } 319 Interpolator customInterpolator = 320 customTransformation.getCustomInterpolator(TRANSFORM_Y, false /* isFrom */); 321 if (customInterpolator != null) { 322 interpolation = customInterpolator.getInterpolation(transformationAmount); 323 } 324 } 325 transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(), 326 endY, 327 interpolation)); 328 } 329 if (transformScale) { 330 View otherView = otherState.getTransformedView(); 331 float transformationStartScaleX = getTransformationStartScaleX(); 332 if (transformationStartScaleX != UNDEFINED) { 333 transformedView.setScaleX( 334 NotificationUtils.interpolate(transformationStartScaleX, 335 (otherView.getWidth() / (float) transformedView.getWidth()), 336 interpolatedValue)); 337 } 338 float transformationStartScaleY = getTransformationStartScaleY(); 339 if (transformationStartScaleY != UNDEFINED) { 340 transformedView.setScaleY( 341 NotificationUtils.interpolate(transformationStartScaleY, 342 (otherView.getHeight() / (float) transformedView.getHeight()), 343 interpolatedValue)); 344 } 345 } 346 } 347 setClippingDeactivated(final View transformedView, boolean deactivated)348 public static void setClippingDeactivated(final View transformedView, boolean deactivated) { 349 if (!(transformedView.getParent() instanceof ViewGroup)) { 350 return; 351 } 352 ViewGroup view = (ViewGroup) transformedView.getParent(); 353 while (true) { 354 ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET); 355 if (clipSet == null) { 356 clipSet = new ArraySet<>(); 357 view.setTag(CLIP_CLIPPING_SET, clipSet); 358 } 359 Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG); 360 if (clipChildren == null) { 361 clipChildren = view.getClipChildren(); 362 view.setTag(CLIP_CHILDREN_TAG, clipChildren); 363 } 364 Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING); 365 if (clipToPadding == null) { 366 clipToPadding = view.getClipToPadding(); 367 view.setTag(CLIP_TO_PADDING, clipToPadding); 368 } 369 ExpandableNotificationRow row = view instanceof ExpandableNotificationRow 370 ? (ExpandableNotificationRow) view 371 : null; 372 if (!deactivated) { 373 clipSet.remove(transformedView); 374 if (clipSet.isEmpty()) { 375 view.setClipChildren(clipChildren); 376 view.setClipToPadding(clipToPadding); 377 view.setTag(CLIP_CLIPPING_SET, null); 378 if (row != null) { 379 row.setClipToActualHeight(true); 380 } 381 } 382 } else { 383 clipSet.add(transformedView); 384 view.setClipChildren(false); 385 view.setClipToPadding(false); 386 if (row != null && row.isChildInGroup()) { 387 // We still want to clip to the parent's height 388 row.setClipToActualHeight(false); 389 } 390 } 391 if (row != null && !row.isChildInGroup()) { 392 return; 393 } 394 final ViewParent parent = view.getParent(); 395 if (parent instanceof ViewGroup) { 396 view = (ViewGroup) parent; 397 } else { 398 return; 399 } 400 } 401 } 402 getLaidOutLocationOnScreen()403 public int[] getLaidOutLocationOnScreen() { 404 int[] location = getLocationOnScreen(); 405 // remove translation 406 location[0] -= mTransformedView.getTranslationX(); 407 location[1] -= mTransformedView.getTranslationY(); 408 return location; 409 } 410 getLocationOnScreen()411 public int[] getLocationOnScreen() { 412 mTransformedView.getLocationOnScreen(mOwnPosition); 413 414 // remove scale 415 mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX(); 416 mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY(); 417 return mOwnPosition; 418 } 419 sameAs(TransformState otherState)420 protected boolean sameAs(TransformState otherState) { 421 return false; 422 } 423 appear(float transformationAmount, TransformableView otherView)424 public void appear(float transformationAmount, TransformableView otherView) { 425 // There's no other view, lets fade us in 426 // Certain views need to prepare the fade in and make sure its children are 427 // completely visible. An example is the notification header. 428 if (transformationAmount == 0.0f) { 429 prepareFadeIn(); 430 } 431 CrossFadeHelper.fadeIn(mTransformedView, transformationAmount); 432 } 433 disappear(float transformationAmount, TransformableView otherView)434 public void disappear(float transformationAmount, TransformableView otherView) { 435 CrossFadeHelper.fadeOut(mTransformedView, transformationAmount); 436 } 437 createFrom(View view)438 public static TransformState createFrom(View view) { 439 if (view instanceof TextView) { 440 TextViewTransformState result = TextViewTransformState.obtain(); 441 result.initFrom(view); 442 return result; 443 } 444 if (view.getId() == com.android.internal.R.id.actions_container) { 445 ActionListTransformState result = ActionListTransformState.obtain(); 446 result.initFrom(view); 447 return result; 448 } 449 if (view instanceof ImageView) { 450 ImageTransformState result = ImageTransformState.obtain(); 451 result.initFrom(view); 452 return result; 453 } 454 if (view instanceof ProgressBar) { 455 ProgressTransformState result = ProgressTransformState.obtain(); 456 result.initFrom(view); 457 return result; 458 } 459 TransformState result = obtain(); 460 result.initFrom(view); 461 return result; 462 } 463 recycle()464 public void recycle() { 465 reset(); 466 if (getClass() == TransformState.class) { 467 sInstancePool.release(this); 468 } 469 } 470 setTransformationEndY(float transformationEndY)471 public void setTransformationEndY(float transformationEndY) { 472 mTransformationEndY = transformationEndY; 473 } 474 setTransformationEndX(float transformationEndX)475 public void setTransformationEndX(float transformationEndX) { 476 mTransformationEndX = transformationEndX; 477 } 478 getTransformationStartX()479 public float getTransformationStartX() { 480 Object tag = mTransformedView.getTag(TRANSFORMATION_START_X); 481 return tag == null ? UNDEFINED : (float) tag; 482 } 483 getTransformationStartY()484 public float getTransformationStartY() { 485 Object tag = mTransformedView.getTag(TRANSFORMATION_START_Y); 486 return tag == null ? UNDEFINED : (float) tag; 487 } 488 getTransformationStartScaleX()489 public float getTransformationStartScaleX() { 490 Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_X); 491 return tag == null ? UNDEFINED : (float) tag; 492 } 493 getTransformationStartScaleY()494 public float getTransformationStartScaleY() { 495 Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_Y); 496 return tag == null ? UNDEFINED : (float) tag; 497 } 498 setTransformationStartX(float transformationStartX)499 public void setTransformationStartX(float transformationStartX) { 500 mTransformedView.setTag(TRANSFORMATION_START_X, transformationStartX); 501 } 502 setTransformationStartY(float transformationStartY)503 public void setTransformationStartY(float transformationStartY) { 504 mTransformedView.setTag(TRANSFORMATION_START_Y, transformationStartY); 505 } 506 setTransformationStartScaleX(float startScaleX)507 private void setTransformationStartScaleX(float startScaleX) { 508 mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, startScaleX); 509 } 510 setTransformationStartScaleY(float startScaleY)511 private void setTransformationStartScaleY(float startScaleY) { 512 mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, startScaleY); 513 } 514 reset()515 protected void reset() { 516 mTransformedView = null; 517 mTransformationEndX = UNDEFINED; 518 mTransformationEndY = UNDEFINED; 519 } 520 setVisible(boolean visible, boolean force)521 public void setVisible(boolean visible, boolean force) { 522 if (!force && mTransformedView.getVisibility() == View.GONE) { 523 return; 524 } 525 if (mTransformedView.getVisibility() != View.GONE) { 526 mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 527 } 528 mTransformedView.animate().cancel(); 529 mTransformedView.setAlpha(visible ? 1.0f : 0.0f); 530 resetTransformedView(); 531 } 532 prepareFadeIn()533 public void prepareFadeIn() { 534 resetTransformedView(); 535 } 536 resetTransformedView()537 protected void resetTransformedView() { 538 mTransformedView.setTranslationX(0); 539 mTransformedView.setTranslationY(0); 540 mTransformedView.setScaleX(1.0f); 541 mTransformedView.setScaleY(1.0f); 542 setClippingDeactivated(mTransformedView, false); 543 abortTransformation(); 544 } 545 abortTransformation()546 public void abortTransformation() { 547 mTransformedView.setTag(TRANSFORMATION_START_X, UNDEFINED); 548 mTransformedView.setTag(TRANSFORMATION_START_Y, UNDEFINED); 549 mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, UNDEFINED); 550 mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, UNDEFINED); 551 } 552 obtain()553 public static TransformState obtain() { 554 TransformState instance = sInstancePool.acquire(); 555 if (instance != null) { 556 return instance; 557 } 558 return new TransformState(); 559 } 560 getTransformedView()561 public View getTransformedView() { 562 return mTransformedView; 563 } 564 } 565