Merge "Implement "shared element" SS->LSS transition in screenshots" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
070852ed44
@@ -32,8 +32,6 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.transition.Transition;
|
||||
import android.transition.TransitionListenerAdapter;
|
||||
import android.util.Log;
|
||||
import android.view.ScrollCaptureResponse;
|
||||
import android.view.View;
|
||||
@@ -74,7 +72,7 @@ public class LongScreenshotActivity extends Activity {
|
||||
private final Executor mUiExecutor;
|
||||
private final Executor mBackgroundExecutor;
|
||||
private final ImageExporter mImageExporter;
|
||||
private final LongScreenshotHolder mLongScreenshotHolder;
|
||||
private final LongScreenshotData mLongScreenshotHolder;
|
||||
|
||||
private ImageView mPreview;
|
||||
private ImageView mTransitionView;
|
||||
@@ -103,7 +101,7 @@ public class LongScreenshotActivity extends Activity {
|
||||
@Inject
|
||||
public LongScreenshotActivity(UiEventLogger uiEventLogger, ImageExporter imageExporter,
|
||||
@Main Executor mainExecutor, @Background Executor bgExecutor,
|
||||
LongScreenshotHolder longScreenshotHolder) {
|
||||
LongScreenshotData longScreenshotHolder) {
|
||||
mUiEventLogger = uiEventLogger;
|
||||
mUiExecutor = mainExecutor;
|
||||
mBackgroundExecutor = bgExecutor;
|
||||
@@ -116,7 +114,6 @@ public class LongScreenshotActivity extends Activity {
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.d(TAG, "onCreate(savedInstanceState = " + savedInstanceState + ")");
|
||||
super.onCreate(savedInstanceState);
|
||||
postponeEnterTransition();
|
||||
setContentView(R.layout.long_screenshot);
|
||||
|
||||
mPreview = requireViewById(R.id.preview);
|
||||
@@ -210,25 +207,22 @@ public class LongScreenshotActivity extends Activity {
|
||||
public boolean onPreDraw() {
|
||||
mEnterTransitionView.getViewTreeObserver().removeOnPreDrawListener(this);
|
||||
updateImageDimensions();
|
||||
startPostponedEnterTransition();
|
||||
if (isActivityTransitionRunning()) {
|
||||
getWindow().getSharedElementEnterTransition().addListener(
|
||||
new TransitionListenerAdapter() {
|
||||
@Override
|
||||
public void onTransitionEnd(Transition transition) {
|
||||
super.onTransitionEnd(transition);
|
||||
mPreview.animate().alpha(1f);
|
||||
mCropView.setBoundaryPosition(
|
||||
CropView.CropBoundary.TOP, topFraction);
|
||||
mCropView.setBoundaryPosition(
|
||||
CropView.CropBoundary.BOTTOM, bottomFraction);
|
||||
mCropView.animateEntrance();
|
||||
mCropView.setVisibility(View.VISIBLE);
|
||||
setButtonsEnabled(true);
|
||||
mEnterTransitionView.setVisibility(View.GONE);
|
||||
}
|
||||
mEnterTransitionView.post(() -> {
|
||||
Rect dest = new Rect();
|
||||
mEnterTransitionView.getBoundsOnScreen(dest);
|
||||
mLongScreenshotHolder.takeTransitionDestinationCallback()
|
||||
.setTransitionDestination(dest, () -> {
|
||||
mPreview.animate().alpha(1f);
|
||||
mCropView.setBoundaryPosition(
|
||||
CropView.CropBoundary.TOP, topFraction);
|
||||
mCropView.setBoundaryPosition(
|
||||
CropView.CropBoundary.BOTTOM, bottomFraction);
|
||||
mCropView.animateEntrance();
|
||||
mCropView.setVisibility(View.VISIBLE);
|
||||
setButtonsEnabled(true);
|
||||
mEnterTransitionView.setVisibility(View.GONE);
|
||||
});
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -23,16 +23,19 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* LongScreenshotHolder holds on to 1 LongScreenshot reference to facilitate indirect in-process
|
||||
* passing.
|
||||
* LongScreenshotData holds on to 1 LongScreenshot reference and 1 TransitionDestination
|
||||
* reference, to facilitate indirect in-process passing.
|
||||
*/
|
||||
@SysUISingleton
|
||||
public class LongScreenshotHolder {
|
||||
public class LongScreenshotData {
|
||||
private final AtomicReference<ScrollCaptureController.LongScreenshot> mLongScreenshot;
|
||||
private final AtomicReference<ScreenshotController.TransitionDestination>
|
||||
mTransitionDestinationCallback;
|
||||
|
||||
@Inject
|
||||
public LongScreenshotHolder() {
|
||||
public LongScreenshotData() {
|
||||
mLongScreenshot = new AtomicReference<>();
|
||||
mTransitionDestinationCallback = new AtomicReference<>();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -56,4 +59,18 @@ public class LongScreenshotHolder {
|
||||
return mLongScreenshot.getAndSet(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the holder's TransitionDestination callback.
|
||||
*/
|
||||
public void setTransitionDestinationCallback(
|
||||
ScreenshotController.TransitionDestination destination) {
|
||||
mTransitionDestinationCallback.set(destination);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current TransitionDestination callback and clear.
|
||||
*/
|
||||
public ScreenshotController.TransitionDestination takeTransitionDestinationCallback() {
|
||||
return mTransitionDestinationCallback.getAndSet(null);
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,7 @@ import android.view.IRemoteAnimationFinishedCallback;
|
||||
import android.view.IRemoteAnimationRunner;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.RemoteAnimationAdapter;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.ScrollCaptureResponse;
|
||||
import android.view.SurfaceControl;
|
||||
@@ -72,6 +73,7 @@ import android.view.ViewTreeObserver;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.widget.Toast;
|
||||
@@ -203,6 +205,15 @@ public class ScreenshotController {
|
||||
void onActionsReady(ScreenshotController.QuickShareData quickShareData);
|
||||
}
|
||||
|
||||
interface TransitionDestination {
|
||||
/**
|
||||
* Allows the long screenshot activity to call back with a destination location (the bounds
|
||||
* on screen of the destination for the transitioning view) and a Runnable to be run once
|
||||
* the transition animation is complete.
|
||||
*/
|
||||
void setTransitionDestination(Rect transitionDestination, Runnable onTransitionEnd);
|
||||
}
|
||||
|
||||
// These strings are used for communicating the action invoked to
|
||||
// ScreenshotNotificationSmartActionsProvider.
|
||||
static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
|
||||
@@ -241,7 +252,7 @@ public class ScreenshotController {
|
||||
private final PhoneWindow mWindow;
|
||||
private final DisplayManager mDisplayManager;
|
||||
private final ScrollCaptureController mScrollCaptureController;
|
||||
private final LongScreenshotHolder mLongScreenshotHolder;
|
||||
private final LongScreenshotData mLongScreenshotHolder;
|
||||
|
||||
private ScreenshotView mScreenshotView;
|
||||
private Bitmap mScreenBitmap;
|
||||
@@ -286,7 +297,7 @@ public class ScreenshotController {
|
||||
ImageExporter imageExporter,
|
||||
@Main Executor mainExecutor,
|
||||
ScrollCaptureController scrollCaptureController,
|
||||
LongScreenshotHolder longScreenshotHolder) {
|
||||
LongScreenshotData longScreenshotHolder) {
|
||||
mScreenshotSmartActions = screenshotSmartActions;
|
||||
mNotificationsController = screenshotNotificationsController;
|
||||
mScrollCaptureClient = scrollCaptureClient;
|
||||
@@ -648,17 +659,29 @@ public class ScreenshotController {
|
||||
}
|
||||
|
||||
mLongScreenshotHolder.setLongScreenshot(longScreenshot);
|
||||
mLongScreenshotHolder.setTransitionDestinationCallback(
|
||||
(transitionDestination, onTransitionEnd) ->
|
||||
mScreenshotView.startLongScreenshotTransition(
|
||||
transitionDestination, onTransitionEnd,
|
||||
longScreenshot));
|
||||
|
||||
final Intent intent = new Intent(mContext, LongScreenshotActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
||||
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
|
||||
ActivityOptions.startSharedElementAnimation(
|
||||
mWindow, new ScreenshotExitTransitionCallbacks(), null,
|
||||
Pair.create(mScreenshotView.getScrollablePreview(),
|
||||
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
|
||||
ActivityOptions.startSharedElementAnimation(mWindow,
|
||||
new ScreenshotExitTransitionCallbacksSupplier(false).get(),
|
||||
null);
|
||||
transition.second.startExit();
|
||||
mContext.startActivity(intent, transition.first.toBundle());
|
||||
RemoteAnimationAdapter runner = new RemoteAnimationAdapter(
|
||||
SCREENSHOT_REMOTE_RUNNER, 0, 0);
|
||||
try {
|
||||
WindowManagerGlobal.getWindowManagerService()
|
||||
.overridePendingAppTransitionRemote(runner, DEFAULT_DISPLAY);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error overriding screenshot app transition", e);
|
||||
}
|
||||
}, mMainExecutor);
|
||||
});
|
||||
} catch (CancellationException e) {
|
||||
@@ -883,8 +906,8 @@ public class ScreenshotController {
|
||||
return () -> {
|
||||
Pair<ActivityOptions, ExitTransitionCoordinator> transition =
|
||||
ActivityOptions.startSharedElementAnimation(
|
||||
mWindow, new ScreenshotExitTransitionCallbacks(), null,
|
||||
Pair.create(mScreenshotView.getScreenshotPreview(),
|
||||
mWindow, new ScreenshotExitTransitionCallbacksSupplier(true).get(),
|
||||
null, Pair.create(mScreenshotView.getScreenshotPreview(),
|
||||
ChooserActivity.FIRST_IMAGE_PREVIEW_TRANSITION_NAME));
|
||||
transition.second.startExit();
|
||||
|
||||
@@ -970,19 +993,33 @@ public class ScreenshotController {
|
||||
return matchWithinTolerance;
|
||||
}
|
||||
|
||||
private class ScreenshotExitTransitionCallbacks implements ExitTransitionCallbacks {
|
||||
@Override
|
||||
public boolean isReturnTransitionAllowed() {
|
||||
return false;
|
||||
private class ScreenshotExitTransitionCallbacksSupplier implements
|
||||
Supplier<ExitTransitionCallbacks> {
|
||||
final boolean mDismissOnHideSharedElements;
|
||||
|
||||
ScreenshotExitTransitionCallbacksSupplier(boolean dismissOnHideSharedElements) {
|
||||
mDismissOnHideSharedElements = dismissOnHideSharedElements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideSharedElements() {
|
||||
finishDismiss();
|
||||
}
|
||||
public ExitTransitionCallbacks get() {
|
||||
return new ExitTransitionCallbacks() {
|
||||
@Override
|
||||
public boolean isReturnTransitionAllowed() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
@Override
|
||||
public void hideSharedElements() {
|
||||
if (mDismissOnHideSharedElements) {
|
||||
finishDismiss();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFinish() {
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,7 +764,53 @@ public class ScreenshotView extends FrameLayout implements
|
||||
return r;
|
||||
}
|
||||
|
||||
void startLongScreenshotTransition(Rect destination, Runnable onTransitionEnd,
|
||||
ScrollCaptureController.LongScreenshot longScreenshot) {
|
||||
mScrollablePreview.setImageBitmap(longScreenshot.toBitmap());
|
||||
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
|
||||
float startX = mScrollablePreview.getX();
|
||||
float startY = mScrollablePreview.getY();
|
||||
int[] locInScreen = mScrollablePreview.getLocationOnScreen();
|
||||
destination.offset((int) startX - locInScreen[0], (int) startY - locInScreen[1]);
|
||||
mScrollablePreview.setPivotX(0);
|
||||
mScrollablePreview.setPivotY(0);
|
||||
mScrollablePreview.setAlpha(1f);
|
||||
float currentScale = mScrollablePreview.getWidth() / (float) longScreenshot.getWidth();
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setScale(currentScale, currentScale);
|
||||
matrix.postTranslate(
|
||||
longScreenshot.getLeft() * currentScale, longScreenshot.getTop() * currentScale);
|
||||
mScrollablePreview.setImageMatrix(matrix);
|
||||
float destinationScale = destination.width() / (float) mScrollablePreview.getWidth();
|
||||
anim.addUpdateListener(animation -> {
|
||||
float t = animation.getAnimatedFraction();
|
||||
mScrollingScrim.setAlpha(1 - t);
|
||||
float currScale = MathUtils.lerp(1, destinationScale, t);
|
||||
mScrollablePreview.setScaleX(currScale);
|
||||
mScrollablePreview.setScaleY(currScale);
|
||||
mScrollablePreview.setX(MathUtils.lerp(startX, destination.left, t));
|
||||
mScrollablePreview.setY(MathUtils.lerp(startY, destination.top, t));
|
||||
});
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
onTransitionEnd.run();
|
||||
mScrollablePreview.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
mCallbacks.onDismiss();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
anim.start();
|
||||
}
|
||||
|
||||
void prepareScrollingTransition(ScrollCaptureResponse response, Bitmap screenBitmap) {
|
||||
mScrollingScrim.setImageBitmap(screenBitmap);
|
||||
mScrollingScrim.setVisibility(View.VISIBLE);
|
||||
Rect scrollableArea = scrollableAreaOnScreen(response);
|
||||
float scale = mCornerSizeX
|
||||
/ (mOrientationPortrait ? screenBitmap.getWidth() : screenBitmap.getHeight());
|
||||
@@ -775,14 +821,12 @@ public class ScreenshotView extends FrameLayout implements
|
||||
params.height = (int) (scale * scrollableArea.height());
|
||||
Matrix matrix = new Matrix();
|
||||
matrix.setScale(scale, scale);
|
||||
matrix.postTranslate(0, -scrollableArea.top * scale);
|
||||
matrix.postTranslate(-scrollableArea.left * scale, -scrollableArea.top * scale);
|
||||
|
||||
mScrollablePreview.setTranslationX(scale * scrollableArea.left);
|
||||
mScrollablePreview.setTranslationY(scale * scrollableArea.top);
|
||||
mScrollablePreview.setImageMatrix(matrix);
|
||||
|
||||
mScrollingScrim.setImageBitmap(screenBitmap);
|
||||
mScrollingScrim.setVisibility(View.VISIBLE);
|
||||
mScrollablePreview.setImageBitmap(screenBitmap);
|
||||
mScrollablePreview.setVisibility(View.VISIBLE);
|
||||
createScreenshotFadeDismissAnimation(true).start();
|
||||
|
||||
Reference in New Issue
Block a user