New cursor controller in TextViews.

Editable TextView now display a cursor controller under the insertion
point so that it can be precisely moved.

Change-Id: Ia2e6ddc57d249647ff6683e10e4226db3df27223
This commit is contained in:
Gilles Debunne
2010-06-23 10:30:27 -07:00
parent a6935ab009
commit cc3ec6cdb2
10 changed files with 489 additions and 55 deletions

View File

@@ -166294,6 +166294,29 @@
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="setCursorController"
return="void"
abstract="false"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="cursorController" type="android.widget.TextView.CursorController">
</parameter>
</method>
<field name="mCursorController"
type="android.widget.TextView.CursorController"
transient="false"
volatile="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="protected"
>
</field>
</class>
<class name="BaseKeyListener"
extends="android.text.method.MetaKeyKeyListener"
@@ -222813,6 +222836,95 @@
>
</method>
</class>
<interface name="TextView.CursorController"
abstract="true"
static="true"
final="false"
deprecated="not deprecated"
visibility="public"
>
<method name="draw"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="canvas" type="android.graphics.Canvas">
</parameter>
</method>
<method name="getOffsetX"
return="int"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="getOffsetY"
return="int"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="hide"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="onTouchEvent"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
<parameter name="event" type="android.view.MotionEvent">
</parameter>
</method>
<method name="show"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
<method name="updatePosition"
return="void"
abstract="true"
native="false"
synchronized="false"
static="false"
final="false"
deprecated="not deprecated"
visibility="public"
>
</method>
</interface>
<interface name="TextView.OnEditorActionListener"
abstract="true"
static="true"

View File

@@ -21,8 +21,8 @@ import com.android.internal.util.ArrayUtils;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.RectF;
import android.text.Layout.Directions;
import android.text.Layout.TabStops;
import android.text.style.CharacterStyle;
@@ -583,7 +583,7 @@ class TextLine {
/**
* Returns the next valid offset within this directional run, skipping
* conjuncts and zero-width characters. This should not be called to walk
* off the end of the line, since the the returned values might not be valid
* off the end of the line, since the returned values might not be valid
* on neighboring lines. If the returned offset is less than zero or
* greater than the line length, the offset should be recomputed on the
* preceding or following line, respectively.

View File

@@ -16,23 +16,28 @@
package android.text.method;
import android.util.Log;
import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Selection;
import android.text.Spannable;
import android.view.KeyEvent;
import android.graphics.Rect;
import android.text.*;
import android.widget.TextView;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.MotionEvent;
import android.widget.TextView;
import android.widget.TextView.CursorController;
// XXX this doesn't extend MetaKeyKeyListener because the signatures
// don't match. Need to figure that out. Meanwhile the meta keys
// won't work in fields that don't take input.
public class
ArrowKeyMovementMethod
implements MovementMethod
{
public class ArrowKeyMovementMethod implements MovementMethod {
/**
* An optional controller for the cursor.
* Use {@link #setCursorController(CursorController)} to set this field.
*/
protected CursorController mCursorController;
private boolean up(TextView widget, Spannable buffer) {
boolean cap = (MetaKeyKeyListener.getMetaState(buffer,
KeyEvent.META_SHIFT_ON) == 1) ||
@@ -226,13 +231,22 @@ implements MovementMethod
return false;
}
public boolean onTrackballEvent(TextView widget, Spannable text,
MotionEvent event) {
public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) {
if (mCursorController != null) {
mCursorController.hide();
}
return false;
}
public boolean onTouchEvent(TextView widget, Spannable buffer,
MotionEvent event) {
public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) {
if (mCursorController != null) {
return onTouchEventCursor(widget, buffer, event);
} else {
return onTouchEventStandard(widget, buffer, event);
}
}
private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) {
int initialScrollX = -1, initialScrollY = -1;
if (event.getAction() == MotionEvent.ACTION_UP) {
initialScrollX = Touch.getInitialScrollX(widget, buffer);
@@ -291,13 +305,14 @@ implements MovementMethod
(MetaKeyKeyListener.getMetaState(buffer,
MetaKeyKeyListener.META_SELECTING) != 0);
if (cap && handled) {
// Before selecting, make sure we've moved out of the "slop".
// handled will be true, if we're in select mode AND we're
// OUT of the slop
// Turn long press off while we're selecting. User needs to
// re-tap on the selection to enable longpress
// re-tap on the selection to enable long press
widget.cancelLongPress();
// Update selection as we're moving the selection area.
@@ -420,6 +435,39 @@ implements MovementMethod
return handled;
}
private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) {
if (widget.isFocused() && !widget.didTouchFocusSelect()) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_MOVE:
widget.cancelLongPress();
// Offset the current touch position (from controller to cursor)
final int x = (int) event.getX() + mCursorController.getOffsetX();
final int y = (int) event.getY() + mCursorController.getOffsetY();
int offset = getOffset(x, y, widget);
Selection.setSelection(buffer, offset);
mCursorController.updatePosition();
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mCursorController = null;
return true;
}
}
return false;
}
/**
* Defines the cursor controller.
*
* When set, this object can be used to handle events, that can be translated in cursor updates.
* @param cursorController A cursor controller implementation
*/
public void setCursorController(CursorController cursorController) {
mCursorController = cursorController;
}
private static class DoubleTapState implements NoCopySpan {
long mWhen;
}

