summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/widget/Editor.java79
-rw-r--r--core/java/android/widget/Magnifier.java206
2 files changed, 241 insertions, 44 deletions
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 2732b2e0285a..b10631b7ef2f 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -220,7 +220,9 @@ public class Editor {
private final boolean mHapticTextHandleEnabled;
- private final MagnifierMotionAnimator mMagnifierAnimator;
+ @Nullable
+ private MagnifierMotionAnimator mMagnifierAnimator;
+
private final Runnable mUpdateMagnifierRunnable = new Runnable() {
@Override
public void run() {
@@ -397,12 +399,6 @@ public class Editor {
mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean(
com.android.internal.R.bool.config_enableHapticTextHandle);
- if (FLAG_USE_MAGNIFIER) {
- final Magnifier magnifier =
- Magnifier.createBuilderWithOldMagnifierDefaults(mTextView).build();
- mMagnifierAnimator = new MagnifierMotionAnimator(magnifier);
- }
-
mCursorControlEnabled = AppGlobals.getIntCoreSetting(
WidgetFlags.KEY_ENABLE_CURSOR_CONTROL , 0) != 0;
if (TextView.DEBUG_CURSOR) {
@@ -421,6 +417,63 @@ public class Editor {
return mCursorControlEnabled;
}
+ // Lazy creates the magnifier animator.
+ private MagnifierMotionAnimator getMagnifierAnimator() {
+ if (FLAG_USE_MAGNIFIER && mMagnifierAnimator == null) {
+ // Lazy creates the magnifier instance because it requires the text height which cannot
+ // be measured at the time of Editor instance being created.
+ final Magnifier.Builder builder = shouldUseNewMagnifier()
+ ? createBuilderWithInlineMagnifierDefaults()
+ : Magnifier.createBuilderWithOldMagnifierDefaults(mTextView);
+ mMagnifierAnimator = new MagnifierMotionAnimator(builder.build());
+ }
+ return mMagnifierAnimator;
+ }
+
+ private boolean shouldUseNewMagnifier() {
+ // TODO: use a separate flag to enable new magnifier.
+ return mCursorControlEnabled;
+ }
+
+ private Magnifier.Builder createBuilderWithInlineMagnifierDefaults() {
+ final Magnifier.Builder params = new Magnifier.Builder(mTextView);
+
+ // TODO: supports changing the height/width dynamically because the text height can be
+ // dynamically changed.
+ final Paint.FontMetrics fontMetrics = mTextView.getPaint().getFontMetrics();
+ final float sourceHeight = fontMetrics.descent - fontMetrics.ascent;
+ final float zoom = 1.5f;
+ final float widthHeightRatio = 5.5f;
+ // Slightly increase the height to avoid tooLargeTextForMagnifier() returns true.
+ int height = (int)(sourceHeight * zoom) + 2;
+ int width = (int)(widthHeightRatio * height);
+
+
+ params.setFishEyeStyle()
+ .setSize(width, height)
+ .setSourceSize(width, Math.round(sourceHeight))
+ .setElevation(0)
+ .setInitialZoom(zoom)
+ .setClippingEnabled(false);
+
+ final Context context = mTextView.getContext();
+ final TypedArray a = context.obtainStyledAttributes(
+ null, com.android.internal.R.styleable.Magnifier,
+ com.android.internal.R.attr.magnifierStyle, 0);
+ params.setDefaultSourceToMagnifierOffset(
+ a.getDimensionPixelSize(
+ com.android.internal.R.styleable.Magnifier_magnifierHorizontalOffset, 0),
+ a.getDimensionPixelSize(
+ com.android.internal.R.styleable.Magnifier_magnifierVerticalOffset, 0));
+ a.recycle();
+
+ return params.setSourceBounds(
+ Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+ Magnifier.SOURCE_BOUND_MAX_IN_SURFACE,
+ Magnifier.SOURCE_BOUND_MAX_VISIBLE,
+ Magnifier.SOURCE_BOUND_MAX_IN_SURFACE);
+ }
+
ParcelableParcel saveInstanceState() {
ParcelableParcel state = new ParcelableParcel(getClass().getClassLoader());
Parcel parcel = state.getParcel();
@@ -5022,7 +5075,7 @@ public class Editor {
}
protected final void updateMagnifier(@NonNull final MotionEvent event) {
- if (mMagnifierAnimator == null) {
+ if (getMagnifierAnimator() == null) {
return;
}
@@ -5036,6 +5089,16 @@ public class Editor {
mTextView.invalidateCursorPath();
suspendBlink();
+ if (shouldUseNewMagnifier()) {
+ // Calculates the line bounds as the content source bounds to the magnifier.
+ Layout layout = mTextView.getLayout();
+ int line = layout.getLineForOffset(getCurrentCursorOffset());
+ int lineLeft = (int) layout.getLineLeft(line);
+ lineLeft += mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
+ int lineRight = (int) layout.getLineRight(line);
+ lineRight -= mTextView.getTotalPaddingRight() + mTextView.getScrollX();
+ mMagnifierAnimator.mMagnifier.setSourceHorizontalBounds(lineLeft, lineRight);
+ }
mMagnifierAnimator.show(showPosInView.x, showPosInView.y);
updateHandlesVisibility();
} else {
diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java
index 2924dd9bf954..57b63a7a9f0d 100644
--- a/core/java/android/widget/Magnifier.java
+++ b/core/java/android/widget/Magnifier.java
@@ -140,6 +140,18 @@ public final class Magnifier {
// The lock used to synchronize the UI and render threads when a #dismiss is performed.
private final Object mDestroyLock = new Object();
+ // Members for new styled magnifier (Eloquent style).
+
+ // Whether the magnifier is in new style.
+ private boolean mIsFishEyeStyle;
+ // The width of the cut region on the left edge of the pixel copy source rect.
+ private int mLeftCutWidth = 0;
+ // The width of the cut region on the right edge of the pixel copy source rect.
+ private int mRightCutWidth = 0;
+ // The horizontal bounds of the content source in pixels, relative to the view.
+ private int mLeftBound = Integer.MIN_VALUE;
+ private int mRightBound = Integer.MAX_VALUE;
+
/**
* Initializes a magnifier.
*
@@ -198,8 +210,14 @@ public final class Magnifier {
mWindowWidth = params.mWidth;
mWindowHeight = params.mHeight;
mZoom = params.mZoom;
- mSourceWidth = Math.round(mWindowWidth / mZoom);
- mSourceHeight = Math.round(mWindowHeight / mZoom);
+ mIsFishEyeStyle = params.mIsFishEyeStyle;
+ if (params.mSourceWidth > 0 && params.mSourceHeight > 0) {
+ mSourceWidth = params.mSourceWidth;
+ mSourceHeight = params.mSourceHeight;
+ } else {
+ mSourceWidth = Math.round(mWindowWidth / mZoom);
+ mSourceHeight = Math.round(mWindowHeight / mZoom);
+ }
mWindowElevation = params.mElevation;
mWindowCornerRadius = params.mCornerRadius;
mOverlay = params.mOverlay;
@@ -221,6 +239,18 @@ public final class Magnifier {
}
/**
+ * Sets the horizontal bounds of the source when showing the magnifier.
+ * This is used for new style magnifier. e.g. limit the source bounds by the text line bounds.
+ *
+ * @param left the left of the bounds, relative to the view.
+ * @param right the right of the bounds, relative to the view.
+ */
+ void setSourceHorizontalBounds(int left, int right) {
+ mLeftBound = left;
+ mRightBound = right;
+ }
+
+ /**
* Shows the magnifier on the screen. The method takes the coordinates of the center
* of the content source going to be magnified and copied to the magnifier. The coordinates
* are relative to the top left corner of the magnified view. The magnifier will be
@@ -265,20 +295,37 @@ public final class Magnifier {
obtainSurfaces();
obtainContentCoordinates(sourceCenterX, sourceCenterY);
- obtainWindowCoordinates(magnifierCenterX, magnifierCenterY);
- final int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2;
+ int startX = mClampedCenterZoomCoords.x - mSourceWidth / 2;
final int startY = mClampedCenterZoomCoords.y - mSourceHeight / 2;
+
+ if (mIsFishEyeStyle) {
+ // The magnifier center is the same as source center in new style.
+ magnifierCenterX = mClampedCenterZoomCoords.x - mViewCoordinatesInSurface[0];
+ magnifierCenterY = mClampedCenterZoomCoords.y - mViewCoordinatesInSurface[1];
+ // Gets the startX for new style, which should be bounded by the horizontal bounds.
+ // Also calculates the left/right cut width for pixel copy.
+ final int left = startX;
+ final int right = startX + mSourceWidth;
+ final int leftBound = mViewCoordinatesInSurface[0] + Math.max(0, mLeftBound);
+ final int rightBound =
+ mViewCoordinatesInSurface[0] + Math.min(mView.getWidth(), mRightBound);
+ startX = Math.max(left, leftBound);
+ mLeftCutWidth = Math.max(0, leftBound - left);
+ mRightCutWidth = Math.max(0, right - rightBound);
+ }
+ obtainWindowCoordinates(magnifierCenterX, magnifierCenterY);
+
if (sourceCenterX != mPrevShowSourceCoords.x || sourceCenterY != mPrevShowSourceCoords.y
|| mDirtyState) {
if (mWindow == null) {
synchronized (mLock) {
mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
- mParentSurface.mSurfaceControl, mWindowWidth, mWindowHeight,
+ mParentSurface.mSurfaceControl, mWindowWidth, mWindowHeight, mZoom,
mWindowElevation, mWindowCornerRadius,
mOverlay != null ? mOverlay : new ColorDrawable(Color.TRANSPARENT),
Handler.getMain() /* draw the magnifier on the UI thread */, mLock,
- mCallback);
+ mCallback, mIsFishEyeStyle);
}
}
performPixelCopy(startX, startY, true /* update window position */);
@@ -387,7 +434,7 @@ public final class Magnifier {
public void setZoom(@FloatRange(from = 0f) float zoom) {
Preconditions.checkArgumentPositive(zoom, "Zoom should be positive");
mZoom = zoom;
- mSourceWidth = Math.round(mWindowWidth / mZoom);
+ mSourceWidth = mIsFishEyeStyle ? mWindowWidth : Math.round(mWindowWidth / mZoom);
mSourceHeight = Math.round(mWindowHeight / mZoom);
mDirtyState = true;
}
@@ -634,8 +681,10 @@ public final class Magnifier {
resolvedBottom = Math.max(resolvedBottom, resolvedTop + mSourceHeight);
// Finally compute the coordinates of the source center.
- mClampedCenterZoomCoords.x = Math.max(resolvedLeft + mSourceWidth / 2, Math.min(
- zoomCenterX, resolvedRight - mSourceWidth / 2));
+ mClampedCenterZoomCoords.x = mIsFishEyeStyle
+ ? Math.max(resolvedLeft, Math.min(zoomCenterX, resolvedRight))
+ : Math.max(resolvedLeft + mSourceWidth / 2, Math.min(
+ zoomCenterX, resolvedRight - mSourceWidth / 2));
mClampedCenterZoomCoords.y = Math.max(resolvedTop + mSourceHeight / 2, Math.min(
zoomCenterY, resolvedBottom - mSourceHeight / 2));
}
@@ -678,11 +727,22 @@ public final class Magnifier {
// Perform the pixel copy.
mPixelCopyRequestRect.set(startXInSurface,
startYInSurface,
- startXInSurface + mSourceWidth,
+ startXInSurface + mSourceWidth - mLeftCutWidth - mRightCutWidth,
startYInSurface + mSourceHeight);
+ mPrevStartCoordsInSurface.x = startXInSurface;
+ mPrevStartCoordsInSurface.y = startYInSurface;
+ mDirtyState = false;
+
final InternalPopupWindow currentWindowInstance = mWindow;
+ if (mPixelCopyRequestRect.width() == 0) {
+ // If the copy rect is empty, updates an empty bitmap to the window.
+ mWindow.updateContent(
+ Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ALPHA_8));
+ return;
+ }
final Bitmap bitmap =
- Bitmap.createBitmap(mSourceWidth, mSourceHeight, Bitmap.Config.ARGB_8888);
+ Bitmap.createBitmap(mSourceWidth - mLeftCutWidth - mRightCutWidth,
+ mSourceHeight, Bitmap.Config.ARGB_8888);
PixelCopy.request(mContentCopySurface.mSurface, mPixelCopyRequestRect, bitmap,
result -> {
if (result != PixelCopy.SUCCESS) {
@@ -696,15 +756,25 @@ public final class Magnifier {
}
if (updateWindowPosition) {
// TODO: pull the position update outside #performPixelCopy
- mWindow.setContentPositionForNextDraw(windowCoords.x, windowCoords.y);
+ mWindow.setContentPositionForNextDraw(windowCoords.x,
+ windowCoords.y);
+ }
+ if (bitmap.getWidth() < mSourceWidth) {
+ // When bitmap width has been cut, re-fills it with full width bitmap.
+ // This only happens in new styled magnifier.
+ final Bitmap newBitmap = Bitmap.createBitmap(
+ mSourceWidth, bitmap.getHeight(), bitmap.getConfig());
+ final Canvas can = new Canvas(newBitmap);
+ final Rect dstRect = new Rect(mLeftCutWidth, 0,
+ mSourceWidth - mRightCutWidth, bitmap.getHeight());
+ can.drawBitmap(bitmap, null, dstRect, null);
+ mWindow.updateContent(newBitmap);
+ } else {
+ mWindow.updateContent(bitmap);
}
- mWindow.updateContent(bitmap);
}
},
sPixelCopyHandlerThread.getThreadHandler());
- mPrevStartCoordsInSurface.x = startXInSurface;
- mPrevStartCoordsInSurface.y = startYInSurface;
- mDirtyState = false;
}
private void onPixelCopyFailed() {
@@ -790,9 +860,6 @@ public final class Magnifier {
// The size of the content of the magnifier.
private final int mContentWidth;
private final int mContentHeight;
- // The size of the allocated surface.
- private final int mSurfaceWidth;
- private final int mSurfaceHeight;
// The insets of the content inside the allocated surface.
private final int mOffsetX;
private final int mOffsetY;
@@ -815,9 +882,6 @@ public final class Magnifier {
private final Handler mHandler;
// The callback to be run after the next draw.
private Callback mCallback;
- // The position of the magnifier content when the last draw was requested.
- private int mLastDrawContentPositionX;
- private int mLastDrawContentPositionY;
// Members below describe the state of the magnifier. Reads/writes to them
// have to be synchronized between the UI thread and the thread that handles
@@ -838,10 +902,19 @@ public final class Magnifier {
// The current content of the magnifier. It is mBitmap + mOverlay, only used for testing.
private Bitmap mCurrentContent;
+ private final float mZoom;
+ // Whether is in the new magnifier style.
+ private boolean mIsFishEyeStyle;
+ // The mesh matrix for the fish-eye effect.
+ private float[] mMesh;
+ private int mMeshWidth;
+ private int mMeshHeight;
+
InternalPopupWindow(final Context context, final Display display,
final SurfaceControl parentSurfaceControl, final int width, final int height,
- final float elevation, final float cornerRadius, final Drawable overlay,
- final Handler handler, final Object lock, final Callback callback) {
+ final float zoom, final float elevation, final float cornerRadius,
+ final Drawable overlay, final Handler handler, final Object lock,
+ final Callback callback, final boolean isFishEyeStyle) {
mDisplay = display;
mOverlay = overlay;
mLock = lock;
@@ -849,15 +922,16 @@ public final class Magnifier {
mContentWidth = width;
mContentHeight = height;
+ mZoom = zoom;
mOffsetX = (int) (1.05f * elevation);
mOffsetY = (int) (1.05f * elevation);
// Setup the surface we will use for drawing the content and shadow.
- mSurfaceWidth = mContentWidth + 2 * mOffsetX;
- mSurfaceHeight = mContentHeight + 2 * mOffsetY;
+ final int surfaceWidth = mContentWidth + 2 * mOffsetX;
+ final int surfaceHeight = mContentHeight + 2 * mOffsetY;
mSurfaceSession = new SurfaceSession();
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(PixelFormat.TRANSLUCENT)
- .setBufferSize(mSurfaceWidth, mSurfaceHeight)
+ .setBufferSize(surfaceWidth, surfaceHeight)
.setName("magnifier surface")
.setFlags(SurfaceControl.HIDDEN)
.setParent(parentSurfaceControl)
@@ -904,6 +978,38 @@ public final class Magnifier {
mHandler = handler;
mMagnifierUpdater = this::doDraw;
mFrameDrawScheduled = false;
+ mIsFishEyeStyle = isFishEyeStyle;
+
+ if (mIsFishEyeStyle) {
+ createMeshMatrixForFishEyeEffect();
+ }
+ }
+
+ private void createMeshMatrixForFishEyeEffect() {
+ mMeshWidth = mZoom < 1.5f ? 5 : 4;
+ mMeshHeight = 6;
+ final float w = mContentWidth;
+ final float h = mContentHeight;
+ final float dx = (w - mZoom * w * (mMeshWidth - 2) / mMeshWidth) / 2;
+ mMesh = new float[2 * (mMeshWidth + 1) * (mMeshHeight + 1)];
+ for (int i = 0; i < 2 * (mMeshWidth + 1) * (mMeshHeight + 1); i += 2) {
+ // Calculates X value.
+ final int colIndex = i % (2 * (mMeshWidth + 1)) / 2;
+ if (colIndex == 0) {
+ mMesh[i] = 0;
+ } else if (colIndex == mMeshWidth) {
+ mMesh[i] = w;
+ } else {
+ mMesh[i] = (colIndex - 1) * (w - 2 * dx) / (mMeshWidth - 2) + dx;
+ }
+ // Calculates Y value.
+ final int rowIndex = i / 2 / (mMeshWidth + 1);
+ final float y0 = colIndex == 0 || colIndex == mMeshWidth
+ ? (h - h / mZoom) / 2 : 0;
+ final float dy = colIndex == 0 || colIndex == mMeshWidth
+ ? h / mZoom / mMeshHeight : h / mMeshHeight;
+ mMesh[i + 1] = y0 + rowIndex * dy;
+ }
}
private RenderNode createRenderNodeForBitmap(final String name,
@@ -1060,15 +1166,19 @@ public final class Magnifier {
final RecordingCanvas canvas =
mBitmapRenderNode.beginRecording(mContentWidth, mContentHeight);
try {
- final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
- final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
- final Paint paint = new Paint();
- paint.setFilterBitmap(true);
- canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
+ if (mIsFishEyeStyle) {
+ canvas.drawBitmapMesh(
+ mBitmap, mMeshWidth, mMeshHeight, mMesh, 0, null, 0, null);
+ } else {
+ final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
+ final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
+ final Paint paint = new Paint();
+ paint.setFilterBitmap(true);
+ canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
+ }
} finally {
mBitmapRenderNode.endRecording();
}
-
if (mPendingWindowPositionUpdate || mFirstDraw) {
// If the window has to be shown or moved, defer this until the next draw.
final boolean firstDraw = mFirstDraw;
@@ -1094,13 +1204,14 @@ public final class Magnifier {
}
mTransaction.apply();
};
- mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
+ if (!mIsFishEyeStyle) {
+ // The new style magnifier doesn't need the light/shadow.
+ mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
+ }
} else {
callback = null;
}
- mLastDrawContentPositionX = mWindowPositionX + mOffsetX;
- mLastDrawContentPositionY = mWindowPositionY + mOffsetY;
mFrameDrawScheduled = false;
}
@@ -1149,6 +1260,9 @@ public final class Magnifier {
private @SourceBound int mTopContentBound;
private @SourceBound int mRightContentBound;
private @SourceBound int mBottomContentBound;
+ private boolean mIsFishEyeStyle;
+ private int mSourceWidth;
+ private int mSourceHeight;
/**
* Construct a new builder for {@link Magnifier} objects.
@@ -1177,6 +1291,7 @@ public final class Magnifier {
mTopContentBound = SOURCE_BOUND_MAX_VISIBLE;
mRightContentBound = SOURCE_BOUND_MAX_VISIBLE;
mBottomContentBound = SOURCE_BOUND_MAX_VISIBLE;
+ mIsFishEyeStyle = false;
}
/**
@@ -1339,6 +1454,25 @@ public final class Magnifier {
}
/**
+ * Sets the source width/height.
+ */
+ @NonNull
+ Builder setSourceSize(int width, int height) {
+ mSourceWidth = width;
+ mSourceHeight = height;
+ return this;
+ }
+
+ /**
+ * Sets the magnifier as the new fish-eye style.
+ */
+ @NonNull
+ Builder setFishEyeStyle() {
+ mIsFishEyeStyle = true;
+ return this;
+ }
+
+ /**
* Builds a {@link Magnifier} instance based on the configuration of this {@link Builder}.
*/
public @NonNull Magnifier build() {