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