View File

@@ -17,9 +17,9 @@
package android.text.method;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.NoCopySpan;
import android.text.Spannable;
import android.text.Layout.Alignment;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
@@ -102,7 +102,7 @@ public class Touch {
MotionEvent event) {
DragState[] ds;
switch (event.getAction()) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
ds = buffer.getSpans(0, buffer.length(), DragState.class);

View File

@@ -61,6 +61,7 @@ import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.DateKeyListener;
import android.text.method.DateTimeKeyListener;
import android.text.method.DialerKeyListener;
@@ -89,10 +90,11 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewRoot;
import android.view.ViewTreeObserver;
import android.view.ViewGroup.LayoutParams;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AnimationUtils;
@@ -185,7 +187,7 @@ import java.util.ArrayList;
*/
@RemoteView
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
static final String TAG = "TextView";
static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
private static int PRIORITY = 100;
@@ -696,9 +698,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
try {
setInputExtras(a.getResourceId(attr, 0));
} catch (XmlPullParserException e) {
Log.w("TextView", "Failure reading input extras", e);
Log.w(LOG_TAG, "Failure reading input extras", e);
} catch (IOException e) {
Log.w("TextView", "Failure reading input extras", e);
Log.w(LOG_TAG, "Failure reading input extras", e);
}
break;
}
@@ -924,6 +926,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setFocusable(focusable);
setClickable(clickable);
setLongClickable(longClickable);
prepareCursorController();
}
private void setTypefaceByIndex(int typefaceIndex, int styleIndex) {
@@ -2444,7 +2448,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
restored = "(restored) ";
}
Log.e("TextView", "Saved cursor position " + ss.selStart +
Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
"/" + ss.selEnd + " out of range for " + restored +
"text " + mText);
} else {
@@ -3009,7 +3013,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else {
input = TextKeyListener.getInstance();
}
mInputType = type;
setRawInputType(type);
if (direct) mInput = input;
else {
setKeyListenerOnly(input);
@@ -4089,6 +4093,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
canvas.restore();
if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.draw(canvas);
}
}
@Override
@@ -4607,7 +4615,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (req != null) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start="
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start="
+ ims.mChangedStart + " end=" + ims.mChangedEnd
+ " delta=" + ims.mChangedDelta);
if (ims.mChangedStart < 0 && !contentChanged) {
@@ -4615,7 +4623,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd,
ims.mChangedDelta, ims.mTmpExtracted)) {
if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start="
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start="
+ ims.mTmpExtracted.partialStartOffset
+ " end=" + ims.mTmpExtracted.partialEndOffset
+ ": " + ims.mTmpExtracted.text);
@@ -4784,7 +4792,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
makeBlink();
}
}
checkForResize();
}
@@ -5508,7 +5516,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// FIXME: Is it okay to truncate this, or should we round?
final int x = (int)mLayout.getPrimaryHorizontal(offset);
final int top = mLayout.getLineTop(line);
final int bottom = mLayout.getLineTop(line+1);
final int bottom = mLayout.getLineTop(line + 1);
int left = (int) FloatMath.floor(mLayout.getLineLeft(line));
int right = (int) FloatMath.ceil(mLayout.getLineRight(line));
@@ -5645,8 +5653,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// viewport coordinates, but requestRectangleOnScreen()
// is in terms of content coordinates.
Rect r = new Rect();
getInterestingRect(r, x, top, bottom, line);
Rect r = new Rect(x, top, x + 1, bottom);
getInterestingRect(r, line);
r.offset(mScrollX, mScrollY);
if (requestRectangleOnScreen(r)) {
@@ -5680,7 +5688,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int line = mLayout.getLineForOffset(start);
final int top = mLayout.getLineTop(line);
final int bottom = mLayout.getLineTop(line+1);
final int bottom = mLayout.getLineTop(line + 1);
final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
int vslack = (bottom - top) / 2;
if (vslack > vspace / 4)
@@ -5730,22 +5738,28 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
private void getInterestingRect(Rect r, int h, int top, int bottom,
int line) {
private void getInterestingRect(Rect r, int line) {
convertFromViewportToContentCoordinates(r);
// Rectangle can can be expanded on first and last line to take
// padding into account.
// TODO Take left/right padding into account too?
if (line == 0) r.top -= getExtendedPaddingTop();
if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
}
private void convertFromViewportToContentCoordinates(Rect r) {
int paddingTop = getExtendedPaddingTop();
if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
paddingTop += getVerticalOffset(false);
}
top += paddingTop;
bottom += paddingTop;
h += getCompoundPaddingLeft();
r.top += paddingTop;
r.bottom += paddingTop;
if (line == 0)
top -= getExtendedPaddingTop();
if (line == mLayout.getLineCount() - 1)
bottom += getExtendedPaddingBottom();
int paddingLeft = getCompoundPaddingLeft();
r.left += paddingLeft;
r.right += paddingLeft;
r.set(h, top, h+1, bottom);
r.offset(-mScrollX, -mScrollY);
}
@@ -5913,6 +5927,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} else if (mBlink != null) {
mBlink.removeCallbacks(mBlink);
}
prepareCursorController();
}
private boolean canMarquee() {
@@ -6327,7 +6342,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
} else {
if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: "
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
+ oldStart + "-" + oldEnd + ","
+ newStart + "-" + newEnd + what);
ims.mContentChanged = true;
@@ -6343,7 +6358,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void beforeTextChanged(CharSequence buffer, int start,
int before, int after) {
if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
if (AccessibilityManager.getInstance(mContext).isEnabled()
@@ -6356,7 +6371,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void onTextChanged(CharSequence buffer, int start,
int before, int after) {
if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
+ " before=" + before + " after=" + after + ": " + buffer);
TextView.this.handleTextChanged(buffer, start, before, after);
@@ -6366,10 +6381,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
mBeforeText = null;
}
// TODO. The cursor controller should hide as soon as text is typed.
// But this method is also used for cosmetic changes (underline current word when
// spell corrections are displayed. There is currently no way to make the difference
// between these cosmetic changes and actual text modifications.
}
public void afterTextChanged(Editable buffer) {
if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer);
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
TextView.this.sendAfterTextChanged(buffer);
if (MetaKeyKeyListener.getMetaState(buffer,
@@ -6380,19 +6400,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void onSpanChanged(Spannable buf,
Object what, int s, int e, int st, int en) {
if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
+ " st=" + st + " en=" + en + " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, s, st, e, en);
}
public void onSpanAdded(Spannable buf, Object what, int s, int e) {
if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
+ " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, -1, s, -1, e);
}
public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e
if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
+ " what=" + what + ": " + buf);
TextView.this.spanChange(buf, what, s, -1, e, -1);
}
@@ -6589,6 +6609,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (resultCode == InputMethodManager.RESULT_SHOWN) {
start = mPrevStart;
end = mPrevEnd;
} else if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.show();
}
final int len = mText.length();
@@ -6631,12 +6653,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int oldSelStart = Selection.getSelectionStart(mText);
int oldSelEnd = Selection.getSelectionEnd(mText);
if (mInsertionPointCursorController != null) {
mInsertionPointCursorController.onTouchEvent(event);
}
if (mMovement != null) {
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
if (mText instanceof Editable && onCheckIsTextEditor()) {
if (isTextEditable()) {
if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) {
InputMethodManager imm = (InputMethodManager)
getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -6650,7 +6676,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
newSelStart, newSelEnd);
}
handled = imm.showSoftInput(this, 0, csr) && (csr != null);
handled |= imm.showSoftInput(this, 0, csr) && (csr != null);
}
}
@@ -6662,6 +6688,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return superResult;
}
private void prepareCursorController() {
// TODO Add an extra android:cursorController flag to disable the controller?
mInsertionPointCursorController =
mCursorVisible ? new InsertionPointCursorController() : null;
}
/**
* @return True iff this TextView contains a text that can be edited.
*/
private boolean isTextEditable() {
return mText instanceof Editable && onCheckIsTextEditor();
}
/**
* Returns true, only while processing a touch gesture, if the initial
* touch down event caused focus to move to the text view and as a result
@@ -7341,6 +7380,229 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
/**
* A CursorController instance can be used to control a cursor in the text.
*
* It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events
* and send them to this object instead of the cursor.
*/
public interface CursorController {
/**
* Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}.
* See also {@link #hide()}.
*/
public void show();
/**
* Hide the cursor controller from screen.
* See also {@link #show()}.
*/
public void hide();
/**
* Update the controller's position.
*/
public void updatePosition();
/**
* The controller and the cursor's positions can be link by a fixed offset,
* computed when the controller is touched, and then maintained as it moves
* @return Horizontal offset between the controller and the cursor.
*/
public int getOffsetX();
/**
* @return Vertical offset between the controller and the cursor.
*/
public int getOffsetY();
/**
* This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller
* a chance to become active and/or visible.
* @param event The touch event
*/
public void onTouchEvent(MotionEvent event);
/**
* Draws a visual representation of the controller on the canvas.
*
* Called at the end of {@link #draw(Canvas)}, in the content coordinates system.
* @param canvas The Canvas used by this TextView.
*/
public void draw(Canvas canvas);
}
class InsertionPointCursorController implements CursorController {
private static final int DELAY_BEFORE_FADE_OUT = 2100;
private static final int FADE_OUT_DURATION = 400;
// Whether or not the cursor control is currently visible
private boolean mIsVisible = false;
// Current cursor control bounds, in content coordinates
private final Rect mBounds = new Rect();
// Starting time of the fade timer
private long mFadeOutTimerStart;
// The cursor controller image
private final Drawable mDrawable;
// Used to detect a tap (vs drag) on the controller
private long mOnDownTimerStart;
// Offset between finger hot point on cursor controller and actual cursor
private int mOffsetX, mOffsetY;
InsertionPointCursorController() {
Resources res = mContext.getResources();
mDrawable = res.getDrawable(com.android.internal.R.drawable.cursor_controller);
}
public void show() {
updatePosition();
// Has to be done after updatePosition, so that previous position invalidate
// in only done if necessary.
mIsVisible = true;
}
public void hide() {
if (mIsVisible) {
long time = System.currentTimeMillis();
// Start fading out, only if not already in progress
if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) {
mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT;
postInvalidate(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
}
}
}
public void draw(Canvas canvas) {
if (mIsVisible) {
int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart);
if (time <= DELAY_BEFORE_FADE_OUT) {
postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time,
mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
} else {
time -= DELAY_BEFORE_FADE_OUT;
if (time <= FADE_OUT_DURATION) {
int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION;
mDrawable.setAlpha(alpha);
postInvalidateDelayed(30,
mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
} else {
mDrawable.setAlpha(0);
mIsVisible = false;
}
}
mDrawable.draw(canvas);
}
}
public void updatePosition() {
if (mIsVisible) {
// Clear previous cursor controller before bounds are updated
postInvalidate(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
}
final int offset = Selection.getSelectionStart(mText);
if (offset < 0) {
// Should never happen, safety check.
Log.w(LOG_TAG, "Update cursor controller position called with no cursor", null);
mIsVisible = false;
return;
}
final int cursorControllerDrawableWidth = mDrawable.getIntrinsicWidth();
final int cursorControllerDrawableHeight = mDrawable.getIntrinsicHeight();
final int line = mLayout.getLineForOffset(offset);
mBounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - 0.5 -
cursorControllerDrawableWidth / 2.0);
mBounds.top = mLayout.getLineTop(line + 1);
// Move cursor controller a little bit up when editing the last line of text
// (or a single line) so that it is visible and easier to grab.
if (line == mLayout.getLineCount() - 1) {
mBounds.top -= Math.max(0,
cursorControllerDrawableHeight / 2 - getExtendedPaddingBottom());
}
mBounds.right = mBounds.left + cursorControllerDrawableWidth;
mBounds.bottom = mBounds.top + cursorControllerDrawableHeight;
convertFromViewportToContentCoordinates(mBounds);
mDrawable.setBounds(mBounds);
mFadeOutTimerStart = System.currentTimeMillis();
mDrawable.setAlpha(255);
postInvalidate(mBounds.left, mBounds.top, mBounds.right, mBounds.bottom);
}
public void onTouchEvent(MotionEvent event) {
if (isFocused() && isTextEditable()) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN && mIsVisible) {
final int x = (int) event.getX();
final int y = (int) event.getY();
// Simulate a 'fat finger' to ease grabbing of the controller.
// Expand according to controller image size instead of using density.
// Assume controller imager has a sensible size, proportionnal to density.
final int cursorControllerDrawableWidth = mDrawable.getIntrinsicWidth();
final int cursorControllerDrawableHeight = mDrawable.getIntrinsicHeight();
final Rect fingerRect = new Rect(
x - cursorControllerDrawableWidth / 2,
y - cursorControllerDrawableHeight,
x + cursorControllerDrawableWidth / 2,
y);
if (Rect.intersects(mBounds, fingerRect)) {
show();
if (mMovement instanceof ArrowKeyMovementMethod) {
((ArrowKeyMovementMethod)mMovement).setCursorController(this);
}
if (mParent != null) {
// Prevent possible scrollView parent from scrolling, so that
// we can use auto-scrolling.
mParent.requestDisallowInterceptTouchEvent(true);
Resources res = mContext.getResources();
final int verticalOffset = res.getDimensionPixelOffset(
com.android.internal.R.dimen.cursor_controller_vertical_offset);
mOffsetX = (mBounds.left + mBounds.right) / 2 - x;
mOffsetY = mBounds.top - verticalOffset - y;
mOnDownTimerStart = System.currentTimeMillis();
}
}
} else if (event.getActionMasked() == MotionEvent.ACTION_UP) {
int time = (int) (System.currentTimeMillis() - mOnDownTimerStart);
if (mIsVisible && (time <= ViewConfiguration.getTapTimeout())) {
// A tap on the controller is not grabbed, move the cursor instead
final int x = (int) event.getX();
final int y = (int) event.getY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int offset = layout.getOffsetForHorizontal(line, x);
Selection.setSelection((Spannable) mText, offset);
// Modified by cancelLongPress and prevents the cursor from changing
mScrolled = false;
}
}
}
}
public int getOffsetX() {
return mOffsetX;
}
public int getOffsetY() {
return mOffsetY;
}
}
@ViewDebug.ExportedProperty
private CharSequence mText;
private CharSequence mTransformed;
@@ -7369,6 +7631,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private Blink mBlink;
private boolean mCursorVisible = true;
// Cursor Controller. Null when disabled.
private CursorController mInsertionPointCursorController;
private boolean mSelectAllOnFocus = false;
private int mGravity = Gravity.TOP | Gravity.LEFT;

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -45,4 +45,6 @@
<dimen name="password_keyboard_key_height">56dip</dimen>
<!-- Default correction for the space key in the password keyboard -->
<dimen name="password_keyboard_spacebar_vertical_correction">4dip</dimen>
<!-- Distance between the text base line and virtual finger position used to position cursor -->
<dimen name="cursor_controller_vertical_offset">12dp</dimen>
</resources>

