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