Hysteresis effect in Text selection.

Vertical movement requires going over a given threshold to change line.
Makes it easier to move down without changing line, so that one can see the
cursor better. Also simplifies long line selection.

Change-Id: I791da500232c6e510af64c637ed994c5da9a4fea
This commit is contained in:
Gilles Debunne
2010-08-23 14:55:06 -07:00
parent 0c76c7c5ee
commit 3e05a0beb2
3 changed files with 67 additions and 41 deletions

View File

@@ -223015,7 +223015,9 @@
deprecated="not deprecated"
visibility="public"
>
<parameter name="offset" type="int">
<parameter name="x" type="int">
</parameter>
<parameter name="y" type="int">
</parameter>
</method>
<field name="FADE_OUT_DURATION"

View File

@@ -287,8 +287,7 @@ public class ArrowKeyMovementMethod implements MovementMethod {
// Offset the current touch position (from controller to cursor)
final float x = event.getX() + mCursorController.getOffsetX();
final float y = event.getY() + mCursorController.getOffsetY();
int offset = widget.getOffset((int) x, (int) y);
mCursorController.updatePosition(offset);
mCursorController.updatePosition((int) x, (int) y);
return true;
case MotionEvent.ACTION_UP:

View File

@@ -7475,7 +7475,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Update the controller's position.
*/
public void updatePosition(int offset);
public void updatePosition(int x, int y);
/**
* The controller and the cursor's positions can be link by a fixed offset,
@@ -7510,7 +7510,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Vertical extension of the touch region
int mTopExtension, mBottomExtension;
// Position of the virtual finger position on screen
int mHopSpotVertcalPosition;
int mHotSpotVerticalPosition;
Handle(Drawable drawable) {
mDrawable = drawable;
@@ -7523,8 +7523,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int lineTop = mLayout.getLineTop(line);
final int lineBottom = mLayout.getLineBottom(line);
mHopSpotVertcalPosition = lineTop + (bottom ? (3 * (lineBottom - lineTop)) / 4 :
(lineBottom - lineTop) / 4);
mHotSpotVerticalPosition = lineTop;
final Rect bounds = sCursorControllerTempRect;
bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0);
@@ -7544,7 +7543,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int boundTopBefore = bounds.top;
convertFromViewportToContentCoordinates(bounds);
mHopSpotVertcalPosition += bounds.top - boundTopBefore;
mHotSpotVerticalPosition += bounds.top - boundTopBefore;
mDrawable.setBounds(bounds);
postInvalidate();
}
@@ -7570,7 +7569,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
void postInvalidateDelayed(long delay) {
final Rect bounds = mDrawable.getBounds();
TextView.this.postInvalidateDelayed(delay, bounds.left, bounds.top,
bounds.right, bounds.bottom);
bounds.right, bounds.bottom);
}
}
@@ -7595,7 +7594,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void show() {
updateDrawablePosition();
// Has to be done after updatePosition, so that previous position invalidate
// Has to be done after updateDrawablePosition, so that previous position invalidate
// in only done if necessary.
mIsVisible = true;
}
@@ -7620,7 +7619,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
time -= DELAY_BEFORE_FADE_OUT;
if (time <= FADE_OUT_DURATION) {
final int alpha = (int)
((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION);
((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION);
mHandle.mDrawable.setAlpha(alpha);
mHandle.postInvalidateDelayed(30);
} else {
@@ -7632,12 +7631,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
public void updatePosition(int offset) {
if (offset == getSelectionStart()) {
return; // No change, no need to redraw
public void updatePosition(int x, int y) {
final int previousOffset = getSelectionStart();
int offset = getHysteresisOffset(x, y, previousOffset);
if (offset != previousOffset) {
Selection.setSelection((Spannable) mText, offset);
updateDrawablePosition();
}
Selection.setSelection((Spannable) mText, offset);
updateDrawablePosition();
}
private void updateDrawablePosition() {
@@ -7683,7 +7684,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final Rect bounds = mHandle.mDrawable.getBounds();
mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
mOffsetY = mHandle.mHopSpotVertcalPosition - y;
mOffsetY = mHandle.mHotSpotVerticalPosition - y;
mOnDownTimerStart = event.getEventTime();
}
@@ -7738,7 +7739,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void show() {
updateDrawablesPositions();
// Has to be done after updatePosition, so that previous position invalidate
// Has to be done after updateDrawablePositions, so that previous position invalidate
// in only done if necessary.
mIsVisible = true;
mFadeOutTimerStart = -1;
@@ -7780,10 +7781,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
public void updatePosition(int offset) {
public void updatePosition(int x, int y) {
int selectionStart = getSelectionStart();
int selectionEnd = getSelectionEnd();
final int previousOffset = mStartIsDragged ? selectionStart : selectionEnd;
int offset = getHysteresisOffset(x, y, previousOffset);
// Handle the case where start and end are swapped, making sure start <= end
if (mStartIsDragged) {
if (offset <= selectionEnd) {
@@ -7865,7 +7869,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final Handle draggedHandle = mStartIsDragged ? mStartHandle : mEndHandle;
final Rect bounds = draggedHandle.mDrawable.getBounds();
mOffsetX = (bounds.left + bounds.right) / 2.0f - x;
mOffsetY = draggedHandle.mHopSpotVertcalPosition - y;
mOffsetY = draggedHandle.mHotSpotVerticalPosition - y;
((ArrowKeyMovementMethod)mMovement).setCursorController(this);
}
@@ -7935,6 +7939,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
stopTextSelectionMode();
}
private int getOffsetForHorizontal(int line, int x) {
x -= getTotalPaddingLeft();
// Clamp the position to inside of the view.
x = Math.max(0, x);
x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
x += getScrollX();
return getLayout().getOffsetForHorizontal(line, x);
}
/**
* Get the offset character closest to the specified absolute position.
*
@@ -7946,32 +7959,44 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
public int getOffset(int x, int y) {
x -= getTotalPaddingLeft();
if (getLayout() == null) return -1;
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
if (x < 0) {
x = 0;
} else if (x >= (getWidth() - getTotalPaddingRight())) {
x = getWidth()-getTotalPaddingRight() - 1;
}
if (y < 0) {
y = 0;
} else if (y >= (getHeight() - getTotalPaddingBottom())) {
y = getHeight()-getTotalPaddingBottom() - 1;
}
x += getScrollX();
y = Math.max(0, y);
y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
y += getScrollY();
Layout layout = getLayout();
if (layout != null) {
final int line = layout.getLineForVertical(y);
final int offset = layout.getOffsetForHorizontal(line, x);
return offset;
} else {
return -1;
final int line = getLayout().getLineForVertical(y);
final int offset = getOffsetForHorizontal(line, x);
return offset;
}
int getHysteresisOffset(int x, int y, int previousOffset) {
final Layout layout = getLayout();
if (layout == null) return -1;
y -= getTotalPaddingTop();
// Clamp the position to inside of the view.
y = Math.max(0, y);
y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
y += getScrollY();
int line = getLayout().getLineForVertical(y);
final int previousLine = layout.getLineForOffset(previousOffset);
final int previousLineTop = layout.getLineTop(previousLine);
final int previousLineBottom = layout.getLineBottom(previousLine);
final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2;
// If new line is just before or after previous line and y position is less than
// hysteresisThreshold away from previous line, keep cursor on previous line.
if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) ||
((line == previousLine - 1) && ((previousLineTop - y) < hysteresisThreshold))) {
line = previousLine;
}
return getOffsetForHorizontal(line, x);
}
@ViewDebug.ExportedProperty