diff options
| author | Jean-Baptiste Queru <jbq@google.com> | 2009-07-25 19:52:22 -0700 |
|---|---|---|
| committer | Jean-Baptiste Queru <jbq@google.com> | 2009-07-25 21:15:25 -0700 |
| commit | 2af1b3db3d4f687d008db74b150f149e956b4bc6 (patch) | |
| tree | 39d7d5bf15667c01f9b6dfe02bdd0e7fa36cd303 /core/java/android/widget/TextView.java | |
| parent | 8ecb36eec61f119f500a805b82438aadb3396a19 (diff) | |
| parent | cf4550c3198d6b3d92cdc52707fe70d7cc0caa9f (diff) | |
Merge korg/donut into korg/master
Diffstat (limited to 'core/java/android/widget/TextView.java')
| -rw-r--r-- | core/java/android/widget/TextView.java | 420 |
1 files changed, 332 insertions, 88 deletions
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 58d688cb1570..7eeb1cab439d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -16,6 +16,11 @@ package android.widget; +import com.android.internal.util.FastMath; +import com.android.internal.widget.EditableInputConnection; + +import org.xmlpull.v1.XmlPullParserException; + import android.content.Context; import android.content.Intent; import android.content.res.ColorStateList; @@ -31,17 +36,17 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; +import android.os.Message; import android.os.Parcel; import android.os.Parcelable; import android.os.ResultReceiver; import android.os.SystemClock; -import android.os.Message; import android.text.BoringLayout; +import android.text.ClipboardManager; import android.text.DynamicLayout; import android.text.Editable; import android.text.GetChars; import android.text.GraphicsOperations; -import android.text.ClipboardManager; import android.text.InputFilter; import android.text.InputType; import android.text.Layout; @@ -49,9 +54,9 @@ import android.text.ParcelableSpan; import android.text.Selection; import android.text.SpanWatcher; import android.text.Spannable; +import android.text.SpannableString; import android.text.Spanned; import android.text.SpannedString; -import android.text.SpannableString; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; @@ -64,19 +69,18 @@ import android.text.method.KeyListener; import android.text.method.LinkMovementMethod; import android.text.method.MetaKeyKeyListener; import android.text.method.MovementMethod; -import android.text.method.TimeKeyListener; - import android.text.method.PasswordTransformationMethod; import android.text.method.SingleLineTransformationMethod; import android.text.method.TextKeyListener; +import android.text.method.TimeKeyListener; import android.text.method.TransformationMethod; import android.text.style.ParagraphStyle; import android.text.style.URLSpan; import android.text.style.UpdateAppearance; import android.text.util.Linkify; import android.util.AttributeSet; -import android.util.Log; import android.util.FloatMath; +import android.util.Log; import android.util.TypedValue; import android.view.ContextMenu; import android.view.Gravity; @@ -89,25 +93,22 @@ import android.view.ViewDebug; 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; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; import android.widget.RemoteViews.RemoteView; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; -import com.android.internal.util.FastMath; -import com.android.internal.widget.EditableInputConnection; - -import org.xmlpull.v1.XmlPullParserException; - /** * Displays text to the user and optionally allows them to edit it. A TextView * is a complete text editor, however the basic class is configured to not @@ -126,6 +127,8 @@ import org.xmlpull.v1.XmlPullParserException; * @attr ref android.R.styleable#TextView_textColor * @attr ref android.R.styleable#TextView_textColorHighlight * @attr ref android.R.styleable#TextView_textColorHint + * @attr ref android.R.styleable#TextView_textAppearance + * @attr ref android.R.styleable#TextView_textColorLink * @attr ref android.R.styleable#TextView_textSize * @attr ref android.R.styleable#TextView_textScaleX * @attr ref android.R.styleable#TextView_typeface @@ -163,13 +166,22 @@ import org.xmlpull.v1.XmlPullParserException; * @attr ref android.R.styleable#TextView_capitalize * @attr ref android.R.styleable#TextView_autoText * @attr ref android.R.styleable#TextView_editable + * @attr ref android.R.styleable#TextView_freezesText + * @attr ref android.R.styleable#TextView_ellipsize * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableBottom * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableLeft + * @attr ref android.R.styleable#TextView_drawablePadding * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier * @attr ref android.R.styleable#TextView_marqueeRepeatLimit + * @attr ref android.R.styleable#TextView_inputType + * @attr ref android.R.styleable#TextView_imeOptions + * @attr ref android.R.styleable#TextView_privateImeOptions + * @attr ref android.R.styleable#TextView_imeActionLabel + * @attr ref android.R.styleable#TextView_imeActionId + * @attr ref android.R.styleable#TextView_editorExtras */ @RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { @@ -404,6 +416,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean singleLine = false; int maxlength = -1; CharSequence text = ""; + CharSequence hint = null; int shadowcolor = 0; float dx = 0, dy = 0, r = 0; boolean password = false; @@ -531,7 +544,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case com.android.internal.R.styleable.TextView_hint: - setHint(a.getText(attr)); + hint = a.getText(attr); break; case com.android.internal.R.styleable.TextView_text: @@ -861,6 +874,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } setText(text, bufferType); + if (hint != null) setHint(hint); /* * Views are not normally focusable unless specified to be. @@ -1328,9 +1342,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { // We need to retain the last set padding, so just clear // out all of the fields in the existing structure. + if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null); dr.mDrawableLeft = null; + if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); dr.mDrawableTop = null; + if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null); dr.mDrawableRight = null; + if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); dr.mDrawableBottom = null; dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; @@ -1343,19 +1361,32 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mDrawables = dr = new Drawables(); } + if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) { + dr.mDrawableLeft.setCallback(null); + } dr.mDrawableLeft = left; + if (dr.mDrawableTop != left && dr.mDrawableTop != null) { + dr.mDrawableTop.setCallback(null); + } dr.mDrawableTop = top; + if (dr.mDrawableRight != left && dr.mDrawableRight != null) { + dr.mDrawableRight.setCallback(null); + } dr.mDrawableRight = right; + if (dr.mDrawableBottom != left && dr.mDrawableBottom != null) { + dr.mDrawableBottom.setCallback(null); + } dr.mDrawableBottom = bottom; final Rect compoundRect = dr.mCompoundRect; - int[] state = null; + int[] state; state = getDrawableState(); if (left != null) { left.setState(state); left.copyBounds(compoundRect); + left.setCallback(this); dr.mDrawableSizeLeft = compoundRect.width(); dr.mDrawableHeightLeft = compoundRect.height(); } else { @@ -1365,6 +1396,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (right != null) { right.setState(state); right.copyBounds(compoundRect); + right.setCallback(this); dr.mDrawableSizeRight = compoundRect.width(); dr.mDrawableHeightRight = compoundRect.height(); } else { @@ -1374,6 +1406,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (top != null) { top.setState(state); top.copyBounds(compoundRect); + top.setCallback(this); dr.mDrawableSizeTop = compoundRect.height(); dr.mDrawableWidthTop = compoundRect.width(); } else { @@ -1383,6 +1416,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (bottom != null) { bottom.setState(state); bottom.copyBounds(compoundRect); + bottom.setCallback(this); dr.mDrawableSizeBottom = compoundRect.height(); dr.mDrawableWidthBottom = compoundRect.width(); } else { @@ -1641,6 +1675,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @android.view.RemotableViewMethod public void setTextScaleX(float size) { if (size != mTextPaint.getTextScaleX()) { + mUserSetTextScaleX = true; mTextPaint.setTextScaleX(size); if (mLayout != null) { @@ -2511,6 +2546,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener text = ""; } + if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); + if (text instanceof Spanned && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { setHorizontalFadingEdgeEnabled(true); @@ -2782,8 +2819,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener checkForRelayout(); } - if (mText.length() == 0) + if (mText.length() == 0) { invalidate(); + } } /** @@ -3643,12 +3681,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override protected boolean isPaddingOffsetRequired() { - return mShadowRadius != 0; + return mShadowRadius != 0 || mDrawables != null; } @Override protected int getLeftPaddingOffset() { - return (int) Math.min(0, mShadowDx - mShadowRadius); + return getCompoundPaddingLeft() - mPaddingLeft + + (int) Math.min(0, mShadowDx - mShadowRadius); } @Override @@ -3663,7 +3702,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override protected int getRightPaddingOffset() { - return (int) Math.max(0, mShadowDx + mShadowRadius); + return -(getCompoundPaddingRight() - mPaddingRight) + + (int) Math.max(0, mShadowDx + mShadowRadius); } @Override @@ -3677,6 +3717,54 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override + public void invalidateDrawable(Drawable drawable) { + if (verifyDrawable(drawable)) { + final Rect dirty = drawable.getBounds(); + int scrollX = mScrollX; + int scrollY = mScrollY; + + // IMPORTANT: The coordinates below are based on the coordinates computed + // for each compound drawable in onDraw(). Make sure to update each section + // accordingly. + final TextView.Drawables drawables = mDrawables; + if (drawables != null) { + if (drawable == drawables.mDrawableLeft) { + final int compoundPaddingTop = getCompoundPaddingTop(); + final int compoundPaddingBottom = getCompoundPaddingBottom(); + final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; + + scrollX += mPaddingLeft; + scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; + } else if (drawable == drawables.mDrawableRight) { + final int compoundPaddingTop = getCompoundPaddingTop(); + final int compoundPaddingBottom = getCompoundPaddingBottom(); + final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; + + scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); + scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; + } else if (drawable == drawables.mDrawableTop) { + final int compoundPaddingLeft = getCompoundPaddingLeft(); + final int compoundPaddingRight = getCompoundPaddingRight(); + final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; + + scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; + scrollY += mPaddingTop; + } else if (drawable == drawables.mDrawableBottom) { + final int compoundPaddingLeft = getCompoundPaddingLeft(); + final int compoundPaddingRight = getCompoundPaddingRight(); + final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; + + scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; + scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); + } + } + + invalidate(dirty.left + scrollX, dirty.top + scrollY, + dirty.right + scrollX, dirty.bottom + scrollY); + } + } + + @Override protected void onDraw(Canvas canvas) { restartMarqueeIfNeeded(); @@ -3704,6 +3792,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. if (dr.mDrawableLeft != null) { canvas.save(); canvas.translate(scrollX + mPaddingLeft, @@ -3713,6 +3803,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener canvas.restore(); } + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. if (dr.mDrawableRight != null) { canvas.save(); canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight, @@ -3721,6 +3813,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener canvas.restore(); } + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. if (dr.mDrawableTop != null) { canvas.save(); canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2, @@ -3729,6 +3823,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener canvas.restore(); } + // IMPORTANT: The coordinates computed are also used in invalidateDrawable() + // Make sure to update invalidateDrawable() when changing this code. if (dr.mDrawableBottom != null) { canvas.save(); canvas.translate(scrollX + compoundPaddingLeft + @@ -3920,6 +4016,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); + if (mMarquee != null && mMarquee.shouldDrawGhost()) { + canvas.translate((int) mMarquee.getGhostOffset(), 0.0f); + layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); + } + /* Comment out until we decide what to do about animations if (currentTransformation != null) { mTextPaint.setLinearTextOn(isLinearTextOn); @@ -4426,29 +4527,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean reportExtractedText() { final InputMethodState ims = mInputMethodState; - final boolean contentChanged = ims.mContentChanged; - if (ims != null && (contentChanged || ims.mSelectionModeChanged)) { - ims.mContentChanged = false; - ims.mSelectionModeChanged = false; - final ExtractedTextRequest req = mInputMethodState.mExtracting; - if (req != null) { - InputMethodManager imm = InputMethodManager.peekInstance(); - if (imm != null) { - if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start=" - + ims.mChangedStart + " end=" + ims.mChangedEnd - + " delta=" + ims.mChangedDelta); - if (ims.mChangedStart < 0 && !contentChanged) { - ims.mChangedStart = EXTRACT_NOTHING; - } - if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, - ims.mChangedDelta, ims.mTmpExtracted)) { - if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start=" - + ims.mTmpExtracted.partialStartOffset - + " end=" + ims.mTmpExtracted.partialEndOffset - + ": " + ims.mTmpExtracted.text); - imm.updateExtractedText(this, req.token, - mInputMethodState.mTmpExtracted); - return true; + if (ims != null) { + final boolean contentChanged = ims.mContentChanged; + if (contentChanged || ims.mSelectionModeChanged) { + ims.mContentChanged = false; + ims.mSelectionModeChanged = false; + final ExtractedTextRequest req = mInputMethodState.mExtracting; + if (req != null) { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start=" + + ims.mChangedStart + " end=" + ims.mChangedEnd + + " delta=" + ims.mChangedDelta); + if (ims.mChangedStart < 0 && !contentChanged) { + ims.mChangedStart = EXTRACT_NOTHING; + } + if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, + ims.mChangedDelta, ims.mTmpExtracted)) { + if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start=" + + ims.mTmpExtracted.partialStartOffset + + " end=" + ims.mTmpExtracted.partialEndOffset + + ": " + ims.mTmpExtracted.text); + imm.updateExtractedText(this, req.token, + mInputMethodState.mTmpExtracted); + return true; + } } } } @@ -4704,10 +4807,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener alignment = Layout.Alignment.ALIGN_NORMAL; } + boolean shouldEllipsize = mEllipsize != null && mInput == null; + if (mText instanceof Spannable) { mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w, alignment, mSpacingMult, - mSpacingAdd, mIncludePad, mEllipsize, + mSpacingAdd, mIncludePad, mInput == null ? mEllipsize : null, ellipsisWidth); } else { if (boring == UNKNOWN_BORING) { @@ -4734,7 +4839,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Log.e("aaa", "Boring: " + mTransformed); mSavedLayout = (BoringLayout) mLayout; - } else if (mEllipsize != null && boring.width <= w) { + } else if (shouldEllipsize && boring.width <= w) { if (mSavedLayout != null) { mLayout = mSavedLayout. replaceOrMake(mTransformed, mTextPaint, @@ -4747,7 +4852,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boring, mIncludePad, mEllipsize, ellipsisWidth); } - } else if (mEllipsize != null) { + } else if (shouldEllipsize) { mLayout = new StaticLayout(mTransformed, 0, mTransformed.length(), mTextPaint, w, alignment, mSpacingMult, @@ -4759,7 +4864,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mIncludePad); // Log.e("aaa", "Boring but wide: " + mTransformed); } - } else if (mEllipsize != null) { + } else if (shouldEllipsize) { mLayout = new StaticLayout(mTransformed, 0, mTransformed.length(), mTextPaint, w, alignment, mSpacingMult, @@ -4772,9 +4877,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + shouldEllipsize = mEllipsize != null; mHintLayout = null; if (mHint != null) { + if (shouldEllipsize) hintWidth = w; + if (hintBoring == UNKNOWN_BORING) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring); @@ -4784,24 +4892,50 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (hintBoring != null) { - if (hintBoring.width <= hintWidth) { + if (hintBoring.width <= hintWidth && + (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { if (mSavedHintLayout != null) { mHintLayout = mSavedHintLayout. replaceOrMake(mHint, mTextPaint, - hintWidth, alignment, mSpacingMult, - mSpacingAdd, hintBoring, mIncludePad); + hintWidth, alignment, mSpacingMult, mSpacingAdd, + hintBoring, mIncludePad); } else { mHintLayout = BoringLayout.make(mHint, mTextPaint, - hintWidth, alignment, mSpacingMult, - mSpacingAdd, hintBoring, mIncludePad); + hintWidth, alignment, mSpacingMult, mSpacingAdd, + hintBoring, mIncludePad); } mSavedHintLayout = (BoringLayout) mHintLayout; + } else if (shouldEllipsize && hintBoring.width <= hintWidth) { + if (mSavedHintLayout != null) { + mHintLayout = mSavedHintLayout. + replaceOrMake(mHint, mTextPaint, + hintWidth, alignment, mSpacingMult, mSpacingAdd, + hintBoring, mIncludePad, mEllipsize, + ellipsisWidth); + } else { + mHintLayout = BoringLayout.make(mHint, mTextPaint, + hintWidth, alignment, mSpacingMult, mSpacingAdd, + hintBoring, mIncludePad, mEllipsize, + ellipsisWidth); + } + } else if (shouldEllipsize) { + mHintLayout = new StaticLayout(mHint, + 0, mHint.length(), + mTextPaint, hintWidth, alignment, mSpacingMult, + mSpacingAdd, mIncludePad, mEllipsize, + ellipsisWidth); } else { mHintLayout = new StaticLayout(mHint, mTextPaint, hintWidth, alignment, mSpacingMult, mSpacingAdd, mIncludePad); } + } else if (shouldEllipsize) { + mHintLayout = new StaticLayout(mHint, + 0, mHint.length(), + mTextPaint, hintWidth, alignment, mSpacingMult, + mSpacingAdd, mIncludePad, mEllipsize, + ellipsisWidth); } else { mHintLayout = new StaticLayout(mHint, mTextPaint, hintWidth, alignment, mSpacingMult, mSpacingAdd, @@ -4814,18 +4948,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { - final int height = mLayoutParams.height; - // If the size of the view does not depend on the size of the text, try to - // start the marquee immediately - if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) { - startMarquee(); - } else { - // Defer the start of the marquee until we know our width (see setFrame()) - mRestartMarquee = true; + if (!compressText(ellipsisWidth)) { + final int height = mLayoutParams.height; + // If the size of the view does not depend on the size of the text, try to + // start the marquee immediately + if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) { + startMarquee(); + } else { + // Defer the start of the marquee until we know our width (see setFrame()) + mRestartMarquee = true; + } } } } + private boolean compressText(float width) { + // Only compress the text if it hasn't been compressed by the previous pass + if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && + mTextPaint.getTextScaleX() == 1.0f) { + final float textWidth = mLayout.getLineWidth(0); + final float overflow = (textWidth + 1.0f - width) / width; + if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { + mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); + post(new Runnable() { + public void run() { + requestLayout(); + } + }); + return true; + } + } + + return false; + } + private static int desired(Layout layout) { int n = layout.getLineCount(); CharSequence text = layout.getText(); @@ -4863,8 +5019,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private static final BoringLayout.Metrics UNKNOWN_BORING = - new BoringLayout.Metrics(); + private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @@ -4891,8 +5046,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (des < 0) { - boring = BoringLayout.isBoring(mTransformed, mTextPaint, - mBoring); + boring = BoringLayout.isBoring(mTransformed, mTextPaint, mBoring); if (boring != null) { mBoring = boring; } @@ -4902,8 +5056,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (boring == null || boring == UNKNOWN_BORING) { if (des < 0) { - des = (int) FloatMath.ceil(Layout. - getDesiredWidth(mTransformed, mTextPaint)); + des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint)); } width = des; @@ -4921,13 +5074,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int hintDes = -1; int hintWidth; - if (mHintLayout != null) { + if (mHintLayout != null && mEllipsize == null) { hintDes = desired(mHintLayout); } if (hintDes < 0) { - hintBoring = BoringLayout.isBoring(mHint, mTextPaint, - mHintBoring); + hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } @@ -4935,8 +5087,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (hintBoring == null || hintBoring == UNKNOWN_BORING) { if (hintDes < 0) { - hintDes = (int) FloatMath.ceil(Layout. - getDesiredWidth(mHint, mTextPaint)); + hintDes = (int) FloatMath.ceil( + Layout.getDesiredWidth(mHint, mTextPaint)); } hintWidth = hintDes; @@ -4982,20 +5134,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mLayout == null) { makeNewLayout(want, hintWant, boring, hintBoring, - width - getCompoundPaddingLeft() - getCompoundPaddingRight(), - false); + width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) || (mLayout.getEllipsizedWidth() != width - getCompoundPaddingLeft() - getCompoundPaddingRight())) { if (mHint == null && mEllipsize == null && want > mLayout.getWidth() && (mLayout instanceof BoringLayout || - (fromexisting && des >= 0 && des <= want))) { + (fromexisting && des >= 0 && des <= want))) { mLayout.increaseWidthTo(want); } else { makeNewLayout(want, hintWant, boring, hintBoring, - width - getCompoundPaddingLeft() - getCompoundPaddingRight(), - false); + width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); } } else { // Width has not changed. @@ -5016,11 +5166,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - int unpaddedHeight = height - getCompoundPaddingTop() - - getCompoundPaddingBottom(); + int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { - unpaddedHeight = Math.min(unpaddedHeight, - mLayout.getLineTop(mMaximum)); + unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); } /* @@ -5039,8 +5187,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private int getDesiredHeight() { - return Math.max(getDesiredHeight(mLayout, true), - getDesiredHeight(mHintLayout, false)); + return Math.max( + getDesiredHeight(mLayout, true), + getDesiredHeight(mHintLayout, mEllipsize != null)); } private int getDesiredHeight(Layout layout, boolean cap) { @@ -5683,8 +5832,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void startMarquee() { + // Do not ellipsize EditText + if (mInput != null) return; + + if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { + return; + } + if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) { + if (mMarquee == null) mMarquee = new Marquee(this); mMarquee.start(mMarqueeRepeatLimit); } @@ -5708,6 +5865,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final class Marquee extends Handler { // TODO: Add an option to configure this + private static final float MARQUEE_DELTA_MAX = 0.07f; private static final int MARQUEE_DELAY = 1200; private static final int MARQUEE_RESTART_DELAY = 1200; private static final int MARQUEE_RESOLUTION = 1000 / 30; @@ -5726,6 +5884,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private byte mStatus = MARQUEE_STOPPED; private float mScrollUnit; private float mMaxScroll; + float mMaxFadeScroll; + private float mGhostStart; + private float mGhostOffset; + private float mFadeStop; private int mRepeatLimit; float mScroll; @@ -5801,13 +5963,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (textView != null && textView.mLayout != null) { mStatus = MARQUEE_STARTING; mScroll = 0.0f; - mMaxScroll = textView.mLayout.getLineWidth(0) - (textView.getWidth() - - textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight()); + final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() - + textView.getCompoundPaddingRight(); + final float lineWidth = textView.mLayout.getLineWidth(0); + final float gap = textWidth / 3.0f; + mGhostStart = lineWidth - textWidth + gap; + mMaxScroll = mGhostStart + textWidth; + mGhostOffset = lineWidth + gap; + mFadeStop = lineWidth + textWidth / 6.0f; + mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; + textView.invalidate(); sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY); } } + float getGhostOffset() { + return mGhostOffset; + } + + boolean shouldDrawLeftFade() { + return mScroll <= mFadeStop; + } + + boolean shouldDrawGhost() { + return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; + } + boolean isRunning() { return mStatus == MARQUEE_RUNNING; } @@ -6067,10 +6249,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private class ChangeWatcher implements TextWatcher, SpanWatcher { + + private CharSequence mBeforeText; + public void beforeTextChanged(CharSequence buffer, int start, int before, int after) { if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); + + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + mBeforeText = buffer.toString(); + } + TextView.this.sendBeforeTextChanged(buffer, start, before, after); } @@ -6079,6 +6269,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); TextView.this.handleTextChanged(buffer, start, before, after); + + if (AccessibilityManager.getInstance(mContext).isEnabled() && + (isFocused() || isSelected() && + isShown())) { + sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); + mBeforeText = null; + } } public void afterTextChanged(Editable buffer) { @@ -6274,6 +6471,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode != InputMethodManager.RESULT_SHOWN) { + final int len = mText.length(); + if (mNewStart > len) { + mNewStart = len; + } + if (mNewEnd > len) { + mNewEnd = len; + } Selection.setSelection((Spannable)mText, mNewStart, mNewEnd); } } @@ -6433,7 +6637,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { if (mMarquee != null && !mMarquee.isStopped()) { final Marquee marquee = mMarquee; - return marquee.mScroll / getHorizontalFadingEdgeLength(); + if (marquee.shouldDrawLeftFade()) { + return marquee.mScroll / getHorizontalFadingEdgeLength(); + } else { + return 0.0f; + } } else if (getLineCount() == 1) { switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: @@ -6455,13 +6663,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { if (mMarquee != null && !mMarquee.isStopped()) { final Marquee marquee = mMarquee; - return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); + return (marquee.mMaxFadeScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); } else if (getLineCount() == 1) { switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: - return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) - - getCompoundPaddingLeft() - getCompoundPaddingRight()) / - getHorizontalFadingEdgeLength(); + final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() - + getCompoundPaddingRight(); + final float lineWidth = mLayout.getLineWidth(0); + return (lineWidth - textWidth) / getHorizontalFadingEdgeLength(); case Gravity.RIGHT: return 0.0f; case Gravity.CENTER_HORIZONTAL: @@ -6710,6 +6919,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } @Override + public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { + boolean isPassword = + (mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) == + (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); + + if (!isPassword) { + CharSequence text = getText(); + if (TextUtils.isEmpty(text)) { + text = getHint(); + } + if (!TextUtils.isEmpty(text)) { + if (text.length() > AccessibilityEvent.MAX_TEXT_LENGTH) { + text = text.subSequence(0, AccessibilityEvent.MAX_TEXT_LENGTH + 1); + } + event.getText().add(text); + } + } else { + event.setPassword(isPassword); + } + return false; + } + + void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, + int fromIndex, int removedCount, int addedCount) { + AccessibilityEvent event = + AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); + event.setFromIndex(fromIndex); + event.setRemovedCount(removedCount); + event.setAddedCount(addedCount); + event.setBeforeText(beforeText); + sendAccessibilityEventUnchecked(event); + } + + @Override protected void onCreateContextMenu(ContextMenu menu) { super.onCreateContextMenu(menu); boolean added = false; @@ -6994,7 +7237,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private ArrayList<TextWatcher> mListeners = null; // display attributes - private TextPaint mTextPaint; + private TextPaint mTextPaint; + private boolean mUserSetTextScaleX; private Paint mHighlightPaint; private int mHighlightColor = 0xFFBBDDFF; private Layout mLayout; |
