diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java index 0e082760ae..3cb6ba67f3 100644 --- a/src/com/android/launcher3/AbstractFloatingView.java +++ b/src/com/android/launcher3/AbstractFloatingView.java @@ -30,6 +30,7 @@ import android.util.AttributeSet; import android.util.Pair; import android.view.MotionEvent; import android.view.View; +import android.view.ViewGroup; import android.widget.LinearLayout; import androidx.annotation.IntDef; @@ -58,6 +59,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch TYPE_ON_BOARD_POPUP, TYPE_DISCOVERY_BOUNCE, TYPE_SNACKBAR, + TYPE_LISTENER, TYPE_TASK_MENU, TYPE_OPTIONS_POPUP @@ -72,15 +74,16 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch public static final int TYPE_ON_BOARD_POPUP = 1 << 5; public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6; public static final int TYPE_SNACKBAR = 1 << 7; + public static final int TYPE_LISTENER = 1 << 8; // Popups related to quickstep UI - public static final int TYPE_TASK_MENU = 1 << 8; - public static final int TYPE_OPTIONS_POPUP = 1 << 9; + public static final int TYPE_TASK_MENU = 1 << 9; + public static final int TYPE_OPTIONS_POPUP = 1 << 10; public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU - | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR; + | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER; // Type of popups which should be kept open during launcher rebind public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET @@ -90,7 +93,7 @@ public abstract class AbstractFloatingView extends LinearLayout implements Touch public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_SNACKBAR; - public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE; + public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER; // These view all have particular operation associated with swipe down interaction. public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET | diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java index f96652ebab..f2fc7182a5 100644 --- a/src/com/android/launcher3/views/FloatingIconView.java +++ b/src/com/android/launcher3/views/FloatingIconView.java @@ -70,7 +70,7 @@ import static com.android.launcher3.Utilities.mapToRange; public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView { public static final float SHAPE_PROGRESS_DURATION = 0.15f; - + private static final int FADE_DURATION_MS = 200; private static final Rect sTmpRect = new Rect(); private Runnable mEndRunnable; @@ -93,10 +93,15 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, private float mBgDrawableStartScale = 1f; private float mBgDrawableEndScale = 1f; + private AnimatorSet mFadeAnimatorSet; + private ListenerView mListenerView; + private FloatingIconView(Context context) { super(context); + mBlurSizeOutline = context.getResources().getDimensionPixelSize( R.dimen.blur_size_medium_outline); + mListenerView = new ListenerView(context, null); } /** @@ -138,6 +143,12 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, if (mRevealAnimator == null) { mRevealAnimator = (ValueAnimator) FolderShape.getShape().createRevealAnimator(this, mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, !isOpening); + mRevealAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mRevealAnimator = null; + } + }); mRevealAnimator.start(); // We pause here so we can set the current fraction ourselves. mRevealAnimator.pause(); @@ -314,7 +325,7 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, @WorkerThread private int getOffsetForIconBounds(Drawable drawable) { - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O || + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !(drawable instanceof AdaptiveIconDrawable)) { return 0; } @@ -364,6 +375,18 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, } } + public void onListenerViewClosed() { + // Fast finish here. + if (mEndRunnable != null) { + mEndRunnable.run(); + mEndRunnable = null; + } + if (mFadeAnimatorSet != null) { + mFadeAnimatorSet.end(); + mFadeAnimatorSet = null; + } + } + @Override public void onAnimationStart(Animator animator) {} @@ -410,54 +433,71 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, // We need to add it to the overlay, but keep it invisible until animation starts.. final DragLayer dragLayer = launcher.getDragLayer(); view.setVisibility(INVISIBLE); - ((ViewGroup) dragLayer.getParent()).getOverlay().add(view); + ((ViewGroup) dragLayer.getParent()).addView(view); + dragLayer.addView(view.mListenerView); + view.mListenerView.setListener(view::onListenerViewClosed); - if (hideOriginal) { - view.mEndRunnable = () -> { - AnimatorSet fade = new AnimatorSet(); - fade.setDuration(200); - fade.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - originalView.setVisibility(VISIBLE); - } + view.mEndRunnable = () -> { + view.mEndRunnable = null; - @Override - public void onAnimationEnd(Animator animation) { - ((ViewGroup) dragLayer.getParent()).getOverlay().remove(view); - - if (view.mRevealAnimator != null) { - view.mRevealAnimator.end(); - } - } - }); - - if (originalView instanceof FolderIcon) { - FolderIcon folderIcon = (FolderIcon) originalView; - folderIcon.setBackgroundVisible(false); - folderIcon.getFolderName().setTextVisibility(false); - fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true)); - fade.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - folderIcon.setBackgroundVisible(true); - folderIcon.animateBgShadowAndStroke(); - if (folderIcon.hasDot()) { - folderIcon.animateDotScale(0, 1f); - } - } - }); + if (hideOriginal) { + if (isOpening) { + originalView.setVisibility(VISIBLE); + view.finish(dragLayer); } else { - fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f)); + view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer); + view.mFadeAnimatorSet.start(); } - fade.start(); - // TODO: Do not run fade animation until we fix b/129421279. - fade.end(); - }; - } + } else { + view.finish(dragLayer); + } + }; return view; } + private AnimatorSet createFadeAnimation(View originalView, DragLayer dragLayer) { + AnimatorSet fade = new AnimatorSet(); + fade.setDuration(FADE_DURATION_MS); + fade.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + originalView.setVisibility(VISIBLE); + } + + @Override + public void onAnimationEnd(Animator animation) { + finish(dragLayer); + } + }); + + if (originalView instanceof FolderIcon) { + FolderIcon folderIcon = (FolderIcon) originalView; + folderIcon.setBackgroundVisible(false); + folderIcon.getFolderName().setTextVisibility(false); + fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true)); + fade.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + folderIcon.setBackgroundVisible(true); + folderIcon.animateBgShadowAndStroke(); + if (folderIcon.hasDot()) { + folderIcon.animateDotScale(0, 1f); + } + } + }); + } else { + fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f)); + } + + return fade; + } + + private void finish(DragLayer dragLayer) { + ((ViewGroup) dragLayer.getParent()).removeView(this); + dragLayer.removeView(mListenerView); + recycle(); + } + private void recycle() { setTranslationX(0); setTranslationY(0); @@ -475,10 +515,15 @@ public class FloatingIconView extends View implements Animator.AnimatorListener, mBackground = null; mClipPath = null; mFinalDrawableBounds.setEmpty(); - mBgDrawableBounds.setEmpty();; + mBgDrawableBounds.setEmpty(); if (mRevealAnimator != null) { mRevealAnimator.cancel(); } mRevealAnimator = null; + if (mFadeAnimatorSet != null) { + mFadeAnimatorSet.cancel(); + } + mFadeAnimatorSet = null; + mListenerView.setListener(null); } } diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java new file mode 100644 index 0000000000..263f7c4c32 --- /dev/null +++ b/src/com/android/launcher3/views/ListenerView.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.views; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.android.launcher3.AbstractFloatingView; + +/** + * An invisible AbstractFloatingView that can run a callback when it is being closed. + */ +public class ListenerView extends AbstractFloatingView { + + public Runnable mCloseListener; + + public ListenerView(Context context, AttributeSet attrs) { + super(context, attrs); + setVisibility(View.GONE); + } + + public void setListener(Runnable listener) { + mCloseListener = listener; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mIsOpen = true; + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mIsOpen = false; + } + + @Override + protected void handleClose(boolean animate) { + if (mIsOpen) { + if (mCloseListener != null) { + mCloseListener.run(); + } else { + if (getParent() instanceof ViewGroup) { + ((ViewGroup) getParent()).removeView(this); + } + } + } + mIsOpen = false; + } + + @Override + public void logActionCommand(int command) { + // Users do not interact with FloatingIconView, so there is nothing to log here. + } + + @Override + protected boolean isOfType(int type) { + return (type & TYPE_LISTENER) != 0; + } + + @Override + public boolean onControllerInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + handleClose(false); + } + // We want other views to be able to intercept the touch so we return false here. + return false; + } +}