From cfc22c533222f0181be7a717fbf721891d3bfd2d Mon Sep 17 00:00:00 2001 From: Gilles Debunne Date: Mon, 7 Mar 2011 15:50:47 -0800 Subject: Text handles positions' are correctly updated. Bug 3510106 The CAB animation does not fire events, which does not update the handles' positions. The solution is to make the handle update their position before any draw traversal, using an onPreDrawListener. The ScrollingPopupWindow onScroll listener is no longer needed with this change. This is also a more robust solution since other events (such as animations) may change the TextView's position. The cost is a recomputation of the Handles' positions at each redraw which was not necessary with listeners. But as mentioned before, not all possible events provide listeners or will in the future. Change-Id: I0f46118de5f660a75d95eecb2cf987fcb4b3b322 --- core/java/android/widget/PopupWindow.java | 2 +- core/java/android/widget/TextView.java | 226 +++++++++++++----------------- 2 files changed, 99 insertions(+), 129 deletions(-) diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 9002b1d..72b70bc 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -1039,7 +1039,7 @@ public class PopupWindow { * * @return true if the popup is translated upwards to fit on screen */ - boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, + private boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, int xoff, int yoff) { anchor.getLocationInWindow(mDrawingLocation); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 09c1ac5..13b9285f 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4498,8 +4498,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ canvas.restore(); - - updateCursorControllerPositions(); } private void updateCursorsPositions() { @@ -4557,15 +4555,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @hide */ protected void updateCursorControllerPositions() { - // No need to create the controllers if they were not already - if (mInsertionPointCursorController != null && - mInsertionPointCursorController.isShowing()) { - mInsertionPointCursorController.updatePosition(); - } - if (mSelectionModifierCursorController != null && - mSelectionModifierCursorController.isShowing()) { - mSelectionModifierCursorController.updatePosition(); - } + // TODO remove } @Override @@ -7356,14 +7346,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (isTextEditable() || mTextIsSelectable) { - if (mScrollX != oldScrollX || mScrollY != oldScrollY) { + if (mScrollX != oldScrollX || mScrollY != oldScrollY) { // TODO remove // Hide insertion anchor while scrolling. Leave selection. - hideInsertionPointCursorController(); - // No need to create the controller, since there is nothing to update. - if (mSelectionModifierCursorController != null && - mSelectionModifierCursorController.isShowing()) { - mSelectionModifierCursorController.updatePosition(); - } + hideInsertionPointCursorController(); // TODO any motion should hide it } if (touchIsFinished) { @@ -7373,7 +7358,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener handled |= imm != null && imm.showSoftInput(this, 0); } - boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect(); if (!selectAllGotFocus && hasSelection()) { startSelectionActionMode(); @@ -8653,26 +8637,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private class HandleView extends View implements ViewTreeObserver.OnScrollChangedListener { + private class HandleView extends View implements ViewTreeObserver.OnPreDrawListener { private Drawable mDrawable; - private final ScrollingPopupWindow mContainer; - private int mPositionX; - private int mPositionY; + private final PopupWindow mContainer; + // Position with respect to the parent TextView + private int mPositionX, mPositionY; private final CursorController mController; private boolean mIsDragging; - private float mTouchToWindowOffsetX; - private float mTouchToWindowOffsetY; + // Offset from touch position to mPosition + private float mTouchToWindowOffsetX, mTouchToWindowOffsetY; private float mHotspotX; // Offsets the hotspot point up, so that cursor is not hidden by the finger when moving up private float mTouchOffsetY; // Where the touch position should be on the handle to ensure a maximum cursor visibility private float mIdealVerticalOffset; - private int mLastParentX; - private int mLastParentY; + // Parent's (TextView) position in window + private int mLastParentX, mLastParentY; private float mDownPositionX, mDownPositionY; + // PopupWindow container absolute position with respect to the enclosing window private int mContainerPositionX, mContainerPositionY; - private long mTouchTimer; + // Visible or not (scrolled off screen), whether or not this handle should be visible + private boolean mIsActive = false; + // The insertion handle can have an associated PastePopupMenu private boolean mIsInsertionHandle = false; + // Used to detect taps on the insertion handle, which will affect the PastePopupMenu + private long mTouchTimer; private PastePopupMenu mPastePopupWindow; // Touch-up filter: number of previous positions remembered @@ -8684,12 +8673,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mPreviousOffsetIndex = 0; private int mNumberPreviousOffsets = 0; - public void startTouchUpFilter(int offset) { + private void startTouchUpFilter(int offset) { mNumberPreviousOffsets = 0; addPositionToTouchUpFilter(offset); } - public void addPositionToTouchUpFilter(int offset) { + private void addPositionToTouchUpFilter(int offset) { if (mNumberPreviousOffsets > 0 && mPreviousOffsets[mPreviousOffsetIndex] == offset) { // Make sure only actual changes of position are recorded. @@ -8702,7 +8691,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mNumberPreviousOffsets++; } - public void filterOnTouchUp() { + private void filterOnTouchUp() { final long now = SystemClock.uptimeMillis(); int i = 0; int index = mPreviousOffsetIndex; @@ -8725,16 +8714,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public HandleView(CursorController controller, int pos) { super(TextView.this.mContext); mController = controller; - mContainer = new ScrollingPopupWindow(TextView.this.mContext, null, + mContainer = new PopupWindow(TextView.this.mContext, null, com.android.internal.R.attr.textSelectHandleWindowStyle); mContainer.setSplitTouchEnabled(true); mContainer.setClippingEnabled(false); mContainer.setWindowLayoutType(WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL); + mContainer.setContentView(this); - setOrientation(pos); + setPosition(pos); } - public void setOrientation(int pos) { + private void setPosition(int pos) { int handleWidth; switch (pos) { case LEFT: { @@ -8774,38 +8764,48 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } final int handleHeight = mDrawable.getIntrinsicHeight(); - mTouchOffsetY = -0.3f * handleHeight; mIdealVerticalOffset = 0.7f * handleHeight; + invalidate(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - setMeasuredDimension(mDrawable.getIntrinsicWidth(), - mDrawable.getIntrinsicHeight()); + setMeasuredDimension(mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); } public void show() { - if (!isPositionVisible()) { - hide(); - return; - } - mContainer.setContentView(this); - mContainerPositionX = mPositionX; - mContainerPositionY = mPositionY - TextView.this.getHeight(); - mContainer.showAsDropDown(TextView.this, mContainerPositionX, mContainerPositionY); + updateContainerPosition(); + if (isShowing()) { + mContainer.update(mContainerPositionX, mContainerPositionY, + mRight - mLeft, mBottom - mTop); - // Hide paste view when handle is moved on screen. - hidePastePopupWindow(); + hidePastePopupWindow(); + } else { + mContainer.showAtLocation(TextView.this, 0, + mContainerPositionX, mContainerPositionY); + + mIsActive = true; + + ViewTreeObserver vto = TextView.this.getViewTreeObserver(); + vto.addOnPreDrawListener(this); + } } - public void hide() { + private void dismiss() { mIsDragging = false; mContainer.dismiss(); hidePastePopupWindow(); + } + + public void hide() { + dismiss(); + + mIsActive = false; + ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.removeOnScrollChangedListener(this); + vto.removeOnPreDrawListener(this); } public boolean isShowing() { @@ -8856,44 +8856,59 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private void moveTo(int x, int y) { mPositionX = x - TextView.this.mScrollX; mPositionY = y - TextView.this.mScrollY; - if (isPositionVisible()) { - int[] coords = null; - if (mContainer.isShowing()) { - final int containerPositionX = mPositionX; - final int containerPositionY = mPositionY - TextView.this.getHeight(); - - if (containerPositionX != mContainerPositionX || - containerPositionY != mContainerPositionY) { - mContainerPositionX = containerPositionX; - mContainerPositionY = containerPositionY; - - mContainer.update(TextView.this, mContainerPositionX, mContainerPositionY, - mRight - mLeft, mBottom - mTop); - - // Hide paste popup window as soon as a scroll occurs. - hidePastePopupWindow(); - } - } else { - show(); + + if (mIsDragging) { + TextView.this.getLocationInWindow(mTempCoords); + if (mTempCoords[0] != mLastParentX || mTempCoords[1] != mLastParentY) { + mTouchToWindowOffsetX += mTempCoords[0] - mLastParentX; + mTouchToWindowOffsetY += mTempCoords[1] - mLastParentY; + mLastParentX = mTempCoords[0]; + mLastParentY = mTempCoords[1]; } + // Hide paste popup window as soon as the handle is dragged. + hidePastePopupWindow(); + } + } - if (mIsDragging) { - if (coords == null) { - coords = mTempCoords; - TextView.this.getLocationInWindow(coords); + /** + * Updates the global container's position. + * @return whether or not the position has actually changed + */ + private boolean updateContainerPosition() { + // TODO Prevent this using different HandleView subclasses + mController.updateOffset(this, mController.getCurrentOffset(this)); + TextView.this.getLocationInWindow(mTempCoords); + final int containerPositionX = mTempCoords[0] + mPositionX; + final int containerPositionY = mTempCoords[1] + mPositionY; + + if (containerPositionX != mContainerPositionX || + containerPositionY != mContainerPositionY) { + mContainerPositionX = containerPositionX; + mContainerPositionY = containerPositionY; + return true; + } + return false; + } + + public boolean onPreDraw() { + if (updateContainerPosition()) { + if (isPositionVisible()) { + mContainer.update(mContainerPositionX, mContainerPositionY, + mRight - mLeft, mBottom - mTop); + + if (mIsActive && !isShowing()) { + show(); } - if (coords[0] != mLastParentX || coords[1] != mLastParentY) { - mTouchToWindowOffsetX += coords[0] - mLastParentX; - mTouchToWindowOffsetY += coords[1] - mLastParentY; - mLastParentX = coords[0]; - mLastParentY = coords[1]; + } else { + if (isShowing()) { + dismiss(); } - // Hide paste popup window as soon as the handle is dragged. - hidePastePopupWindow(); } - } else { - hide(); + + // Hide paste popup as soon as the view is scrolled or moved + hidePastePopupWindow(); } + return true; } @Override @@ -8979,7 +8994,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mIsDragging; } - void positionAtCursor(final int offset) { + void positionAtCursor(int offset) { addPositionToTouchUpFilter(offset); final int width = mDrawable.getIntrinsicWidth(); final int height = mDrawable.getIntrinsicHeight(); @@ -9013,50 +9028,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mPastePopupWindow.hide(); } } - - /** - * A popup window, attached to a view, and that listens to scroll events in its anchors' - * view hierarchy, so that it is automatically moved on such events. - */ - private class ScrollingPopupWindow extends PopupWindow { - - private int[] mDrawingLocations = new int[2]; - - public ScrollingPopupWindow(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - public boolean findDropDownPosition(View anchor, WindowManager.LayoutParams p, - int xoff, int yoff) { - anchor.getLocationInWindow(mDrawingLocations); - p.x = mDrawingLocations[0] + xoff; - p.y = mDrawingLocations[1] + anchor.getHeight() + yoff; - - // Hide paste popup as soon as the view is scrolled. - hidePastePopupWindow(); - - if (!isPositionVisible()) { - dismiss(); - onHandleBecomeInvisible(); - } - - return false; - } - } - - public void onScrollChanged() { - if (isPositionVisible()) { - show(); - ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.removeOnScrollChangedListener(this); - } - } - - public void onHandleBecomeInvisible() { - ViewTreeObserver vto = TextView.this.getViewTreeObserver(); - vto.addOnScrollChangedListener(this); - } } private class InsertionPointCursorController implements CursorController { @@ -9074,7 +9045,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public void show(int delayBeforePaste) { - updatePosition(); + getHandle().show(); hideDelayed(); removePastePopupCallback(); final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime; @@ -9213,7 +9184,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEndHandle == null) mEndHandle = new HandleView(this, HandleView.RIGHT); mIsShowing = true; - updatePosition(); mStartHandle.show(); mEndHandle.show(); -- cgit v1.1