| Index: remoting/android/java/src/org/chromium/chromoting/DesktopCanvas.java
|
| diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopCanvas.java b/remoting/android/java/src/org/chromium/chromoting/DesktopCanvas.java
|
| index e93364f249bf980843f5ad4556cb0376ba473eeb..cf8353b58a1fd9d41f70a6ead29d1b6d5b517d89 100644
|
| --- a/remoting/android/java/src/org/chromium/chromoting/DesktopCanvas.java
|
| +++ b/remoting/android/java/src/org/chromium/chromoting/DesktopCanvas.java
|
| @@ -8,67 +8,54 @@ import android.graphics.Matrix;
|
| import android.graphics.PointF;
|
| import android.graphics.Rect;
|
| import android.graphics.RectF;
|
| +import android.os.SystemClock;
|
| +import android.view.animation.DecelerateInterpolator;
|
| +import android.view.animation.Interpolator;
|
|
|
| /**
|
| * This class is responsible for transforming the desktop image matrix.
|
| */
|
| public class DesktopCanvas {
|
| - /**
|
| - * Maximum allowed zoom level - see {@link #scaleAndRepositionImage()}.
|
| - */
|
| - private static final float MAX_ZOOM_FACTOR = 100.0f;
|
| + /** Used for floating point comparisons. */
|
| + private static final float EPSILON = 0.0001f;
|
|
|
| - /**
|
| - * Used to smoothly reduce the amount of padding while the user is zooming.
|
| - */
|
| - private static final float PADDING_REDUCTION_FACTOR = 0.85f;
|
| + /** Maximum allowed zoom level - see {@link #scaleAndRepositionImage()}. */
|
| + private static final float MAX_ZOOM_FACTOR = 100.0f;
|
|
|
| private final RenderStub mRenderStub;
|
| private final RenderData mRenderData;
|
|
|
| /**
|
| - * Represents the actual center of the viewport in image space. This value needs to be a pair
|
| - * of floats so the desktop image can be positioned with sub-pixel accuracy for smoother panning
|
| - * animations at high zoom levels.
|
| - */
|
| - // TODO(joedow): See if we can collapse Viewport and Cursor position members. They were needed
|
| - // in the past due to how we calculated the center positions but may not be needed now.
|
| - private PointF mViewportPosition = new PointF();
|
| -
|
| - /**
|
| * Represents the desired center of the viewport in image space. This value may not represent
|
| * the actual center of the viewport as adjustments are made to ensure as much of the desktop is
|
| * visible as possible. This value needs to be a pair of floats so the desktop image can be
|
| * positioned with sub-pixel accuracy for smoother panning animations at high zoom levels.
|
| - * Note: The internal cursor position may be placed outside of the image boundary, however the
|
| - * cursor position we inject on the host side is restricted to the actual image dimensions.
|
| */
|
| - private PointF mCursorPosition = new PointF();
|
| + private PointF mDesiredCenter = new PointF();
|
|
|
| /**
|
| - * Represents the amount of space, in pixels, used by System UI on each edge of the screen.
|
| + * If System UI exists, this contains the area of the screen which is unobscured by it,
|
| + * otherwise it is empty.
|
| */
|
| - // TODO(joedow): Update usage of this member so it is a true Rect instead of a set of offsets.
|
| - private Rect mSystemUiScreenSize = new Rect();
|
| + private Rect mSystemUiScreenRect = new Rect();
|
|
|
| /**
|
| - * Represents the amount of padding, in screen pixels, added to each edge of the desktop image.
|
| - * This extra space allows the user to reveal portions of the desktop image which are obscured
|
| - * by System UI.
|
| + * Represents the amount of space, in pixels, to shift the image under the viewport. This value
|
| + * is used to allow panning the image further than would be possible when using the normal
|
| + * boundary values to account for System UI. This functionality ensures the user can view and
|
| + * interact with any area of the remote image, even when System UI might otherwise obscure it.
|
| */
|
| - private RectF mVisibleImagePadding = new RectF();
|
| + private PointF mViewportOffset = new PointF();
|
|
|
| /**
|
| - * Tracks whether to adjust the viewport to account for System UI. If false, the viewport
|
| - * center is the center of the screen. If true, then System UI offsets will be used to
|
| - * adjust the position of the viewport to ensure the cursor is visible.
|
| + * Tracks whether to adjust the size of the viewport to account for System UI. If false, the
|
| + * viewport center is mapped to the center of the screen. If true, then System UI sizes will be
|
| + * used to determine the center of the viewport.
|
| */
|
| - private boolean mAdjustViewportForSystemUi = false;
|
| + private boolean mAdjustViewportSizeForSystemUi = false;
|
|
|
| - /**
|
| - * Represents the amount of space, in pixels, to adjust the cursor center along the y-axis.
|
| - */
|
| - private float mCursorOffsetScreenY = 0.0f;
|
| + /* Used to perform per-frame rendering tasks. */
|
| + private Event.ParameterCallback<Boolean, Void> mFrameRenderedCallback;
|
|
|
| public DesktopCanvas(RenderStub renderStub, RenderData renderData) {
|
| mRenderStub = renderStub;
|
| @@ -76,52 +63,47 @@ public class DesktopCanvas {
|
| }
|
|
|
| /**
|
| - * Sets the desired center position of the viewport (a.k.a. the cursor position) and ensures
|
| - * the viewport is updated to include the cursor within it.
|
| + * Sets the desired center position of the viewport (a.k.a. the cursor position).
|
| *
|
| * @param newX The new x coordinate value for the desired center position.
|
| * @param newY The new y coordinate value for the desired center position.
|
| */
|
| public void setCursorPosition(float newX, float newY) {
|
| - // First set the cursor position since its potential values are a superset of the viewport.
|
| - mCursorPosition.set(newX, newY);
|
| - constrainPointToBounds(mCursorPosition, getImageBounds());
|
| -
|
| - // Now set the viewport position based on the cursor.
|
| - mViewportPosition.set(mCursorPosition);
|
| - constrainPointToBounds(mViewportPosition, getViewportBounds());
|
| -
|
| - repositionImage();
|
| + updateCursorPosition(newX, newY, getImageBounds());
|
| }
|
|
|
| /**
|
| - * Shifts the viewport by the passed in values (in image coordinates).
|
| + * Sets the center of the viewport using an absolute position (in image coordinates).
|
| *
|
| - * @param deltaX The distance (in image coordinates) to move the viewport along the x-axis.
|
| - * @param deltaY The distance (in image coordinates) to move the viewport along the y-axis.
|
| + * @param newX The new x coordinate value for the center position of the viewport.
|
| + * @param newY The new y coordinate value for the center position of the viewport.
|
| */
|
| - public void moveViewportCenter(float deltaX, float deltaY) {
|
| - // Offset and adjust the viewport center position to fit the screen.
|
| - mViewportPosition.offset(deltaX, deltaY);
|
| - constrainPointToBounds(mViewportPosition, getViewportBounds());
|
| -
|
| - // We don't need to constrain the cursor position as the viewport position is always within
|
| - // the bounds of the potential cursor positions.
|
| - mCursorPosition.set(mViewportPosition);
|
| -
|
| - repositionImage();
|
| + public void setViewportCenter(float newX, float newY) {
|
| + updateCursorPosition(newX, newY, getViewportBounds());
|
| }
|
|
|
| /**
|
| - * Shifts the cursor by the passed in values (in image coordinates) and adjusts the viewport.
|
| + * Shifts the cursor by the passed in values (in image coordinates).
|
| *
|
| * @param deltaX The distance (in image coordinates) to move the cursor along the x-axis.
|
| * @param deltaY The distance (in image coordinates) to move the cursor along the y-axis.
|
| * @return A point representing the new cursor position.
|
| */
|
| public PointF moveCursorPosition(float deltaX, float deltaY) {
|
| - setCursorPosition(mCursorPosition.x + deltaX, mCursorPosition.y + deltaY);
|
| - return new PointF(mCursorPosition.x, mCursorPosition.y);
|
| + updateCursorPosition(
|
| + mDesiredCenter.x + deltaX, mDesiredCenter.y + deltaY, getImageBounds());
|
| + return new PointF(mDesiredCenter.x, mDesiredCenter.y);
|
| + }
|
| +
|
| + /**
|
| + * Shifts the viewport by the passed in values (in image coordinates).
|
| + *
|
| + * @param deltaX The distance (in image coordinates) to move the viewport along the x-axis.
|
| + * @param deltaY The distance (in image coordinates) to move the viewport along the y-axis.
|
| + */
|
| + public void moveViewportCenter(float deltaX, float deltaY) {
|
| + updateCursorPosition(
|
| + mDesiredCenter.x + deltaX, mDesiredCenter.y + deltaY, getViewportBounds());
|
| }
|
|
|
| /**
|
| @@ -131,33 +113,23 @@ public class DesktopCanvas {
|
| */
|
| public void onSystemUiVisibilityChanged(SystemUiVisibilityChangedEventParameter parameter) {
|
| if (parameter.softInputMethodVisible) {
|
| - mSystemUiScreenSize.set(parameter.left, parameter.top,
|
| - mRenderData.screenWidth - parameter.right,
|
| - mRenderData.screenHeight - parameter.bottom);
|
| -
|
| - if (mAdjustViewportForSystemUi) {
|
| - // Adjust the cursor position to ensure it's visible when large System UI (1/3 or
|
| - // more of the total screen size) is displayed (typically the Soft Keyboard).
|
| - // Without this change, it is difficult for users to enter text into edit controls
|
| - // which are located bottom of the screen and may not see the cursor at all.
|
| - if (mSystemUiScreenSize.bottom > (mRenderData.screenHeight / 3)) {
|
| - // Center the cursor within the viewable area (not obscured by System UI).
|
| - mCursorOffsetScreenY = (float) parameter.bottom / 2.0f;
|
| - } else {
|
| - mCursorOffsetScreenY = 0.0f;
|
| - }
|
| + mSystemUiScreenRect.set(
|
| + parameter.left, parameter.top, parameter.right, parameter.bottom);
|
|
|
| - // Apply the cursor offset.
|
| - setCursorPosition(mCursorPosition.x, mCursorPosition.y);
|
| - }
|
| + stopOffsetReductionAnimation();
|
| } else {
|
| - mCursorOffsetScreenY = 0.0f;
|
| - mSystemUiScreenSize.setEmpty();
|
| + mSystemUiScreenRect.setEmpty();
|
| + startOffsetReductionAnimation();
|
| }
|
| +
|
| + repositionImage();
|
| }
|
|
|
| public void adjustViewportForSystemUi(boolean adjustViewportForSystemUi) {
|
| - mAdjustViewportForSystemUi = adjustViewportForSystemUi;
|
| + mAdjustViewportSizeForSystemUi = adjustViewportForSystemUi;
|
| +
|
| + // The viewport center may have changed so reposition the image to reflect the new value.
|
| + repositionImage();
|
| }
|
|
|
| /** Resizes the image by zooming it such that the image is displayed without borders. */
|
| @@ -180,7 +152,7 @@ public class DesktopCanvas {
|
|
|
| /**
|
| * Repositions the image by translating and zooming it, to keep the zoom level within sensible
|
| - * limits. The minimum zoom level is chosen to avoid black space around all 4 sides. The
|
| + * limits. The minimum zoom level is chosen to avoid letterboxing on all 4 sides. The
|
| * maximum zoom level is set arbitrarily, so that the user can zoom out again in a reasonable
|
| * time, and to prevent arithmetic overflow problems from displaying the image.
|
| *
|
| @@ -217,47 +189,75 @@ public class DesktopCanvas {
|
| mRenderData.transform.setScale(scale, scale);
|
| }
|
|
|
| - // Trim the image padding if the user is zooming out. This prevents cases where the image
|
| - // pops to the center when it reaches its minimum size. Note that we do not need to do
|
| - // anything when the user is zooming in as the canvas will expand and absorb the padding.
|
| - if (scaleFactor < 1.0f) {
|
| - mVisibleImagePadding.set(mVisibleImagePadding.left * PADDING_REDUCTION_FACTOR,
|
| - mVisibleImagePadding.top * PADDING_REDUCTION_FACTOR,
|
| - mVisibleImagePadding.right * PADDING_REDUCTION_FACTOR,
|
| - mVisibleImagePadding.bottom * PADDING_REDUCTION_FACTOR);
|
| - }
|
| -
|
| if (centerOnCursor) {
|
| - setCursorPosition(mCursorPosition.x, mCursorPosition.y);
|
| + setCursorPosition(mDesiredCenter.x, mDesiredCenter.y);
|
| } else {
|
| - // Find the new screen center (it was probably changed during the zoom operation) and
|
| - // update the viewport and cursor.
|
| - float[] mappedPoints = {
|
| - ((float) mRenderData.screenWidth / 2), ((float) mRenderData.screenHeight / 2)};
|
| + // Find the new screen center (it probably changed during the zoom operation) and update
|
| + // the viewport to smoothly track the zoom gesture.
|
| + float[] mappedPoints = {((float) mRenderData.screenWidth / 2) - mViewportOffset.x,
|
| + ((float) mRenderData.screenHeight / 2) - mViewportOffset.y};
|
| Matrix screenToImage = new Matrix();
|
| mRenderData.transform.invert(screenToImage);
|
| screenToImage.mapPoints(mappedPoints);
|
| // The cursor is mapped to the center of the viewport in this case.
|
| - setCursorPosition(mappedPoints[0], mappedPoints[1]);
|
| + setViewportCenter(mappedPoints[0], mappedPoints[1]);
|
| }
|
| }
|
|
|
| /**
|
| + * Sets the new cursor position, bounded by the given rect, and updates the image transform to
|
| + * reflect the new position.
|
| + */
|
| + private void updateCursorPosition(float newX, float newY, RectF bounds) {
|
| + mDesiredCenter.set(newX, newY);
|
| + constrainPointToBounds(mDesiredCenter, bounds);
|
| +
|
| + calculateViewportOffset(newX - mDesiredCenter.x, newY - mDesiredCenter.y);
|
| +
|
| + repositionImage();
|
| + }
|
| +
|
| + /**
|
| + * Returns the height of the screen (in screen coordinates) for use in calculations involving
|
| + * viewport positioning.
|
| + */
|
| + private float getAdjustedScreenHeight() {
|
| + float adjustedScreenHeight;
|
| + if (mAdjustViewportSizeForSystemUi && !mSystemUiScreenRect.isEmpty()) {
|
| + // Find the center point of the viewport on the screen.
|
| + adjustedScreenHeight = mSystemUiScreenRect.bottom;
|
| + } else {
|
| + adjustedScreenHeight = ((float) mRenderData.screenHeight);
|
| + }
|
| +
|
| + return adjustedScreenHeight;
|
| + }
|
| +
|
| + /**
|
| + * Returns the center position of the viewport (in screen coordinates) taking System UI into
|
| + * account.
|
| + */
|
| + private PointF getViewportScreenCenter() {
|
| + return new PointF((float) mRenderData.screenWidth / 2, getAdjustedScreenHeight() / 2);
|
| + }
|
| +
|
| + /**
|
| * Repositions the image by translating it (without affecting the zoom level).
|
| */
|
| private void repositionImage() {
|
| - // Map the current viewport position to screen coordinates and adjust the image position.
|
| - float[] viewportPosition = {mViewportPosition.x, mViewportPosition.y};
|
| - mRenderData.transform.mapPoints(viewportPosition);
|
| + PointF viewportPosition = new PointF(mDesiredCenter.x, mDesiredCenter.y);
|
| + constrainPointToBounds(viewportPosition, getViewportBounds());
|
| + float[] viewportAdjustment = {viewportPosition.x, viewportPosition.y};
|
| + mRenderData.transform.mapPoints(viewportAdjustment);
|
|
|
| - float viewportTransX = ((float) mRenderData.screenWidth / 2) - viewportPosition[0];
|
| - float viewportTransY =
|
| - ((float) mRenderData.screenHeight / 2) - viewportPosition[1] - mCursorOffsetScreenY;
|
| + // Adjust the viewport to include the overpan amount.
|
| + viewportAdjustment[0] += mViewportOffset.x;
|
| + viewportAdjustment[1] += mViewportOffset.y;
|
|
|
| // Translate the image to move the viewport to the expected screen location.
|
| - mRenderData.transform.postTranslate(viewportTransX, viewportTransY);
|
| -
|
| - updateVisibleImagePadding();
|
| + PointF viewportCenter = getViewportScreenCenter();
|
| + mRenderData.transform.postTranslate(
|
| + viewportCenter.x - viewportAdjustment[0], viewportCenter.y - viewportAdjustment[1]);
|
|
|
| mRenderStub.setTransformation(mRenderData.transform);
|
| }
|
| @@ -284,27 +284,7 @@ public class DesktopCanvas {
|
|
|
| /** Returns a region which defines the set of valid cursor positions in image space. */
|
| private RectF getImageBounds() {
|
| - // The set of valid cursor positions includes any point on the image as well as the padding.
|
| - // Padding is additional space added to the image which is the larger value of:
|
| - // - Potential overlap of the System UI and image content
|
| - // - Actual amount of padding already being used
|
| - //
|
| - // By expanding the area, we allow the user to move the cursor 'under' the System UI which
|
| - // pulls the content out from under it and allows it to be visible. Once the System UI has
|
| - // been dismissed or changes size, we use the actual padding value instead which prevents
|
| - // the desktop image from 'snapping' back to pre-System UI state.
|
| - RectF systemUiOverlap = getSystemUiOverlap();
|
| - float[] padding = {Math.max(mVisibleImagePadding.left, systemUiOverlap.left),
|
| - Math.max(mVisibleImagePadding.top + mCursorOffsetScreenY, systemUiOverlap.top),
|
| - Math.max(mVisibleImagePadding.right, systemUiOverlap.right),
|
| - Math.max(mVisibleImagePadding.bottom - mCursorOffsetScreenY,
|
| - systemUiOverlap.bottom)};
|
| - Matrix screenToImage = new Matrix();
|
| - mRenderData.transform.invert(screenToImage);
|
| - screenToImage.mapVectors(padding);
|
| -
|
| - return new RectF(-padding[0], -padding[1], mRenderData.imageWidth + padding[2],
|
| - mRenderData.imageHeight + padding[3]);
|
| + return new RectF(0, 0, mRenderData.imageWidth, mRenderData.imageHeight);
|
| }
|
|
|
| /** Returns a region which defines the set of valid viewport center values in image space. */
|
| @@ -315,7 +295,8 @@ public class DesktopCanvas {
|
| Matrix screenToImage = new Matrix();
|
| mRenderData.transform.invert(screenToImage);
|
|
|
| - float[] screenVectors = {(float) mRenderData.screenWidth, (float) mRenderData.screenHeight};
|
| + PointF viewportCenter = getViewportScreenCenter();
|
| + float[] screenVectors = {viewportCenter.x, viewportCenter.y};
|
| screenToImage.mapVectors(screenVectors);
|
|
|
| PointF letterboxPadding = getLetterboxPadding();
|
| @@ -323,14 +304,36 @@ public class DesktopCanvas {
|
| screenToImage.mapVectors(letterboxPaddingVectors);
|
|
|
| // screenCenter values are 1/2 of a particular screen dimension mapped to image space.
|
| - float screenCenterX = (screenVectors[0] / 2.0f) - letterboxPaddingVectors[0];
|
| - float screenCenterY = (screenVectors[1] / 2.0f) - letterboxPaddingVectors[1];
|
| + float screenCenterX = screenVectors[0] - letterboxPaddingVectors[0];
|
| + float screenCenterY = screenVectors[1] - letterboxPaddingVectors[1];
|
| RectF imageBounds = getImageBounds();
|
| imageBounds.inset(screenCenterX, screenCenterY);
|
| return imageBounds;
|
| }
|
|
|
| /**
|
| + * Returns a region defining the maximum offset distance required to view the entire image
|
| + * given the current amount of System UI overlapping it.
|
| + */
|
| + private RectF getViewportOffsetBounds() {
|
| + // The allowable region is determined by:
|
| + // - Overlap of the System UI and image content
|
| + // - Current viewport offset
|
| + //
|
| + // The System UI overlap represents the maximum allowable offset, this is used to bound the
|
| + // viewport movement in each direction. The current offset is used to prevent 'snapping'
|
| + // the image when the System UI overlap is reduced.
|
| + RectF viewportOffsetRect = new RectF();
|
| + viewportOffsetRect.union(mViewportOffset.x, mViewportOffset.y);
|
| +
|
| + RectF systemUiOverlap = getSystemUiOverlap();
|
| + return new RectF(Math.min(viewportOffsetRect.left, -systemUiOverlap.left),
|
| + Math.min(viewportOffsetRect.top, -systemUiOverlap.top),
|
| + Math.max(viewportOffsetRect.right, systemUiOverlap.right),
|
| + Math.max(viewportOffsetRect.bottom, systemUiOverlap.bottom));
|
| + }
|
| +
|
| + /**
|
| * Provides the amount of padding needed to center the image content on the screen.
|
| */
|
| private PointF getLetterboxPadding() {
|
| @@ -339,10 +342,8 @@ public class DesktopCanvas {
|
|
|
| // We want to letterbox when the image is smaller than the screen in a specific dimension.
|
| // Since we center the image, split the difference so it is equally distributed.
|
| - float widthAdjust =
|
| - Math.max(((float) mRenderData.screenWidth - imageVectors[0]) / 2.0f, 0.0f);
|
| - float heightAdjust =
|
| - Math.max(((float) mRenderData.screenHeight - imageVectors[1]) / 2.0f, 0.0f);
|
| + float widthAdjust = Math.max(((float) mRenderData.screenWidth - imageVectors[0]) / 2, 0);
|
| + float heightAdjust = Math.max((getAdjustedScreenHeight() - imageVectors[1]) / 2, 0);
|
|
|
| return new PointF(widthAdjust, heightAdjust);
|
| }
|
| @@ -352,29 +353,98 @@ public class DesktopCanvas {
|
| * desktop image below it. This is the maximum amount that could overlap, not the actual value.
|
| */
|
| private RectF getSystemUiOverlap() {
|
| - // letterBox padding represents the space added to the image to center it on the screen.
|
| + if (mSystemUiScreenRect.isEmpty()) {
|
| + return new RectF();
|
| + }
|
| +
|
| + // Letterbox padding represents the space added to the image to center it on the screen.
|
| // Since it does not contain any interactable UI, we ignore it when calculating the overlap
|
| // between the System UI and the remote desktop image.
|
| // Note: Ignore negative padding (clamp to 0) since that means no overlap exists.
|
| + float adjustedScreenHeight = getAdjustedScreenHeight();
|
| PointF letterboxPadding = getLetterboxPadding();
|
| - return new RectF(Math.max(mSystemUiScreenSize.left - letterboxPadding.x, 0.0f),
|
| - Math.max(mSystemUiScreenSize.top - letterboxPadding.y + mCursorOffsetScreenY, 0.0f),
|
| - Math.max(mSystemUiScreenSize.right - letterboxPadding.x, 0.0f),
|
| - Math.max(mSystemUiScreenSize.bottom - letterboxPadding.y - mCursorOffsetScreenY,
|
| + return new RectF(Math.max(mSystemUiScreenRect.left - letterboxPadding.x, 0.0f),
|
| + Math.max(mSystemUiScreenRect.top - letterboxPadding.y, 0.0f),
|
| + Math.max(mRenderData.screenWidth - mSystemUiScreenRect.right - letterboxPadding.x,
|
| + 0.0f),
|
| + Math.max(adjustedScreenHeight - mSystemUiScreenRect.bottom - letterboxPadding.y,
|
| 0.0f));
|
| }
|
|
|
| /**
|
| - * Calculates the amount of padding visible on each edge of the desktop image.
|
| + * Applies the given offset, as needed, to allow moving the image outside its normal bounds.
|
| */
|
| - private void updateVisibleImagePadding() {
|
| - PointF letterboxPadding = getLetterboxPadding();
|
| - float[] imagePoints = {0.0f, 0.0f, mRenderData.imageWidth, mRenderData.imageHeight};
|
| - mRenderData.transform.mapPoints(imagePoints);
|
| + private void calculateViewportOffset(float offsetX, float offsetY) {
|
| + if (mSystemUiScreenRect.isEmpty()) {
|
| + // We only want to directly change the viewport offset when System UI is present.
|
| + return;
|
| + }
|
| +
|
| + float[] offsets = {offsetX, offsetY};
|
| + mRenderData.transform.mapVectors(offsets);
|
|
|
| - mVisibleImagePadding.set(Math.max(imagePoints[0] - letterboxPadding.x, 0.0f),
|
| - Math.max(imagePoints[1] - letterboxPadding.y, 0.0f),
|
| - Math.max(mRenderData.screenWidth - imagePoints[2] - letterboxPadding.x, 0.0f),
|
| - Math.max(mRenderData.screenHeight - imagePoints[3] - letterboxPadding.y, 0.0f));
|
| + // Use a temporary variable here as {@link #getViewportOffsetBounds()} uses the current
|
| + // viewport offset as a maximum boundary. If we add the offset first, the result ends up
|
| + // being unbounded. Thus we use a temporary object for the boundary calculation.
|
| + PointF requestedOffset =
|
| + new PointF(mViewportOffset.x + offsets[0], mViewportOffset.y + offsets[1]);
|
| + constrainPointToBounds(requestedOffset, getViewportOffsetBounds());
|
| + mViewportOffset.set(requestedOffset);
|
| + }
|
| +
|
| + /**
|
| + * Starts an animation to smoothly reduce the viewport offset. Does nothing if an animation is
|
| + * already running or the offset is already 0.
|
| + */
|
| + private void startOffsetReductionAnimation() {
|
| + if (mFrameRenderedCallback != null || mViewportOffset.length() < EPSILON) {
|
| + return;
|
| + }
|
| +
|
| + mFrameRenderedCallback = new Event.ParameterCallback<Boolean, Void>() {
|
| + private static final float DURATION_MS = 250.0f;
|
| +
|
| + private final Interpolator mInterpolator = new DecelerateInterpolator();
|
| +
|
| + private long mStartTime = 0;
|
| + private float mOriginalX = 0.0f;
|
| + private float mOriginalY = 0.0f;
|
| +
|
| + @Override
|
| + public Boolean run(Void p) {
|
| + if (mFrameRenderedCallback == null) {
|
| + return false;
|
| + }
|
| +
|
| + if (mStartTime == 0) {
|
| + mStartTime = SystemClock.elapsedRealtime();
|
| + mOriginalX = mViewportOffset.x;
|
| + mOriginalY = mViewportOffset.y;
|
| + }
|
| +
|
| + float progress = (SystemClock.elapsedRealtime() - mStartTime) / DURATION_MS;
|
| + if (progress < 1.0f) {
|
| + float reductionFactor = 1.0f - mInterpolator.getInterpolation(progress);
|
| + mViewportOffset.set(mOriginalX * reductionFactor, mOriginalY * reductionFactor);
|
| + } else {
|
| + mViewportOffset.set(0.0f, 0.0f);
|
| + mFrameRenderedCallback = null;
|
| + }
|
| +
|
| + repositionImage();
|
| +
|
| + return mFrameRenderedCallback != null;
|
| + }
|
| + };
|
| +
|
| + mRenderStub.onCanvasRendered().addSelfRemovable(mFrameRenderedCallback);
|
| + }
|
| +
|
| + /**
|
| + * Stops an existing offset reduction animation.
|
| + */
|
| + private void stopOffsetReductionAnimation() {
|
| + // Setting this value this null will prevent it from continuing to execute.
|
| + mFrameRenderedCallback = null;
|
| }
|
| }
|
|
|