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