diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 0076c51326738..a773b2242eaab 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -202,7 +202,7 @@ #40000000 - #9444 + #6444 #F8F9FA diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java index 1ec175c01be7b..78ee896075703 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/CropView.java @@ -16,8 +16,6 @@ package com.android.systemui.screenshot; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; @@ -25,6 +23,7 @@ import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.RectF; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -32,6 +31,7 @@ import android.util.AttributeSet; import android.util.IntArray; import android.util.Log; import android.util.MathUtils; +import android.util.Range; import android.view.MotionEvent; import android.view.View; import android.view.accessibility.AccessibilityEvent; @@ -49,29 +49,30 @@ import com.android.systemui.R; */ public class CropView extends View { private static final String TAG = "CropView"; + public enum CropBoundary { - NONE, TOP, BOTTOM + NONE, TOP, BOTTOM, LEFT, RIGHT } private final float mCropTouchMargin; private final Paint mShadePaint; private final Paint mHandlePaint; - // Top and bottom crops are stored as floats [0, 1], representing the top and bottom of the - // view, respectively. - private float mTopCrop = 0f; - private float mBottomCrop = 1f; - - // When the user is dragging a handle, these variables store the distance between the top/bottom - // crop values and - private float mTopDelta = 0f; - private float mBottomDelta = 0f; + // Crop rect with each element represented as [0,1] along its proper axis. + private RectF mCrop = new RectF(0, 0, 1, 1); private int mExtraTopPadding; private int mExtraBottomPadding; + private int mImageWidth; private CropBoundary mCurrentDraggingBoundary = CropBoundary.NONE; + // The starting value of mCurrentDraggingBoundary's crop, used to compute touch deltas. + private float mMovementStartValue; private float mStartingY; // y coordinate of ACTION_DOWN + private float mStartingX; + // The allowable values for the current boundary being dragged + private Range mMotionRange; + private CropInteractionListener mCropInteractionListener; public CropView(Context context, @Nullable AttributeSet attrs) { @@ -86,6 +87,7 @@ public class CropView extends View { mShadePaint.setColor(t.getColor(R.styleable.CropView_scrimColor, Color.TRANSPARENT)); mHandlePaint = new Paint(); mHandlePaint.setColor(t.getColor(R.styleable.CropView_handleColor, Color.BLACK)); + mHandlePaint.setStrokeCap(Paint.Cap.ROUND); mHandlePaint.setStrokeWidth( t.getDimensionPixelSize(R.styleable.CropView_handleThickness, 20)); t.recycle(); @@ -100,8 +102,7 @@ public class CropView extends View { Parcelable superState = super.onSaveInstanceState(); SavedState ss = new SavedState(superState); - ss.mTopBoundary = getTopBoundary(); - ss.mBottomBoundary = getBottomBoundary(); + ss.mCrop = mCrop; return ss; } @@ -110,45 +111,67 @@ public class CropView extends View { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); - setBoundaryTo(CropBoundary.TOP, ss.mTopBoundary); - setBoundaryTo(CropBoundary.BOTTOM, ss.mBottomBoundary); + mCrop = ss.mCrop; } @Override public void onDraw(Canvas canvas) { super.onDraw(canvas); - float top = mTopCrop + mTopDelta; - float bottom = mBottomCrop + mBottomDelta; - drawShade(canvas, 0, top); - drawShade(canvas, bottom, 1f); - drawHandle(canvas, top, /* draw the handle tab down */ false); - drawHandle(canvas, bottom, /* draw the handle tab up */ true); + drawShade(canvas, 0, 0, 1, mCrop.top); + drawShade(canvas, 0, mCrop.bottom, 1, 1); + drawShade(canvas, 0, mCrop.top, mCrop.left, mCrop.bottom); + drawShade(canvas, mCrop.right, mCrop.top, 1, mCrop.bottom); + drawHorizontalHandle(canvas, mCrop.top, /* draw the handle tab up */ true); + drawHorizontalHandle(canvas, mCrop.bottom, /* draw the handle tab down */ false); + drawVerticalHandle(canvas, mCrop.left, /* left */ true); + drawVerticalHandle(canvas, mCrop.right, /* right */ false); } @Override public boolean onTouchEvent(MotionEvent event) { - int topPx = fractionToPixels(mTopCrop); - int bottomPx = fractionToPixels(mBottomCrop); + int topPx = fractionToVerticalPixels(mCrop.top); + int bottomPx = fractionToVerticalPixels(mCrop.bottom); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx); + mCurrentDraggingBoundary = nearestBoundary(event, topPx, bottomPx, + fractionToHorizontalPixels(mCrop.left), + fractionToHorizontalPixels(mCrop.right)); if (mCurrentDraggingBoundary != CropBoundary.NONE) { mStartingY = event.getY(); + mStartingX = event.getX(); + mMovementStartValue = getBoundaryPosition(mCurrentDraggingBoundary); updateListener(event); + switch (mCurrentDraggingBoundary) { + case TOP: + mMotionRange = new Range<>(0f, + mCrop.bottom - pixelDistanceToFraction(mCropTouchMargin, + CropBoundary.BOTTOM)); + break; + case BOTTOM: + mMotionRange = new Range<>( + mCrop.top + pixelDistanceToFraction(mCropTouchMargin, + CropBoundary.TOP), 1f); + break; + case LEFT: + mMotionRange = new Range<>(0f, + mCrop.right - pixelDistanceToFraction(mCropTouchMargin, + CropBoundary.RIGHT)); + break; + case RIGHT: + mMotionRange = new Range<>( + mCrop.left + pixelDistanceToFraction(mCropTouchMargin, + CropBoundary.LEFT), 1f); + break; + } } return true; case MotionEvent.ACTION_MOVE: if (mCurrentDraggingBoundary != CropBoundary.NONE) { - float delta = event.getY() - mStartingY; - if (mCurrentDraggingBoundary == CropBoundary.TOP) { - mTopDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta, - -topPx + mExtraTopPadding, - bottomPx - 2 * mCropTouchMargin - topPx)); - } else { // Bottom - mBottomDelta = pixelDistanceToFraction((int) MathUtils.constrain(delta, - topPx + 2 * mCropTouchMargin - bottomPx, - getHeight() - bottomPx - mExtraBottomPadding)); - } + float deltaPx = isVertical(mCurrentDraggingBoundary) ? event.getY() - mStartingY + : event.getX() - mStartingX; + float delta = pixelDistanceToFraction((int) deltaPx, mCurrentDraggingBoundary); + setBoundaryPosition(mCurrentDraggingBoundary, + mMotionRange.clamp(mMovementStartValue + delta)); updateListener(event); invalidate(); return true; @@ -156,8 +179,6 @@ public class CropView extends View { case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (mCurrentDraggingBoundary != CropBoundary.NONE) { - // Commit the delta to the stored crop values. - commitDeltas(mCurrentDraggingBoundary); updateListener(event); } } @@ -167,22 +188,46 @@ public class CropView extends View { /** * Set the given boundary to the given value without animation. */ - public void setBoundaryTo(CropBoundary boundary, float value) { + public void setBoundaryPosition(CropBoundary boundary, float position) { switch (boundary) { case TOP: - mTopCrop = value; + mCrop.top = position; break; case BOTTOM: - mBottomCrop = value; + mCrop.bottom = position; + break; + case LEFT: + mCrop.left = position; + break; + case RIGHT: + mCrop.right = position; break; case NONE: - Log.w(TAG, "No boundary selected for animation"); + Log.w(TAG, "No boundary selected"); break; } invalidate(); } + private float getBoundaryPosition(CropBoundary boundary) { + switch (boundary) { + case TOP: + return mCrop.top; + case BOTTOM: + return mCrop.bottom; + case LEFT: + return mCrop.left; + case RIGHT: + return mCrop.right; + } + return 0; + } + + private static boolean isVertical(CropBoundary boundary) { + return boundary == CropBoundary.TOP || boundary == CropBoundary.BOTTOM; + } + /** * Animate the given boundary to the given value. */ @@ -191,28 +236,13 @@ public class CropView extends View { Log.w(TAG, "No boundary selected for animation"); return; } - float totalDelta = (boundary == CropBoundary.TOP) ? (value - mTopCrop) - : (value - mBottomCrop); + float start = getBoundaryPosition(boundary); ValueAnimator animator = new ValueAnimator(); animator.addUpdateListener(animation -> { - if (boundary == CropBoundary.TOP) { - mTopDelta = animation.getAnimatedFraction() * totalDelta; - } else { - mBottomDelta = animation.getAnimatedFraction() * totalDelta; - } + setBoundaryPosition(boundary, + MathUtils.lerp(start, value, animation.getAnimatedFraction())); invalidate(); }); - animator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - commitDeltas(boundary); - } - - @Override - public void onAnimationCancel(Animator animation) { - commitDeltas(boundary); - } - }); animator.setFloatValues(0f, 1f); animator.setDuration(750); animator.setInterpolator(new FastOutSlowInInterpolator()); @@ -230,65 +260,79 @@ public class CropView extends View { } /** - * @return value [0,1] representing the position of the top crop boundary. Does not reflect - * changes from any in-progress touch input. + * Set the pixel width of the image on the screen (on-screen dimension, not actual bitmap + * dimension) */ - public float getTopBoundary() { - return mTopCrop; + public void setImageWidth(int width) { + mImageWidth = width; + invalidate(); } /** - * @return value [0,1] representing the position of the bottom crop boundary. Does not reflect - * changes from any in-progress touch input. + * @return RectF with values [0,1] representing the position of the boundaries along image axes. */ - public float getBottomBoundary() { - return mBottomCrop; + public Rect getCropBoundaries(int imageWidth, int imageHeight) { + return new Rect((int) (mCrop.left * imageWidth), (int) (mCrop.top * imageHeight), + (int) (mCrop.right * imageWidth), (int) (mCrop.bottom * imageHeight)); } public void setCropInteractionListener(CropInteractionListener listener) { mCropInteractionListener = listener; } - private void commitDeltas(CropBoundary boundary) { - if (boundary == CropBoundary.TOP) { - mTopCrop += mTopDelta; - mTopDelta = 0; - } else if (boundary == CropBoundary.BOTTOM) { - mBottomCrop += mBottomDelta; - mBottomDelta = 0; - } - } - private void updateListener(MotionEvent event) { - if (mCropInteractionListener != null) { - float boundaryPosition = (mCurrentDraggingBoundary == CropBoundary.TOP) - ? mTopCrop + mTopDelta : mBottomCrop + mBottomDelta; + if (mCropInteractionListener != null && (isVertical(mCurrentDraggingBoundary))) { + float boundaryPosition = getBoundaryPosition(mCurrentDraggingBoundary); mCropInteractionListener.onCropMotionEvent(event, mCurrentDraggingBoundary, - boundaryPosition, fractionToPixels(boundaryPosition)); + boundaryPosition, fractionToVerticalPixels(boundaryPosition), + (mCrop.left + mCrop.right) / 2); } } - private void drawShade(Canvas canvas, float fracStart, float fracEnd) { - canvas.drawRect(0, fractionToPixels(fracStart), getWidth(), - fractionToPixels(fracEnd), mShadePaint); + /** + * Draw a shade to the given canvas with the given [0,1] fractional image bounds. + */ + private void drawShade(Canvas canvas, float left, float top, float right, float bottom) { + canvas.drawRect(fractionToHorizontalPixels(left), fractionToVerticalPixels(top), + fractionToHorizontalPixels(right), + fractionToVerticalPixels(bottom), mShadePaint); } - private void drawHandle(Canvas canvas, float frac, boolean handleTabUp) { - int y = fractionToPixels(frac); - canvas.drawLine(0, y, getWidth(), y, mHandlePaint); - float radius = 15 * getResources().getDisplayMetrics().density; - float x = getWidth() * .9f; + private void drawHorizontalHandle(Canvas canvas, float frac, boolean handleTabUp) { + int y = fractionToVerticalPixels(frac); + canvas.drawLine(fractionToHorizontalPixels(mCrop.left), y, + fractionToHorizontalPixels(mCrop.right), y, mHandlePaint); + float radius = 8 * getResources().getDisplayMetrics().density; + int x = (fractionToHorizontalPixels(mCrop.left) + fractionToHorizontalPixels(mCrop.right)) + / 2; canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabUp ? 180 : 0, 180, true, mHandlePaint); } + private void drawVerticalHandle(Canvas canvas, float frac, boolean handleTabLeft) { + int x = fractionToHorizontalPixels(frac); + canvas.drawLine(x, fractionToVerticalPixels(mCrop.top), x, + fractionToVerticalPixels(mCrop.bottom), mHandlePaint); + float radius = 8 * getResources().getDisplayMetrics().density; + int y = (fractionToVerticalPixels(getBoundaryPosition(CropBoundary.TOP)) + + fractionToVerticalPixels( + getBoundaryPosition(CropBoundary.BOTTOM))) / 2; + canvas.drawArc(x - radius, y - radius, x + radius, y + radius, handleTabLeft ? 90 : 270, + 180, + true, mHandlePaint); + } + /** * Convert the given fraction position to pixel position within the View. */ - private int fractionToPixels(float frac) { + private int fractionToVerticalPixels(float frac) { return (int) (mExtraTopPadding + frac * getImageHeight()); } + private int fractionToHorizontalPixels(float frac) { + return (int) ((getWidth() - mImageWidth) / 2 + frac * mImageWidth); + } + private int getImageHeight() { return getHeight() - mExtraTopPadding - mExtraBottomPadding; } @@ -296,17 +340,30 @@ public class CropView extends View { /** * Convert the given pixel distance to fraction of the image. */ - private float pixelDistanceToFraction(int px) { - return px / (float) getImageHeight(); + private float pixelDistanceToFraction(float px, CropBoundary boundary) { + if (isVertical(boundary)) { + return px / getImageHeight(); + } else { + return px / mImageWidth; + } } - private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx) { + private CropBoundary nearestBoundary(MotionEvent event, int topPx, int bottomPx, int leftPx, + int rightPx) { if (Math.abs(event.getY() - topPx) < mCropTouchMargin) { return CropBoundary.TOP; } if (Math.abs(event.getY() - bottomPx) < mCropTouchMargin) { return CropBoundary.BOTTOM; } + if (event.getY() > topPx || event.getY() < bottomPx) { + if (Math.abs(event.getX() - leftPx) < mCropTouchMargin) { + return CropBoundary.LEFT; + } + if (Math.abs(event.getX() - rightPx) < mCropTouchMargin) { + return CropBoundary.RIGHT; + } + } return CropBoundary.NONE; } @@ -321,10 +378,10 @@ public class CropView extends View { @Override protected int getVirtualViewAt(float x, float y) { - if (Math.abs(y - fractionToPixels(mTopCrop)) < mCropTouchMargin) { + if (Math.abs(y - fractionToVerticalPixels(mCrop.top)) < mCropTouchMargin) { return TOP_HANDLE_ID; } - if (Math.abs(y - fractionToPixels(mBottomCrop)) < mCropTouchMargin) { + if (Math.abs(y - fractionToVerticalPixels(mCrop.bottom)) < mCropTouchMargin) { return BOTTOM_HANDLE_ID; } return ExploreByTouchHelper.INVALID_ID; @@ -338,7 +395,7 @@ public class CropView extends View { @Override protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) { - switch(virtualViewId) { + switch (virtualViewId) { case TOP_HANDLE_ID: event.setContentDescription( getResources().getString(R.string.screenshot_top_boundary)); @@ -353,16 +410,16 @@ public class CropView extends View { @Override protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfo node) { - switch(virtualViewId) { + switch (virtualViewId) { case TOP_HANDLE_ID: node.setContentDescription( getResources().getString(R.string.screenshot_top_boundary)); - setNodePositions(mTopCrop, node); + setNodePositions(mCrop.top, node); break; case BOTTOM_HANDLE_ID: node.setContentDescription( getResources().getString(R.string.screenshot_bottom_boundary)); - setNodePositions(mBottomCrop, node); + setNodePositions(mCrop.bottom, node); break; } @@ -380,7 +437,7 @@ public class CropView extends View { } private void setNodePositions(float fraction, AccessibilityNodeInfo node) { - int pixels = fractionToPixels(fraction); + int pixels = fractionToVerticalPixels(fraction); Rect rect = new Rect(0, (int) (pixels - mCropTouchMargin), getWidth(), (int) (pixels + mCropTouchMargin)); node.setBoundsInParent(rect); @@ -400,12 +457,11 @@ public class CropView extends View { * boundaries. */ void onCropMotionEvent(MotionEvent event, CropBoundary boundary, float boundaryPosition, - int boundaryPositionPx); + int boundaryPositionPx, float horizontalCenter); } static class SavedState extends BaseSavedState { - float mTopBoundary; - float mBottomBoundary; + RectF mCrop; /** * Constructor called from {@link CropView#onSaveInstanceState()} @@ -419,15 +475,13 @@ public class CropView extends View { */ private SavedState(Parcel in) { super(in); - mTopBoundary = in.readFloat(); - mBottomBoundary = in.readFloat(); + mCrop = in.readParcelable(ClassLoader.getSystemClassLoader()); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); - out.writeFloat(mTopBoundary); - out.writeFloat(mBottomBoundary); + out.writeParcelable(mCrop, 0); } public static final Parcelable.Creator CREATOR diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java index 31cdadab070d8..46091a68a591c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LongScreenshotActivity.java @@ -131,7 +131,7 @@ public class LongScreenshotActivity extends Activity { mPreview.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> - updateCropLocation()); + updateImageDimensions()); Intent intent = getIntent(); mScrollCaptureResponse = intent.getParcelableExtra(EXTRA_CAPTURE_RESPONSE); @@ -209,7 +209,7 @@ public class LongScreenshotActivity extends Activity { Log.d(TAG, "onCaptureCompleted(longScreenshot=" + longScreenshot + ")"); mLongScreenshot = longScreenshot; mPreview.setImageDrawable(mLongScreenshot.getDrawable()); - updateCropLocation(); + updateImageDimensions(); mMagnifierView.setDrawable(mLongScreenshot.getDrawable(), mLongScreenshot.getWidth(), mLongScreenshot.getHeight()); // Original boundaries go from the image tile set's y=0 to y=pageSize, so @@ -369,10 +369,8 @@ public class LongScreenshotActivity extends Activity { return; } - Rect bounds = new Rect(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); - int height = bounds.height(); - bounds.top = (int) (height * mCropView.getTopBoundary()); - bounds.bottom = (int) (height * mCropView.getBottomBoundary()); + Rect bounds = mCropView.getCropBoundaries(drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight()); if (bounds.isEmpty()) { Log.w(TAG, "Crop bounds empty, skipping export."); @@ -410,7 +408,7 @@ public class LongScreenshotActivity extends Activity { } } - private void updateCropLocation() { + private void updateImageDimensions() { Drawable drawable = mPreview.getDrawable(); if (drawable == null) { return; @@ -425,9 +423,12 @@ public class LongScreenshotActivity extends Activity { float imageHeight = mPreview.getHeight() * viewRatio / imageRatio; int extraPadding = (int) (mPreview.getHeight() - imageHeight) / 2; mCropView.setExtraPadding(extraPadding, extraPadding); + mCropView.setImageWidth(mPreview.getWidth()); } else { // Image is full height mCropView.setExtraPadding(0, 0); + mCropView.setImageWidth((int) (mPreview.getHeight() * imageRatio)); } + } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java index 90f304262ea19..08cd91ccada54 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/MagnifierView.java @@ -58,6 +58,7 @@ public class MagnifierView extends View implements CropView.CropInteractionListe private float mCheckerboardBoxSize = 40; private float mLastCropPosition; + private float mLastCenter = 0.5f; private CropView.CropBoundary mCropBoundary; private ViewPropertyAnimator mTranslationAnimator; @@ -131,7 +132,7 @@ public class MagnifierView extends View implements CropView.CropInteractionListe canvas.save(); // Translate such that the center of this view represents the center of the crop // boundary. - canvas.translate(-mDrawable.getBounds().width() / 2 + getWidth() / 2, + canvas.translate(-mDrawable.getBounds().width() * mLastCenter + getWidth() / 2, -mDrawable.getBounds().height() * mLastCropPosition + getHeight() / 2); mDrawable.draw(canvas); canvas.restore(); @@ -148,8 +149,9 @@ public class MagnifierView extends View implements CropView.CropInteractionListe @Override public void onCropMotionEvent(MotionEvent event, CropView.CropBoundary boundary, - float cropPosition, int cropPositionPx) { + float cropPosition, int cropPositionPx, float horizontalCenter) { mCropBoundary = boundary; + mLastCenter = horizontalCenter; boolean touchOnRight = event.getX() > getParentWidth() / 2; float translateXTarget = touchOnRight ? 0 : getParentWidth() - getWidth(); switch (event.getAction()) {