Add ListenerView so that we can fast finish the FloatingIconView fade anim.
This change allows us to quickly end the animation if the user begins interacting with launcher before the animation is over. This can currently happen when the user swipes up to go home, and then quickly swipes up again to enter overview. Bug: 129421279 Change-Id: I88c7d55ef8ac09f999c082317de3bb3693c11466
This commit is contained in:
@@ -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 |
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user