diff --git a/api/current.txt b/api/current.txt index 7e30e857eec5f..a713ecfb29f7a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -53398,6 +53398,9 @@ package android.widget { public final class Magnifier { ctor public Magnifier(android.view.View); method public void dismiss(); + method public int getHeight(); + method public int getWidth(); + method public float getZoom(); method public void show(float, float); method public void update(); } diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index fe49c02d91452..27dd39b3224f8 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -37,6 +37,7 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; @@ -4648,10 +4649,14 @@ public class Editor { return 0; } - protected final void showMagnifier(@NonNull final MotionEvent event) { - if (mMagnifier == null) { - return; - } + /** + * Computes the position where the magnifier should be shown, relative to + * {@code mTextView}, and writes them to {@code showPosInView}. Also decides + * whether the magnifier should be shown or dismissed after this touch event. + * @return Whether the magnifier should be shown at the computed coordinates or dismissed. + */ + private boolean obtainMagnifierShowCoordinates(@NonNull final MotionEvent event, + final PointF showPosInView) { final int trigger = getMagnifierHandleTrigger(); final int offset; @@ -4669,26 +4674,52 @@ public class Editor { } if (offset == -1) { - dismissMagnifier(); + return false; } final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); - // Horizontally move the magnifier smoothly. + + // Horizontally move the magnifier smoothly but clamp inside the current line. final int[] textViewLocationOnScreen = new int[2]; mTextView.getLocationOnScreen(textViewLocationOnScreen); - final float xPosInView = event.getRawX() - textViewLocationOnScreen[0]; + final float touchXInView = event.getRawX() - textViewLocationOnScreen[0]; + final float lineLeft = mTextView.getLayout().getLineLeft(lineNumber) + + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + final float lineRight = mTextView.getLayout().getLineRight(lineNumber) + + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + final float contentWidth = Math.round(mMagnifier.getWidth() / mMagnifier.getZoom()); + if (touchXInView < lineLeft - contentWidth / 2 + || touchXInView > lineRight + contentWidth / 2) { + // The touch is too out of the bounds of the current line, so hide the magnifier. + return false; + } + showPosInView.x = Math.max(lineLeft, Math.min(lineRight, touchXInView)); + // Vertically snap to middle of current line. - final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + showPosInView.y = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f + mTextView.getTotalPaddingTop() - mTextView.getScrollY(); - // Make the cursor visible and stop blinking. - mRenderCursorRegardlessTiming = true; - mTextView.invalidateCursorPath(); - suspendBlink(); + return true; + } - mMagnifier.show(xPosInView, yPosInView); + protected final void updateMagnifier(@NonNull final MotionEvent event) { + if (mMagnifier == null) { + return; + } + + final PointF showPosInView = new PointF(); + final boolean shouldShow = obtainMagnifierShowCoordinates(event, showPosInView); + if (shouldShow) { + // Make the cursor visible and stop blinking. + mRenderCursorRegardlessTiming = true; + mTextView.invalidateCursorPath(); + suspendBlink(); + mMagnifier.show(showPosInView.x, showPosInView.y); + } else { + dismissMagnifier(); + } } protected final void dismissMagnifier() { @@ -4877,11 +4908,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); - showMagnifier(ev); + updateMagnifier(ev); break; case MotionEvent.ACTION_MOVE: - showMagnifier(ev); + updateMagnifier(ev); break; case MotionEvent.ACTION_UP: @@ -5235,11 +5266,11 @@ public class Editor { // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; - showMagnifier(event); + updateMagnifier(event); break; case MotionEvent.ACTION_MOVE: - showMagnifier(event); + updateMagnifier(event); break; case MotionEvent.ACTION_UP: diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 85f68d7c29f40..3db149a442de6 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -75,6 +75,8 @@ public final class Magnifier { private final int mWindowWidth; // The height of the window containing the magnifier. private final int mWindowHeight; + // The zoom applied to the view region copied to the magnifier window. + private final float mZoom; // The width of the bitmaps where the magnifier content is copied. private final int mBitmapWidth; // The height of the bitmaps where the magnifier content is copied. @@ -111,10 +113,10 @@ public final class Magnifier { com.android.internal.R.dimen.magnifier_height); mWindowElevation = context.getResources().getDimension( com.android.internal.R.dimen.magnifier_elevation); - final float zoomScale = context.getResources().getFloat( + mZoom = context.getResources().getFloat( com.android.internal.R.dimen.magnifier_zoom_scale); - mBitmapWidth = Math.round(mWindowWidth / zoomScale); - mBitmapHeight = Math.round(mWindowHeight / zoomScale); + mBitmapWidth = Math.round(mWindowWidth / mZoom); + mBitmapHeight = Math.round(mWindowHeight / mZoom); // The view's surface coordinates will not be updated until the magnifier is first shown. mViewCoordinatesInSurface = new int[2]; } @@ -187,21 +189,6 @@ public final class Magnifier { } } - @Nullable - private Surface getValidViewSurface() { - // TODO: deduplicate this against the first part of #performPixelCopy - final Surface surface; - if (mView instanceof SurfaceView) { - surface = ((SurfaceView) mView).getHolder().getSurface(); - } else if (mView.getViewRootImpl() != null) { - surface = mView.getViewRootImpl().mSurface; - } else { - surface = null; - } - - return (surface != null && surface.isValid()) ? surface : null; - } - /** * Dismisses the magnifier from the screen. Calling this on a dismissed magnifier is a no-op. */ @@ -226,6 +213,44 @@ public final class Magnifier { } } + /** + * @return The width of the magnifier window, in pixels. + */ + public int getWidth() { + return mWindowWidth; + } + + /** + * @return The height of the magnifier window, in pixels. + */ + public int getHeight() { + return mWindowHeight; + } + + /** + * @return The zoom applied to the magnified view region copied to the magnifier window. + * If the zoom is x and the magnifier window size is (width, height), the original size + * of the content copied in the magnifier will be (width / x, height / x). + */ + public float getZoom() { + return mZoom; + } + + @Nullable + private Surface getValidViewSurface() { + // TODO: deduplicate this against the first part of #performPixelCopy + final Surface surface; + if (mView instanceof SurfaceView) { + surface = ((SurfaceView) mView).getHolder().getSurface(); + } else if (mView.getViewRootImpl() != null) { + surface = mView.getViewRootImpl().mSurface; + } else { + surface = null; + } + + return (surface != null && surface.isValid()) ? surface : null; + } + private void configureCoordinates(final float xPosInView, final float yPosInView) { // Compute the coordinates of the center of the content going to be displayed in the // magnifier. These are relative to the surface the content is copied from. @@ -602,7 +627,7 @@ public final class Magnifier { return null; } synchronized (mWindow.mLock) { - return mWindow.mBitmap; + return Bitmap.createScaledBitmap(mWindow.mBitmap, mWindowWidth, mWindowHeight, true); } }