1 /* 2 * Copyright (C) 2019 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 package com.android.launcher3.views; 17 18 import static com.android.launcher3.LauncherAnimUtils.DRAWABLE_ALPHA; 19 import static com.android.launcher3.Utilities.getBadge; 20 import static com.android.launcher3.Utilities.getFullDrawable; 21 import static com.android.launcher3.config.FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM; 22 import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; 23 24 import android.animation.Animator; 25 import android.animation.AnimatorListenerAdapter; 26 import android.animation.AnimatorSet; 27 import android.animation.ObjectAnimator; 28 import android.annotation.TargetApi; 29 import android.content.Context; 30 import android.graphics.Canvas; 31 import android.graphics.Rect; 32 import android.graphics.RectF; 33 import android.graphics.drawable.AdaptiveIconDrawable; 34 import android.graphics.drawable.Drawable; 35 import android.os.Build; 36 import android.os.CancellationSignal; 37 import android.util.AttributeSet; 38 import android.util.Log; 39 import android.view.View; 40 import android.view.ViewGroup; 41 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 42 import android.widget.FrameLayout; 43 import android.widget.ImageView; 44 45 import androidx.annotation.Nullable; 46 import androidx.annotation.UiThread; 47 import androidx.annotation.WorkerThread; 48 49 import com.android.launcher3.BubbleTextView; 50 import com.android.launcher3.DeviceProfile; 51 import com.android.launcher3.InsettableFrameLayout; 52 import com.android.launcher3.Launcher; 53 import com.android.launcher3.R; 54 import com.android.launcher3.Utilities; 55 import com.android.launcher3.dragndrop.DragLayer; 56 import com.android.launcher3.dragndrop.FolderAdaptiveIcon; 57 import com.android.launcher3.folder.FolderIcon; 58 import com.android.launcher3.icons.LauncherIcons; 59 import com.android.launcher3.model.data.ItemInfo; 60 import com.android.launcher3.popup.SystemShortcut; 61 import com.android.launcher3.shortcuts.DeepShortcutView; 62 import com.android.launcher3.testing.TestProtocol; 63 64 /** 65 * A view that is created to look like another view with the purpose of creating fluid animations. 66 */ 67 @TargetApi(Build.VERSION_CODES.Q) 68 public class FloatingIconView extends FrameLayout implements 69 Animator.AnimatorListener, OnGlobalLayoutListener { 70 71 private static final String TAG = FloatingIconView.class.getSimpleName(); 72 73 // Manages loading the icon on a worker thread 74 private static @Nullable IconLoadResult sIconLoadResult; 75 76 public static final float SHAPE_PROGRESS_DURATION = 0.10f; 77 private static final int FADE_DURATION_MS = 200; 78 private static final RectF sTmpRectF = new RectF(); 79 private static final Object[] sTmpObjArray = new Object[1]; 80 81 private Runnable mEndRunnable; 82 private CancellationSignal mLoadIconSignal; 83 84 private final Launcher mLauncher; 85 private final boolean mIsRtl; 86 87 private boolean mIsVerticalBarLayout = false; 88 private boolean mIsOpening; 89 90 private IconLoadResult mIconLoadResult; 91 92 private ClipIconView mClipIconView; 93 private @Nullable Drawable mBadge; 94 95 private View mOriginalIcon; 96 private RectF mPositionOut; 97 private Runnable mOnTargetChangeRunnable; 98 99 private final Rect mFinalDrawableBounds = new Rect(); 100 101 private AnimatorSet mFadeAnimatorSet; 102 private ListenerView mListenerView; 103 private Runnable mFastFinishRunnable; 104 FloatingIconView(Context context)105 public FloatingIconView(Context context) { 106 this(context, null); 107 } 108 FloatingIconView(Context context, AttributeSet attrs)109 public FloatingIconView(Context context, AttributeSet attrs) { 110 this(context, attrs, 0); 111 } 112 FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr)113 public FloatingIconView(Context context, AttributeSet attrs, int defStyleAttr) { 114 super(context, attrs, defStyleAttr); 115 mLauncher = Launcher.getLauncher(context); 116 mIsRtl = Utilities.isRtl(getResources()); 117 mListenerView = new ListenerView(context, attrs); 118 mClipIconView = new ClipIconView(context, attrs); 119 addView(mClipIconView); 120 setWillNotDraw(false); 121 } 122 123 @Override onAttachedToWindow()124 protected void onAttachedToWindow() { 125 super.onAttachedToWindow(); 126 if (!mIsOpening) { 127 getViewTreeObserver().addOnGlobalLayoutListener(this); 128 } 129 } 130 131 @Override onDetachedFromWindow()132 protected void onDetachedFromWindow() { 133 getViewTreeObserver().removeOnGlobalLayoutListener(this); 134 super.onDetachedFromWindow(); 135 } 136 137 /** 138 * Positions this view to match the size and location of {@param rect}. 139 * @param alpha The alpha to set this view. 140 * @param progress A value from [0, 1] that represents the animation progress. 141 * @param shapeProgressStart The progress value at which to start the shape reveal. 142 * @param cornerRadius The corner radius of {@param rect}. 143 */ update(RectF rect, float alpha, float progress, float shapeProgressStart, float cornerRadius, boolean isOpening)144 public void update(RectF rect, float alpha, float progress, float shapeProgressStart, 145 float cornerRadius, boolean isOpening) { 146 setAlpha(alpha); 147 148 InsettableFrameLayout.LayoutParams lp = 149 (InsettableFrameLayout.LayoutParams) getLayoutParams(); 150 151 DeviceProfile dp = mLauncher.getDeviceProfile(); 152 float dX = mIsRtl 153 ? rect.left - (dp.widthPx - lp.getMarginStart() - lp.width) 154 : rect.left - lp.getMarginStart(); 155 float dY = rect.top - lp.topMargin; 156 setTranslationX(dX); 157 setTranslationY(dY); 158 159 float minSize = Math.min(lp.width, lp.height); 160 float scaleX = rect.width() / minSize; 161 float scaleY = rect.height() / minSize; 162 float scale = Math.max(1f, Math.min(scaleX, scaleY)); 163 164 mClipIconView.update(rect, progress, shapeProgressStart, cornerRadius, isOpening, scale, 165 minSize, lp, mIsVerticalBarLayout); 166 167 setPivotX(0); 168 setPivotY(0); 169 setScaleX(scale); 170 setScaleY(scale); 171 172 invalidate(); 173 } 174 175 @Override onAnimationEnd(Animator animator)176 public void onAnimationEnd(Animator animator) { 177 if (mLoadIconSignal != null) { 178 mLoadIconSignal.cancel(); 179 } 180 if (mEndRunnable != null) { 181 mEndRunnable.run(); 182 } else { 183 // End runnable also ends the reveal animator, so we manually handle it here. 184 mClipIconView.endReveal(); 185 } 186 } 187 188 /** 189 * Sets the size and position of this view to match {@param v}. 190 * 191 * @param v The view to copy 192 * @param positionOut Rect that will hold the size and position of v. 193 */ matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut)194 private void matchPositionOf(Launcher launcher, View v, boolean isOpening, RectF positionOut) { 195 getLocationBoundsForView(launcher, v, isOpening, positionOut); 196 final InsettableFrameLayout.LayoutParams lp = new InsettableFrameLayout.LayoutParams( 197 Math.round(positionOut.width()), 198 Math.round(positionOut.height())); 199 updatePosition(positionOut, lp); 200 setLayoutParams(lp); 201 202 mClipIconView.setLayoutParams(new FrameLayout.LayoutParams(lp.width, lp.height)); 203 } 204 updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp)205 private void updatePosition(RectF pos, InsettableFrameLayout.LayoutParams lp) { 206 mPositionOut.set(pos); 207 lp.ignoreInsets = true; 208 // Position the floating view exactly on top of the original 209 lp.topMargin = Math.round(pos.top); 210 if (mIsRtl) { 211 lp.setMarginStart(Math.round(mLauncher.getDeviceProfile().widthPx - pos.right)); 212 } else { 213 lp.setMarginStart(Math.round(pos.left)); 214 } 215 // Set the properties here already to make sure they are available when running the first 216 // animation frame. 217 int left = mIsRtl 218 ? mLauncher.getDeviceProfile().widthPx - lp.getMarginStart() - lp.width 219 : lp.leftMargin; 220 layout(left, lp.topMargin, left + lp.width, lp.topMargin + lp.height); 221 } 222 223 /** 224 * Gets the location bounds of a view and returns the overall rotation. 225 * - For DeepShortcutView, we return the bounds of the icon view. 226 * - For BubbleTextView, we return the icon bounds. 227 */ getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, RectF outRect)228 private static void getLocationBoundsForView(Launcher launcher, View v, boolean isOpening, 229 RectF outRect) { 230 boolean ignoreTransform = !isOpening; 231 if (v instanceof DeepShortcutView) { 232 v = ((DeepShortcutView) v).getBubbleText(); 233 ignoreTransform = false; 234 } else if (v.getParent() instanceof DeepShortcutView) { 235 v = ((DeepShortcutView) v.getParent()).getIconView(); 236 ignoreTransform = false; 237 } 238 if (v == null) { 239 return; 240 } 241 242 Rect iconBounds = new Rect(); 243 if (v instanceof BubbleTextView) { 244 ((BubbleTextView) v).getIconBounds(iconBounds); 245 } else if (v instanceof FolderIcon) { 246 ((FolderIcon) v).getPreviewBounds(iconBounds); 247 } else { 248 iconBounds.set(0, 0, v.getWidth(), v.getHeight()); 249 } 250 251 float[] points = new float[] {iconBounds.left, iconBounds.top, iconBounds.right, 252 iconBounds.bottom}; 253 Utilities.getDescendantCoordRelativeToAncestor(v, launcher.getDragLayer(), points, 254 false, ignoreTransform); 255 outRect.set( 256 Math.min(points[0], points[2]), 257 Math.min(points[1], points[3]), 258 Math.max(points[0], points[2]), 259 Math.max(points[1], points[3])); 260 } 261 262 /** 263 * Loads the icon and saves the results to {@link #sIconLoadResult}. 264 * Runs onIconLoaded callback (if any), which signifies that the FloatingIconView is 265 * ready to display the icon. Otherwise, the FloatingIconView will grab the results when its 266 * initialized. 267 * 268 * @param originalView The View that the FloatingIconView will replace. 269 * @param info ItemInfo of the originalView 270 * @param pos The position of the view. 271 */ 272 @WorkerThread 273 @SuppressWarnings("WrongThread") getIconResult(Launcher l, View originalView, ItemInfo info, RectF pos, IconLoadResult iconLoadResult)274 private static void getIconResult(Launcher l, View originalView, ItemInfo info, RectF pos, 275 IconLoadResult iconLoadResult) { 276 Drawable drawable = null; 277 Drawable badge = null; 278 boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get() 279 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O 280 && !info.isDisabled(); // Use original icon for disabled icons. 281 Drawable btvIcon = originalView instanceof BubbleTextView 282 ? ((BubbleTextView) originalView).getIcon() : null; 283 if (info instanceof SystemShortcut) { 284 if (originalView instanceof ImageView) { 285 drawable = ((ImageView) originalView).getDrawable(); 286 } else if (originalView instanceof DeepShortcutView) { 287 drawable = ((DeepShortcutView) originalView).getIconView().getBackground(); 288 } else { 289 drawable = originalView.getBackground(); 290 } 291 } else { 292 int width = (int) pos.width(); 293 int height = (int) pos.height(); 294 if (supportsAdaptiveIcons) { 295 drawable = getFullDrawable(l, info, width, height, sTmpObjArray); 296 if (drawable instanceof AdaptiveIconDrawable) { 297 badge = getBadge(l, info, sTmpObjArray[0]); 298 } else { 299 // The drawable we get back is not an adaptive icon, so we need to use the 300 // BubbleTextView icon that is already legacy treated. 301 drawable = btvIcon; 302 } 303 } else { 304 if (originalView instanceof BubbleTextView) { 305 // Similar to DragView, we simply use the BubbleTextView icon here. 306 drawable = btvIcon; 307 } else { 308 drawable = getFullDrawable(l, info, width, height, sTmpObjArray); 309 } 310 } 311 } 312 313 drawable = drawable == null ? null : drawable.getConstantState().newDrawable(); 314 int iconOffset = getOffsetForIconBounds(l, drawable, pos); 315 synchronized (iconLoadResult) { 316 iconLoadResult.drawable = drawable; 317 iconLoadResult.badge = badge; 318 iconLoadResult.iconOffset = iconOffset; 319 if (iconLoadResult.onIconLoaded != null) { 320 l.getMainExecutor().execute(iconLoadResult.onIconLoaded); 321 iconLoadResult.onIconLoaded = null; 322 } 323 iconLoadResult.isIconLoaded = true; 324 } 325 } 326 327 /** 328 * Sets the drawables of the {@param originalView} onto this view. 329 * 330 * @param drawable The drawable of the original view. 331 * @param badge The badge of the original view. 332 * @param iconOffset The amount of offset needed to match this view with the original view. 333 */ 334 @UiThread setIcon(@ullable Drawable drawable, @Nullable Drawable badge, int iconOffset)335 private void setIcon(@Nullable Drawable drawable, @Nullable Drawable badge, int iconOffset) { 336 final InsettableFrameLayout.LayoutParams lp = 337 (InsettableFrameLayout.LayoutParams) getLayoutParams(); 338 mBadge = badge; 339 mClipIconView.setIcon(drawable, iconOffset, lp, mIsOpening, mIsVerticalBarLayout); 340 if (drawable instanceof AdaptiveIconDrawable) { 341 final int originalHeight = lp.height; 342 final int originalWidth = lp.width; 343 344 mFinalDrawableBounds.set(0, 0, originalWidth, originalHeight); 345 346 float aspectRatio = mLauncher.getDeviceProfile().aspectRatio; 347 if (mIsVerticalBarLayout) { 348 lp.width = (int) Math.max(lp.width, lp.height * aspectRatio); 349 } else { 350 lp.height = (int) Math.max(lp.height, lp.width * aspectRatio); 351 } 352 setLayoutParams(lp); 353 354 final LayoutParams clipViewLp = (LayoutParams) mClipIconView.getLayoutParams(); 355 final int clipViewOgHeight = clipViewLp.height; 356 final int clipViewOgWidth = clipViewLp.width; 357 clipViewLp.width = lp.width; 358 clipViewLp.height = lp.height; 359 mClipIconView.setLayoutParams(clipViewLp); 360 361 if (mBadge != null) { 362 mBadge.setBounds(0, 0, clipViewOgWidth, clipViewOgHeight); 363 } 364 } 365 invalidate(); 366 } 367 368 /** 369 * Checks if the icon result is loaded. If true, we set the icon immediately. Else, we add a 370 * callback to set the icon once the icon result is loaded. 371 */ checkIconResult(View originalView)372 private void checkIconResult(View originalView) { 373 CancellationSignal cancellationSignal = new CancellationSignal(); 374 375 if (mIconLoadResult == null) { 376 Log.w(TAG, "No icon load result found in checkIconResult"); 377 return; 378 } 379 380 synchronized (mIconLoadResult) { 381 if (mIconLoadResult.isIconLoaded) { 382 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge, 383 mIconLoadResult.iconOffset); 384 hideOriginalView(originalView); 385 } else { 386 mIconLoadResult.onIconLoaded = () -> { 387 if (cancellationSignal.isCanceled()) { 388 return; 389 } 390 391 setIcon(mIconLoadResult.drawable, mIconLoadResult.badge, 392 mIconLoadResult.iconOffset); 393 394 setVisibility(VISIBLE); 395 hideOriginalView(originalView); 396 }; 397 mLoadIconSignal = cancellationSignal; 398 } 399 } 400 } 401 hideOriginalView(View originalView)402 private void hideOriginalView(View originalView) { 403 if (originalView instanceof IconLabelDotView) { 404 ((IconLabelDotView) originalView).setIconVisible(false); 405 ((IconLabelDotView) originalView).setForceHideDot(true); 406 } else { 407 originalView.setVisibility(INVISIBLE); 408 } 409 } 410 411 @WorkerThread 412 @SuppressWarnings("WrongThread") getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position)413 private static int getOffsetForIconBounds(Launcher l, Drawable drawable, RectF position) { 414 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O 415 || !(drawable instanceof AdaptiveIconDrawable) 416 || (drawable instanceof FolderAdaptiveIcon)) { 417 return 0; 418 } 419 int blurSizeOutline = 420 l.getResources().getDimensionPixelSize(R.dimen.blur_size_medium_outline); 421 422 Rect bounds = new Rect(0, 0, (int) position.width() + blurSizeOutline, 423 (int) position.height() + blurSizeOutline); 424 bounds.inset(blurSizeOutline / 2, blurSizeOutline / 2); 425 426 try (LauncherIcons li = LauncherIcons.obtain(l)) { 427 Utilities.scaleRectAboutCenter(bounds, li.getNormalizer().getScale(drawable, null, 428 null, null)); 429 } 430 431 bounds.inset( 432 (int) (-bounds.width() * AdaptiveIconDrawable.getExtraInsetFraction()), 433 (int) (-bounds.height() * AdaptiveIconDrawable.getExtraInsetFraction()) 434 ); 435 436 return bounds.left; 437 } 438 439 @Override dispatchDraw(Canvas canvas)440 protected void dispatchDraw(Canvas canvas) { 441 super.dispatchDraw(canvas); 442 if (mBadge != null) { 443 mBadge.draw(canvas); 444 } 445 } 446 447 /** 448 * Sets a runnable that is called after a call to {@link #fastFinish()}. 449 */ setFastFinishRunnable(Runnable runnable)450 public void setFastFinishRunnable(Runnable runnable) { 451 mFastFinishRunnable = runnable; 452 } 453 fastFinish()454 public void fastFinish() { 455 if (mFastFinishRunnable != null) { 456 mFastFinishRunnable.run(); 457 mFastFinishRunnable = null; 458 } 459 if (mLoadIconSignal != null) { 460 mLoadIconSignal.cancel(); 461 mLoadIconSignal = null; 462 } 463 if (mEndRunnable != null) { 464 mEndRunnable.run(); 465 mEndRunnable = null; 466 } 467 if (mFadeAnimatorSet != null) { 468 mFadeAnimatorSet.end(); 469 mFadeAnimatorSet = null; 470 } 471 } 472 473 @Override onAnimationStart(Animator animator)474 public void onAnimationStart(Animator animator) { 475 if (mIconLoadResult != null && mIconLoadResult.isIconLoaded) { 476 setVisibility(View.VISIBLE); 477 } 478 if (!mIsOpening) { 479 // When closing an app, we want the item on the workspace to be invisible immediately 480 hideOriginalView(mOriginalIcon); 481 } 482 } 483 484 @Override onAnimationCancel(Animator animator)485 public void onAnimationCancel(Animator animator) {} 486 487 @Override onAnimationRepeat(Animator animator)488 public void onAnimationRepeat(Animator animator) {} 489 490 @Override onGlobalLayout()491 public void onGlobalLayout() { 492 if (mOriginalIcon.isAttachedToWindow() && mPositionOut != null) { 493 getLocationBoundsForView(mLauncher, mOriginalIcon, mIsOpening, 494 sTmpRectF); 495 if (!sTmpRectF.equals(mPositionOut)) { 496 updatePosition(sTmpRectF, (InsettableFrameLayout.LayoutParams) getLayoutParams()); 497 if (mOnTargetChangeRunnable != null) { 498 mOnTargetChangeRunnable.run(); 499 } 500 } 501 } 502 } 503 setOnTargetChangeListener(Runnable onTargetChangeListener)504 public void setOnTargetChangeListener(Runnable onTargetChangeListener) { 505 mOnTargetChangeRunnable = onTargetChangeListener; 506 } 507 508 /** 509 * Loads the icon drawable on a worker thread to reduce latency between swapping views. 510 */ 511 @UiThread fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening)512 public static IconLoadResult fetchIcon(Launcher l, View v, ItemInfo info, boolean isOpening) { 513 IconLoadResult result = new IconLoadResult(info); 514 MODEL_EXECUTOR.getHandler().postAtFrontOfQueue(() -> { 515 RectF position = new RectF(); 516 getLocationBoundsForView(l, v, isOpening, position); 517 getIconResult(l, v, info, position, result); 518 }); 519 520 sIconLoadResult = result; 521 return result; 522 } 523 524 /** 525 * Creates a floating icon view for {@param originalView}. 526 * @param originalView The view to copy 527 * @param hideOriginal If true, it will hide {@param originalView} while this view is visible. 528 * Else, we will not draw anything in this view. 529 * @param positionOut Rect that will hold the size and position of v. 530 * @param isOpening True if this view replaces the icon for app open animation. 531 */ getFloatingIconView(Launcher launcher, View originalView, boolean hideOriginal, RectF positionOut, boolean isOpening)532 public static FloatingIconView getFloatingIconView(Launcher launcher, View originalView, 533 boolean hideOriginal, RectF positionOut, boolean isOpening) { 534 final DragLayer dragLayer = launcher.getDragLayer(); 535 ViewGroup parent = (ViewGroup) dragLayer.getParent(); 536 FloatingIconView view = launcher.getViewCache().getView(R.layout.floating_icon_view, 537 launcher, parent); 538 view.recycle(); 539 540 // Get the drawable on the background thread 541 boolean shouldLoadIcon = originalView.getTag() instanceof ItemInfo && hideOriginal; 542 if (shouldLoadIcon) { 543 if (sIconLoadResult != null && sIconLoadResult.itemInfo == originalView.getTag()) { 544 view.mIconLoadResult = sIconLoadResult; 545 } else { 546 view.mIconLoadResult = fetchIcon(launcher, originalView, 547 (ItemInfo) originalView.getTag(), isOpening); 548 } 549 } 550 sIconLoadResult = null; 551 552 view.mIsVerticalBarLayout = launcher.getDeviceProfile().isVerticalBarLayout(); 553 view.mIsOpening = isOpening; 554 view.mOriginalIcon = originalView; 555 view.mPositionOut = positionOut; 556 557 // Match the position of the original view. 558 view.matchPositionOf(launcher, originalView, isOpening, positionOut); 559 560 // We need to add it to the overlay, but keep it invisible until animation starts.. 561 view.setVisibility(INVISIBLE); 562 parent.addView(view); 563 dragLayer.addView(view.mListenerView); 564 if (TestProtocol.sDebugTracing) { 565 Log.d(TestProtocol.PAUSE_NOT_DETECTED, "getFloatingIconView. listenerView " 566 + "added to dragLayer. listenerView=" + view.mListenerView + ", fiv=" + view, 567 new Exception()); 568 } 569 view.mListenerView.setListener(view::fastFinish); 570 571 view.mEndRunnable = () -> { 572 view.mEndRunnable = null; 573 574 if (hideOriginal) { 575 if (isOpening) { 576 if (originalView instanceof BubbleTextView) { 577 ((BubbleTextView) originalView).setIconVisible(true); 578 ((BubbleTextView) originalView).setForceHideDot(false); 579 } else { 580 originalView.setVisibility(VISIBLE); 581 } 582 view.finish(dragLayer); 583 } else { 584 view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer); 585 view.mFadeAnimatorSet.start(); 586 } 587 } else { 588 view.finish(dragLayer); 589 } 590 }; 591 592 // Must be called after matchPositionOf so that we know what size to load. 593 // Must be called after the fastFinish listener and end runnable is created so that 594 // the icon is not left in a hidden state. 595 if (shouldLoadIcon) { 596 view.checkIconResult(originalView); 597 } 598 599 return view; 600 } 601 createFadeAnimation(View originalView, DragLayer dragLayer)602 private AnimatorSet createFadeAnimation(View originalView, DragLayer dragLayer) { 603 AnimatorSet fade = new AnimatorSet(); 604 fade.setDuration(FADE_DURATION_MS); 605 fade.addListener(new AnimatorListenerAdapter() { 606 @Override 607 public void onAnimationStart(Animator animation) { 608 originalView.setVisibility(VISIBLE); 609 } 610 611 @Override 612 public void onAnimationEnd(Animator animation) { 613 finish(dragLayer); 614 } 615 }); 616 617 if (originalView instanceof IconLabelDotView) { 618 IconLabelDotView view = (IconLabelDotView) originalView; 619 fade.addListener(new AnimatorListenerAdapter() { 620 @Override 621 public void onAnimationEnd(Animator animation) { 622 view.setIconVisible(true); 623 view.setForceHideDot(false); 624 } 625 }); 626 } 627 628 if (originalView instanceof BubbleTextView) { 629 BubbleTextView btv = (BubbleTextView) originalView; 630 fade.addListener(new AnimatorListenerAdapter() { 631 @Override 632 public void onAnimationStart(Animator animation) { 633 btv.setIconVisible(true); 634 btv.setForceHideDot(true); 635 } 636 }); 637 fade.play(ObjectAnimator.ofInt(btv.getIcon(), DRAWABLE_ALPHA, 0, 255)); 638 } else if (!(originalView instanceof FolderIcon)) { 639 fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f)); 640 } 641 642 return fade; 643 } 644 finish(DragLayer dragLayer)645 private void finish(DragLayer dragLayer) { 646 ((ViewGroup) dragLayer.getParent()).removeView(this); 647 dragLayer.removeView(mListenerView); 648 if (TestProtocol.sDebugTracing) { 649 Log.d(TestProtocol.PAUSE_NOT_DETECTED, "listenerView removed from dragLayer. " 650 + "listenerView=" + mListenerView + ", fiv=" + this, new Exception()); 651 } 652 recycle(); 653 mLauncher.getViewCache().recycleView(R.layout.floating_icon_view, this); 654 } 655 recycle()656 private void recycle() { 657 setTranslationX(0); 658 setTranslationY(0); 659 setScaleX(1); 660 setScaleY(1); 661 setAlpha(1); 662 if (mLoadIconSignal != null) { 663 mLoadIconSignal.cancel(); 664 } 665 mLoadIconSignal = null; 666 mEndRunnable = null; 667 mFinalDrawableBounds.setEmpty(); 668 if (mFadeAnimatorSet != null) { 669 mFadeAnimatorSet.cancel(); 670 } 671 mPositionOut = null; 672 mFadeAnimatorSet = null; 673 mListenerView.setListener(null); 674 mOriginalIcon = null; 675 mOnTargetChangeRunnable = null; 676 mBadge = null; 677 sTmpObjArray[0] = null; 678 mIconLoadResult = null; 679 mClipIconView.recycle(); 680 mFastFinishRunnable = null; 681 } 682 683 private static class IconLoadResult { 684 final ItemInfo itemInfo; 685 Drawable drawable; 686 Drawable badge; 687 int iconOffset; 688 Runnable onIconLoaded; 689 boolean isIconLoaded; 690 IconLoadResult(ItemInfo itemInfo)691 IconLoadResult(ItemInfo itemInfo) { 692 this.itemInfo = itemInfo; 693 } 694 } 695 } 696