From 12d91ce8e3e9b96329860e9fecdf3573ef17b0a1 Mon Sep 17 00:00:00 2001 From: Gilles Debunne Date: Fri, 10 Dec 2010 11:36:29 -0800 Subject: [PATCH] Added visual feedback to text correction. Change-Id: Id2b85b7b649a59a2666157c7ff540858b1ad403c --- core/java/android/widget/TextView.java | 113 ++++++++++++++++++++++++- core/res/res/values/colors.xml | 4 +- 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e4d26f0f19096..918edbe96ca97 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -32,6 +32,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Rect; @@ -4334,6 +4335,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ final InputMethodState ims = mInputMethodState; + final int cursorOffsetVertical = voffsetCursor - voffsetText; if (ims != null && ims.mBatchEditNesting == 0) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { @@ -4364,7 +4366,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener canvas.getMatrix().mapPoints(ims.mTmpOffset); ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); - ims.mTmpRectF.offset(0, voffsetCursor - voffsetText); + ims.mTmpRectF.offset(0, cursorOffsetVertical); ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), (int)(ims.mTmpRectF.top + 0.5), @@ -4378,11 +4380,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); + if (mCorrectionHighlighter != null) { + mCorrectionHighlighter.draw(canvas, cursorOffsetVertical); + } + + layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); if (mMarquee != null && mMarquee.shouldDrawGhost()) { canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); - layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); + layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } /* Comment out until we decide what to do about animations @@ -5067,7 +5073,104 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @param info The auto correct info about the text that was corrected. */ public void onCommitCorrection(CorrectionInfo info) { - // TODO + if (mCorrectionHighlighter == null) { + mCorrectionHighlighter = new CorrectionHighlighter(); + } else { + mCorrectionHighlighter.invalidate(false); + } + + mCorrectionHighlighter.highlight(info); + } + + private class CorrectionHighlighter { + private final Path mPath = new Path(); + private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private int mStart, mEnd; + private long mFadingStartTime; + private final static int FADE_OUT_DURATION = 400; + + public CorrectionHighlighter() { + mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale); + mPaint.setStyle(Paint.Style.FILL); + } + + public void highlight(CorrectionInfo info) { + mStart = info.getOffset(); + mEnd = mStart + info.getNewText().length(); + mFadingStartTime = SystemClock.uptimeMillis(); + + if (mStart < 0 || mEnd < 0) { + stopAnimation(); + } + } + + public void draw(Canvas canvas, int cursorOffsetVertical) { + if (updatePath() && updatePaint()) { + if (cursorOffsetVertical != 0) { + canvas.translate(0, cursorOffsetVertical); + } + + canvas.drawPath(mPath, mPaint); + + if (cursorOffsetVertical != 0) { + canvas.translate(0, -cursorOffsetVertical); + } + invalidate(true); + } else { + stopAnimation(); + invalidate(false); + } + } + + private boolean updatePaint() { + final long duration = SystemClock.uptimeMillis() - mFadingStartTime; + if (duration > FADE_OUT_DURATION) return false; + + final float coef = 1.0f - (float) duration / FADE_OUT_DURATION; + final int highlightColorAlpha = Color.alpha(mHighlightColor); + final int color = (mHighlightColor & 0x00FFFFFF) + + ((int) (highlightColorAlpha * coef) << 24); + mPaint.setColor(color); + return true; + } + + private boolean updatePath() { + final Layout layout = TextView.this.mLayout; + if (layout == null) return false; + + // Update in case text is edited while the animation is run + final int length = mText.length(); + int start = Math.min(length, mStart); + int end = Math.min(length, mEnd); + + mPath.reset(); + TextView.this.mLayout.getSelectionPath(start, end, mPath); + return true; + } + + private void invalidate(boolean delayed) { + if (TextView.this.mLayout == null) return; + + synchronized (sTempRect) { + mPath.computeBounds(sTempRect, false); + + int left = getCompoundPaddingLeft(); + int top = getExtendedPaddingTop() + getVerticalOffset(true); + + if (delayed) { + TextView.this.postInvalidateDelayed(16, // 60 Hz update + left + (int) sTempRect.left, top + (int) sTempRect.top, + left + (int) sTempRect.right, top + (int) sTempRect.bottom); + } else { + TextView.this.postInvalidate((int) sTempRect.left, (int) sTempRect.top, + (int) sTempRect.right, (int) sTempRect.bottom); + } + } + } + + private void stopAnimation() { + TextView.this.mCorrectionHighlighter = null; + } } public void beginBatchEdit() { @@ -9227,4 +9330,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static int DRAG_THUMBNAIL_MAX_TEXT_LENGTH = 20; // System wide time for last cut or copy action. private static long sLastCutOrCopyTime; + // Used to highlight a word when it is corrected by the IME + private CorrectionHighlighter mCorrectionHighlighter; } diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml index d64fb74329598..b1c54ff068308 100644 --- a/core/res/res/values/colors.xml +++ b/core/res/res/values/colors.xml @@ -126,8 +126,8 @@ #808080 #cc475925 #ccd2e461 - #cc475925 - #ccd2e461 + #9983CC39 + #9983CC39 #5c5cff #0000ee