View File

@@ -126,6 +126,7 @@ public class Canvas {
*
* @deprecated This constructor is not supported and should not be invoked.
*/
@Deprecated
public Canvas(GL gl) {
mNativeCanvas = initGL();
mGL = gl;
@@ -151,6 +152,7 @@ public class Canvas {
*
* @deprecated This method is not supported and should not be invoked.
*/
@Deprecated
public GL getGL() {
return mGL;
}
@@ -162,6 +164,7 @@ public class Canvas {
*
* @deprecated This method is not supported and should not be invoked.
*/
@Deprecated
public static void freeGlCaches() {
freeCaches();
}
@@ -198,6 +201,7 @@ public class Canvas {
*
* @deprecated This method is not supported and should not be invoked.
*/
@Deprecated
public void setViewport(int width, int height) {
if (mGL != null) {
nativeSetViewport(mNativeCanvas, width, height);
@@ -415,8 +419,8 @@ public class Canvas {
*
* @param sx The amount to scale in X
* @param sy The amount to scale in Y
* @param px The x-coord for the pivot point (unchanged by the rotation)
* @param py The y-coord for the pivot point (unchanged by the rotation)
* @param px The x-coord for the pivot point (unchanged by the scale)
* @param py The y-coord for the pivot point (unchanged by the scale)
*/
public final void scale(float sx, float sy, float px, float py) {
translate(px, py);
@@ -1585,6 +1589,7 @@ public class Canvas {
restore();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
// If the constructor threw an exception before setting mNativeCanvas, the native finalizer

View File

@@ -75,6 +75,7 @@ public final class Rect implements Parcelable {
bottom = r.bottom;
}
@Override
public boolean equals(Object obj) {
Rect r = (Rect) obj;
if (r != null) {
@@ -84,6 +85,7 @@ public final class Rect implements Parcelable {
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(32);
sb.append("Rect("); sb.append(left); sb.append(", ");
@@ -351,7 +353,7 @@ public final class Rect implements Parcelable {
* rectangle, return true and set this rectangle to that intersection,
* otherwise return false and do not change this rectangle. No check is
* performed to see if either rectangle is empty. Note: To just test for
* intersection, use intersects()
* intersection, use {@link #intersects(Rect, Rect)}.
*
* @param left The left side of the rectangle being intersected with this
* rectangle
@@ -445,7 +447,7 @@ public final class Rect implements Parcelable {
/**
* Returns true iff the two specified rectangles intersect. In no event are
* either of the rectangles modified. To record the intersection,
* use intersect() or setIntersect().
* use {@link #intersect(Rect)} or {@link #setIntersect(Rect, Rect)}.
*
* @param a The first rectangle being tested for intersection
* @param b The second rectangle being tested for intersection