From 9dd850f43d8f3e07cc7c692351d827ac06e1fbe3 Mon Sep 17 00:00:00 2001 From: Miranda Kephart Date: Wed, 29 Apr 2020 13:54:03 -0400 Subject: [PATCH] Use ConstraintLayout for screenshot UI There are some problems with the screenshot UI layout, especially in RTL (where the dismiss button disappears). Using constraints rather than setting the position of the button directly fixes this problem, and should make updates to the appearance of the UI in landscape simpler. Bug: 151814483 Fix: 151814483 Test: manual -- made sure that the animation and final UI state looked correct in both LTR and RTL layouts Change-Id: Ia8d21c028279bc6b70a1b338bea979f2ce2808bc --- .../SystemUI/res/layout/global_screenshot.xml | 88 ++++++---- .../layout/global_screenshot_action_chip.xml | 4 +- packages/SystemUI/res/values/dimens.xml | 1 + .../systemui/screenshot/GlobalScreenshot.java | 153 +++++++++++------- 4 files changed, 154 insertions(+), 92 deletions(-) diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml index 94a6bc566e73e..de19303b4948d 100644 --- a/packages/SystemUI/res/layout/global_screenshot.xml +++ b/packages/SystemUI/res/layout/global_screenshot.xml @@ -14,60 +14,89 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - + + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent"/> + + app:layout_constraintHorizontal_bias="0" + app:layout_constraintWidth_percent="1.0" + app:layout_constraintWidth_max="wrap" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview" + app:layout_constraintEnd_toEndOf="parent"> + + android:contentDescription="@string/screenshot_preview_description" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent"/> + android:contentDescription="@string/screenshot_dismiss_ui_description" + app:layout_constraintStart_toEndOf="@+id/global_screenshot_preview" + app:layout_constraintEnd_toEndOf="@+id/global_screenshot_preview" + app:layout_constraintTop_toTopOf="@+id/global_screenshot_preview" + app:layout_constraintBottom_toTopOf="@+id/global_screenshot_preview"> + android:src="@android:color/white"/> - - + diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml index bd91ddb5f406a..e4ae7c1f58272 100644 --- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml +++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml @@ -19,11 +19,11 @@ android:id="@+id/global_screenshot_action_chip" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginRight="@dimen/screenshot_action_chip_margin_right" + android:layout_marginEnd="@dimen/screenshot_action_chip_margin_right" android:layout_gravity="center" android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical" android:background="@drawable/action_chip_background" - android:alpha="0" + android:alpha="0.0" android:gravity="center"> 20dp 20dp + 400dp 80dp 6dp 48dp diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java index 581422116c8f8..414828953778d 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java @@ -17,7 +17,6 @@ package com.android.systemui.screenshot; import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI; -import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED; @@ -72,7 +71,6 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; -import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; @@ -187,10 +185,11 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private final View mScreenshotLayout; private final ScreenshotSelectorView mScreenshotSelectorView; - private final ImageView mBackgroundView; - private final ImageView mScreenshotView; + private final ImageView mScreenshotAnimatedView; + private final ImageView mScreenshotPreview; private final ImageView mScreenshotFlash; - private final HorizontalScrollView mActionsContainer; + private final ImageView mActionsContainerBackground; + private final FrameLayout mActionsContainer; private final LinearLayout mActionsView; private final ImageView mBackgroundProtection; private final FrameLayout mDismissButton; @@ -205,8 +204,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private float mScreenshotOffsetXPx; private float mScreenshotOffsetYPx; - private float mScreenshotHeightPx; - private float mDismissButtonSize; private float mCornerSizeX; private float mDismissDeltaY; @@ -244,10 +241,19 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset // Inflate the screenshot layout mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot, null); - mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_background); - mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot); - mScreenshotView.setClipToOutline(true); - mScreenshotView.setOutlineProvider(new ViewOutlineProvider() { + mScreenshotAnimatedView = + mScreenshotLayout.findViewById(R.id.global_screenshot_animated_view); + mScreenshotAnimatedView.setClipToOutline(true); + mScreenshotAnimatedView.setOutlineProvider(new ViewOutlineProvider() { + @Override + public void getOutline(View view, Outline outline) { + outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()), + ROUNDED_CORNER_RADIUS * view.getWidth()); + } + }); + mScreenshotPreview = mScreenshotLayout.findViewById(R.id.global_screenshot_preview); + mScreenshotPreview.setClipToOutline(true); + mScreenshotPreview.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setRoundRect(new Rect(0, 0, view.getWidth(), view.getHeight()), @@ -255,6 +261,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } }); + mActionsContainerBackground = mScreenshotLayout.findViewById( + R.id.global_screenshot_actions_container_background); mActionsContainer = mScreenshotLayout.findViewById( R.id.global_screenshot_actions_container); mActionsView = mScreenshotLayout.findViewById(R.id.global_screenshot_actions); @@ -273,9 +281,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotLayout.setFocusable(true); mScreenshotSelectorView.setFocusable(true); mScreenshotSelectorView.setFocusableInTouchMode(true); - mScreenshotView.setPivotX(0); - mScreenshotView.setPivotY(0); - mActionsContainer.setPivotX(0); + mScreenshotAnimatedView.setPivotX(0); + mScreenshotAnimatedView.setPivotY(0); // Setup the window that we are going to use mWindowLayoutParams = new WindowManager.LayoutParams( @@ -297,10 +304,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotOffsetXPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_x); mScreenshotOffsetYPx = resources.getDimensionPixelSize(R.dimen.screenshot_offset_y); - mScreenshotHeightPx = - resources.getDimensionPixelSize(R.dimen.screenshot_action_container_offset_y); - mDismissButtonSize = resources.getDimensionPixelSize( - R.dimen.screenshot_dismiss_button_tappable_size); mCornerSizeX = resources.getDimensionPixelSize(R.dimen.global_screenshot_x_scale); mDismissDeltaY = resources.getDimensionPixelSize(R.dimen.screenshot_dismissal_height_delta); @@ -318,7 +321,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Region touchRegion = new Region(); Rect screenshotRect = new Rect(); - mScreenshotView.getBoundsOnScreen(screenshotRect); + mScreenshotPreview.getBoundsOnScreen(screenshotRect); touchRegion.op(screenshotRect, Region.Op.UNION); Rect actionsRect = new Rect(); mActionsContainer.getBoundsOnScreen(actionsRect); @@ -469,6 +472,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset */ private void dismissScreenshot(String reason, boolean immediate) { Log.v(TAG, "clearing screenshot: " + reason); + mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); + mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); if (!immediate) { mDismissAnimation = createScreenshotDismissAnimation(); mDismissAnimation.addListener(new AnimatorListenerAdapter() { @@ -488,23 +493,23 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (mScreenshotLayout.isAttachedToWindow()) { mWindowManager.removeView(mScreenshotLayout); } - mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT); - mScreenshotLayout.getViewTreeObserver().removeOnComputeInternalInsetsListener(this); // Clear any references to the bitmap - mScreenshotView.setImageBitmap(null); + mScreenshotPreview.setImageBitmap(null); + mScreenshotAnimatedView.setImageBitmap(null); + mActionsContainerBackground.setVisibility(View.GONE); mActionsContainer.setVisibility(View.GONE); - mBackgroundView.setVisibility(View.GONE); mBackgroundProtection.setAlpha(0f); mDismissButton.setVisibility(View.GONE); - mScreenshotView.setVisibility(View.GONE); - mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null); - mScreenshotView.setContentDescription( + mScreenshotPreview.setVisibility(View.GONE); + mScreenshotPreview.setLayerType(View.LAYER_TYPE_NONE, null); + mScreenshotPreview.setContentDescription( mContext.getResources().getString(R.string.screenshot_preview_description)); mScreenshotLayout.setAlpha(1); mDismissButton.setTranslationY(0); mActionsContainer.setTranslationY(0); - mScreenshotView.setTranslationY(0); + mActionsContainerBackground.setTranslationY(0); + mScreenshotPreview.setTranslationY(0); } /** @@ -514,9 +519,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset */ private void reloadAssets() { mDismissImage.setImageDrawable(mContext.getDrawable(R.drawable.screenshot_cancel)); - mActionsContainer.setBackground( + mActionsContainerBackground.setBackground( mContext.getDrawable(R.drawable.action_chip_container_background)); - } /** @@ -558,10 +562,6 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Toast.LENGTH_SHORT).show(); } - - // Add the view for the animation - mScreenshotView.setImageBitmap(mScreenBitmap); - mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect); saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() { @@ -600,8 +600,8 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset // Play the shutter sound to notify that we've taken a screenshot mCameraSound.play(MediaActionSound.SHUTTER_CLICK); - mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null); - mScreenshotView.buildLayer(); + mScreenshotPreview.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mScreenshotPreview.buildLayer(); mScreenshotAnimation.start(); }); } @@ -609,6 +609,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset private AnimatorSet createScreenshotDropInAnimation(int width, int height, Rect bounds) { float cornerScale = mCornerSizeX / (float) width; + mScreenshotAnimatedView.setScaleX(1); + mScreenshotAnimatedView.setScaleY(1); + mScreenshotAnimatedView.setX(0); + mScreenshotAnimatedView.setY(0); + + mScreenshotAnimatedView.setImageBitmap(mScreenBitmap); + mScreenshotPreview.setImageBitmap(mScreenBitmap); + AnimatorSet dropInAnimation = new AnimatorSet(); ValueAnimator flashInAnimator = ValueAnimator.ofFloat(0, 1); flashInAnimator.setDuration(SCREENSHOT_FLASH_IN_DURATION_MS); @@ -623,9 +631,16 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mScreenshotFlash.setAlpha((float) animation.getAnimatedValue())); final PointF startPos = new PointF(bounds.centerX(), bounds.centerY()); - final PointF finalPos = new PointF(mScreenshotOffsetXPx + width * cornerScale / 2f, - mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - - height * cornerScale / 2f); + float finalX; + if (mContext.getResources().getConfiguration().getLayoutDirection() + == View.LAYOUT_DIRECTION_LTR) { + finalX = mScreenshotOffsetXPx + width * cornerScale / 2f; + } else { + finalX = width - mScreenshotOffsetXPx - width * cornerScale / 2f; + } + float finalY = + mDisplayMetrics.heightPixels - mScreenshotOffsetYPx - height * cornerScale / 2f; + final PointF finalPos = new PointF(finalX, finalY); ValueAnimator toCorner = ValueAnimator.ofFloat(0, 1); toCorner.setDuration(SCREENSHOT_TO_CORNER_Y_DURATION_MS); @@ -639,30 +654,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset if (t < scalePct) { float scale = MathUtils.lerp( 1, cornerScale, mFastOutSlowIn.getInterpolation(t / scalePct)); - mScreenshotView.setScaleX(scale); - mScreenshotView.setScaleY(scale); + mScreenshotAnimatedView.setScaleX(scale); + mScreenshotAnimatedView.setScaleY(scale); } else { - mScreenshotView.setScaleX(cornerScale); - mScreenshotView.setScaleY(cornerScale); + mScreenshotAnimatedView.setScaleX(cornerScale); + mScreenshotAnimatedView.setScaleY(cornerScale); } + float currentScaleX = mScreenshotAnimatedView.getScaleX(); + float currentScaleY = mScreenshotAnimatedView.getScaleY(); + if (t < xPositionPct) { float xCenter = MathUtils.lerp(startPos.x, finalPos.x, mFastOutSlowIn.getInterpolation(t / xPositionPct)); - mScreenshotView.setX(xCenter - width * mScreenshotView.getScaleX() / 2f); + mScreenshotAnimatedView.setX(xCenter - width * currentScaleX / 2f); } else { - mScreenshotView.setX(finalPos.x - width * mScreenshotView.getScaleX() / 2f); + mScreenshotAnimatedView.setX(finalPos.x - width * currentScaleX / 2f); } float yCenter = MathUtils.lerp(startPos.y, finalPos.y, mFastOutSlowIn.getInterpolation(t)); - mScreenshotView.setY(yCenter - height * mScreenshotView.getScaleY() / 2f); + mScreenshotAnimatedView.setY(yCenter - height * currentScaleY / 2f); }); toCorner.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { super.onAnimationStart(animation); - mScreenshotView.setVisibility(View.VISIBLE); + mScreenshotAnimatedView.setVisibility(View.VISIBLE); } }); @@ -676,15 +694,16 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - mScreenshotView.setScaleX(cornerScale); - mScreenshotView.setScaleY(cornerScale); - mScreenshotView.setX(finalPos.x - width * cornerScale / 2f); - mScreenshotView.setY(finalPos.y - height * cornerScale / 2f); + mScreenshotAnimatedView.setScaleX(1); + mScreenshotAnimatedView.setScaleY(1); + mScreenshotAnimatedView.setX(finalPos.x - width * cornerScale / 2f); + mScreenshotAnimatedView.setY(finalPos.y - height * cornerScale / 2f); Rect bounds = new Rect(); - mScreenshotView.getBoundsOnScreen(bounds); - mDismissButton.setX(bounds.right - mDismissButtonSize / 2f); - mDismissButton.setY(bounds.top - mDismissButtonSize / 2f); + mDismissButton.getBoundsOnScreen(bounds); + mScreenshotAnimatedView.setVisibility(View.GONE); + mScreenshotPreview.setVisibility(View.VISIBLE); mDismissButton.setVisibility(View.VISIBLE); + mScreenshotLayout.forceLayout(); } }); @@ -747,7 +766,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset mActionsView.addView(editChip); chips.add(editChip); - mScreenshotView.setOnClickListener(v -> { + mScreenshotPreview.setOnClickListener(v -> { try { imageData.editAction.actionIntent.send(); mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED); @@ -757,7 +776,7 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset Log.e(TAG, "Intent cancelled", e); } }); - mScreenshotView.setContentDescription(imageData.editAction.title); + mScreenshotPreview.setContentDescription(imageData.editAction.title); if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) { ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate( @@ -775,20 +794,33 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset chips.add(scrollChip); } + // remove the margin from the last chip so that it's correctly aligned with the end + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) + mActionsView.getChildAt(mActionsView.getChildCount() - 1).getLayoutParams(); + params.setMarginEnd(0); + ValueAnimator animator = ValueAnimator.ofFloat(0, 1); animator.setDuration(SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS); float alphaFraction = (float) SCREENSHOT_ACTIONS_ALPHA_DURATION_MS / SCREENSHOT_ACTIONS_EXPANSION_DURATION_MS; - mActionsContainer.setVisibility(VISIBLE); - mActionsContainer.setAlpha(0); + mActionsContainer.setVisibility(View.VISIBLE); + mActionsContainer.setAlpha(0f); + mActionsContainerBackground.setAlpha(0f); + mActionsContainerBackground.setVisibility(View.VISIBLE); + + mActionsContainer.setPivotX(0); + mActionsContainerBackground.setPivotX(0); animator.addUpdateListener(animation -> { float t = animation.getAnimatedFraction(); mBackgroundProtection.setAlpha(t); - mActionsContainer.setAlpha(t < alphaFraction ? t / alphaFraction : 1); + float containerAlpha = t < alphaFraction ? t / alphaFraction : 1; + mActionsContainer.setAlpha(containerAlpha); + mActionsContainerBackground.setAlpha(containerAlpha); float containerScale = SCREENSHOT_ACTIONS_START_SCALE_X + (t * (1 - SCREENSHOT_ACTIONS_START_SCALE_X)); mActionsContainer.setScaleX(containerScale); + mActionsContainerBackground.setScaleX(containerScale); for (ScreenshotActionChip chip : chips) { chip.setAlpha(t); chip.setScaleX(1 / containerScale); // invert to keep size of children constant @@ -808,13 +840,14 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset ValueAnimator yAnim = ValueAnimator.ofFloat(0, 1); yAnim.setInterpolator(mAccelerateInterpolator); yAnim.setDuration(SCREENSHOT_DISMISS_Y_DURATION_MS); - float screenshotStartY = mScreenshotView.getTranslationY(); + float screenshotStartY = mScreenshotPreview.getTranslationY(); float dismissStartY = mDismissButton.getTranslationY(); yAnim.addUpdateListener(animation -> { float yDelta = MathUtils.lerp(0, mDismissDeltaY, animation.getAnimatedFraction()); - mScreenshotView.setTranslationY(screenshotStartY + yDelta); + mScreenshotPreview.setTranslationY(screenshotStartY + yDelta); mDismissButton.setTranslationY(dismissStartY + yDelta); mActionsContainer.setTranslationY(yDelta); + mActionsContainerBackground.setTranslationY(yDelta); }); AnimatorSet animSet = new AnimatorSet();