Merge "Add dismiss animation for screenshot UI" into rvc-dev

This commit is contained in:
Miranda Kephart
2020-04-30 14:41:02 +00:00
committed by Android (Google) Code Review
2 changed files with 90 additions and 22 deletions

View File

@@ -326,6 +326,7 @@
<dimen name="screenshot_action_chip_padding_middle">8dp</dimen> <dimen name="screenshot_action_chip_padding_middle">8dp</dimen>
<dimen name="screenshot_action_chip_padding_end">16dp</dimen> <dimen name="screenshot_action_chip_padding_end">16dp</dimen>
<dimen name="screenshot_action_chip_text_size">14sp</dimen> <dimen name="screenshot_action_chip_text_size">14sp</dimen>
<dimen name="screenshot_dismissal_height_delta">80dp</dimen>
<!-- The width of the view containing navigation buttons --> <!-- The width of the view containing navigation buttons -->

View File

@@ -68,6 +68,7 @@ import android.view.ViewGroup;
import android.view.ViewOutlineProvider; import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator; import android.view.animation.Interpolator;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@@ -165,11 +166,16 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234; private static final long SCREENSHOT_TO_CORNER_SCALE_DURATION_MS = 234;
private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400; private static final long SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS = 400;
private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100; private static final long SCREENSHOT_ACTIONS_ALPHA_DURATION_MS = 100;
private static final long SCREENSHOT_DISMISS_Y_DURATION_MS = 350;
private static final long SCREENSHOT_DISMISS_ALPHA_DURATION_MS = 183;
private static final long SCREENSHOT_DISMISS_ALPHA_OFFSET_MS = 50; // delay before starting fade
private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f; private static final float SCREENSHOT_ACTIONS_START_SCALE_X = .7f;
private static final float ROUNDED_CORNER_RADIUS = .05f; private static final float ROUNDED_CORNER_RADIUS = .05f;
private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000; private static final long SCREENSHOT_CORNER_TIMEOUT_MILLIS = 6000;
private static final int MESSAGE_CORNER_TIMEOUT = 2; private static final int MESSAGE_CORNER_TIMEOUT = 2;
private final Interpolator mAccelerateInterpolator = new AccelerateInterpolator();
private final ScreenshotNotificationsController mNotificationsController; private final ScreenshotNotificationsController mNotificationsController;
private final UiEventLogger mUiEventLogger; private final UiEventLogger mUiEventLogger;
@@ -195,12 +201,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
private Animator mScreenshotAnimation; private Animator mScreenshotAnimation;
private Runnable mOnCompleteRunnable; private Runnable mOnCompleteRunnable;
private boolean mInDarkMode = false; private boolean mInDarkMode = false;
private Animator mDismissAnimation;
private float mScreenshotOffsetXPx; private float mScreenshotOffsetXPx;
private float mScreenshotOffsetYPx; private float mScreenshotOffsetYPx;
private float mScreenshotHeightPx; private float mScreenshotHeightPx;
private float mDismissButtonSize; private float mDismissButtonSize;
private float mCornerSizeX; private float mCornerSizeX;
private float mDismissDeltaY;
private MediaActionSound mCameraSound; private MediaActionSound mCameraSound;
@@ -213,7 +221,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
switch (msg.what) { switch (msg.what) {
case MESSAGE_CORNER_TIMEOUT: case MESSAGE_CORNER_TIMEOUT:
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_INTERACTION_TIMEOUT);
GlobalScreenshot.this.clearScreenshot("timeout"); GlobalScreenshot.this.dismissScreenshot("timeout", false);
mOnCompleteRunnable.run(); mOnCompleteRunnable.run();
break; break;
default: default:
@@ -255,7 +263,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button); mDismissButton = mScreenshotLayout.findViewById(R.id.global_screenshot_dismiss_button);
mDismissButton.setOnClickListener(view -> { mDismissButton.setOnClickListener(view -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
clearScreenshot("dismiss_button"); dismissScreenshot("dismiss_button", false);
mOnCompleteRunnable.run(); mOnCompleteRunnable.run();
}); });
mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image); mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image);
@@ -294,6 +302,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mDismissButtonSize = resources.getDimensionPixelSize( mDismissButtonSize = resources.getDimensionPixelSize(
R.dimen.screenshot_dismiss_button_tappable_size); R.dimen.screenshot_dismiss_button_tappable_size);
mCornerSizeX = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale); mCornerSizeX = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale);
mDismissDeltaY = resources.getDimensionPixelSize(R.dimen.screenshot_dismissal_height_delta);
mFastOutSlowIn = mFastOutSlowIn =
AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in); AnimationUtils.loadInterpolator(mContext, android.R.interpolator.fast_out_slow_in);
@@ -354,6 +363,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
} }
private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) { private void takeScreenshot(Bitmap screenshot, Consumer<Uri> finisher, Rect screenRect) {
dismissScreenshot("new screenshot requested", true);
mScreenBitmap = screenshot; mScreenBitmap = screenshot;
if (mScreenBitmap == null) { if (mScreenBitmap == null) {
@@ -373,12 +384,15 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this); mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
if (mDismissAnimation != null && mDismissAnimation.isRunning()) {
mDismissAnimation.cancel();
}
// Start the post-screenshot animation // Start the post-screenshot animation
startAnimation(finisher, screenRect.width(), screenRect.height(), screenRect); startAnimation(finisher, screenRect.width(), screenRect.height(), screenRect);
} }
void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) { void takeScreenshot(Consumer<Uri> finisher, Runnable onComplete) {
clearScreenshot("new screenshot requested"); dismissScreenshot("new screenshot requested", true);
mOnCompleteRunnable = onComplete; mOnCompleteRunnable = onComplete;
mDisplay.getRealMetrics(mDisplayMetrics); mDisplay.getRealMetrics(mDisplayMetrics);
@@ -390,9 +404,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds, void handleImageAsScreenshot(Bitmap screenshot, Rect screenshotScreenBounds,
Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) { Insets visibleInsets, int taskId, Consumer<Uri> finisher, Runnable onComplete) {
// TODO use taskId and visibleInsets // TODO use taskId and visibleInsets
clearScreenshot("new screenshot requested"); dismissScreenshot("new screenshot requested", true);
mOnCompleteRunnable = onComplete; mOnCompleteRunnable = onComplete;
takeScreenshot(screenshot, finisher, screenshotScreenBounds); takeScreenshot(screenshot, finisher, screenshotScreenBounds);
} }
@@ -401,7 +414,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
*/ */
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) { void takeScreenshotPartial(final Consumer<Uri> finisher, Runnable onComplete) {
clearScreenshot("new screenshot requested"); dismissScreenshot("new screenshot requested", true);
mOnCompleteRunnable = onComplete; mOnCompleteRunnable = onComplete;
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams); mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
@@ -454,8 +467,24 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
/** /**
* Clears current screenshot * Clears current screenshot
*/ */
private void clearScreenshot(String reason) { private void dismissScreenshot(String reason, boolean immediate) {
Log.v(TAG, "clearing screenshot: " + reason); Log.v(TAG, "clearing screenshot: " + reason);
if (!immediate) {
mDismissAnimation = createScreenshotDismissAnimation();
mDismissAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
clearScreenshot();
}
});
mDismissAnimation.start();
} else {
clearScreenshot();
}
}
private void clearScreenshot() {
if (mScreenshotLayout.isAttachedToWindow()) { if (mScreenshotLayout.isAttachedToWindow()) {
mWindowManager.removeView(mScreenshotLayout); mWindowManager.removeView(mScreenshotLayout);
} }
@@ -472,10 +501,15 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
mScreenshotView.setContentDescription( mScreenshotView.setContentDescription(
mContext.getResources().getString(R.string.screenshot_preview_description)); mContext.getResources().getString(R.string.screenshot_preview_description));
mScreenshotLayout.setAlpha(1);
mDismissButton.setTranslationY(0);
mActionsContainer.setTranslationY(0);
mScreenshotView.setTranslationY(0);
} }
/** /**
* Update assets (called when the dark theme status changes). We only need to update the dismiss * Update assets (called when the dark theme status changes). We only need to update the
* dismiss
* button and the actions container background, since the buttons are re-inflated on demand. * button and the actions container background, since the buttons are re-inflated on demand.
*/ */
private void reloadAssets() { private void reloadAssets() {
@@ -514,13 +548,17 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
*/ */
private void startAnimation(final Consumer<Uri> finisher, int w, int h, private void startAnimation(final Consumer<Uri> finisher, int w, int h,
@Nullable Rect screenRect) { @Nullable Rect screenRect) {
// If power save is on, show a toast so there is some visual indication that a screenshot // If power save is on, show a toast so there is some visual indication that a
// screenshot
// has been taken. // has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); PowerManager powerManager = (PowerManager) mContext.getSystemService(
Context.POWER_SERVICE);
if (powerManager.isPowerSaveMode()) { if (powerManager.isPowerSaveMode()) {
Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show(); Toast.makeText(mContext, R.string.screenshot_saved_title,
Toast.LENGTH_SHORT).show();
} }
// Add the view for the animation // Add the view for the animation
mScreenshotView.setImageBitmap(mScreenBitmap); mScreenshotView.setImageBitmap(mScreenBitmap);
@@ -550,12 +588,12 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
} else { } else {
createScreenshotActionsShadeAnimation(imageData).start(); createScreenshotActionsShadeAnimation(imageData).start();
} }
mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
mScreenshotHandler.sendMessageDelayed(
mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
SCREENSHOT_CORNER_TIMEOUT_MILLIS);
}); });
} }
mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
mScreenshotHandler.sendMessageDelayed(
mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
SCREENSHOT_CORNER_TIMEOUT_MILLIS);
} }
}); });
mScreenshotHandler.post(() -> { mScreenshotHandler.post(() -> {
@@ -586,14 +624,16 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
final PointF startPos = new PointF(bounds.centerX(), bounds.centerY()); final PointF startPos = new PointF(bounds.centerX(), bounds.centerY());
final PointF finalPos = new PointF(mScreenshotOffsetXPx + width * cornerScale / 2f, final PointF finalPos = new PointF(mScreenshotOffsetXPx + width * cornerScale / 2f,
mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f); mDisplayMetrics.heightPixels - mScreenshotOffsetYPx
- height * cornerScale / 2f);
ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1); ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1);
toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS); toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS);
float xPositionPct = float xPositionPct =
SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; SCREENSHOT_TO_CORNER_X_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
float scalePct = float scalePct =
SCREENSHOT_TO_CORNER_SCALE_DURATION_MS / (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS; SCREENSHOT_TO_CORNER_SCALE_DURATION_MS
/ (float) SCREENSHOT_TO_CORNER_Y_DURATION_MS;
toCorner.addUpdateListener(animation -> { toCorner.addUpdateListener(animation -> {
float t = animation.getAnimatedFraction(); float t = animation.getAnimatedFraction();
if (t < scalePct) { if (t < scalePct) {
@@ -676,7 +716,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
actionChip.setPendingIntent(smartAction.actionIntent, actionChip.setPendingIntent(smartAction.actionIntent,
() -> { () -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED);
clearScreenshot("chip tapped"); dismissScreenshot("chip tapped", false);
mOnCompleteRunnable.run(); mOnCompleteRunnable.run();
}); });
mActionsView.addView(actionChip); mActionsView.addView(actionChip);
@@ -689,7 +729,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
shareChip.setIcon(imageData.shareAction.getIcon(), true); shareChip.setIcon(imageData.shareAction.getIcon(), true);
shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> { shareChip.setPendingIntent(imageData.shareAction.actionIntent, () -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED);
clearScreenshot("chip tapped"); dismissScreenshot("chip tapped", false);
mOnCompleteRunnable.run(); mOnCompleteRunnable.run();
}); });
mActionsView.addView(shareChip); mActionsView.addView(shareChip);
@@ -701,7 +741,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
editChip.setIcon(imageData.editAction.getIcon(), true); editChip.setIcon(imageData.editAction.getIcon(), true);
editChip.setPendingIntent(imageData.editAction.actionIntent, () -> { editChip.setPendingIntent(imageData.editAction.actionIntent, () -> {
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED);
clearScreenshot("chip tapped"); dismissScreenshot("chip tapped", false);
mOnCompleteRunnable.run(); mOnCompleteRunnable.run();
}); });
mActionsView.addView(editChip); mActionsView.addView(editChip);
@@ -711,7 +751,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
try { try {
imageData.editAction.actionIntent.send(); imageData.editAction.actionIntent.send();
mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED);
clearScreenshot("screenshot preview tapped"); dismissScreenshot("screenshot preview tapped", false);
mOnCompleteRunnable.run(); mOnCompleteRunnable.run();
} catch (PendingIntent.CanceledException e) { } catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Intent cancelled", e); Log.e(TAG, "Intent cancelled", e);
@@ -757,6 +797,32 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
return animator; return animator;
} }
private AnimatorSet createScreenshotDismissAnimation() {
ValueAnimator alphaAnim = ValueAnimator.ofFloat(0, 1);
alphaAnim.setStartDelay(SCREENSHOT_DISMISS_ALPHA_OFFSET_MS);
alphaAnim.setDuration(SCREENSHOT_DISMISS_ALPHA_DURATION_MS);
alphaAnim.addUpdateListener(animation -> {
mScreenshotLayout.setAlpha(1 - animation.getAnimatedFraction());
});
ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1);
yAnim.setInterpolator(mAccelerateInterpolator);
yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS);
float screenshotStartY = mScreenshotView.getTranslationY();
float dismissStartY = mDismissButton.getTranslationY();
yAnim.addUpdateListener(animation -> {
float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction());
mScreenshotView.setTranslationY(screenshotStartY + yDelta);
mDismissButton.setTranslationY(dismissStartY + yDelta);
mActionsContainer.setTranslationY(yDelta);
});
AnimatorSet animSet = new AnimatorSet();
animSet.play(yAnim).with(alphaAnim);
return animSet;
}
/** /**
* Receiver to proxy the share or edit intent, used to clean up the notification and send * Receiver to proxy the share or edit intent, used to clean up the notification and send
* appropriate signals to the system (ie. to dismiss the keyguard if necessary). * appropriate signals to the system (ie. to dismiss the keyguard if necessary).
@@ -802,7 +868,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset
} }
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) { if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
String actionType = Intent.ACTION_EDIT.equals(intent.getAction()) ? ACTION_TYPE_EDIT String actionType = Intent.ACTION_EDIT.equals(intent.getAction())
? ACTION_TYPE_EDIT
: ACTION_TYPE_SHARE; : ACTION_TYPE_SHARE;
ScreenshotSmartActions.notifyScreenshotAction( ScreenshotSmartActions.notifyScreenshotAction(
context, intent.getStringExtra(EXTRA_ID), actionType, false); context, intent.getStringExtra(EXTRA_ID), actionType, false);