Loading core/java/android/view/ViewRootImpl.java +1 −1 Original line number Diff line number Diff line Loading @@ -366,7 +366,7 @@ public final class ViewRootImpl implements ViewParent, // These can be accessed by any thread, must be protected with a lock. // Surface can never be reassigned or cleared (use Surface.clear()). final Surface mSurface = new Surface(); public final Surface mSurface = new Surface(); boolean mAdded; boolean mAddedTouchMode; Loading core/java/android/widget/Editor.java +109 −12 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.EditableInputConnection; import com.android.internal.widget.Magnifier; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading @@ -138,6 +139,9 @@ import java.util.List; public class Editor { private static final String TAG = "Editor"; private static final boolean DEBUG_UNDO = false; // Specifies whether to use or not the magnifier when pressing the insertion or selection // handles. private static final boolean FLAG_USE_MAGNIFIER = false; static final int BLINK = 500; private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; Loading @@ -161,6 +165,17 @@ public class Editor { private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100; private static final float MAGNIFIER_ZOOM = 1.5f; @IntDef({MagnifierHandleTrigger.SELECTION_START, MagnifierHandleTrigger.SELECTION_END, MagnifierHandleTrigger.INSERTION}) @Retention(RetentionPolicy.SOURCE) private @interface MagnifierHandleTrigger { int INSERTION = 0; int SELECTION_START = 1; int SELECTION_END = 2; } // Each Editor manages its own undo stack. private final UndoManager mUndoManager = new UndoManager(); private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this); Loading @@ -179,6 +194,8 @@ public class Editor { private final boolean mHapticTextHandleEnabled; private final Magnifier mMagnifier; // Used to highlight a word when it is corrected by the IME private CorrectionHighlighter mCorrectionHighlighter; Loading Loading @@ -325,6 +342,8 @@ public class Editor { mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this); mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); mMagnifier = FLAG_USE_MAGNIFIER ? new Magnifier(mTextView) : null; } ParcelableParcel saveInstanceState() { Loading Loading @@ -4353,6 +4372,9 @@ public class Editor { protected abstract void updatePosition(float x, float y, boolean fromTouchScreen); @MagnifierHandleTrigger protected abstract int getMagnifierHandleTrigger(); protected boolean isAtRtlRun(@NonNull Layout layout, int offset) { return layout.isRtlCharAt(offset); } Loading Loading @@ -4490,6 +4512,53 @@ public class Editor { return 0; } protected final void showMagnifier() { if (mMagnifier == null) { return; } final int trigger = getMagnifierHandleTrigger(); final int offset; switch (trigger) { case MagnifierHandleTrigger.INSERTION: // Fall through. case MagnifierHandleTrigger.SELECTION_START: offset = mTextView.getSelectionStart(); break; case MagnifierHandleTrigger.SELECTION_END: offset = mTextView.getSelectionEnd(); break; default: offset = -1; break; } if (offset == -1) { dismissMagnifier(); } final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); // Horizontally snap to character offset. final float xPosInView = getHorizontal(mTextView.getLayout(), offset); // Vertically snap to middle of current line. final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f; final int[] coordinatesOnScreen = new int[2]; mTextView.getLocationOnScreen(coordinatesOnScreen); final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft() - mTextView.getScrollX() + coordinatesOnScreen[0]; final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop() - mTextView.getScrollY() + coordinatesOnScreen[1]; mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM); } protected final void dismissMagnifier() { if (mMagnifier != null) { mMagnifier.dismiss(); } } @Override public boolean onTouchEvent(MotionEvent ev) { updateFloatingToolbarVisibility(ev); Loading Loading @@ -4542,10 +4611,7 @@ public class Editor { case MotionEvent.ACTION_UP: filterOnTouchUp(ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)); mIsDragging = false; updateDrawable(); break; // Fall through. case MotionEvent.ACTION_CANCEL: mIsDragging = false; updateDrawable(); Loading Loading @@ -4671,6 +4737,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); showMagnifier(); break; case MotionEvent.ACTION_MOVE: showMagnifier(); break; case MotionEvent.ACTION_UP: Loading @@ -4696,11 +4767,10 @@ public class Editor { mTextActionMode.invalidateContentRect(); } } hideAfterDelay(); break; // Fall through. case MotionEvent.ACTION_CANCEL: hideAfterDelay(); dismissMagnifier(); break; default: Loading Loading @@ -4751,6 +4821,12 @@ public class Editor { super.onDetached(); removeHiderCallback(); } @Override @MagnifierHandleTrigger protected int getMagnifierHandleTrigger() { return MagnifierHandleTrigger.INSERTION; } } @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -5009,12 +5085,26 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent event) { boolean superResult = super.onTouchEvent(event); if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // Reset the touch word offset and x value when the user // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; showMagnifier(); break; case MotionEvent.ACTION_MOVE: showMagnifier(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: dismissMagnifier(); break; } return superResult; } Loading Loading @@ -5110,6 +5200,13 @@ public class Editor { return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset; } } @MagnifierHandleTrigger protected int getMagnifierHandleTrigger() { return isStartHandle() ? MagnifierHandleTrigger.SELECTION_START : MagnifierHandleTrigger.SELECTION_END; } } private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { Loading core/java/com/android/internal/widget/Magnifier.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.internal.widget; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.UiThread; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.PixelCopy; import android.view.View; import android.view.ViewRootImpl; import android.widget.ImageView; import android.widget.PopupWindow; import com.android.internal.R; import com.android.internal.util.Preconditions; /** * Android magnifier widget. Can be used by any view which is attached to window. */ public final class Magnifier { private static final String LOG_TAG = "magnifier"; // The view for which this magnifier is attached. private final View mView; // The window containing the magnifier. private final PopupWindow mWindow; // The center coordinates of the window containing the magnifier. private final Point mWindowCoords = new Point(); // The width of the window containing the magnifier. private final int mWindowWidth; // The height of the window containing the magnifier. private final int mWindowHeight; // The bitmap used to display the contents of the magnifier. private final Bitmap mBitmap; // The center coordinates of the content that is to be magnified. private final Point mCenterZoomCoords = new Point(); // The callback of the pixel copy request will be invoked on this Handler when // the copy is finished. private final Handler mPixelCopyHandler = Handler.getMain(); /** * Initializes a magnifier. * * @param view the view for which this magnifier is attached */ @UiThread public Magnifier(@NonNull View view) { mView = Preconditions.checkNotNull(view); final Context context = mView.getContext(); final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null); mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width); mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height); final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation); mWindow = new PopupWindow(context); mWindow.setContentView(content); mWindow.setWidth(mWindowWidth); mWindow.setHeight(mWindowHeight); mWindow.setElevation(elevation); mWindow.setTouchable(false); mWindow.setBackgroundDrawable(null); mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888); getImageView().setImageBitmap(mBitmap); } /** * Shows the magnifier on the screen. * * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source * @param centerYOnScreen vertical coordinate of the center point of the magnifier source * @param scale the scale at which the magnifier zooms on the source content */ public void show(@FloatRange(from=0) float centerXOnScreen, @FloatRange(from=0) float centerYOnScreen, @FloatRange(from=1, to=10) float scale) { maybeResizeBitmap(scale); configureCoordinates(centerXOnScreen, centerYOnScreen); performPixelCopy(); if (mWindow.isShowing()) { mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(), mWindow.getHeight()); } else { mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y); } } /** * Dismisses the magnifier from the screen. */ public void dismiss() { mWindow.dismiss(); } /** * @return the height of the magnifier window. */ public int getHeight() { return mWindowHeight; } /** * @return the width of the magnifier window. */ public int getWidth() { return mWindowWidth; } private void maybeResizeBitmap(float scale) { final int bitmapWidth = (int) (mWindowWidth / scale); final int bitmapHeight = (int) (mWindowHeight / scale); if (mBitmap.getWidth() != bitmapWidth || mBitmap.getHeight() != bitmapHeight) { mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); getImageView().setImageBitmap(mBitmap); } } private void configureCoordinates(float posXOnScreen, float posYOnScreen) { mCenterZoomCoords.x = (int) posXOnScreen; mCenterZoomCoords.y = (int) posYOnScreen; final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize( R.dimen.magnifier_offset); final int availableTopSpace = (mCenterZoomCoords.y - mWindowHeight / 2) - verticalMagnifierOffset - (mBitmap.getHeight() / 2); mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2; mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 + verticalMagnifierOffset * (availableTopSpace > 0 ? -1 : 1); } private void performPixelCopy() { int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2; // Clamp startX value to avoid distorting the rendering of the magnifier content. if (startX < 0) { startX = 0; } else if (startX + mBitmap.getWidth() > mView.getWidth()) { startX = mView.getWidth() - mBitmap.getWidth(); } final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; final ViewRootImpl viewRootImpl = mView.getViewRootImpl(); if (viewRootImpl != null && viewRootImpl.mSurface != null && viewRootImpl.mSurface.isValid()) { PixelCopy.request( viewRootImpl.mSurface, new Rect(startX, startY, startX + mBitmap.getWidth(), startY + mBitmap.getHeight()), mBitmap, result -> getImageView().invalidate(), mPixelCopyHandler); } else { Log.d(LOG_TAG, "Could not perform PixelCopy request"); } } private ImageView getImageView() { return mWindow.getContentView().findViewById(R.id.magnifier_image); } } core/res/res/layout/magnifier.xml 0 → 100644 +27 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2017 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"> <ImageView android:id="@+id/magnifier_image" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout> core/res/res/values/dimens.xml +6 −0 Original line number Diff line number Diff line Loading @@ -520,6 +520,12 @@ <dimen name="floating_toolbar_vertical_margin">8dp</dimen> <dimen name="content_rect_bottom_clip_allowance">20dp</dimen> <!-- Magnifier dimensions --> <dimen name="magnifier_width">200dp</dimen> <dimen name="magnifier_height">48dp</dimen> <dimen name="magnifier_elevation">2dp</dimen> <dimen name="magnifier_offset">42dp</dimen> <dimen name="chooser_grid_padding">0dp</dimen> <!-- Spacing around the background change frome service to non-service --> <dimen name="chooser_service_spacing">8dp</dimen> Loading Loading
core/java/android/view/ViewRootImpl.java +1 −1 Original line number Diff line number Diff line Loading @@ -366,7 +366,7 @@ public final class ViewRootImpl implements ViewParent, // These can be accessed by any thread, must be protected with a lock. // Surface can never be reassigned or cleared (use Surface.clear()). final Surface mSurface = new Surface(); public final Surface mSurface = new Surface(); boolean mAdded; boolean mAddedTouchMode; Loading
core/java/android/widget/Editor.java +109 −12 Original line number Diff line number Diff line Loading @@ -119,6 +119,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.Preconditions; import com.android.internal.widget.EditableInputConnection; import com.android.internal.widget.Magnifier; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; Loading @@ -138,6 +139,9 @@ import java.util.List; public class Editor { private static final String TAG = "Editor"; private static final boolean DEBUG_UNDO = false; // Specifies whether to use or not the magnifier when pressing the insertion or selection // handles. private static final boolean FLAG_USE_MAGNIFIER = false; static final int BLINK = 500; private static final int DRAG_SHADOW_MAX_TEXT_LENGTH = 20; Loading @@ -161,6 +165,17 @@ public class Editor { private static final int MENU_ITEM_ORDER_PASTE_AS_PLAIN_TEXT = 11; private static final int MENU_ITEM_ORDER_PROCESS_TEXT_INTENT_ACTIONS_START = 100; private static final float MAGNIFIER_ZOOM = 1.5f; @IntDef({MagnifierHandleTrigger.SELECTION_START, MagnifierHandleTrigger.SELECTION_END, MagnifierHandleTrigger.INSERTION}) @Retention(RetentionPolicy.SOURCE) private @interface MagnifierHandleTrigger { int INSERTION = 0; int SELECTION_START = 1; int SELECTION_END = 2; } // Each Editor manages its own undo stack. private final UndoManager mUndoManager = new UndoManager(); private UndoOwner mUndoOwner = mUndoManager.getOwner(UNDO_OWNER_TAG, this); Loading @@ -179,6 +194,8 @@ public class Editor { private final boolean mHapticTextHandleEnabled; private final Magnifier mMagnifier; // Used to highlight a word when it is corrected by the IME private CorrectionHighlighter mCorrectionHighlighter; Loading Loading @@ -325,6 +342,8 @@ public class Editor { mProcessTextIntentActionsHandler = new ProcessTextIntentActionsHandler(this); mHapticTextHandleEnabled = mTextView.getContext().getResources().getBoolean( com.android.internal.R.bool.config_enableHapticTextHandle); mMagnifier = FLAG_USE_MAGNIFIER ? new Magnifier(mTextView) : null; } ParcelableParcel saveInstanceState() { Loading Loading @@ -4353,6 +4372,9 @@ public class Editor { protected abstract void updatePosition(float x, float y, boolean fromTouchScreen); @MagnifierHandleTrigger protected abstract int getMagnifierHandleTrigger(); protected boolean isAtRtlRun(@NonNull Layout layout, int offset) { return layout.isRtlCharAt(offset); } Loading Loading @@ -4490,6 +4512,53 @@ public class Editor { return 0; } protected final void showMagnifier() { if (mMagnifier == null) { return; } final int trigger = getMagnifierHandleTrigger(); final int offset; switch (trigger) { case MagnifierHandleTrigger.INSERTION: // Fall through. case MagnifierHandleTrigger.SELECTION_START: offset = mTextView.getSelectionStart(); break; case MagnifierHandleTrigger.SELECTION_END: offset = mTextView.getSelectionEnd(); break; default: offset = -1; break; } if (offset == -1) { dismissMagnifier(); } final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); // Horizontally snap to character offset. final float xPosInView = getHorizontal(mTextView.getLayout(), offset); // Vertically snap to middle of current line. final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f; final int[] coordinatesOnScreen = new int[2]; mTextView.getLocationOnScreen(coordinatesOnScreen); final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft() - mTextView.getScrollX() + coordinatesOnScreen[0]; final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop() - mTextView.getScrollY() + coordinatesOnScreen[1]; mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM); } protected final void dismissMagnifier() { if (mMagnifier != null) { mMagnifier.dismiss(); } } @Override public boolean onTouchEvent(MotionEvent ev) { updateFloatingToolbarVisibility(ev); Loading Loading @@ -4542,10 +4611,7 @@ public class Editor { case MotionEvent.ACTION_UP: filterOnTouchUp(ev.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)); mIsDragging = false; updateDrawable(); break; // Fall through. case MotionEvent.ACTION_CANCEL: mIsDragging = false; updateDrawable(); Loading Loading @@ -4671,6 +4737,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); showMagnifier(); break; case MotionEvent.ACTION_MOVE: showMagnifier(); break; case MotionEvent.ACTION_UP: Loading @@ -4696,11 +4767,10 @@ public class Editor { mTextActionMode.invalidateContentRect(); } } hideAfterDelay(); break; // Fall through. case MotionEvent.ACTION_CANCEL: hideAfterDelay(); dismissMagnifier(); break; default: Loading Loading @@ -4751,6 +4821,12 @@ public class Editor { super.onDetached(); removeHiderCallback(); } @Override @MagnifierHandleTrigger protected int getMagnifierHandleTrigger() { return MagnifierHandleTrigger.INSERTION; } } @Retention(RetentionPolicy.SOURCE) Loading Loading @@ -5009,12 +5085,26 @@ public class Editor { @Override public boolean onTouchEvent(MotionEvent event) { boolean superResult = super.onTouchEvent(event); if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: // Reset the touch word offset and x value when the user // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; showMagnifier(); break; case MotionEvent.ACTION_MOVE: showMagnifier(); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: dismissMagnifier(); break; } return superResult; } Loading Loading @@ -5110,6 +5200,13 @@ public class Editor { return isRtlChar == isRtlParagraph ? primaryOffset : secondaryOffset; } } @MagnifierHandleTrigger protected int getMagnifierHandleTrigger() { return isStartHandle() ? MagnifierHandleTrigger.SELECTION_START : MagnifierHandleTrigger.SELECTION_END; } } private int getCurrentLineAdjustedForSlop(Layout layout, int prevLine, float y) { Loading
core/java/com/android/internal/widget/Magnifier.java 0 → 100644 +184 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.internal.widget; import android.annotation.FloatRange; import android.annotation.NonNull; import android.annotation.UiThread; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.PixelCopy; import android.view.View; import android.view.ViewRootImpl; import android.widget.ImageView; import android.widget.PopupWindow; import com.android.internal.R; import com.android.internal.util.Preconditions; /** * Android magnifier widget. Can be used by any view which is attached to window. */ public final class Magnifier { private static final String LOG_TAG = "magnifier"; // The view for which this magnifier is attached. private final View mView; // The window containing the magnifier. private final PopupWindow mWindow; // The center coordinates of the window containing the magnifier. private final Point mWindowCoords = new Point(); // The width of the window containing the magnifier. private final int mWindowWidth; // The height of the window containing the magnifier. private final int mWindowHeight; // The bitmap used to display the contents of the magnifier. private final Bitmap mBitmap; // The center coordinates of the content that is to be magnified. private final Point mCenterZoomCoords = new Point(); // The callback of the pixel copy request will be invoked on this Handler when // the copy is finished. private final Handler mPixelCopyHandler = Handler.getMain(); /** * Initializes a magnifier. * * @param view the view for which this magnifier is attached */ @UiThread public Magnifier(@NonNull View view) { mView = Preconditions.checkNotNull(view); final Context context = mView.getContext(); final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null); mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width); mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height); final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation); mWindow = new PopupWindow(context); mWindow.setContentView(content); mWindow.setWidth(mWindowWidth); mWindow.setHeight(mWindowHeight); mWindow.setElevation(elevation); mWindow.setTouchable(false); mWindow.setBackgroundDrawable(null); mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888); getImageView().setImageBitmap(mBitmap); } /** * Shows the magnifier on the screen. * * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source * @param centerYOnScreen vertical coordinate of the center point of the magnifier source * @param scale the scale at which the magnifier zooms on the source content */ public void show(@FloatRange(from=0) float centerXOnScreen, @FloatRange(from=0) float centerYOnScreen, @FloatRange(from=1, to=10) float scale) { maybeResizeBitmap(scale); configureCoordinates(centerXOnScreen, centerYOnScreen); performPixelCopy(); if (mWindow.isShowing()) { mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(), mWindow.getHeight()); } else { mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y); } } /** * Dismisses the magnifier from the screen. */ public void dismiss() { mWindow.dismiss(); } /** * @return the height of the magnifier window. */ public int getHeight() { return mWindowHeight; } /** * @return the width of the magnifier window. */ public int getWidth() { return mWindowWidth; } private void maybeResizeBitmap(float scale) { final int bitmapWidth = (int) (mWindowWidth / scale); final int bitmapHeight = (int) (mWindowHeight / scale); if (mBitmap.getWidth() != bitmapWidth || mBitmap.getHeight() != bitmapHeight) { mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888); getImageView().setImageBitmap(mBitmap); } } private void configureCoordinates(float posXOnScreen, float posYOnScreen) { mCenterZoomCoords.x = (int) posXOnScreen; mCenterZoomCoords.y = (int) posYOnScreen; final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize( R.dimen.magnifier_offset); final int availableTopSpace = (mCenterZoomCoords.y - mWindowHeight / 2) - verticalMagnifierOffset - (mBitmap.getHeight() / 2); mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2; mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 + verticalMagnifierOffset * (availableTopSpace > 0 ? -1 : 1); } private void performPixelCopy() { int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2; // Clamp startX value to avoid distorting the rendering of the magnifier content. if (startX < 0) { startX = 0; } else if (startX + mBitmap.getWidth() > mView.getWidth()) { startX = mView.getWidth() - mBitmap.getWidth(); } final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2; final ViewRootImpl viewRootImpl = mView.getViewRootImpl(); if (viewRootImpl != null && viewRootImpl.mSurface != null && viewRootImpl.mSurface.isValid()) { PixelCopy.request( viewRootImpl.mSurface, new Rect(startX, startY, startX + mBitmap.getWidth(), startY + mBitmap.getHeight()), mBitmap, result -> getImageView().invalidate(), mPixelCopyHandler); } else { Log.d(LOG_TAG, "Could not perform PixelCopy request"); } } private ImageView getImageView() { return mWindow.getContentView().findViewById(R.id.magnifier_image); } }
core/res/res/layout/magnifier.xml 0 → 100644 +27 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright (C) 2017 The Android Open Source Project ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. ~ You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and ~ limitations under the License --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"> <ImageView android:id="@+id/magnifier_image" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
core/res/res/values/dimens.xml +6 −0 Original line number Diff line number Diff line Loading @@ -520,6 +520,12 @@ <dimen name="floating_toolbar_vertical_margin">8dp</dimen> <dimen name="content_rect_bottom_clip_allowance">20dp</dimen> <!-- Magnifier dimensions --> <dimen name="magnifier_width">200dp</dimen> <dimen name="magnifier_height">48dp</dimen> <dimen name="magnifier_elevation">2dp</dimen> <dimen name="magnifier_offset">42dp</dimen> <dimen name="chooser_grid_padding">0dp</dimen> <!-- Spacing around the background change frome service to non-service --> <dimen name="chooser_service_spacing">8dp</dimen> Loading