2 * Copyright (C) 2006 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package android.widget;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.content.ClipData;
23 import android.content.ClipboardManager;
24 import android.content.Context;
25 import android.content.UndoManager;
26 import android.content.res.ColorStateList;
27 import android.content.res.CompatibilityInfo;
28 import android.content.res.Resources;
29 import android.content.res.TypedArray;
30 import android.content.res.XmlResourceParser;
31 import android.graphics.Canvas;
32 import android.graphics.Insets;
33 import android.graphics.Paint;
34 import android.graphics.Path;
35 import android.graphics.Rect;
36 import android.graphics.RectF;
37 import android.graphics.Typeface;
38 import android.graphics.drawable.Drawable;
39 import android.inputmethodservice.ExtractEditText;
40 import android.os.AsyncTask;
41 import android.os.Bundle;
42 import android.os.Parcel;
43 import android.os.Parcelable;
44 import android.os.SystemClock;
45 import android.os.UserHandle;
46 import android.provider.Settings;
47 import android.text.BoringLayout;
48 import android.text.DynamicLayout;
49 import android.text.Editable;
50 import android.text.GetChars;
51 import android.text.GraphicsOperations;
52 import android.text.InputFilter;
53 import android.text.InputType;
54 import android.text.Layout;
55 import android.text.ParcelableSpan;
56 import android.text.Selection;
57 import android.text.SpanWatcher;
58 import android.text.Spannable;
59 import android.text.SpannableString;
60 import android.text.SpannableStringBuilder;
61 import android.text.Spanned;
62 import android.text.SpannedString;
63 import android.text.StaticLayout;
64 import android.text.TextDirectionHeuristic;
65 import android.text.TextDirectionHeuristics;
66 import android.text.TextPaint;
67 import android.text.TextUtils;
68 import android.text.TextUtils.TruncateAt;
69 import android.text.TextWatcher;
70 import android.text.method.AllCapsTransformationMethod;
71 import android.text.method.ArrowKeyMovementMethod;
72 import android.text.method.DateKeyListener;
73 import android.text.method.DateTimeKeyListener;
74 import android.text.method.DialerKeyListener;
75 import android.text.method.DigitsKeyListener;
76 import android.text.method.KeyListener;
77 import android.text.method.LinkMovementMethod;
78 import android.text.method.MetaKeyKeyListener;
79 import android.text.method.MovementMethod;
80 import android.text.method.PasswordTransformationMethod;
81 import android.text.method.SingleLineTransformationMethod;
82 import android.text.method.TextKeyListener;
83 import android.text.method.TimeKeyListener;
84 import android.text.method.TransformationMethod;
85 import android.text.method.TransformationMethod2;
86 import android.text.method.WordIterator;
87 import android.text.style.CharacterStyle;
88 import android.text.style.ClickableSpan;
89 import android.text.style.ParagraphStyle;
90 import android.text.style.SpellCheckSpan;
91 import android.text.style.SuggestionSpan;
92 import android.text.style.URLSpan;
93 import android.text.style.UpdateAppearance;
94 import android.text.util.Linkify;
95 import android.util.AttributeSet;
96 import android.util.FloatMath;
97 import android.util.Log;
98 import android.util.TypedValue;
99 import android.view.AccessibilityIterators.TextSegmentIterator;
100 import android.view.ActionMode;
101 import android.view.Choreographer;
102 import android.view.DragEvent;
103 import android.view.Gravity;
104 import android.view.HapticFeedbackConstants;
105 import android.view.KeyCharacterMap;
106 import android.view.KeyEvent;
107 import android.view.Menu;
108 import android.view.MenuItem;
109 import android.view.MotionEvent;
110 import android.view.View;
111 import android.view.ViewConfiguration;
112 import android.view.ViewDebug;
113 import android.view.ViewGroup.LayoutParams;
114 import android.view.ViewRootImpl;
115 import android.view.ViewTreeObserver;
116 import android.view.accessibility.AccessibilityEvent;
117 import android.view.accessibility.AccessibilityManager;
118 import android.view.accessibility.AccessibilityNodeInfo;
119 import android.view.animation.AnimationUtils;
120 import android.view.inputmethod.BaseInputConnection;
121 import android.view.inputmethod.CompletionInfo;
122 import android.view.inputmethod.CorrectionInfo;
123 import android.view.inputmethod.EditorInfo;
124 import android.view.inputmethod.ExtractedText;
125 import android.view.inputmethod.ExtractedTextRequest;
126 import android.view.inputmethod.InputConnection;
127 import android.view.inputmethod.InputMethodManager;
128 import android.view.textservice.SpellCheckerSubtype;
129 import android.view.textservice.TextServicesManager;
130 import android.widget.RemoteViews.RemoteView;
132 import com.android.internal.util.FastMath;
133 import com.android.internal.widget.EditableInputConnection;
135 import org.xmlpull.v1.XmlPullParserException;
137 import java.io.IOException;
138 import java.lang.ref.WeakReference;
139 import java.util.ArrayList;
140 import java.util.Locale;
142 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
145 * Displays text to the user and optionally allows them to edit it. A TextView
146 * is a complete text editor, however the basic class is configured to not
147 * allow editing; see {@link EditText} for a subclass that configures the text
151 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
152 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
153 * android:textIsSelectable} to "true" or call
154 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
155 * allows users to make selection gestures in the TextView, which in turn triggers the system's
156 * built-in copy/paste controls.
158 * <b>XML attributes</b>
160 * See {@link android.R.styleable#TextView TextView Attributes},
161 * {@link android.R.styleable#View View Attributes}
163 * @attr ref android.R.styleable#TextView_text
164 * @attr ref android.R.styleable#TextView_bufferType
165 * @attr ref android.R.styleable#TextView_hint
166 * @attr ref android.R.styleable#TextView_textColor
167 * @attr ref android.R.styleable#TextView_textColorHighlight
168 * @attr ref android.R.styleable#TextView_textColorHint
169 * @attr ref android.R.styleable#TextView_textAppearance
170 * @attr ref android.R.styleable#TextView_textColorLink
171 * @attr ref android.R.styleable#TextView_textSize
172 * @attr ref android.R.styleable#TextView_textScaleX
173 * @attr ref android.R.styleable#TextView_fontFamily
174 * @attr ref android.R.styleable#TextView_typeface
175 * @attr ref android.R.styleable#TextView_textStyle
176 * @attr ref android.R.styleable#TextView_cursorVisible
177 * @attr ref android.R.styleable#TextView_maxLines
178 * @attr ref android.R.styleable#TextView_maxHeight
179 * @attr ref android.R.styleable#TextView_lines
180 * @attr ref android.R.styleable#TextView_height
181 * @attr ref android.R.styleable#TextView_minLines
182 * @attr ref android.R.styleable#TextView_minHeight
183 * @attr ref android.R.styleable#TextView_maxEms
184 * @attr ref android.R.styleable#TextView_maxWidth
185 * @attr ref android.R.styleable#TextView_ems
186 * @attr ref android.R.styleable#TextView_width
187 * @attr ref android.R.styleable#TextView_minEms
188 * @attr ref android.R.styleable#TextView_minWidth
189 * @attr ref android.R.styleable#TextView_gravity
190 * @attr ref android.R.styleable#TextView_scrollHorizontally
191 * @attr ref android.R.styleable#TextView_password
192 * @attr ref android.R.styleable#TextView_singleLine
193 * @attr ref android.R.styleable#TextView_selectAllOnFocus
194 * @attr ref android.R.styleable#TextView_includeFontPadding
195 * @attr ref android.R.styleable#TextView_maxLength
196 * @attr ref android.R.styleable#TextView_shadowColor
197 * @attr ref android.R.styleable#TextView_shadowDx
198 * @attr ref android.R.styleable#TextView_shadowDy
199 * @attr ref android.R.styleable#TextView_shadowRadius
200 * @attr ref android.R.styleable#TextView_autoLink
201 * @attr ref android.R.styleable#TextView_linksClickable
202 * @attr ref android.R.styleable#TextView_numeric
203 * @attr ref android.R.styleable#TextView_digits
204 * @attr ref android.R.styleable#TextView_phoneNumber
205 * @attr ref android.R.styleable#TextView_inputMethod
206 * @attr ref android.R.styleable#TextView_capitalize
207 * @attr ref android.R.styleable#TextView_autoText
208 * @attr ref android.R.styleable#TextView_editable
209 * @attr ref android.R.styleable#TextView_freezesText
210 * @attr ref android.R.styleable#TextView_ellipsize
211 * @attr ref android.R.styleable#TextView_drawableTop
212 * @attr ref android.R.styleable#TextView_drawableBottom
213 * @attr ref android.R.styleable#TextView_drawableRight
214 * @attr ref android.R.styleable#TextView_drawableLeft
215 * @attr ref android.R.styleable#TextView_drawableStart
216 * @attr ref android.R.styleable#TextView_drawableEnd
217 * @attr ref android.R.styleable#TextView_drawablePadding
218 * @attr ref android.R.styleable#TextView_lineSpacingExtra
219 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
220 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
221 * @attr ref android.R.styleable#TextView_inputType
222 * @attr ref android.R.styleable#TextView_imeOptions
223 * @attr ref android.R.styleable#TextView_privateImeOptions
224 * @attr ref android.R.styleable#TextView_imeActionLabel
225 * @attr ref android.R.styleable#TextView_imeActionId
226 * @attr ref android.R.styleable#TextView_editorExtras
227 * @attr ref android.R.styleable#TextView_elegantTextHeight
228 * @attr ref android.R.styleable#TextView_letterSpacing
229 * @attr ref android.R.styleable#TextView_fontFeatureSettings
232 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
233 static final String LOG_TAG = "TextView";
234 static final boolean DEBUG_EXTRACT = false;
236 // Enum for the "typeface" XML parameter.
237 // TODO: How can we get this from the XML instead of hardcoding it here?
238 private static final int SANS = 1;
239 private static final int SERIF = 2;
240 private static final int MONOSPACE = 3;
242 // Bitfield for the "numeric" XML parameter.
243 // TODO: How can we get this from the XML instead of hardcoding it here?
244 private static final int SIGNED = 2;
245 private static final int DECIMAL = 4;
248 * Draw marquee text with fading edges as usual
250 private static final int MARQUEE_FADE_NORMAL = 0;
253 * Draw marquee text as ellipsize end while inactive instead of with the fade.
254 * (Useful for devices where the fade can be expensive if overdone)
256 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
259 * Draw marquee text with fading edges because it is currently active/animating.
261 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
263 private static final int LINES = 1;
264 private static final int EMS = LINES;
265 private static final int PIXELS = 2;
267 private static final RectF TEMP_RECTF = new RectF();
269 // XXX should be much larger
270 private static final int VERY_WIDE = 1024*1024;
271 private static final int ANIMATED_SCROLL_GAP = 250;
273 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
274 private static final Spanned EMPTY_SPANNED = new SpannedString("");
276 private static final int CHANGE_WATCHER_PRIORITY = 100;
278 // New state used to change background based on whether this TextView is multiline.
279 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
281 // System wide time for last cut or copy action.
282 static long LAST_CUT_OR_COPY_TIME;
284 private ColorStateList mTextColor;
285 private ColorStateList mHintTextColor;
286 private ColorStateList mLinkTextColor;
287 @ViewDebug.ExportedProperty(category = "text")
288 private int mCurTextColor;
289 private int mCurHintTextColor;
290 private boolean mFreezesText;
291 private boolean mDispatchTemporaryDetach;
293 /** Whether this view is temporarily detached from the parent view. */
294 boolean mTemporaryDetach;
296 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
297 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
299 private float mShadowRadius, mShadowDx, mShadowDy;
300 private int mShadowColor;
303 private boolean mPreDrawRegistered;
304 private boolean mPreDrawListenerDetached;
306 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
307 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
308 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
309 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
310 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
311 private boolean mPreventDefaultMovement;
313 private TextUtils.TruncateAt mEllipsize;
315 static class Drawables {
316 final static int DRAWABLE_NONE = -1;
317 final static int DRAWABLE_RIGHT = 0;
318 final static int DRAWABLE_LEFT = 1;
320 final Rect mCompoundRect = new Rect();
322 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
323 mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
325 Drawable mDrawableLeftInitial, mDrawableRightInitial;
326 boolean mIsRtlCompatibilityMode;
329 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
330 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
332 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
333 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
335 int mDrawablePadding;
337 int mDrawableSaved = DRAWABLE_NONE;
339 public Drawables(Context context) {
340 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
341 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
342 !context.getApplicationInfo().hasRtlSupport());
346 public void resolveWithLayoutDirection(int layoutDirection) {
347 // First reset "left" and "right" drawables to their initial values
348 mDrawableLeft = mDrawableLeftInitial;
349 mDrawableRight = mDrawableRightInitial;
351 if (mIsRtlCompatibilityMode) {
352 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
353 if (mDrawableStart != null && mDrawableLeft == null) {
354 mDrawableLeft = mDrawableStart;
355 mDrawableSizeLeft = mDrawableSizeStart;
356 mDrawableHeightLeft = mDrawableHeightStart;
358 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
359 if (mDrawableEnd != null && mDrawableRight == null) {
360 mDrawableRight = mDrawableEnd;
361 mDrawableSizeRight = mDrawableSizeEnd;
362 mDrawableHeightRight = mDrawableHeightEnd;
365 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
366 // drawable if and only if they have been defined
367 switch(layoutDirection) {
368 case LAYOUT_DIRECTION_RTL:
370 mDrawableRight = mDrawableStart;
371 mDrawableSizeRight = mDrawableSizeStart;
372 mDrawableHeightRight = mDrawableHeightStart;
374 mDrawableLeft = mDrawableEnd;
375 mDrawableSizeLeft = mDrawableSizeEnd;
376 mDrawableHeightLeft = mDrawableHeightEnd;
380 case LAYOUT_DIRECTION_LTR:
383 mDrawableLeft = mDrawableStart;
384 mDrawableSizeLeft = mDrawableSizeStart;
385 mDrawableHeightLeft = mDrawableHeightStart;
387 mDrawableRight = mDrawableEnd;
388 mDrawableSizeRight = mDrawableSizeEnd;
389 mDrawableHeightRight = mDrawableHeightEnd;
394 applyErrorDrawableIfNeeded(layoutDirection);
395 updateDrawablesLayoutDirection(layoutDirection);
398 private void updateDrawablesLayoutDirection(int layoutDirection) {
399 if (mDrawableLeft != null) {
400 mDrawableLeft.setLayoutDirection(layoutDirection);
402 if (mDrawableRight != null) {
403 mDrawableRight.setLayoutDirection(layoutDirection);
405 if (mDrawableTop != null) {
406 mDrawableTop.setLayoutDirection(layoutDirection);
408 if (mDrawableBottom != null) {
409 mDrawableBottom.setLayoutDirection(layoutDirection);
413 public void setErrorDrawable(Drawable dr, TextView tv) {
414 if (mDrawableError != dr && mDrawableError != null) {
415 mDrawableError.setCallback(null);
419 final Rect compoundRect = mCompoundRect;
420 int[] state = tv.getDrawableState();
422 if (mDrawableError != null) {
423 mDrawableError.setState(state);
424 mDrawableError.copyBounds(compoundRect);
425 mDrawableError.setCallback(tv);
426 mDrawableSizeError = compoundRect.width();
427 mDrawableHeightError = compoundRect.height();
429 mDrawableSizeError = mDrawableHeightError = 0;
433 private void applyErrorDrawableIfNeeded(int layoutDirection) {
434 // first restore the initial state if needed
435 switch (mDrawableSaved) {
437 mDrawableLeft = mDrawableTemp;
438 mDrawableSizeLeft = mDrawableSizeTemp;
439 mDrawableHeightLeft = mDrawableHeightTemp;
442 mDrawableRight = mDrawableTemp;
443 mDrawableSizeRight = mDrawableSizeTemp;
444 mDrawableHeightRight = mDrawableHeightTemp;
449 // then, if needed, assign the Error drawable to the correct location
450 if (mDrawableError != null) {
451 switch(layoutDirection) {
452 case LAYOUT_DIRECTION_RTL:
453 mDrawableSaved = DRAWABLE_LEFT;
455 mDrawableTemp = mDrawableLeft;
456 mDrawableSizeTemp = mDrawableSizeLeft;
457 mDrawableHeightTemp = mDrawableHeightLeft;
459 mDrawableLeft = mDrawableError;
460 mDrawableSizeLeft = mDrawableSizeError;
461 mDrawableHeightLeft = mDrawableHeightError;
463 case LAYOUT_DIRECTION_LTR:
465 mDrawableSaved = DRAWABLE_RIGHT;
467 mDrawableTemp = mDrawableRight;
468 mDrawableSizeTemp = mDrawableSizeRight;
469 mDrawableHeightTemp = mDrawableHeightRight;
471 mDrawableRight = mDrawableError;
472 mDrawableSizeRight = mDrawableSizeError;
473 mDrawableHeightRight = mDrawableHeightError;
480 Drawables mDrawables;
482 private CharWrapper mCharWrapper;
484 private Marquee mMarquee;
485 private boolean mRestartMarquee;
487 private int mMarqueeRepeatLimit = 3;
489 private int mLastLayoutDirection = -1;
492 * On some devices the fading edges add a performance penalty if used
493 * extensively in the same layout. This mode indicates how the marquee
494 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
496 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
499 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
500 * the layout that should be used when the mode switches.
502 private Layout mSavedMarqueeModeLayout;
504 @ViewDebug.ExportedProperty(category = "text")
505 private CharSequence mText;
506 private CharSequence mTransformed;
507 private BufferType mBufferType = BufferType.NORMAL;
509 private CharSequence mHint;
510 private Layout mHintLayout;
512 private MovementMethod mMovement;
514 private TransformationMethod mTransformation;
515 private boolean mAllowTransformationLengthChange;
516 private ChangeWatcher mChangeWatcher;
518 private ArrayList<TextWatcher> mListeners;
520 // display attributes
521 private final TextPaint mTextPaint;
522 private boolean mUserSetTextScaleX;
523 private Layout mLayout;
525 private int mGravity = Gravity.TOP | Gravity.START;
526 private boolean mHorizontallyScrolling;
528 private int mAutoLinkMask;
529 private boolean mLinksClickable = true;
531 private float mSpacingMult = 1.0f;
532 private float mSpacingAdd = 0.0f;
534 private int mMaximum = Integer.MAX_VALUE;
535 private int mMaxMode = LINES;
536 private int mMinimum = 0;
537 private int mMinMode = LINES;
539 private int mOldMaximum = mMaximum;
540 private int mOldMaxMode = mMaxMode;
542 private int mMaxWidth = Integer.MAX_VALUE;
543 private int mMaxWidthMode = PIXELS;
544 private int mMinWidth = 0;
545 private int mMinWidthMode = PIXELS;
547 private boolean mSingleLine;
548 private int mDesiredHeightAtMeasure = -1;
549 private boolean mIncludePad = true;
550 private int mDeferScroll = -1;
552 // tmp primitives, so we don't alloc them on each draw
553 private Rect mTempRect;
554 private long mLastScroll;
555 private Scroller mScroller;
557 private BoringLayout.Metrics mBoring, mHintBoring;
558 private BoringLayout mSavedLayout, mSavedHintLayout;
560 private TextDirectionHeuristic mTextDir;
562 private InputFilter[] mFilters = NO_FILTERS;
564 private volatile Locale mCurrentSpellCheckerLocaleCache;
566 // It is possible to have a selection even when mEditor is null (programmatically set, like when
567 // a link is pressed). These highlight-related fields do not go in mEditor.
568 int mHighlightColor = 0x6633B5E5;
569 private Path mHighlightPath;
570 private final Paint mHighlightPaint;
571 private boolean mHighlightPathBogus = true;
573 // Although these fields are specific to editable text, they are not added to Editor because
574 // they are defined by the TextView's style and are theme-dependent.
575 int mCursorDrawableRes;
576 // These four fields, could be moved to Editor, since we know their default values and we
577 // could condition the creation of the Editor to a non standard value. This is however
578 // brittle since the hardcoded values here (such as
579 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
580 // default style is modified.
581 int mTextSelectHandleLeftRes;
582 int mTextSelectHandleRightRes;
583 int mTextSelectHandleRes;
584 int mTextEditSuggestionItemLayout;
587 * EditText specific data, created on demand when one of the Editor fields is used.
588 * See {@link #createEditorIfNeeded()}.
590 private Editor mEditor;
593 * Kick-start the font cache for the zygote process (to pay the cost of
594 * initializing freetype for our default font only once).
597 Paint p = new Paint();
598 p.setAntiAlias(true);
599 // We don't care about the result, just the side-effect of measuring.
604 * Interface definition for a callback to be invoked when an action is
605 * performed on the editor.
607 public interface OnEditorActionListener {
609 * Called when an action is being performed.
611 * @param v The view that was clicked.
612 * @param actionId Identifier of the action. This will be either the
613 * identifier you supplied, or {@link EditorInfo#IME_NULL
614 * EditorInfo.IME_NULL} if being called due to the enter key
616 * @param event If triggered by an enter key, this is the event;
617 * otherwise, this is null.
618 * @return Return true if you have consumed the action, else false.
620 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
623 public TextView(Context context) {
627 public TextView(Context context, AttributeSet attrs) {
628 this(context, attrs, com.android.internal.R.attr.textViewStyle);
631 public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
632 this(context, attrs, defStyleAttr, 0);
635 @SuppressWarnings("deprecation")
636 public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
637 super(context, attrs, defStyleAttr, defStyleRes);
641 final Resources res = getResources();
642 final CompatibilityInfo compat = res.getCompatibilityInfo();
644 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
645 mTextPaint.density = res.getDisplayMetrics().density;
646 mTextPaint.setCompatibilityScaling(compat.applicationScale);
648 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
649 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
651 mMovement = getDefaultMovementMethod();
653 mTransformation = null;
655 int textColorHighlight = 0;
656 ColorStateList textColor = null;
657 ColorStateList textColorHint = null;
658 ColorStateList textColorLink = null;
660 String fontFamily = null;
661 boolean fontFamilyExplicit = false;
662 int typefaceIndex = -1;
664 boolean allCaps = false;
666 float dx = 0, dy = 0, r = 0;
667 boolean elegant = false;
668 float letterSpacing = 0;
669 String fontFeatureSettings = null;
671 final Resources.Theme theme = context.getTheme();
674 * Look the appearance up without checking first if it exists because
675 * almost every TextView has one and it greatly simplifies the logic
676 * to be able to parse the appearance first and then let specific tags
677 * for this View override it.
679 TypedArray a = theme.obtainStyledAttributes(attrs,
680 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
681 TypedArray appearance = null;
682 int ap = a.getResourceId(
683 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
686 appearance = theme.obtainStyledAttributes(
687 ap, com.android.internal.R.styleable.TextAppearance);
689 if (appearance != null) {
690 int n = appearance.getIndexCount();
691 for (int i = 0; i < n; i++) {
692 int attr = appearance.getIndex(i);
695 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
696 textColorHighlight = appearance.getColor(attr, textColorHighlight);
699 case com.android.internal.R.styleable.TextAppearance_textColor:
700 textColor = appearance.getColorStateList(attr);
703 case com.android.internal.R.styleable.TextAppearance_textColorHint:
704 textColorHint = appearance.getColorStateList(attr);
707 case com.android.internal.R.styleable.TextAppearance_textColorLink:
708 textColorLink = appearance.getColorStateList(attr);
711 case com.android.internal.R.styleable.TextAppearance_textSize:
712 textSize = appearance.getDimensionPixelSize(attr, textSize);
715 case com.android.internal.R.styleable.TextAppearance_typeface:
716 typefaceIndex = appearance.getInt(attr, -1);
719 case com.android.internal.R.styleable.TextAppearance_fontFamily:
720 fontFamily = appearance.getString(attr);
723 case com.android.internal.R.styleable.TextAppearance_textStyle:
724 styleIndex = appearance.getInt(attr, -1);
727 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
728 allCaps = appearance.getBoolean(attr, false);
731 case com.android.internal.R.styleable.TextAppearance_shadowColor:
732 shadowcolor = appearance.getInt(attr, 0);
735 case com.android.internal.R.styleable.TextAppearance_shadowDx:
736 dx = appearance.getFloat(attr, 0);
739 case com.android.internal.R.styleable.TextAppearance_shadowDy:
740 dy = appearance.getFloat(attr, 0);
743 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
744 r = appearance.getFloat(attr, 0);
747 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
748 elegant = appearance.getBoolean(attr, false);
751 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
752 letterSpacing = appearance.getFloat(attr, 0);
755 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
756 fontFeatureSettings = appearance.getString(attr);
761 appearance.recycle();
764 boolean editable = getDefaultEditable();
765 CharSequence inputMethod = null;
767 CharSequence digits = null;
768 boolean phone = false;
769 boolean autotext = false;
772 boolean selectallonfocus = false;
773 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
774 drawableBottom = null, drawableStart = null, drawableEnd = null;
775 int drawablePadding = 0;
777 boolean singleLine = false;
779 CharSequence text = "";
780 CharSequence hint = null;
781 boolean password = false;
782 int inputType = EditorInfo.TYPE_NULL;
784 a = theme.obtainStyledAttributes(
785 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
787 int n = a.getIndexCount();
788 for (int i = 0; i < n; i++) {
789 int attr = a.getIndex(i);
792 case com.android.internal.R.styleable.TextView_editable:
793 editable = a.getBoolean(attr, editable);
796 case com.android.internal.R.styleable.TextView_inputMethod:
797 inputMethod = a.getText(attr);
800 case com.android.internal.R.styleable.TextView_numeric:
801 numeric = a.getInt(attr, numeric);
804 case com.android.internal.R.styleable.TextView_digits:
805 digits = a.getText(attr);
808 case com.android.internal.R.styleable.TextView_phoneNumber:
809 phone = a.getBoolean(attr, phone);
812 case com.android.internal.R.styleable.TextView_autoText:
813 autotext = a.getBoolean(attr, autotext);
816 case com.android.internal.R.styleable.TextView_capitalize:
817 autocap = a.getInt(attr, autocap);
820 case com.android.internal.R.styleable.TextView_bufferType:
821 buffertype = a.getInt(attr, buffertype);
824 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
825 selectallonfocus = a.getBoolean(attr, selectallonfocus);
828 case com.android.internal.R.styleable.TextView_autoLink:
829 mAutoLinkMask = a.getInt(attr, 0);
832 case com.android.internal.R.styleable.TextView_linksClickable:
833 mLinksClickable = a.getBoolean(attr, true);
836 case com.android.internal.R.styleable.TextView_drawableLeft:
837 drawableLeft = a.getDrawable(attr);
840 case com.android.internal.R.styleable.TextView_drawableTop:
841 drawableTop = a.getDrawable(attr);
844 case com.android.internal.R.styleable.TextView_drawableRight:
845 drawableRight = a.getDrawable(attr);
848 case com.android.internal.R.styleable.TextView_drawableBottom:
849 drawableBottom = a.getDrawable(attr);
852 case com.android.internal.R.styleable.TextView_drawableStart:
853 drawableStart = a.getDrawable(attr);
856 case com.android.internal.R.styleable.TextView_drawableEnd:
857 drawableEnd = a.getDrawable(attr);
860 case com.android.internal.R.styleable.TextView_drawablePadding:
861 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
864 case com.android.internal.R.styleable.TextView_maxLines:
865 setMaxLines(a.getInt(attr, -1));
868 case com.android.internal.R.styleable.TextView_maxHeight:
869 setMaxHeight(a.getDimensionPixelSize(attr, -1));
872 case com.android.internal.R.styleable.TextView_lines:
873 setLines(a.getInt(attr, -1));
876 case com.android.internal.R.styleable.TextView_height:
877 setHeight(a.getDimensionPixelSize(attr, -1));
880 case com.android.internal.R.styleable.TextView_minLines:
881 setMinLines(a.getInt(attr, -1));
884 case com.android.internal.R.styleable.TextView_minHeight:
885 setMinHeight(a.getDimensionPixelSize(attr, -1));
888 case com.android.internal.R.styleable.TextView_maxEms:
889 setMaxEms(a.getInt(attr, -1));
892 case com.android.internal.R.styleable.TextView_maxWidth:
893 setMaxWidth(a.getDimensionPixelSize(attr, -1));
896 case com.android.internal.R.styleable.TextView_ems:
897 setEms(a.getInt(attr, -1));
900 case com.android.internal.R.styleable.TextView_width:
901 setWidth(a.getDimensionPixelSize(attr, -1));
904 case com.android.internal.R.styleable.TextView_minEms:
905 setMinEms(a.getInt(attr, -1));
908 case com.android.internal.R.styleable.TextView_minWidth:
909 setMinWidth(a.getDimensionPixelSize(attr, -1));
912 case com.android.internal.R.styleable.TextView_gravity:
913 setGravity(a.getInt(attr, -1));
916 case com.android.internal.R.styleable.TextView_hint:
917 hint = a.getText(attr);
920 case com.android.internal.R.styleable.TextView_text:
921 text = a.getText(attr);
924 case com.android.internal.R.styleable.TextView_scrollHorizontally:
925 if (a.getBoolean(attr, false)) {
926 setHorizontallyScrolling(true);
930 case com.android.internal.R.styleable.TextView_singleLine:
931 singleLine = a.getBoolean(attr, singleLine);
934 case com.android.internal.R.styleable.TextView_ellipsize:
935 ellipsize = a.getInt(attr, ellipsize);
938 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
939 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
942 case com.android.internal.R.styleable.TextView_includeFontPadding:
943 if (!a.getBoolean(attr, true)) {
944 setIncludeFontPadding(false);
948 case com.android.internal.R.styleable.TextView_cursorVisible:
949 if (!a.getBoolean(attr, true)) {
950 setCursorVisible(false);
954 case com.android.internal.R.styleable.TextView_maxLength:
955 maxlength = a.getInt(attr, -1);
958 case com.android.internal.R.styleable.TextView_textScaleX:
959 setTextScaleX(a.getFloat(attr, 1.0f));
962 case com.android.internal.R.styleable.TextView_freezesText:
963 mFreezesText = a.getBoolean(attr, false);
966 case com.android.internal.R.styleable.TextView_shadowColor:
967 shadowcolor = a.getInt(attr, 0);
970 case com.android.internal.R.styleable.TextView_shadowDx:
971 dx = a.getFloat(attr, 0);
974 case com.android.internal.R.styleable.TextView_shadowDy:
975 dy = a.getFloat(attr, 0);
978 case com.android.internal.R.styleable.TextView_shadowRadius:
979 r = a.getFloat(attr, 0);
982 case com.android.internal.R.styleable.TextView_enabled:
983 setEnabled(a.getBoolean(attr, isEnabled()));
986 case com.android.internal.R.styleable.TextView_textColorHighlight:
987 textColorHighlight = a.getColor(attr, textColorHighlight);
990 case com.android.internal.R.styleable.TextView_textColor:
991 textColor = a.getColorStateList(attr);
994 case com.android.internal.R.styleable.TextView_textColorHint:
995 textColorHint = a.getColorStateList(attr);
998 case com.android.internal.R.styleable.TextView_textColorLink:
999 textColorLink = a.getColorStateList(attr);
1002 case com.android.internal.R.styleable.TextView_textSize:
1003 textSize = a.getDimensionPixelSize(attr, textSize);
1006 case com.android.internal.R.styleable.TextView_typeface:
1007 typefaceIndex = a.getInt(attr, typefaceIndex);
1010 case com.android.internal.R.styleable.TextView_textStyle:
1011 styleIndex = a.getInt(attr, styleIndex);
1014 case com.android.internal.R.styleable.TextView_fontFamily:
1015 fontFamily = a.getString(attr);
1016 fontFamilyExplicit = true;
1019 case com.android.internal.R.styleable.TextView_password:
1020 password = a.getBoolean(attr, password);
1023 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1024 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1027 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1028 mSpacingMult = a.getFloat(attr, mSpacingMult);
1031 case com.android.internal.R.styleable.TextView_inputType:
1032 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1035 case com.android.internal.R.styleable.TextView_imeOptions:
1036 createEditorIfNeeded();
1037 mEditor.createInputContentTypeIfNeeded();
1038 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1039 mEditor.mInputContentType.imeOptions);
1042 case com.android.internal.R.styleable.TextView_imeActionLabel:
1043 createEditorIfNeeded();
1044 mEditor.createInputContentTypeIfNeeded();
1045 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1048 case com.android.internal.R.styleable.TextView_imeActionId:
1049 createEditorIfNeeded();
1050 mEditor.createInputContentTypeIfNeeded();
1051 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1052 mEditor.mInputContentType.imeActionId);
1055 case com.android.internal.R.styleable.TextView_privateImeOptions:
1056 setPrivateImeOptions(a.getString(attr));
1059 case com.android.internal.R.styleable.TextView_editorExtras:
1061 setInputExtras(a.getResourceId(attr, 0));
1062 } catch (XmlPullParserException e) {
1063 Log.w(LOG_TAG, "Failure reading input extras", e);
1064 } catch (IOException e) {
1065 Log.w(LOG_TAG, "Failure reading input extras", e);
1069 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1070 mCursorDrawableRes = a.getResourceId(attr, 0);
1073 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1074 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1077 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1078 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1081 case com.android.internal.R.styleable.TextView_textSelectHandle:
1082 mTextSelectHandleRes = a.getResourceId(attr, 0);
1085 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1086 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1089 case com.android.internal.R.styleable.TextView_textIsSelectable:
1090 setTextIsSelectable(a.getBoolean(attr, false));
1093 case com.android.internal.R.styleable.TextView_textAllCaps:
1094 allCaps = a.getBoolean(attr, false);
1097 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1098 elegant = a.getBoolean(attr, false);
1101 case com.android.internal.R.styleable.TextView_letterSpacing:
1102 letterSpacing = a.getFloat(attr, 0);
1105 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1106 fontFeatureSettings = a.getString(attr);
1112 BufferType bufferType = BufferType.EDITABLE;
1114 final int variation =
1115 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1116 final boolean passwordInputType = variation
1117 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1118 final boolean webPasswordInputType = variation
1119 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1120 final boolean numberPasswordInputType = variation
1121 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1123 if (inputMethod != null) {
1127 c = Class.forName(inputMethod.toString());
1128 } catch (ClassNotFoundException ex) {
1129 throw new RuntimeException(ex);
1133 createEditorIfNeeded();
1134 mEditor.mKeyListener = (KeyListener) c.newInstance();
1135 } catch (InstantiationException ex) {
1136 throw new RuntimeException(ex);
1137 } catch (IllegalAccessException ex) {
1138 throw new RuntimeException(ex);
1141 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1143 : mEditor.mKeyListener.getInputType();
1144 } catch (IncompatibleClassChangeError e) {
1145 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1147 } else if (digits != null) {
1148 createEditorIfNeeded();
1149 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1150 // If no input type was specified, we will default to generic
1151 // text, since we can't tell the IME about the set of digits
1152 // that was selected.
1153 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1154 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1155 } else if (inputType != EditorInfo.TYPE_NULL) {
1156 setInputType(inputType, true);
1157 // If set, the input type overrides what was set using the deprecated singleLine flag.
1158 singleLine = !isMultilineInputType(inputType);
1160 createEditorIfNeeded();
1161 mEditor.mKeyListener = DialerKeyListener.getInstance();
1162 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1163 } else if (numeric != 0) {
1164 createEditorIfNeeded();
1165 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
1166 (numeric & DECIMAL) != 0);
1167 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1168 if ((numeric & SIGNED) != 0) {
1169 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1171 if ((numeric & DECIMAL) != 0) {
1172 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1174 mEditor.mInputType = inputType;
1175 } else if (autotext || autocap != -1) {
1176 TextKeyListener.Capitalize cap;
1178 inputType = EditorInfo.TYPE_CLASS_TEXT;
1182 cap = TextKeyListener.Capitalize.SENTENCES;
1183 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1187 cap = TextKeyListener.Capitalize.WORDS;
1188 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1192 cap = TextKeyListener.Capitalize.CHARACTERS;
1193 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1197 cap = TextKeyListener.Capitalize.NONE;
1201 createEditorIfNeeded();
1202 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1203 mEditor.mInputType = inputType;
1204 } else if (isTextSelectable()) {
1205 // Prevent text changes from keyboard.
1206 if (mEditor != null) {
1207 mEditor.mKeyListener = null;
1208 mEditor.mInputType = EditorInfo.TYPE_NULL;
1210 bufferType = BufferType.SPANNABLE;
1211 // So that selection can be changed using arrow keys and touch is handled.
1212 setMovementMethod(ArrowKeyMovementMethod.getInstance());
1213 } else if (editable) {
1214 createEditorIfNeeded();
1215 mEditor.mKeyListener = TextKeyListener.getInstance();
1216 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1218 if (mEditor != null) mEditor.mKeyListener = null;
1220 switch (buffertype) {
1222 bufferType = BufferType.NORMAL;
1225 bufferType = BufferType.SPANNABLE;
1228 bufferType = BufferType.EDITABLE;
1233 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1234 webPasswordInputType, numberPasswordInputType);
1236 if (selectallonfocus) {
1237 createEditorIfNeeded();
1238 mEditor.mSelectAllOnFocus = true;
1240 if (bufferType == BufferType.NORMAL)
1241 bufferType = BufferType.SPANNABLE;
1244 // This call will save the initial left/right drawables
1245 setCompoundDrawablesWithIntrinsicBounds(
1246 drawableLeft, drawableTop, drawableRight, drawableBottom);
1247 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1248 setCompoundDrawablePadding(drawablePadding);
1250 // Same as setSingleLine(), but make sure the transformation method and the maximum number
1251 // of lines of height are unchanged for multi-line TextViews.
1252 setInputTypeSingleLine(singleLine);
1253 applySingleLine(singleLine, singleLine, singleLine);
1255 if (singleLine && getKeyListener() == null && ellipsize < 0) {
1256 ellipsize = 3; // END
1259 switch (ellipsize) {
1261 setEllipsize(TextUtils.TruncateAt.START);
1264 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1267 setEllipsize(TextUtils.TruncateAt.END);
1270 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1271 setHorizontalFadingEdgeEnabled(true);
1272 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1274 setHorizontalFadingEdgeEnabled(false);
1275 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1277 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1281 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1282 setHintTextColor(textColorHint);
1283 setLinkTextColor(textColorLink);
1284 if (textColorHighlight != 0) {
1285 setHighlightColor(textColorHighlight);
1287 setRawTextSize(textSize);
1288 setElegantTextHeight(elegant);
1289 setLetterSpacing(letterSpacing);
1290 setFontFeatureSettings(fontFeatureSettings);
1293 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1296 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1297 setTransformationMethod(PasswordTransformationMethod.getInstance());
1298 typefaceIndex = MONOSPACE;
1299 } else if (mEditor != null &&
1300 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1301 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1302 typefaceIndex = MONOSPACE;
1305 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1308 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
1310 if (shadowcolor != 0) {
1311 setShadowLayer(r, dx, dy, shadowcolor);
1314 if (maxlength >= 0) {
1315 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1317 setFilters(NO_FILTERS);
1320 setText(text, bufferType);
1321 if (hint != null) setHint(hint);
1324 * Views are not normally focusable unless specified to be.
1325 * However, TextViews that have input or movement methods *are*
1326 * focusable by default.
1328 a = context.obtainStyledAttributes(
1329 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1331 boolean focusable = mMovement != null || getKeyListener() != null;
1332 boolean clickable = focusable || isClickable();
1333 boolean longClickable = focusable || isLongClickable();
1335 n = a.getIndexCount();
1336 for (int i = 0; i < n; i++) {
1337 int attr = a.getIndex(i);
1340 case com.android.internal.R.styleable.View_focusable:
1341 focusable = a.getBoolean(attr, focusable);
1344 case com.android.internal.R.styleable.View_clickable:
1345 clickable = a.getBoolean(attr, clickable);
1348 case com.android.internal.R.styleable.View_longClickable:
1349 longClickable = a.getBoolean(attr, longClickable);
1355 setFocusable(focusable);
1356 setClickable(clickable);
1357 setLongClickable(longClickable);
1359 if (mEditor != null) mEditor.prepareCursorControllers();
1361 // If not explicitly specified this view is important for accessibility.
1362 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1363 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1367 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
1369 if (familyName != null) {
1370 tf = Typeface.create(familyName, styleIndex);
1376 switch (typefaceIndex) {
1378 tf = Typeface.SANS_SERIF;
1382 tf = Typeface.SERIF;
1386 tf = Typeface.MONOSPACE;
1390 setTypeface(tf, styleIndex);
1393 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1394 boolean hasRelativeDrawables = (start != null) || (end != null);
1395 if (hasRelativeDrawables) {
1396 Drawables dr = mDrawables;
1398 mDrawables = dr = new Drawables(getContext());
1400 mDrawables.mOverride = true;
1401 final Rect compoundRect = dr.mCompoundRect;
1402 int[] state = getDrawableState();
1403 if (start != null) {
1404 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1405 start.setState(state);
1406 start.copyBounds(compoundRect);
1407 start.setCallback(this);
1409 dr.mDrawableStart = start;
1410 dr.mDrawableSizeStart = compoundRect.width();
1411 dr.mDrawableHeightStart = compoundRect.height();
1413 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1416 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1417 end.setState(state);
1418 end.copyBounds(compoundRect);
1419 end.setCallback(this);
1421 dr.mDrawableEnd = end;
1422 dr.mDrawableSizeEnd = compoundRect.width();
1423 dr.mDrawableHeightEnd = compoundRect.height();
1425 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1427 resetResolvedDrawables();
1433 public void setEnabled(boolean enabled) {
1434 if (enabled == isEnabled()) {
1439 // Hide the soft input if the currently active TextView is disabled
1440 InputMethodManager imm = InputMethodManager.peekInstance();
1441 if (imm != null && imm.isActive(this)) {
1442 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1446 super.setEnabled(enabled);
1449 // Make sure IME is updated with current editor info.
1450 InputMethodManager imm = InputMethodManager.peekInstance();
1451 if (imm != null) imm.restartInput(this);
1454 // Will change text color
1455 if (mEditor != null) {
1456 mEditor.invalidateTextDisplayList();
1457 mEditor.prepareCursorControllers();
1459 // start or stop the cursor blinking as appropriate
1460 mEditor.makeBlink();
1465 * Sets the typeface and style in which the text should be displayed,
1466 * and turns on the fake bold and italic bits in the Paint if the
1467 * Typeface that you provided does not have all the bits in the
1468 * style that you specified.
1470 * @attr ref android.R.styleable#TextView_typeface
1471 * @attr ref android.R.styleable#TextView_textStyle
1473 public void setTypeface(Typeface tf, int style) {
1476 tf = Typeface.defaultFromStyle(style);
1478 tf = Typeface.create(tf, style);
1482 // now compute what (if any) algorithmic styling is needed
1483 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1484 int need = style & ~typefaceStyle;
1485 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1486 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1488 mTextPaint.setFakeBoldText(false);
1489 mTextPaint.setTextSkewX(0);
1495 * Subclasses override this to specify that they have a KeyListener
1496 * by default even if not specifically called for in the XML options.
1498 protected boolean getDefaultEditable() {
1503 * Subclasses override this to specify a default movement method.
1505 protected MovementMethod getDefaultMovementMethod() {
1510 * Return the text the TextView is displaying. If setText() was called with
1511 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1512 * the return value from this method to Spannable or Editable, respectively.
1514 * Note: The content of the return value should not be modified. If you want
1515 * a modifiable one, you should make your own copy first.
1517 * @attr ref android.R.styleable#TextView_text
1519 @ViewDebug.CapturedViewProperty
1520 public CharSequence getText() {
1525 * Returns the length, in characters, of the text managed by this TextView
1527 public int length() {
1528 return mText.length();
1532 * Return the text the TextView is displaying as an Editable object. If
1533 * the text is not editable, null is returned.
1537 public Editable getEditableText() {
1538 return (mText instanceof Editable) ? (Editable)mText : null;
1542 * @return the height of one standard line in pixels. Note that markup
1543 * within the text can cause individual lines to be taller or shorter
1544 * than this height, and the layout may contain additional first-
1545 * or last-line padding.
1547 public int getLineHeight() {
1548 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
1552 * @return the Layout that is currently being used to display the text.
1553 * This can be null if the text or width has recently changes.
1555 public final Layout getLayout() {
1560 * @return the Layout that is currently being used to display the hint text.
1563 final Layout getHintLayout() {
1568 * Retrieve the {@link android.content.UndoManager} that is currently associated
1569 * with this TextView. By default there is no associated UndoManager, so null
1570 * is returned. One can be associated with the TextView through
1571 * {@link #setUndoManager(android.content.UndoManager, String)}
1575 public final UndoManager getUndoManager() {
1576 return mEditor == null ? null : mEditor.mUndoManager;
1580 * Associate an {@link android.content.UndoManager} with this TextView. Once
1581 * done, all edit operations on the TextView will result in appropriate
1582 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1585 * @param undoManager The {@link android.content.UndoManager} to associate with
1586 * this TextView, or null to clear any existing association.
1587 * @param tag String tag identifying this particular TextView owner in the
1588 * UndoManager. This is used to keep the correct association with the
1589 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
1593 public final void setUndoManager(UndoManager undoManager, String tag) {
1594 if (undoManager != null) {
1595 createEditorIfNeeded();
1596 mEditor.mUndoManager = undoManager;
1597 mEditor.mUndoOwner = undoManager.getOwner(tag, this);
1598 mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor);
1599 if (!(mText instanceof Editable)) {
1600 setText(mText, BufferType.EDITABLE);
1603 setFilters((Editable) mText, mFilters);
1604 } else if (mEditor != null) {
1605 // XXX need to destroy all associated state.
1606 mEditor.mUndoManager = null;
1607 mEditor.mUndoOwner = null;
1608 mEditor.mUndoInputFilter = null;
1613 * @return the current key listener for this TextView.
1614 * This will frequently be null for non-EditText TextViews.
1616 * @attr ref android.R.styleable#TextView_numeric
1617 * @attr ref android.R.styleable#TextView_digits
1618 * @attr ref android.R.styleable#TextView_phoneNumber
1619 * @attr ref android.R.styleable#TextView_inputMethod
1620 * @attr ref android.R.styleable#TextView_capitalize
1621 * @attr ref android.R.styleable#TextView_autoText
1623 public final KeyListener getKeyListener() {
1624 return mEditor == null ? null : mEditor.mKeyListener;
1628 * Sets the key listener to be used with this TextView. This can be null
1629 * to disallow user input. Note that this method has significant and
1630 * subtle interactions with soft keyboards and other input method:
1631 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1632 * for important details. Calling this method will replace the current
1633 * content type of the text view with the content type returned by the
1636 * Be warned that if you want a TextView with a key listener or movement
1637 * method not to be focusable, or if you want a TextView without a
1638 * key listener or movement method to be focusable, you must call
1639 * {@link #setFocusable} again after calling this to get the focusability
1640 * back the way you want it.
1642 * @attr ref android.R.styleable#TextView_numeric
1643 * @attr ref android.R.styleable#TextView_digits
1644 * @attr ref android.R.styleable#TextView_phoneNumber
1645 * @attr ref android.R.styleable#TextView_inputMethod
1646 * @attr ref android.R.styleable#TextView_capitalize
1647 * @attr ref android.R.styleable#TextView_autoText
1649 public void setKeyListener(KeyListener input) {
1650 setKeyListenerOnly(input);
1651 fixFocusableAndClickableSettings();
1653 if (input != null) {
1654 createEditorIfNeeded();
1656 mEditor.mInputType = mEditor.mKeyListener.getInputType();
1657 } catch (IncompatibleClassChangeError e) {
1658 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1660 // Change inputType, without affecting transformation.
1661 // No need to applySingleLine since mSingleLine is unchanged.
1662 setInputTypeSingleLine(mSingleLine);
1664 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
1667 InputMethodManager imm = InputMethodManager.peekInstance();
1668 if (imm != null) imm.restartInput(this);
1671 private void setKeyListenerOnly(KeyListener input) {
1672 if (mEditor == null && input == null) return; // null is the default value
1674 createEditorIfNeeded();
1675 if (mEditor.mKeyListener != input) {
1676 mEditor.mKeyListener = input;
1677 if (input != null && !(mText instanceof Editable)) {
1681 setFilters((Editable) mText, mFilters);
1686 * @return the movement method being used for this TextView.
1687 * This will frequently be null for non-EditText TextViews.
1689 public final MovementMethod getMovementMethod() {
1694 * Sets the movement method (arrow key handler) to be used for
1695 * this TextView. This can be null to disallow using the arrow keys
1696 * to move the cursor or scroll the view.
1698 * Be warned that if you want a TextView with a key listener or movement
1699 * method not to be focusable, or if you want a TextView without a
1700 * key listener or movement method to be focusable, you must call
1701 * {@link #setFocusable} again after calling this to get the focusability
1702 * back the way you want it.
1704 public final void setMovementMethod(MovementMethod movement) {
1705 if (mMovement != movement) {
1706 mMovement = movement;
1708 if (movement != null && !(mText instanceof Spannable)) {
1712 fixFocusableAndClickableSettings();
1714 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1716 if (mEditor != null) mEditor.prepareCursorControllers();
1720 private void fixFocusableAndClickableSettings() {
1721 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
1724 setLongClickable(true);
1726 setFocusable(false);
1727 setClickable(false);
1728 setLongClickable(false);
1733 * @return the current transformation method for this TextView.
1734 * This will frequently be null except for single-line and password
1737 * @attr ref android.R.styleable#TextView_password
1738 * @attr ref android.R.styleable#TextView_singleLine
1740 public final TransformationMethod getTransformationMethod() {
1741 return mTransformation;
1745 * Sets the transformation that is applied to the text that this
1746 * TextView is displaying.
1748 * @attr ref android.R.styleable#TextView_password
1749 * @attr ref android.R.styleable#TextView_singleLine
1751 public final void setTransformationMethod(TransformationMethod method) {
1752 if (method == mTransformation) {
1753 // Avoid the setText() below if the transformation is
1757 if (mTransformation != null) {
1758 if (mText instanceof Spannable) {
1759 ((Spannable) mText).removeSpan(mTransformation);
1763 mTransformation = method;
1765 if (method instanceof TransformationMethod2) {
1766 TransformationMethod2 method2 = (TransformationMethod2) method;
1767 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
1768 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1770 mAllowTransformationLengthChange = false;
1775 if (hasPasswordTransformationMethod()) {
1776 notifyViewAccessibilityStateChangedIfNeeded(
1777 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
1782 * Returns the top padding of the view, plus space for the top
1785 public int getCompoundPaddingTop() {
1786 final Drawables dr = mDrawables;
1787 if (dr == null || dr.mDrawableTop == null) {
1790 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1795 * Returns the bottom padding of the view, plus space for the bottom
1798 public int getCompoundPaddingBottom() {
1799 final Drawables dr = mDrawables;
1800 if (dr == null || dr.mDrawableBottom == null) {
1801 return mPaddingBottom;
1803 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1808 * Returns the left padding of the view, plus space for the left
1811 public int getCompoundPaddingLeft() {
1812 final Drawables dr = mDrawables;
1813 if (dr == null || dr.mDrawableLeft == null) {
1814 return mPaddingLeft;
1816 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1821 * Returns the right padding of the view, plus space for the right
1824 public int getCompoundPaddingRight() {
1825 final Drawables dr = mDrawables;
1826 if (dr == null || dr.mDrawableRight == null) {
1827 return mPaddingRight;
1829 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1834 * Returns the start padding of the view, plus space for the start
1837 public int getCompoundPaddingStart() {
1839 switch(getLayoutDirection()) {
1841 case LAYOUT_DIRECTION_LTR:
1842 return getCompoundPaddingLeft();
1843 case LAYOUT_DIRECTION_RTL:
1844 return getCompoundPaddingRight();
1849 * Returns the end padding of the view, plus space for the end
1852 public int getCompoundPaddingEnd() {
1854 switch(getLayoutDirection()) {
1856 case LAYOUT_DIRECTION_LTR:
1857 return getCompoundPaddingRight();
1858 case LAYOUT_DIRECTION_RTL:
1859 return getCompoundPaddingLeft();
1864 * Returns the extended top padding of the view, including both the
1865 * top Drawable if any and any extra space to keep more than maxLines
1866 * of text from showing. It is only valid to call this after measuring.
1868 public int getExtendedPaddingTop() {
1869 if (mMaxMode != LINES) {
1870 return getCompoundPaddingTop();
1873 if (mLayout == null) {
1877 if (mLayout.getLineCount() <= mMaximum) {
1878 return getCompoundPaddingTop();
1881 int top = getCompoundPaddingTop();
1882 int bottom = getCompoundPaddingBottom();
1883 int viewht = getHeight() - top - bottom;
1884 int layoutht = mLayout.getLineTop(mMaximum);
1886 if (layoutht >= viewht) {
1890 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1891 if (gravity == Gravity.TOP) {
1893 } else if (gravity == Gravity.BOTTOM) {
1894 return top + viewht - layoutht;
1895 } else { // (gravity == Gravity.CENTER_VERTICAL)
1896 return top + (viewht - layoutht) / 2;
1901 * Returns the extended bottom padding of the view, including both the
1902 * bottom Drawable if any and any extra space to keep more than maxLines
1903 * of text from showing. It is only valid to call this after measuring.
1905 public int getExtendedPaddingBottom() {
1906 if (mMaxMode != LINES) {
1907 return getCompoundPaddingBottom();
1910 if (mLayout == null) {
1914 if (mLayout.getLineCount() <= mMaximum) {
1915 return getCompoundPaddingBottom();
1918 int top = getCompoundPaddingTop();
1919 int bottom = getCompoundPaddingBottom();
1920 int viewht = getHeight() - top - bottom;
1921 int layoutht = mLayout.getLineTop(mMaximum);
1923 if (layoutht >= viewht) {
1927 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1928 if (gravity == Gravity.TOP) {
1929 return bottom + viewht - layoutht;
1930 } else if (gravity == Gravity.BOTTOM) {
1932 } else { // (gravity == Gravity.CENTER_VERTICAL)
1933 return bottom + (viewht - layoutht) / 2;
1938 * Returns the total left padding of the view, including the left
1941 public int getTotalPaddingLeft() {
1942 return getCompoundPaddingLeft();
1946 * Returns the total right padding of the view, including the right
1949 public int getTotalPaddingRight() {
1950 return getCompoundPaddingRight();
1954 * Returns the total start padding of the view, including the start
1957 public int getTotalPaddingStart() {
1958 return getCompoundPaddingStart();
1962 * Returns the total end padding of the view, including the end
1965 public int getTotalPaddingEnd() {
1966 return getCompoundPaddingEnd();
1970 * Returns the total top padding of the view, including the top
1971 * Drawable if any, the extra space to keep more than maxLines
1972 * from showing, and the vertical offset for gravity, if any.
1974 public int getTotalPaddingTop() {
1975 return getExtendedPaddingTop() + getVerticalOffset(true);
1979 * Returns the total bottom padding of the view, including the bottom
1980 * Drawable if any, the extra space to keep more than maxLines
1981 * from showing, and the vertical offset for gravity, if any.
1983 public int getTotalPaddingBottom() {
1984 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
1988 * Sets the Drawables (if any) to appear to the left of, above, to the
1989 * right of, and below the text. Use {@code null} if you do not want a
1990 * Drawable there. The Drawables must already have had
1991 * {@link Drawable#setBounds} called.
1993 * Calling this method will overwrite any Drawables previously set using
1994 * {@link #setCompoundDrawablesRelative} or related methods.
1996 * @attr ref android.R.styleable#TextView_drawableLeft
1997 * @attr ref android.R.styleable#TextView_drawableTop
1998 * @attr ref android.R.styleable#TextView_drawableRight
1999 * @attr ref android.R.styleable#TextView_drawableBottom
2001 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2002 @Nullable Drawable right, @Nullable Drawable bottom) {
2003 Drawables dr = mDrawables;
2005 // We're switching to absolute, discard relative.
2007 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2008 dr.mDrawableStart = null;
2009 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2010 dr.mDrawableEnd = null;
2011 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2012 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2015 final boolean drawables = left != null || top != null || right != null || bottom != null;
2017 // Clearing drawables... can we free the data structure?
2019 if (dr.mDrawablePadding == 0) {
2022 // We need to retain the last set padding, so just clear
2023 // out all of the fields in the existing structure.
2024 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
2025 dr.mDrawableLeft = null;
2026 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2027 dr.mDrawableTop = null;
2028 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
2029 dr.mDrawableRight = null;
2030 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2031 dr.mDrawableBottom = null;
2032 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2033 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2034 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2035 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2040 mDrawables = dr = new Drawables(getContext());
2043 mDrawables.mOverride = false;
2045 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) {
2046 dr.mDrawableLeft.setCallback(null);
2048 dr.mDrawableLeft = left;
2050 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2051 dr.mDrawableTop.setCallback(null);
2053 dr.mDrawableTop = top;
2055 if (dr.mDrawableRight != right && dr.mDrawableRight != null) {
2056 dr.mDrawableRight.setCallback(null);
2058 dr.mDrawableRight = right;
2060 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2061 dr.mDrawableBottom.setCallback(null);
2063 dr.mDrawableBottom = bottom;
2065 final Rect compoundRect = dr.mCompoundRect;
2068 state = getDrawableState();
2071 left.setState(state);
2072 left.copyBounds(compoundRect);
2073 left.setCallback(this);
2074 dr.mDrawableSizeLeft = compoundRect.width();
2075 dr.mDrawableHeightLeft = compoundRect.height();
2077 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2080 if (right != null) {
2081 right.setState(state);
2082 right.copyBounds(compoundRect);
2083 right.setCallback(this);
2084 dr.mDrawableSizeRight = compoundRect.width();
2085 dr.mDrawableHeightRight = compoundRect.height();
2087 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2091 top.setState(state);
2092 top.copyBounds(compoundRect);
2093 top.setCallback(this);
2094 dr.mDrawableSizeTop = compoundRect.height();
2095 dr.mDrawableWidthTop = compoundRect.width();
2097 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2100 if (bottom != null) {
2101 bottom.setState(state);
2102 bottom.copyBounds(compoundRect);
2103 bottom.setCallback(this);
2104 dr.mDrawableSizeBottom = compoundRect.height();
2105 dr.mDrawableWidthBottom = compoundRect.width();
2107 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2111 // Save initial left/right drawables
2113 dr.mDrawableLeftInitial = left;
2114 dr.mDrawableRightInitial = right;
2117 resetResolvedDrawables();
2124 * Sets the Drawables (if any) to appear to the left of, above, to the
2125 * right of, and below the text. Use 0 if you do not want a Drawable there.
2126 * The Drawables' bounds will be set to their intrinsic bounds.
2128 * Calling this method will overwrite any Drawables previously set using
2129 * {@link #setCompoundDrawablesRelative} or related methods.
2131 * @param left Resource identifier of the left Drawable.
2132 * @param top Resource identifier of the top Drawable.
2133 * @param right Resource identifier of the right Drawable.
2134 * @param bottom Resource identifier of the bottom Drawable.
2136 * @attr ref android.R.styleable#TextView_drawableLeft
2137 * @attr ref android.R.styleable#TextView_drawableTop
2138 * @attr ref android.R.styleable#TextView_drawableRight
2139 * @attr ref android.R.styleable#TextView_drawableBottom
2141 @android.view.RemotableViewMethod
2142 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) {
2143 final Context context = getContext();
2144 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2145 top != 0 ? context.getDrawable(top) : null,
2146 right != 0 ? context.getDrawable(right) : null,
2147 bottom != 0 ? context.getDrawable(bottom) : null);
2151 * Sets the Drawables (if any) to appear to the left of, above, to the
2152 * right of, and below the text. Use {@code null} if you do not want a
2153 * Drawable there. The Drawables' bounds will be set to their intrinsic
2156 * Calling this method will overwrite any Drawables previously set using
2157 * {@link #setCompoundDrawablesRelative} or related methods.
2159 * @attr ref android.R.styleable#TextView_drawableLeft
2160 * @attr ref android.R.styleable#TextView_drawableTop
2161 * @attr ref android.R.styleable#TextView_drawableRight
2162 * @attr ref android.R.styleable#TextView_drawableBottom
2164 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2165 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2168 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2170 if (right != null) {
2171 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2174 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2176 if (bottom != null) {
2177 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2179 setCompoundDrawables(left, top, right, bottom);
2183 * Sets the Drawables (if any) to appear to the start of, above, to the end
2184 * of, and below the text. Use {@code null} if you do not want a Drawable
2185 * there. The Drawables must already have had {@link Drawable#setBounds}
2188 * Calling this method will overwrite any Drawables previously set using
2189 * {@link #setCompoundDrawables} or related methods.
2191 * @attr ref android.R.styleable#TextView_drawableStart
2192 * @attr ref android.R.styleable#TextView_drawableTop
2193 * @attr ref android.R.styleable#TextView_drawableEnd
2194 * @attr ref android.R.styleable#TextView_drawableBottom
2196 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2197 @Nullable Drawable end, @Nullable Drawable bottom) {
2198 Drawables dr = mDrawables;
2200 // We're switching to relative, discard absolute.
2202 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null);
2203 dr.mDrawableLeft = dr.mDrawableLeftInitial = null;
2204 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null);
2205 dr.mDrawableRight = dr.mDrawableRightInitial = null;
2206 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2207 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2210 final boolean drawables = start != null || top != null
2211 || end != null || bottom != null;
2214 // Clearing drawables... can we free the data structure?
2216 if (dr.mDrawablePadding == 0) {
2219 // We need to retain the last set padding, so just clear
2220 // out all of the fields in the existing structure.
2221 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2222 dr.mDrawableStart = null;
2223 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null);
2224 dr.mDrawableTop = null;
2225 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2226 dr.mDrawableEnd = null;
2227 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null);
2228 dr.mDrawableBottom = null;
2229 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2230 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2231 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2232 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2237 mDrawables = dr = new Drawables(getContext());
2240 mDrawables.mOverride = true;
2242 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2243 dr.mDrawableStart.setCallback(null);
2245 dr.mDrawableStart = start;
2247 if (dr.mDrawableTop != top && dr.mDrawableTop != null) {
2248 dr.mDrawableTop.setCallback(null);
2250 dr.mDrawableTop = top;
2252 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2253 dr.mDrawableEnd.setCallback(null);
2255 dr.mDrawableEnd = end;
2257 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) {
2258 dr.mDrawableBottom.setCallback(null);
2260 dr.mDrawableBottom = bottom;
2262 final Rect compoundRect = dr.mCompoundRect;
2265 state = getDrawableState();
2267 if (start != null) {
2268 start.setState(state);
2269 start.copyBounds(compoundRect);
2270 start.setCallback(this);
2271 dr.mDrawableSizeStart = compoundRect.width();
2272 dr.mDrawableHeightStart = compoundRect.height();
2274 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2278 end.setState(state);
2279 end.copyBounds(compoundRect);
2280 end.setCallback(this);
2281 dr.mDrawableSizeEnd = compoundRect.width();
2282 dr.mDrawableHeightEnd = compoundRect.height();
2284 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2288 top.setState(state);
2289 top.copyBounds(compoundRect);
2290 top.setCallback(this);
2291 dr.mDrawableSizeTop = compoundRect.height();
2292 dr.mDrawableWidthTop = compoundRect.width();
2294 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2297 if (bottom != null) {
2298 bottom.setState(state);
2299 bottom.copyBounds(compoundRect);
2300 bottom.setCallback(this);
2301 dr.mDrawableSizeBottom = compoundRect.height();
2302 dr.mDrawableWidthBottom = compoundRect.width();
2304 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2308 resetResolvedDrawables();
2315 * Sets the Drawables (if any) to appear to the start of, above, to the end
2316 * of, and below the text. Use 0 if you do not want a Drawable there. The
2317 * Drawables' bounds will be set to their intrinsic bounds.
2319 * Calling this method will overwrite any Drawables previously set using
2320 * {@link #setCompoundDrawables} or related methods.
2322 * @param start Resource identifier of the start Drawable.
2323 * @param top Resource identifier of the top Drawable.
2324 * @param end Resource identifier of the end Drawable.
2325 * @param bottom Resource identifier of the bottom Drawable.
2327 * @attr ref android.R.styleable#TextView_drawableStart
2328 * @attr ref android.R.styleable#TextView_drawableTop
2329 * @attr ref android.R.styleable#TextView_drawableEnd
2330 * @attr ref android.R.styleable#TextView_drawableBottom
2332 @android.view.RemotableViewMethod
2333 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end,
2335 final Context context = getContext();
2336 setCompoundDrawablesRelativeWithIntrinsicBounds(
2337 start != 0 ? context.getDrawable(start) : null,
2338 top != 0 ? context.getDrawable(top) : null,
2339 end != 0 ? context.getDrawable(end) : null,
2340 bottom != 0 ? context.getDrawable(bottom) : null);
2344 * Sets the Drawables (if any) to appear to the start of, above, to the end
2345 * of, and below the text. Use {@code null} if you do not want a Drawable
2346 * there. The Drawables' bounds will be set to their intrinsic bounds.
2348 * Calling this method will overwrite any Drawables previously set using
2349 * {@link #setCompoundDrawables} or related methods.
2351 * @attr ref android.R.styleable#TextView_drawableStart
2352 * @attr ref android.R.styleable#TextView_drawableTop
2353 * @attr ref android.R.styleable#TextView_drawableEnd
2354 * @attr ref android.R.styleable#TextView_drawableBottom
2356 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2357 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
2359 if (start != null) {
2360 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2363 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2366 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2368 if (bottom != null) {
2369 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2371 setCompoundDrawablesRelative(start, top, end, bottom);
2375 * Returns drawables for the left, top, right, and bottom borders.
2377 * @attr ref android.R.styleable#TextView_drawableLeft
2378 * @attr ref android.R.styleable#TextView_drawableTop
2379 * @attr ref android.R.styleable#TextView_drawableRight
2380 * @attr ref android.R.styleable#TextView_drawableBottom
2383 public Drawable[] getCompoundDrawables() {
2384 final Drawables dr = mDrawables;
2386 return new Drawable[] {
2387 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom
2390 return new Drawable[] { null, null, null, null };
2395 * Returns drawables for the start, top, end, and bottom borders.
2397 * @attr ref android.R.styleable#TextView_drawableStart
2398 * @attr ref android.R.styleable#TextView_drawableTop
2399 * @attr ref android.R.styleable#TextView_drawableEnd
2400 * @attr ref android.R.styleable#TextView_drawableBottom
2403 public Drawable[] getCompoundDrawablesRelative() {
2404 final Drawables dr = mDrawables;
2406 return new Drawable[] {
2407 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom
2410 return new Drawable[] { null, null, null, null };
2415 * Sets the size of the padding between the compound drawables and
2418 * @attr ref android.R.styleable#TextView_drawablePadding
2420 @android.view.RemotableViewMethod
2421 public void setCompoundDrawablePadding(int pad) {
2422 Drawables dr = mDrawables;
2425 dr.mDrawablePadding = pad;
2429 mDrawables = dr = new Drawables(getContext());
2431 dr.mDrawablePadding = pad;
2439 * Returns the padding between the compound drawables and the text.
2441 * @attr ref android.R.styleable#TextView_drawablePadding
2443 public int getCompoundDrawablePadding() {
2444 final Drawables dr = mDrawables;
2445 return dr != null ? dr.mDrawablePadding : 0;
2449 public void setPadding(int left, int top, int right, int bottom) {
2450 if (left != mPaddingLeft ||
2451 right != mPaddingRight ||
2452 top != mPaddingTop ||
2453 bottom != mPaddingBottom) {
2457 // the super call will requestLayout()
2458 super.setPadding(left, top, right, bottom);
2463 public void setPaddingRelative(int start, int top, int end, int bottom) {
2464 if (start != getPaddingStart() ||
2465 end != getPaddingEnd() ||
2466 top != mPaddingTop ||
2467 bottom != mPaddingBottom) {
2471 // the super call will requestLayout()
2472 super.setPaddingRelative(start, top, end, bottom);
2477 * Gets the autolink mask of the text. See {@link
2478 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2481 * @attr ref android.R.styleable#TextView_autoLink
2483 public final int getAutoLinkMask() {
2484 return mAutoLinkMask;
2488 * Sets the text color, size, style, hint color, and highlight color
2489 * from the specified TextAppearance resource.
2491 public void setTextAppearance(Context context, int resid) {
2492 TypedArray appearance =
2493 context.obtainStyledAttributes(resid,
2494 com.android.internal.R.styleable.TextAppearance);
2497 ColorStateList colors;
2500 color = appearance.getColor(
2501 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0);
2503 setHighlightColor(color);
2506 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2507 TextAppearance_textColor);
2508 if (colors != null) {
2509 setTextColor(colors);
2512 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
2513 TextAppearance_textSize, 0);
2518 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2519 TextAppearance_textColorHint);
2520 if (colors != null) {
2521 setHintTextColor(colors);
2524 colors = appearance.getColorStateList(com.android.internal.R.styleable.
2525 TextAppearance_textColorLink);
2526 if (colors != null) {
2527 setLinkTextColor(colors);
2531 int typefaceIndex, styleIndex;
2533 familyName = appearance.getString(com.android.internal.R.styleable.
2534 TextAppearance_fontFamily);
2535 typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
2536 TextAppearance_typeface, -1);
2537 styleIndex = appearance.getInt(com.android.internal.R.styleable.
2538 TextAppearance_textStyle, -1);
2540 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex);
2542 final int shadowcolor = appearance.getInt(
2543 com.android.internal.R.styleable.TextAppearance_shadowColor, 0);
2544 if (shadowcolor != 0) {
2545 final float dx = appearance.getFloat(
2546 com.android.internal.R.styleable.TextAppearance_shadowDx, 0);
2547 final float dy = appearance.getFloat(
2548 com.android.internal.R.styleable.TextAppearance_shadowDy, 0);
2549 final float r = appearance.getFloat(
2550 com.android.internal.R.styleable.TextAppearance_shadowRadius, 0);
2552 setShadowLayer(r, dx, dy, shadowcolor);
2555 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps,
2557 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2560 if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) {
2561 setElegantTextHeight(appearance.getBoolean(
2562 com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false));
2565 if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_letterSpacing)) {
2566 setLetterSpacing(appearance.getFloat(
2567 com.android.internal.R.styleable.TextAppearance_letterSpacing, 0));
2570 if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)) {
2571 setFontFeatureSettings(appearance.getString(
2572 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings));
2575 appearance.recycle();
2579 * Get the default {@link Locale} of the text in this TextView.
2580 * @return the default {@link Locale} of the text in this TextView.
2582 public Locale getTextLocale() {
2583 return mTextPaint.getTextLocale();
2587 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2588 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2589 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2591 * @param locale the {@link Locale} for drawing text, must not be null.
2593 * @see Paint#setTextLocale
2595 public void setTextLocale(Locale locale) {
2596 mTextPaint.setTextLocale(locale);
2600 * @return the size (in pixels) of the default text size in this TextView.
2602 @ViewDebug.ExportedProperty(category = "text")
2603 public float getTextSize() {
2604 return mTextPaint.getTextSize();
2608 * @return the size (in scaled pixels) of thee default text size in this TextView.
2611 @ViewDebug.ExportedProperty(category = "text")
2612 public float getScaledTextSize() {
2613 return mTextPaint.getTextSize() / mTextPaint.density;
2617 @ViewDebug.ExportedProperty(category = "text", mapping = {
2618 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2619 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2620 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2621 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2623 public int getTypefaceStyle() {
2624 return mTextPaint.getTypeface().getStyle();
2628 * Set the default text size to the given value, interpreted as "scaled
2629 * pixel" units. This size is adjusted based on the current density and
2630 * user font size preference.
2632 * @param size The scaled pixel size.
2634 * @attr ref android.R.styleable#TextView_textSize
2636 @android.view.RemotableViewMethod
2637 public void setTextSize(float size) {
2638 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2642 * Set the default text size to a given unit and value. See {@link
2643 * TypedValue} for the possible dimension units.
2645 * @param unit The desired dimension unit.
2646 * @param size The desired size in the given units.
2648 * @attr ref android.R.styleable#TextView_textSize
2650 public void setTextSize(int unit, float size) {
2651 Context c = getContext();
2655 r = Resources.getSystem();
2657 r = c.getResources();
2659 setRawTextSize(TypedValue.applyDimension(
2660 unit, size, r.getDisplayMetrics()));
2663 private void setRawTextSize(float size) {
2664 if (size != mTextPaint.getTextSize()) {
2665 mTextPaint.setTextSize(size);
2667 if (mLayout != null) {
2676 * @return the extent by which text is currently being stretched
2677 * horizontally. This will usually be 1.
2679 public float getTextScaleX() {
2680 return mTextPaint.getTextScaleX();
2684 * Sets the extent by which text should be stretched horizontally.
2686 * @attr ref android.R.styleable#TextView_textScaleX
2688 @android.view.RemotableViewMethod
2689 public void setTextScaleX(float size) {
2690 if (size != mTextPaint.getTextScaleX()) {
2691 mUserSetTextScaleX = true;
2692 mTextPaint.setTextScaleX(size);
2694 if (mLayout != null) {
2703 * Sets the typeface and style in which the text should be displayed.
2704 * Note that not all Typeface families actually have bold and italic
2705 * variants, so you may need to use
2706 * {@link #setTypeface(Typeface, int)} to get the appearance
2707 * that you actually want.
2709 * @see #getTypeface()
2711 * @attr ref android.R.styleable#TextView_fontFamily
2712 * @attr ref android.R.styleable#TextView_typeface
2713 * @attr ref android.R.styleable#TextView_textStyle
2715 public void setTypeface(Typeface tf) {
2716 if (mTextPaint.getTypeface() != tf) {
2717 mTextPaint.setTypeface(tf);
2719 if (mLayout != null) {
2728 * @return the current typeface and style in which the text is being
2731 * @see #setTypeface(Typeface)
2733 * @attr ref android.R.styleable#TextView_fontFamily
2734 * @attr ref android.R.styleable#TextView_typeface
2735 * @attr ref android.R.styleable#TextView_textStyle
2737 public Typeface getTypeface() {
2738 return mTextPaint.getTypeface();
2742 * Set the TextView's elegant height metrics flag. This setting selects font
2743 * variants that have not been compacted to fit Latin-based vertical
2744 * metrics, and also increases top and bottom bounds to provide more space.
2746 * @param elegant set the paint's elegant metrics flag.
2748 * @attr ref android.R.styleable#TextView_elegantTextHeight
2750 public void setElegantTextHeight(boolean elegant) {
2751 mTextPaint.setElegantTextHeight(elegant);
2755 * @return the extent by which text is currently being letter-spaced.
2756 * This will normally be 0.
2758 * @see #setLetterSpacing(float)
2759 * @see Paint#setLetterSpacing
2761 public float getLetterSpacing() {
2762 return mTextPaint.getLetterSpacing();
2766 * Sets text letter-spacing. The value is in 'EM' units. Typical values
2767 * for slight expansion will be around 0.05. Negative values tighten text.
2769 * @see #getLetterSpacing()
2770 * @see Paint#getLetterSpacing
2772 * @attr ref android.R.styleable#TextView_letterSpacing
2774 @android.view.RemotableViewMethod
2775 public void setLetterSpacing(float letterSpacing) {
2776 if (letterSpacing != mTextPaint.getLetterSpacing()) {
2777 mTextPaint.setLetterSpacing(letterSpacing);
2779 if (mLayout != null) {
2788 * @return the currently set font feature settings. Default is null.
2790 * @see #setFontFeatureSettings(String)
2791 * @see Paint#setFontFeatureSettings
2794 public String getFontFeatureSettings() {
2795 return mTextPaint.getFontFeatureSettings();
2799 * Sets font feature settings. The format is the same as the CSS
2800 * font-feature-settings attribute:
2801 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
2803 * @param fontFeatureSettings font feature settings represented as CSS compatible string
2804 * @see #getFontFeatureSettings()
2805 * @see Paint#getFontFeatureSettings
2807 * @attr ref android.R.styleable#TextView_fontFeatureSettings
2809 @android.view.RemotableViewMethod
2810 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
2811 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
2812 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
2814 if (mLayout != null) {
2824 * Sets the text color for all the states (normal, selected,
2825 * focused) to be this color.
2827 * @see #setTextColor(ColorStateList)
2828 * @see #getTextColors()
2830 * @attr ref android.R.styleable#TextView_textColor
2832 @android.view.RemotableViewMethod
2833 public void setTextColor(int color) {
2834 mTextColor = ColorStateList.valueOf(color);
2839 * Sets the text color.
2841 * @see #setTextColor(int)
2842 * @see #getTextColors()
2843 * @see #setHintTextColor(ColorStateList)
2844 * @see #setLinkTextColor(ColorStateList)
2846 * @attr ref android.R.styleable#TextView_textColor
2848 public void setTextColor(ColorStateList colors) {
2849 if (colors == null) {
2850 throw new NullPointerException();
2853 mTextColor = colors;
2858 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
2860 * @see #setTextColor(ColorStateList)
2861 * @see #setTextColor(int)
2863 * @attr ref android.R.styleable#TextView_textColor
2865 public final ColorStateList getTextColors() {
2870 * <p>Return the current color selected for normal text.</p>
2872 * @return Returns the current text color.
2874 public final int getCurrentTextColor() {
2875 return mCurTextColor;
2879 * Sets the color used to display the selection highlight.
2881 * @attr ref android.R.styleable#TextView_textColorHighlight
2883 @android.view.RemotableViewMethod
2884 public void setHighlightColor(int color) {
2885 if (mHighlightColor != color) {
2886 mHighlightColor = color;
2892 * @return the color used to display the selection highlight
2894 * @see #setHighlightColor(int)
2896 * @attr ref android.R.styleable#TextView_textColorHighlight
2898 public int getHighlightColor() {
2899 return mHighlightColor;
2903 * Sets whether the soft input method will be made visible when this
2904 * TextView gets focused. The default is true.
2906 @android.view.RemotableViewMethod
2907 public final void setShowSoftInputOnFocus(boolean show) {
2908 createEditorIfNeeded();
2909 mEditor.mShowSoftInputOnFocus = show;
2913 * Returns whether the soft input method will be made visible when this
2914 * TextView gets focused. The default is true.
2916 public final boolean getShowSoftInputOnFocus() {
2917 // When there is no Editor, return default true value
2918 return mEditor == null || mEditor.mShowSoftInputOnFocus;
2922 * Gives the text a shadow of the specified blur radius and color, the specified
2923 * distance from its drawn position.
2925 * The text shadow produced does not interact with the properties on view
2926 * that are responsible for real time shadows,
2927 * {@link View#getElevation() elevation} and
2928 * {@link View#getTranslationZ() translationZ}.
2930 * @see Paint#setShadowLayer(float, float, float, int)
2932 * @attr ref android.R.styleable#TextView_shadowColor
2933 * @attr ref android.R.styleable#TextView_shadowDx
2934 * @attr ref android.R.styleable#TextView_shadowDy
2935 * @attr ref android.R.styleable#TextView_shadowRadius
2937 public void setShadowLayer(float radius, float dx, float dy, int color) {
2938 mTextPaint.setShadowLayer(radius, dx, dy, color);
2940 mShadowRadius = radius;
2943 mShadowColor = color;
2945 // Will change text clip region
2946 if (mEditor != null) mEditor.invalidateTextDisplayList();
2951 * Gets the radius of the shadow layer.
2953 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
2955 * @see #setShadowLayer(float, float, float, int)
2957 * @attr ref android.R.styleable#TextView_shadowRadius
2959 public float getShadowRadius() {
2960 return mShadowRadius;
2964 * @return the horizontal offset of the shadow layer
2966 * @see #setShadowLayer(float, float, float, int)
2968 * @attr ref android.R.styleable#TextView_shadowDx
2970 public float getShadowDx() {
2975 * @return the vertical offset of the shadow layer
2977 * @see #setShadowLayer(float, float, float, int)
2979 * @attr ref android.R.styleable#TextView_shadowDy
2981 public float getShadowDy() {
2986 * @return the color of the shadow layer
2988 * @see #setShadowLayer(float, float, float, int)
2990 * @attr ref android.R.styleable#TextView_shadowColor
2992 public int getShadowColor() {
2993 return mShadowColor;
2997 * @return the base paint used for the text. Please use this only to
2998 * consult the Paint's properties and not to change them.
3000 public TextPaint getPaint() {
3005 * Sets the autolink mask of the text. See {@link
3006 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3009 * @attr ref android.R.styleable#TextView_autoLink
3011 @android.view.RemotableViewMethod
3012 public final void setAutoLinkMask(int mask) {
3013 mAutoLinkMask = mask;
3017 * Sets whether the movement method will automatically be set to
3018 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3019 * set to nonzero and links are detected in {@link #setText}.
3020 * The default is true.
3022 * @attr ref android.R.styleable#TextView_linksClickable
3024 @android.view.RemotableViewMethod
3025 public final void setLinksClickable(boolean whether) {
3026 mLinksClickable = whether;
3030 * Returns whether the movement method will automatically be set to
3031 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3032 * set to nonzero and links are detected in {@link #setText}.
3033 * The default is true.
3035 * @attr ref android.R.styleable#TextView_linksClickable
3037 public final boolean getLinksClickable() {
3038 return mLinksClickable;
3042 * Returns the list of URLSpans attached to the text
3043 * (by {@link Linkify} or otherwise) if any. You can call
3044 * {@link URLSpan#getURL} on them to find where they link to
3045 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3046 * to find the region of the text they are attached to.
3048 public URLSpan[] getUrls() {
3049 if (mText instanceof Spanned) {
3050 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3052 return new URLSpan[0];
3057 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3060 * @see #setHintTextColor(ColorStateList)
3061 * @see #getHintTextColors()
3062 * @see #setTextColor(int)
3064 * @attr ref android.R.styleable#TextView_textColorHint
3066 @android.view.RemotableViewMethod
3067 public final void setHintTextColor(int color) {
3068 mHintTextColor = ColorStateList.valueOf(color);
3073 * Sets the color of the hint text.
3075 * @see #getHintTextColors()
3076 * @see #setHintTextColor(int)
3077 * @see #setTextColor(ColorStateList)
3078 * @see #setLinkTextColor(ColorStateList)
3080 * @attr ref android.R.styleable#TextView_textColorHint
3082 public final void setHintTextColor(ColorStateList colors) {
3083 mHintTextColor = colors;
3088 * @return the color of the hint text, for the different states of this TextView.
3090 * @see #setHintTextColor(ColorStateList)
3091 * @see #setHintTextColor(int)
3092 * @see #setTextColor(ColorStateList)
3093 * @see #setLinkTextColor(ColorStateList)
3095 * @attr ref android.R.styleable#TextView_textColorHint
3097 public final ColorStateList getHintTextColors() {
3098 return mHintTextColor;
3102 * <p>Return the current color selected to paint the hint text.</p>
3104 * @return Returns the current hint text color.
3106 public final int getCurrentHintTextColor() {
3107 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3111 * Sets the color of links in the text.
3113 * @see #setLinkTextColor(ColorStateList)
3114 * @see #getLinkTextColors()
3116 * @attr ref android.R.styleable#TextView_textColorLink
3118 @android.view.RemotableViewMethod
3119 public final void setLinkTextColor(int color) {
3120 mLinkTextColor = ColorStateList.valueOf(color);
3125 * Sets the color of links in the text.
3127 * @see #setLinkTextColor(int)
3128 * @see #getLinkTextColors()
3129 * @see #setTextColor(ColorStateList)
3130 * @see #setHintTextColor(ColorStateList)
3132 * @attr ref android.R.styleable#TextView_textColorLink
3134 public final void setLinkTextColor(ColorStateList colors) {
3135 mLinkTextColor = colors;
3140 * @return the list of colors used to paint the links in the text, for the different states of
3143 * @see #setLinkTextColor(ColorStateList)
3144 * @see #setLinkTextColor(int)
3146 * @attr ref android.R.styleable#TextView_textColorLink
3148 public final ColorStateList getLinkTextColors() {
3149 return mLinkTextColor;
3153 * Sets the horizontal alignment of the text and the
3154 * vertical gravity that will be used when there is extra space
3155 * in the TextView beyond what is required for the text itself.
3157 * @see android.view.Gravity
3158 * @attr ref android.R.styleable#TextView_gravity
3160 public void setGravity(int gravity) {
3161 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
3162 gravity |= Gravity.START;
3164 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3165 gravity |= Gravity.TOP;
3168 boolean newLayout = false;
3170 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3171 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
3175 if (gravity != mGravity) {
3181 if (mLayout != null && newLayout) {
3182 // XXX this is heavy-handed because no actual content changes.
3183 int want = mLayout.getWidth();
3184 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3186 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3187 mRight - mLeft - getCompoundPaddingLeft() -
3188 getCompoundPaddingRight(), true);
3193 * Returns the horizontal and vertical alignment of this TextView.
3195 * @see android.view.Gravity
3196 * @attr ref android.R.styleable#TextView_gravity
3198 public int getGravity() {
3203 * @return the flags on the Paint being used to display the text.
3204 * @see Paint#getFlags
3206 public int getPaintFlags() {
3207 return mTextPaint.getFlags();
3211 * Sets flags on the Paint being used to display the text and
3212 * reflows the text if they are different from the old flags.
3213 * @see Paint#setFlags
3215 @android.view.RemotableViewMethod
3216 public void setPaintFlags(int flags) {
3217 if (mTextPaint.getFlags() != flags) {
3218 mTextPaint.setFlags(flags);
3220 if (mLayout != null) {
3229 * Sets whether the text should be allowed to be wider than the
3230 * View is. If false, it will be wrapped to the width of the View.
3232 * @attr ref android.R.styleable#TextView_scrollHorizontally
3234 public void setHorizontallyScrolling(boolean whether) {
3235 if (mHorizontallyScrolling != whether) {
3236 mHorizontallyScrolling = whether;
3238 if (mLayout != null) {
3247 * Returns whether the text is allowed to be wider than the View is.
3248 * If false, the text will be wrapped to the width of the View.
3250 * @attr ref android.R.styleable#TextView_scrollHorizontally
3253 public boolean getHorizontallyScrolling() {
3254 return mHorizontallyScrolling;
3258 * Makes the TextView at least this many lines tall.
3260 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3261 * set this value to 1.
3263 * @see #getMinLines()
3265 * @attr ref android.R.styleable#TextView_minLines
3267 @android.view.RemotableViewMethod
3268 public void setMinLines(int minlines) {
3269 mMinimum = minlines;
3277 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3278 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3280 * @see #setMinLines(int)
3282 * @attr ref android.R.styleable#TextView_minLines
3284 public int getMinLines() {
3285 return mMinMode == LINES ? mMinimum : -1;
3289 * Makes the TextView at least this many pixels tall.
3291 * Setting this value overrides any other (minimum) number of lines setting.
3293 * @attr ref android.R.styleable#TextView_minHeight
3295 @android.view.RemotableViewMethod
3296 public void setMinHeight(int minHeight) {
3297 mMinimum = minHeight;
3305 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3306 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3308 * @see #setMinHeight(int)
3310 * @attr ref android.R.styleable#TextView_minHeight
3312 public int getMinHeight() {
3313 return mMinMode == PIXELS ? mMinimum : -1;
3317 * Makes the TextView at most this many lines tall.
3319 * Setting this value overrides any other (maximum) height setting.
3321 * @attr ref android.R.styleable#TextView_maxLines
3323 @android.view.RemotableViewMethod
3324 public void setMaxLines(int maxlines) {
3325 mMaximum = maxlines;
3333 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3334 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3336 * @see #setMaxLines(int)
3338 * @attr ref android.R.styleable#TextView_maxLines
3340 public int getMaxLines() {
3341 return mMaxMode == LINES ? mMaximum : -1;
3345 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3346 * {@link #setMaxLines(int)} method.
3348 * Setting this value overrides any other (maximum) number of lines setting.
3350 * @attr ref android.R.styleable#TextView_maxHeight
3352 @android.view.RemotableViewMethod
3353 public void setMaxHeight(int maxHeight) {
3354 mMaximum = maxHeight;
3362 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3363 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3365 * @see #setMaxHeight(int)
3367 * @attr ref android.R.styleable#TextView_maxHeight
3369 public int getMaxHeight() {
3370 return mMaxMode == PIXELS ? mMaximum : -1;
3374 * Makes the TextView exactly this many lines tall.
3376 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3377 * height setting. A single line TextView will set this value to 1.
3379 * @attr ref android.R.styleable#TextView_lines
3381 @android.view.RemotableViewMethod
3382 public void setLines(int lines) {
3383 mMaximum = mMinimum = lines;
3384 mMaxMode = mMinMode = LINES;
3391 * Makes the TextView exactly this many pixels tall.
3392 * You could do the same thing by specifying this number in the
3395 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3398 * @attr ref android.R.styleable#TextView_height
3400 @android.view.RemotableViewMethod
3401 public void setHeight(int pixels) {
3402 mMaximum = mMinimum = pixels;
3403 mMaxMode = mMinMode = PIXELS;
3410 * Makes the TextView at least this many ems wide
3412 * @attr ref android.R.styleable#TextView_minEms
3414 @android.view.RemotableViewMethod
3415 public void setMinEms(int minems) {
3417 mMinWidthMode = EMS;
3424 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3425 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3427 * @see #setMinEms(int)
3430 * @attr ref android.R.styleable#TextView_minEms
3432 public int getMinEms() {
3433 return mMinWidthMode == EMS ? mMinWidth : -1;
3437 * Makes the TextView at least this many pixels wide
3439 * @attr ref android.R.styleable#TextView_minWidth
3441 @android.view.RemotableViewMethod
3442 public void setMinWidth(int minpixels) {
3443 mMinWidth = minpixels;
3444 mMinWidthMode = PIXELS;
3451 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3452 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3454 * @see #setMinWidth(int)
3455 * @see #setWidth(int)
3457 * @attr ref android.R.styleable#TextView_minWidth
3459 public int getMinWidth() {
3460 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3464 * Makes the TextView at most this many ems wide
3466 * @attr ref android.R.styleable#TextView_maxEms
3468 @android.view.RemotableViewMethod
3469 public void setMaxEms(int maxems) {
3471 mMaxWidthMode = EMS;
3478 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3479 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3481 * @see #setMaxEms(int)
3484 * @attr ref android.R.styleable#TextView_maxEms
3486 public int getMaxEms() {
3487 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3491 * Makes the TextView at most this many pixels wide
3493 * @attr ref android.R.styleable#TextView_maxWidth
3495 @android.view.RemotableViewMethod
3496 public void setMaxWidth(int maxpixels) {
3497 mMaxWidth = maxpixels;
3498 mMaxWidthMode = PIXELS;
3505 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3506 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3508 * @see #setMaxWidth(int)
3509 * @see #setWidth(int)
3511 * @attr ref android.R.styleable#TextView_maxWidth
3513 public int getMaxWidth() {
3514 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3518 * Makes the TextView exactly this many ems wide
3520 * @see #setMaxEms(int)
3521 * @see #setMinEms(int)
3525 * @attr ref android.R.styleable#TextView_ems
3527 @android.view.RemotableViewMethod
3528 public void setEms(int ems) {
3529 mMaxWidth = mMinWidth = ems;
3530 mMaxWidthMode = mMinWidthMode = EMS;
3537 * Makes the TextView exactly this many pixels wide.
3538 * You could do the same thing by specifying this number in the
3541 * @see #setMaxWidth(int)
3542 * @see #setMinWidth(int)
3543 * @see #getMinWidth()
3544 * @see #getMaxWidth()
3546 * @attr ref android.R.styleable#TextView_width
3548 @android.view.RemotableViewMethod
3549 public void setWidth(int pixels) {
3550 mMaxWidth = mMinWidth = pixels;
3551 mMaxWidthMode = mMinWidthMode = PIXELS;
3558 * Sets line spacing for this TextView. Each line will have its height
3559 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3561 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3562 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3564 public void setLineSpacing(float add, float mult) {
3565 if (mSpacingAdd != add || mSpacingMult != mult) {
3567 mSpacingMult = mult;
3569 if (mLayout != null) {
3578 * Gets the line spacing multiplier
3580 * @return the value by which each line's height is multiplied to get its actual height.
3582 * @see #setLineSpacing(float, float)
3583 * @see #getLineSpacingExtra()
3585 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3587 public float getLineSpacingMultiplier() {
3588 return mSpacingMult;
3592 * Gets the line spacing extra space
3594 * @return the extra space that is added to the height of each lines of this TextView.
3596 * @see #setLineSpacing(float, float)
3597 * @see #getLineSpacingMultiplier()
3599 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3601 public float getLineSpacingExtra() {
3606 * Convenience method: Append the specified text to the TextView's
3607 * display buffer, upgrading it to BufferType.EDITABLE if it was
3608 * not already editable.
3610 public final void append(CharSequence text) {
3611 append(text, 0, text.length());
3615 * Convenience method: Append the specified text slice to the TextView's
3616 * display buffer, upgrading it to BufferType.EDITABLE if it was
3617 * not already editable.
3619 public void append(CharSequence text, int start, int end) {
3620 if (!(mText instanceof Editable)) {
3621 setText(mText, BufferType.EDITABLE);
3624 ((Editable) mText).append(text, start, end);
3627 private void updateTextColors() {
3628 boolean inval = false;
3629 int color = mTextColor.getColorForState(getDrawableState(), 0);
3630 if (color != mCurTextColor) {
3631 mCurTextColor = color;
3634 if (mLinkTextColor != null) {
3635 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3636 if (color != mTextPaint.linkColor) {
3637 mTextPaint.linkColor = color;
3641 if (mHintTextColor != null) {
3642 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3643 if (color != mCurHintTextColor) {
3644 mCurHintTextColor = color;
3645 if (mText.length() == 0) {
3651 // Text needs to be redrawn with the new color
3652 if (mEditor != null) mEditor.invalidateTextDisplayList();
3658 protected void drawableStateChanged() {
3659 super.drawableStateChanged();
3660 if (mTextColor != null && mTextColor.isStateful()
3661 || (mHintTextColor != null && mHintTextColor.isStateful())
3662 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3666 final Drawables dr = mDrawables;
3668 int[] state = getDrawableState();
3669 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) {
3670 dr.mDrawableTop.setState(state);
3672 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) {
3673 dr.mDrawableBottom.setState(state);
3675 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) {
3676 dr.mDrawableLeft.setState(state);
3678 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) {
3679 dr.mDrawableRight.setState(state);
3681 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) {
3682 dr.mDrawableStart.setState(state);
3684 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) {
3685 dr.mDrawableEnd.setState(state);
3691 public void drawableHotspotChanged(float x, float y) {
3692 super.drawableHotspotChanged(x, y);
3694 final Drawables dr = mDrawables;
3696 if (dr.mDrawableTop != null) {
3697 dr.mDrawableTop.setHotspot(x, y);
3699 if (dr.mDrawableBottom != null) {
3700 dr.mDrawableBottom.setHotspot(x, y);
3702 if (dr.mDrawableLeft != null) {
3703 dr.mDrawableLeft.setHotspot(x, y);
3705 if (dr.mDrawableRight != null) {
3706 dr.mDrawableRight.setHotspot(x, y);
3708 if (dr.mDrawableStart != null) {
3709 dr.mDrawableStart.setHotspot(x, y);
3711 if (dr.mDrawableEnd != null) {
3712 dr.mDrawableEnd.setHotspot(x, y);
3718 public Parcelable onSaveInstanceState() {
3719 Parcelable superState = super.onSaveInstanceState();
3721 // Save state if we are forced to
3722 boolean save = mFreezesText;
3726 if (mText != null) {
3727 start = getSelectionStart();
3728 end = getSelectionEnd();
3729 if (start >= 0 || end >= 0) {
3730 // Or save state if there is a selection
3736 SavedState ss = new SavedState(superState);
3737 // XXX Should also save the current scroll position!
3738 ss.selStart = start;
3741 if (mText instanceof Spanned) {
3742 Spannable sp = new SpannableStringBuilder(mText);
3744 if (mEditor != null) {
3745 removeMisspelledSpans(sp);
3746 sp.removeSpan(mEditor.mSuggestionRangeSpan);
3751 ss.text = mText.toString();
3754 if (isFocused() && start >= 0 && end >= 0) {
3755 ss.frozenWithFocus = true;
3758 ss.error = getError();
3766 void removeMisspelledSpans(Spannable spannable) {
3767 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
3768 SuggestionSpan.class);
3769 for (int i = 0; i < suggestionSpans.length; i++) {
3770 int flags = suggestionSpans[i].getFlags();
3771 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
3772 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
3773 spannable.removeSpan(suggestionSpans[i]);
3779 public void onRestoreInstanceState(Parcelable state) {
3780 if (!(state instanceof SavedState)) {
3781 super.onRestoreInstanceState(state);
3785 SavedState ss = (SavedState)state;
3786 super.onRestoreInstanceState(ss.getSuperState());
3788 // XXX restore buffer type too, as well as lots of other stuff
3789 if (ss.text != null) {
3793 if (ss.selStart >= 0 && ss.selEnd >= 0) {
3794 if (mText instanceof Spannable) {
3795 int len = mText.length();
3797 if (ss.selStart > len || ss.selEnd > len) {
3798 String restored = "";
3800 if (ss.text != null) {
3801 restored = "(restored) ";
3804 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
3805 "/" + ss.selEnd + " out of range for " + restored +
3808 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
3810 if (ss.frozenWithFocus) {
3811 createEditorIfNeeded();
3812 mEditor.mFrozenWithFocus = true;
3818 if (ss.error != null) {
3819 final CharSequence error = ss.error;
3820 // Display the error later, after the first layout pass
3821 post(new Runnable() {
3830 * Control whether this text view saves its entire text contents when
3831 * freezing to an icicle, in addition to dynamic state such as cursor
3832 * position. By default this is false, not saving the text. Set to true
3833 * if the text in the text view is not being saved somewhere else in
3834 * persistent storage (such as in a content provider) so that if the
3835 * view is later thawed the user will not lose their data.
3837 * @param freezesText Controls whether a frozen icicle should include the
3838 * entire text data: true to include it, false to not.
3840 * @attr ref android.R.styleable#TextView_freezesText
3842 @android.view.RemotableViewMethod
3843 public void setFreezesText(boolean freezesText) {
3844 mFreezesText = freezesText;
3848 * Return whether this text view is including its entire text contents
3849 * in frozen icicles.
3851 * @return Returns true if text is included, false if it isn't.
3853 * @see #setFreezesText
3855 public boolean getFreezesText() {
3856 return mFreezesText;
3859 ///////////////////////////////////////////////////////////////////////////
3862 * Sets the Factory used to create new Editables.
3864 public final void setEditableFactory(Editable.Factory factory) {
3865 mEditableFactory = factory;
3870 * Sets the Factory used to create new Spannables.
3872 public final void setSpannableFactory(Spannable.Factory factory) {
3873 mSpannableFactory = factory;
3878 * Sets the string value of the TextView. TextView <em>does not</em> accept
3879 * HTML-like formatting, which you can do with text strings in XML resource files.
3880 * To style your strings, attach android.text.style.* objects to a
3881 * {@link android.text.SpannableString SpannableString}, or see the
3882 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
3883 * Available Resource Types</a> documentation for an example of setting
3884 * formatted text in the XML resource file.
3886 * @attr ref android.R.styleable#TextView_text
3888 @android.view.RemotableViewMethod
3889 public final void setText(CharSequence text) {
3890 setText(text, mBufferType);
3894 * Like {@link #setText(CharSequence)},
3895 * except that the cursor position (if any) is retained in the new text.
3897 * @param text The new text to place in the text view.
3899 * @see #setText(CharSequence)
3901 @android.view.RemotableViewMethod
3902 public final void setTextKeepState(CharSequence text) {
3903 setTextKeepState(text, mBufferType);
3907 * Sets the text that this TextView is to display (see
3908 * {@link #setText(CharSequence)}) and also sets whether it is stored
3909 * in a styleable/spannable buffer and whether it is editable.
3911 * @attr ref android.R.styleable#TextView_text
3912 * @attr ref android.R.styleable#TextView_bufferType
3914 public void setText(CharSequence text, BufferType type) {
3915 setText(text, type, true, 0);
3917 if (mCharWrapper != null) {
3918 mCharWrapper.mChars = null;
3922 private void setText(CharSequence text, BufferType type,
3923 boolean notifyBefore, int oldlen) {
3928 // If suggestions are not enabled, remove the suggestion spans from the text
3929 if (!isSuggestionsEnabled()) {
3930 text = removeSuggestionSpans(text);
3933 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
3935 if (text instanceof Spanned &&
3936 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
3937 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
3938 setHorizontalFadingEdgeEnabled(true);
3939 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
3941 setHorizontalFadingEdgeEnabled(false);
3942 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
3944 setEllipsize(TextUtils.TruncateAt.MARQUEE);
3947 int n = mFilters.length;
3948 for (int i = 0; i < n; i++) {
3949 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
3956 if (mText != null) {
3957 oldlen = mText.length();
3958 sendBeforeTextChanged(mText, 0, oldlen, text.length());
3960 sendBeforeTextChanged("", 0, 0, text.length());
3964 boolean needEditableForNotification = false;
3966 if (mListeners != null && mListeners.size() != 0) {
3967 needEditableForNotification = true;
3970 if (type == BufferType.EDITABLE || getKeyListener() != null ||
3971 needEditableForNotification) {
3972 createEditorIfNeeded();
3973 Editable t = mEditableFactory.newEditable(text);
3975 setFilters(t, mFilters);
3976 InputMethodManager imm = InputMethodManager.peekInstance();
3977 if (imm != null) imm.restartInput(this);
3978 } else if (type == BufferType.SPANNABLE || mMovement != null) {
3979 text = mSpannableFactory.newSpannable(text);
3980 } else if (!(text instanceof CharWrapper)) {
3981 text = TextUtils.stringOrSpannedString(text);
3984 if (mAutoLinkMask != 0) {
3987 if (type == BufferType.EDITABLE || text instanceof Spannable) {
3988 s2 = (Spannable) text;
3990 s2 = mSpannableFactory.newSpannable(text);
3993 if (Linkify.addLinks(s2, mAutoLinkMask)) {
3995 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
3998 * We must go ahead and set the text before changing the
3999 * movement method, because setMovementMethod() may call
4000 * setText() again to try to upgrade the buffer type.
4004 // Do not change the movement method for text that support text selection as it
4005 // would prevent an arbitrary cursor displacement.
4006 if (mLinksClickable && !textCanBeSelected()) {
4007 setMovementMethod(LinkMovementMethod.getInstance());
4015 if (mTransformation == null) {
4016 mTransformed = text;
4018 mTransformed = mTransformation.getTransformation(text, this);
4021 final int textLength = text.length();
4023 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
4024 Spannable sp = (Spannable) text;
4026 // Remove any ChangeWatchers that might have come from other TextViews.
4027 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4028 final int count = watchers.length;
4029 for (int i = 0; i < count; i++) {
4030 sp.removeSpan(watchers[i]);
4033 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
4035 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
4036 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
4038 if (mEditor != null) mEditor.addSpanWatchers(sp);
4040 if (mTransformation != null) {
4041 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4044 if (mMovement != null) {
4045 mMovement.initialize(this, (Spannable) text);
4048 * Initializing the movement method will have set the
4049 * selection, so reset mSelectionMoved to keep that from
4050 * interfering with the normal on-focus selection-setting.
4052 if (mEditor != null) mEditor.mSelectionMoved = false;
4056 if (mLayout != null) {
4060 sendOnTextChanged(text, 0, oldlen, textLength);
4061 onTextChanged(text, 0, oldlen, textLength);
4063 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4065 if (needEditableForNotification) {
4066 sendAfterTextChanged((Editable) text);
4069 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
4070 if (mEditor != null) mEditor.prepareCursorControllers();
4074 * Sets the TextView to display the specified slice of the specified
4075 * char array. You must promise that you will not change the contents
4076 * of the array except for right before another call to setText(),
4077 * since the TextView has no way to know that the text
4078 * has changed and that it needs to invalidate and re-layout.
4080 public final void setText(char[] text, int start, int len) {
4083 if (start < 0 || len < 0 || start + len > text.length) {
4084 throw new IndexOutOfBoundsException(start + ", " + len);
4088 * We must do the before-notification here ourselves because if
4089 * the old text is a CharWrapper we destroy it before calling
4090 * into the normal path.
4092 if (mText != null) {
4093 oldlen = mText.length();
4094 sendBeforeTextChanged(mText, 0, oldlen, len);
4096 sendBeforeTextChanged("", 0, 0, len);
4099 if (mCharWrapper == null) {
4100 mCharWrapper = new CharWrapper(text, start, len);
4102 mCharWrapper.set(text, start, len);
4105 setText(mCharWrapper, mBufferType, false, oldlen);
4109 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4110 * except that the cursor position (if any) is retained in the new text.
4112 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4114 public final void setTextKeepState(CharSequence text, BufferType type) {
4115 int start = getSelectionStart();
4116 int end = getSelectionEnd();
4117 int len = text.length();
4119 setText(text, type);
4121 if (start >= 0 || end >= 0) {
4122 if (mText instanceof Spannable) {
4123 Selection.setSelection((Spannable) mText,
4124 Math.max(0, Math.min(start, len)),
4125 Math.max(0, Math.min(end, len)));
4130 @android.view.RemotableViewMethod
4131 public final void setText(int resid) {
4132 setText(getContext().getResources().getText(resid));
4135 public final void setText(int resid, BufferType type) {
4136 setText(getContext().getResources().getText(resid), type);
4140 * Sets the text to be displayed when the text of the TextView is empty.
4141 * Null means to use the normal empty text. The hint does not currently
4142 * participate in determining the size of the view.
4144 * @attr ref android.R.styleable#TextView_hint
4146 @android.view.RemotableViewMethod
4147 public final void setHint(CharSequence hint) {
4148 mHint = TextUtils.stringOrSpannedString(hint);
4150 if (mLayout != null) {
4154 if (mText.length() == 0) {
4158 // Invalidate display list if hint is currently used
4159 if (mEditor != null && mText.length() == 0 && mHint != null) {
4160 mEditor.invalidateTextDisplayList();
4165 * Sets the text to be displayed when the text of the TextView is empty,
4168 * @attr ref android.R.styleable#TextView_hint
4170 @android.view.RemotableViewMethod
4171 public final void setHint(int resid) {
4172 setHint(getContext().getResources().getText(resid));
4176 * Returns the hint that is displayed when the text of the TextView
4179 * @attr ref android.R.styleable#TextView_hint
4181 @ViewDebug.CapturedViewProperty
4182 public CharSequence getHint() {
4186 boolean isSingleLine() {
4190 private static boolean isMultilineInputType(int type) {
4191 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4192 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4196 * Removes the suggestion spans.
4198 CharSequence removeSuggestionSpans(CharSequence text) {
4199 if (text instanceof Spanned) {
4200 Spannable spannable;
4201 if (text instanceof Spannable) {
4202 spannable = (Spannable) text;
4204 spannable = new SpannableString(text);
4208 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4209 for (int i = 0; i < spans.length; i++) {
4210 spannable.removeSpan(spans[i]);
4217 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4218 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4219 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4220 * then a soft keyboard will not be displayed for this text view.
4222 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4223 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4226 * @see #getInputType()
4227 * @see #setRawInputType(int)
4228 * @see android.text.InputType
4229 * @attr ref android.R.styleable#TextView_inputType
4231 public void setInputType(int type) {
4232 final boolean wasPassword = isPasswordInputType(getInputType());
4233 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
4234 setInputType(type, false);
4235 final boolean isPassword = isPasswordInputType(type);
4236 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
4237 boolean forceUpdate = false;
4239 setTransformationMethod(PasswordTransformationMethod.getInstance());
4240 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4241 } else if (isVisiblePassword) {
4242 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4245 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4246 } else if (wasPassword || wasVisiblePassword) {
4247 // not in password mode, clean up typeface and transformation
4248 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
4249 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4254 boolean singleLine = !isMultilineInputType(type);
4256 // We need to update the single line mode if it has changed or we
4257 // were previously in password mode.
4258 if (mSingleLine != singleLine || forceUpdate) {
4259 // Change single line mode, but only change the transformation if
4260 // we are not in password mode.
4261 applySingleLine(singleLine, !isPassword, true);
4264 if (!isSuggestionsEnabled()) {
4265 mText = removeSuggestionSpans(mText);
4268 InputMethodManager imm = InputMethodManager.peekInstance();
4269 if (imm != null) imm.restartInput(this);
4273 * It would be better to rely on the input type for everything. A password inputType should have
4274 * a password transformation. We should hence use isPasswordInputType instead of this method.
4277 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4278 * would install the correct transformation).
4279 * - Refuse the installation of a non-password transformation in setTransformation if the input
4282 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4283 * is useful since it matches what the user can see (obfuscated text or not).
4285 * @return true if the current transformation method is of the password type.
4287 private boolean hasPasswordTransformationMethod() {
4288 return mTransformation instanceof PasswordTransformationMethod;
4291 private static boolean isPasswordInputType(int inputType) {
4292 final int variation =
4293 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4295 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4297 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4299 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
4302 private static boolean isVisiblePasswordInputType(int inputType) {
4303 final int variation =
4304 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4306 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
4310 * Directly change the content type integer of the text view, without
4311 * modifying any other state.
4312 * @see #setInputType(int)
4313 * @see android.text.InputType
4314 * @attr ref android.R.styleable#TextView_inputType
4316 public void setRawInputType(int type) {
4317 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
4318 createEditorIfNeeded();
4319 mEditor.mInputType = type;
4322 private void setInputType(int type, boolean direct) {
4323 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4325 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
4326 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
4327 TextKeyListener.Capitalize cap;
4328 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4329 cap = TextKeyListener.Capitalize.CHARACTERS;
4330 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4331 cap = TextKeyListener.Capitalize.WORDS;
4332 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4333 cap = TextKeyListener.Capitalize.SENTENCES;
4335 cap = TextKeyListener.Capitalize.NONE;
4337 input = TextKeyListener.getInstance(autotext, cap);
4338 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4339 input = DigitsKeyListener.getInstance(
4340 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4341 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4342 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4343 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4344 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4345 input = DateKeyListener.getInstance();
4347 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4348 input = TimeKeyListener.getInstance();
4351 input = DateTimeKeyListener.getInstance();
4354 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4355 input = DialerKeyListener.getInstance();
4357 input = TextKeyListener.getInstance();
4359 setRawInputType(type);
4361 createEditorIfNeeded();
4362 mEditor.mKeyListener = input;
4364 setKeyListenerOnly(input);
4369 * Get the type of the editable content.
4371 * @see #setInputType(int)
4372 * @see android.text.InputType
4374 public int getInputType() {
4375 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
4379 * Change the editor type integer associated with the text view, which
4380 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4382 * @see #getImeOptions
4383 * @see android.view.inputmethod.EditorInfo
4384 * @attr ref android.R.styleable#TextView_imeOptions
4386 public void setImeOptions(int imeOptions) {
4387 createEditorIfNeeded();
4388 mEditor.createInputContentTypeIfNeeded();
4389 mEditor.mInputContentType.imeOptions = imeOptions;
4393 * Get the type of the IME editor.
4395 * @see #setImeOptions(int)
4396 * @see android.view.inputmethod.EditorInfo
4398 public int getImeOptions() {
4399 return mEditor != null && mEditor.mInputContentType != null
4400 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
4404 * Change the custom IME action associated with the text view, which
4405 * will be reported to an IME with {@link EditorInfo#actionLabel}
4406 * and {@link EditorInfo#actionId} when it has focus.
4407 * @see #getImeActionLabel
4408 * @see #getImeActionId
4409 * @see android.view.inputmethod.EditorInfo
4410 * @attr ref android.R.styleable#TextView_imeActionLabel
4411 * @attr ref android.R.styleable#TextView_imeActionId
4413 public void setImeActionLabel(CharSequence label, int actionId) {
4414 createEditorIfNeeded();
4415 mEditor.createInputContentTypeIfNeeded();
4416 mEditor.mInputContentType.imeActionLabel = label;
4417 mEditor.mInputContentType.imeActionId = actionId;
4421 * Get the IME action label previous set with {@link #setImeActionLabel}.
4423 * @see #setImeActionLabel
4424 * @see android.view.inputmethod.EditorInfo
4426 public CharSequence getImeActionLabel() {
4427 return mEditor != null && mEditor.mInputContentType != null
4428 ? mEditor.mInputContentType.imeActionLabel : null;
4432 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4434 * @see #setImeActionLabel
4435 * @see android.view.inputmethod.EditorInfo
4437 public int getImeActionId() {
4438 return mEditor != null && mEditor.mInputContentType != null
4439 ? mEditor.mInputContentType.imeActionId : 0;
4443 * Set a special listener to be called when an action is performed
4444 * on the text view. This will be called when the enter key is pressed,
4445 * or when an action supplied to the IME is selected by the user. Setting
4446 * this means that the normal hard key event will not insert a newline
4447 * into the text view, even if it is multi-line; holding down the ALT
4448 * modifier will, however, allow the user to insert a newline character.
4450 public void setOnEditorActionListener(OnEditorActionListener l) {
4451 createEditorIfNeeded();
4452 mEditor.createInputContentTypeIfNeeded();
4453 mEditor.mInputContentType.onEditorActionListener = l;
4457 * Called when an attached input method calls
4458 * {@link InputConnection#performEditorAction(int)
4459 * InputConnection.performEditorAction()}
4460 * for this text view. The default implementation will call your action
4461 * listener supplied to {@link #setOnEditorActionListener}, or perform
4462 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
4463 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4464 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
4465 * EditorInfo.IME_ACTION_DONE}.
4467 * <p>For backwards compatibility, if no IME options have been set and the
4468 * text view would not normally advance focus on enter, then
4469 * the NEXT and DONE actions received here will be turned into an enter
4470 * key down/up pair to go through the normal key handling.
4472 * @param actionCode The code of the action being performed.
4474 * @see #setOnEditorActionListener
4476 public void onEditorAction(int actionCode) {
4477 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
4479 if (ict.onEditorActionListener != null) {
4480 if (ict.onEditorActionListener.onEditorAction(this,
4481 actionCode, null)) {
4486 // This is the handling for some default action.
4487 // Note that for backwards compatibility we don't do this
4488 // default handling if explicit ime options have not been given,
4489 // instead turning this into the normal enter key codes that an
4490 // app may be expecting.
4491 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
4492 View v = focusSearch(FOCUS_FORWARD);
4494 if (!v.requestFocus(FOCUS_FORWARD)) {
4495 throw new IllegalStateException("focus search returned a view " +
4496 "that wasn't able to take focus!");
4501 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
4502 View v = focusSearch(FOCUS_BACKWARD);
4504 if (!v.requestFocus(FOCUS_BACKWARD)) {
4505 throw new IllegalStateException("focus search returned a view " +
4506 "that wasn't able to take focus!");
4511 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4512 InputMethodManager imm = InputMethodManager.peekInstance();
4513 if (imm != null && imm.isActive(this)) {
4514 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4520 ViewRootImpl viewRootImpl = getViewRootImpl();
4521 if (viewRootImpl != null) {
4522 long eventTime = SystemClock.uptimeMillis();
4523 viewRootImpl.dispatchKeyFromIme(
4524 new KeyEvent(eventTime, eventTime,
4525 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4526 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4527 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4528 | KeyEvent.FLAG_EDITOR_ACTION));
4529 viewRootImpl.dispatchKeyFromIme(
4530 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
4531 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4532 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4533 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4534 | KeyEvent.FLAG_EDITOR_ACTION));
4539 * Set the private content type of the text, which is the
4540 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4541 * field that will be filled in when creating an input connection.
4543 * @see #getPrivateImeOptions()
4544 * @see EditorInfo#privateImeOptions
4545 * @attr ref android.R.styleable#TextView_privateImeOptions
4547 public void setPrivateImeOptions(String type) {
4548 createEditorIfNeeded();
4549 mEditor.createInputContentTypeIfNeeded();
4550 mEditor.mInputContentType.privateImeOptions = type;
4554 * Get the private type of the content.
4556 * @see #setPrivateImeOptions(String)
4557 * @see EditorInfo#privateImeOptions
4559 public String getPrivateImeOptions() {
4560 return mEditor != null && mEditor.mInputContentType != null
4561 ? mEditor.mInputContentType.privateImeOptions : null;
4565 * Set the extra input data of the text, which is the
4566 * {@link EditorInfo#extras TextBoxAttribute.extras}
4567 * Bundle that will be filled in when creating an input connection. The
4568 * given integer is the resource ID of an XML resource holding an
4569 * {@link android.R.styleable#InputExtras <input-extras>} XML tree.
4571 * @see #getInputExtras(boolean)
4572 * @see EditorInfo#extras
4573 * @attr ref android.R.styleable#TextView_editorExtras
4575 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
4576 createEditorIfNeeded();
4577 XmlResourceParser parser = getResources().getXml(xmlResId);
4578 mEditor.createInputContentTypeIfNeeded();
4579 mEditor.mInputContentType.extras = new Bundle();
4580 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
4584 * Retrieve the input extras currently associated with the text view, which
4585 * can be viewed as well as modified.
4587 * @param create If true, the extras will be created if they don't already
4588 * exist. Otherwise, null will be returned if none have been created.
4589 * @see #setInputExtras(int)
4590 * @see EditorInfo#extras
4591 * @attr ref android.R.styleable#TextView_editorExtras
4593 public Bundle getInputExtras(boolean create) {
4594 if (mEditor == null && !create) return null;
4595 createEditorIfNeeded();
4596 if (mEditor.mInputContentType == null) {
4597 if (!create) return null;
4598 mEditor.createInputContentTypeIfNeeded();
4600 if (mEditor.mInputContentType.extras == null) {
4601 if (!create) return null;
4602 mEditor.mInputContentType.extras = new Bundle();
4604 return mEditor.mInputContentType.extras;
4608 * Returns the error message that was set to be displayed with
4609 * {@link #setError}, or <code>null</code> if no error was set
4610 * or if it the error was cleared by the widget after user input.
4612 public CharSequence getError() {
4613 return mEditor == null ? null : mEditor.mError;
4617 * Sets the right-hand compound drawable of the TextView to the "error"
4618 * icon and sets an error message that will be displayed in a popup when
4619 * the TextView has focus. The icon and error message will be reset to
4620 * null when any key events cause changes to the TextView's text. If the
4621 * <code>error</code> is <code>null</code>, the error message and icon
4624 @android.view.RemotableViewMethod
4625 public void setError(CharSequence error) {
4626 if (error == null) {
4627 setError(null, null);
4629 Drawable dr = getContext().getDrawable(
4630 com.android.internal.R.drawable.indicator_input_error);
4632 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4633 setError(error, dr);
4638 * Sets the right-hand compound drawable of the TextView to the specified
4639 * icon and sets an error message that will be displayed in a popup when
4640 * the TextView has focus. The icon and error message will be reset to
4641 * null when any key events cause changes to the TextView's text. The
4642 * drawable must already have had {@link Drawable#setBounds} set on it.
4643 * If the <code>error</code> is <code>null</code>, the error message will
4644 * be cleared (and you should provide a <code>null</code> icon as well).
4646 public void setError(CharSequence error, Drawable icon) {
4647 createEditorIfNeeded();
4648 mEditor.setError(error, icon);
4649 notifyViewAccessibilityStateChangedIfNeeded(
4650 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
4654 protected boolean setFrame(int l, int t, int r, int b) {
4655 boolean result = super.setFrame(l, t, r, b);
4657 if (mEditor != null) mEditor.setFrame();
4659 restartMarqueeIfNeeded();
4664 private void restartMarqueeIfNeeded() {
4665 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4666 mRestartMarquee = false;
4672 * Sets the list of input filters that will be used if the buffer is
4673 * Editable. Has no effect otherwise.
4675 * @attr ref android.R.styleable#TextView_maxLength
4677 public void setFilters(InputFilter[] filters) {
4678 if (filters == null) {
4679 throw new IllegalArgumentException();
4684 if (mText instanceof Editable) {
4685 setFilters((Editable) mText, filters);
4690 * Sets the list of input filters on the specified Editable,
4691 * and includes mInput in the list if it is an InputFilter.
4693 private void setFilters(Editable e, InputFilter[] filters) {
4694 if (mEditor != null) {
4695 final boolean undoFilter = mEditor.mUndoInputFilter != null;
4696 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4698 if (undoFilter) num++;
4699 if (keyFilter) num++;
4701 InputFilter[] nf = new InputFilter[filters.length + num];
4703 System.arraycopy(filters, 0, nf, 0, filters.length);
4706 nf[filters.length] = mEditor.mUndoInputFilter;
4710 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
4717 e.setFilters(filters);
4721 * Returns the current list of input filters.
4723 * @attr ref android.R.styleable#TextView_maxLength
4725 public InputFilter[] getFilters() {
4729 /////////////////////////////////////////////////////////////////////////
4731 private int getBoxHeight(Layout l) {
4732 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
4733 int padding = (l == mHintLayout) ?
4734 getCompoundPaddingTop() + getCompoundPaddingBottom() :
4735 getExtendedPaddingTop() + getExtendedPaddingBottom();
4736 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
4739 int getVerticalOffset(boolean forceNormal) {
4741 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4744 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4748 if (gravity != Gravity.TOP) {
4749 int boxht = getBoxHeight(l);
4750 int textht = l.getHeight();
4752 if (textht < boxht) {
4753 if (gravity == Gravity.BOTTOM)
4754 voffset = boxht - textht;
4755 else // (gravity == Gravity.CENTER_VERTICAL)
4756 voffset = (boxht - textht) >> 1;
4762 private int getBottomVerticalOffset(boolean forceNormal) {
4764 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
4767 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
4771 if (gravity != Gravity.BOTTOM) {
4772 int boxht = getBoxHeight(l);
4773 int textht = l.getHeight();
4775 if (textht < boxht) {
4776 if (gravity == Gravity.TOP)
4777 voffset = boxht - textht;
4778 else // (gravity == Gravity.CENTER_VERTICAL)
4779 voffset = (boxht - textht) >> 1;
4785 void invalidateCursorPath() {
4786 if (mHighlightPathBogus) {
4789 final int horizontalPadding = getCompoundPaddingLeft();
4790 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4792 if (mEditor.mCursorCount == 0) {
4793 synchronized (TEMP_RECTF) {
4795 * The reason for this concern about the thickness of the
4796 * cursor and doing the floor/ceil on the coordinates is that
4797 * some EditTexts (notably textfields in the Browser) have
4798 * anti-aliased text where not all the characters are
4799 * necessarily at integer-multiple locations. This should
4800 * make sure the entire cursor gets invalidated instead of
4801 * sometimes missing half a pixel.
4803 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth());
4810 // mHighlightPath is guaranteed to be non null at that point.
4811 mHighlightPath.computeBounds(TEMP_RECTF, false);
4813 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
4814 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
4815 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
4816 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
4819 for (int i = 0; i < mEditor.mCursorCount; i++) {
4820 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
4821 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
4822 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
4828 void invalidateCursor() {
4829 int where = getSelectionEnd();
4831 invalidateCursor(where, where, where);
4834 private void invalidateCursor(int a, int b, int c) {
4835 if (a >= 0 || b >= 0 || c >= 0) {
4836 int start = Math.min(Math.min(a, b), c);
4837 int end = Math.max(Math.max(a, b), c);
4838 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
4843 * Invalidates the region of text enclosed between the start and end text offsets.
4845 void invalidateRegion(int start, int end, boolean invalidateCursor) {
4846 if (mLayout == null) {
4849 int lineStart = mLayout.getLineForOffset(start);
4850 int top = mLayout.getLineTop(lineStart);
4852 // This is ridiculous, but the descent from the line above
4853 // can hang down into the line we really want to redraw,
4854 // so we have to invalidate part of the line above to make
4855 // sure everything that needs to be redrawn really is.
4856 // (But not the whole line above, because that would cause
4857 // the same problem with the descenders on the line above it!)
4858 if (lineStart > 0) {
4859 top -= mLayout.getLineDescent(lineStart - 1);
4865 lineEnd = lineStart;
4867 lineEnd = mLayout.getLineForOffset(end);
4869 int bottom = mLayout.getLineBottom(lineEnd);
4871 // mEditor can be null in case selection is set programmatically.
4872 if (invalidateCursor && mEditor != null) {
4873 for (int i = 0; i < mEditor.mCursorCount; i++) {
4874 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
4875 top = Math.min(top, bounds.top);
4876 bottom = Math.max(bottom, bounds.bottom);
4880 final int compoundPaddingLeft = getCompoundPaddingLeft();
4881 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
4884 if (lineStart == lineEnd && !invalidateCursor) {
4885 left = (int) mLayout.getPrimaryHorizontal(start);
4886 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
4887 left += compoundPaddingLeft;
4888 right += compoundPaddingLeft;
4890 // Rectangle bounding box when the region spans several lines
4891 left = compoundPaddingLeft;
4892 right = getWidth() - getCompoundPaddingRight();
4895 invalidate(mScrollX + left, verticalPadding + top,
4896 mScrollX + right, verticalPadding + bottom);
4900 private void registerForPreDraw() {
4901 if (!mPreDrawRegistered) {
4902 getViewTreeObserver().addOnPreDrawListener(this);
4903 mPreDrawRegistered = true;
4907 private void unregisterForPreDraw() {
4908 getViewTreeObserver().removeOnPreDrawListener(this);
4909 mPreDrawRegistered = false;
4910 mPreDrawListenerDetached = false;
4916 public boolean onPreDraw() {
4917 if (mLayout == null) {
4921 if (mMovement != null) {
4922 /* This code also provides auto-scrolling when a cursor is moved using a
4923 * CursorController (insertion point or selection limits).
4924 * For selection, ensure start or end is visible depending on controller's state.
4926 int curs = getSelectionEnd();
4927 // Do not create the controller if it is not already created.
4928 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
4929 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
4930 curs = getSelectionStart();
4934 * TODO: This should really only keep the end in view if
4935 * it already was before the text changed. I'm not sure
4936 * of a good way to tell from here if it was.
4938 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
4939 curs = mText.length();
4943 bringPointIntoView(curs);
4946 bringTextIntoView();
4949 // This has to be checked here since:
4950 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
4951 // a screen rotation) since layout is not yet initialized at that point.
4952 if (mEditor != null && mEditor.mCreatedWithASelection) {
4953 mEditor.startSelectionActionMode();
4954 mEditor.mCreatedWithASelection = false;
4957 // Phone specific code (there is no ExtractEditText on tablets).
4958 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
4959 // not be set. Do the test here instead.
4960 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
4961 mEditor.startSelectionActionMode();
4964 unregisterForPreDraw();
4970 protected void onAttachedToWindow() {
4971 super.onAttachedToWindow();
4973 mTemporaryDetach = false;
4975 if (mEditor != null) mEditor.onAttachedToWindow();
4977 if (mPreDrawListenerDetached) {
4978 getViewTreeObserver().addOnPreDrawListener(this);
4979 mPreDrawListenerDetached = false;
4985 protected void onDetachedFromWindowInternal() {
4986 if (mPreDrawRegistered) {
4987 getViewTreeObserver().removeOnPreDrawListener(this);
4988 mPreDrawListenerDetached = true;
4991 resetResolvedDrawables();
4993 if (mEditor != null) mEditor.onDetachedFromWindow();
4995 super.onDetachedFromWindowInternal();
4999 public void onScreenStateChanged(int screenState) {
5000 super.onScreenStateChanged(screenState);
5001 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5005 protected boolean isPaddingOffsetRequired() {
5006 return mShadowRadius != 0 || mDrawables != null;
5010 protected int getLeftPaddingOffset() {
5011 return getCompoundPaddingLeft() - mPaddingLeft +
5012 (int) Math.min(0, mShadowDx - mShadowRadius);
5016 protected int getTopPaddingOffset() {
5017 return (int) Math.min(0, mShadowDy - mShadowRadius);
5021 protected int getBottomPaddingOffset() {
5022 return (int) Math.max(0, mShadowDy + mShadowRadius);
5025 private int getFudgedPaddingRight() {
5026 // Add sufficient space for cursor and tone marks
5027 int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
5028 return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
5032 protected int getRightPaddingOffset() {
5033 return -(getFudgedPaddingRight() - mPaddingRight) +
5034 (int) Math.max(0, mShadowDx + mShadowRadius);
5038 protected boolean verifyDrawable(Drawable who) {
5039 final boolean verified = super.verifyDrawable(who);
5040 if (!verified && mDrawables != null) {
5041 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop ||
5042 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom ||
5043 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd;
5049 public void jumpDrawablesToCurrentState() {
5050 super.jumpDrawablesToCurrentState();
5051 if (mDrawables != null) {
5052 if (mDrawables.mDrawableLeft != null) {
5053 mDrawables.mDrawableLeft.jumpToCurrentState();
5055 if (mDrawables.mDrawableTop != null) {
5056 mDrawables.mDrawableTop.jumpToCurrentState();
5058 if (mDrawables.mDrawableRight != null) {
5059 mDrawables.mDrawableRight.jumpToCurrentState();
5061 if (mDrawables.mDrawableBottom != null) {
5062 mDrawables.mDrawableBottom.jumpToCurrentState();
5064 if (mDrawables.mDrawableStart != null) {
5065 mDrawables.mDrawableStart.jumpToCurrentState();
5067 if (mDrawables.mDrawableEnd != null) {
5068 mDrawables.mDrawableEnd.jumpToCurrentState();
5074 public void invalidateDrawable(Drawable drawable) {
5075 boolean handled = false;
5077 if (verifyDrawable(drawable)) {
5078 final Rect dirty = drawable.getBounds();
5079 int scrollX = mScrollX;
5080 int scrollY = mScrollY;
5082 // IMPORTANT: The coordinates below are based on the coordinates computed
5083 // for each compound drawable in onDraw(). Make sure to update each section
5085 final TextView.Drawables drawables = mDrawables;
5086 if (drawables != null) {
5087 if (drawable == drawables.mDrawableLeft) {
5088 final int compoundPaddingTop = getCompoundPaddingTop();
5089 final int compoundPaddingBottom = getCompoundPaddingBottom();
5090 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5092 scrollX += mPaddingLeft;
5093 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5095 } else if (drawable == drawables.mDrawableRight) {
5096 final int compoundPaddingTop = getCompoundPaddingTop();
5097 final int compoundPaddingBottom = getCompoundPaddingBottom();
5098 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5100 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5101 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5103 } else if (drawable == drawables.mDrawableTop) {
5104 final int compoundPaddingLeft = getCompoundPaddingLeft();
5105 final int compoundPaddingRight = getCompoundPaddingRight();
5106 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5108 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5109 scrollY += mPaddingTop;
5111 } else if (drawable == drawables.mDrawableBottom) {
5112 final int compoundPaddingLeft = getCompoundPaddingLeft();
5113 final int compoundPaddingRight = getCompoundPaddingRight();
5114 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5116 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5117 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5123 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5124 dirty.right + scrollX, dirty.bottom + scrollY);
5129 super.invalidateDrawable(drawable);
5134 public boolean hasOverlappingRendering() {
5135 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5136 return ((getBackground() != null && getBackground().getCurrent() != null)
5137 || mText instanceof Spannable || hasSelection()
5138 || isHorizontalFadingEdgeEnabled());
5143 * Returns the state of the {@code textIsSelectable} flag (See
5144 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5145 * to allow users to select and copy text in a non-editable TextView, the content of an
5146 * {@link EditText} can always be selected, independently of the value of this flag.
5149 * @return True if the text displayed in this TextView can be selected by the user.
5151 * @attr ref android.R.styleable#TextView_textIsSelectable
5153 public boolean isTextSelectable() {
5154 return mEditor == null ? false : mEditor.mTextIsSelectable;
5158 * Sets whether the content of this view is selectable by the user. The default is
5159 * {@code false}, meaning that the content is not selectable.
5161 * When you use a TextView to display a useful piece of information to the user (such as a
5162 * contact's address), make it selectable, so that the user can select and copy its
5163 * content. You can also use set the XML attribute
5164 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5166 * When you call this method to set the value of {@code textIsSelectable}, it sets
5167 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5168 * and {@code longClickable} to the same value. These flags correspond to the attributes
5169 * {@link android.R.styleable#View_focusable android:focusable},
5170 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5171 * {@link android.R.styleable#View_clickable android:clickable}, and
5172 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5173 * flags to a state you had set previously, call one or more of the following methods:
5174 * {@link #setFocusable(boolean) setFocusable()},
5175 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5176 * {@link #setClickable(boolean) setClickable()} or
5177 * {@link #setLongClickable(boolean) setLongClickable()}.
5179 * @param selectable Whether the content of this TextView should be selectable.
5181 public void setTextIsSelectable(boolean selectable) {
5182 if (!selectable && mEditor == null) return; // false is default value with no edit data
5184 createEditorIfNeeded();
5185 if (mEditor.mTextIsSelectable == selectable) return;
5187 mEditor.mTextIsSelectable = selectable;
5188 setFocusableInTouchMode(selectable);
5189 setFocusable(selectable);
5190 setClickable(selectable);
5191 setLongClickable(selectable);
5193 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5195 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5196 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5198 // Called by setText above, but safer in case of future code changes
5199 mEditor.prepareCursorControllers();
5203 protected int[] onCreateDrawableState(int extraSpace) {
5204 final int[] drawableState;
5207 drawableState = super.onCreateDrawableState(extraSpace);
5209 drawableState = super.onCreateDrawableState(extraSpace + 1);
5210 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5213 if (isTextSelectable()) {
5214 // Disable pressed state, which was introduced when TextView was made clickable.
5215 // Prevents text color change.
5216 // setClickable(false) would have a similar effect, but it also disables focus changes
5217 // and long press actions, which are both needed by text selection.
5218 final int length = drawableState.length;
5219 for (int i = 0; i < length; i++) {
5220 if (drawableState[i] == R.attr.state_pressed) {
5221 final int[] nonPressedState = new int[length - 1];
5222 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5223 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5224 return nonPressedState;
5229 return drawableState;
5232 private Path getUpdatedHighlightPath() {
5233 Path highlight = null;
5234 Paint highlightPaint = mHighlightPaint;
5236 final int selStart = getSelectionStart();
5237 final int selEnd = getSelectionEnd();
5238 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5239 if (selStart == selEnd) {
5240 if (mEditor != null && mEditor.isCursorVisible() &&
5241 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5242 (2 * Editor.BLINK) < Editor.BLINK) {
5243 if (mHighlightPathBogus) {
5244 if (mHighlightPath == null) mHighlightPath = new Path();
5245 mHighlightPath.reset();
5246 mLayout.getCursorPath(selStart, mHighlightPath, mText);
5247 mEditor.updateCursorsPositions();
5248 mHighlightPathBogus = false;
5251 // XXX should pass to skin instead of drawing directly
5252 highlightPaint.setColor(mCurTextColor);
5253 highlightPaint.setStyle(Paint.Style.STROKE);
5254 highlight = mHighlightPath;
5257 if (mHighlightPathBogus) {
5258 if (mHighlightPath == null) mHighlightPath = new Path();
5259 mHighlightPath.reset();
5260 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5261 mHighlightPathBogus = false;
5264 // XXX should pass to skin instead of drawing directly
5265 highlightPaint.setColor(mHighlightColor);
5266 highlightPaint.setStyle(Paint.Style.FILL);
5268 highlight = mHighlightPath;
5277 public int getHorizontalOffsetForDrawables() {
5282 protected void onDraw(Canvas canvas) {
5283 restartMarqueeIfNeeded();
5285 // Draw the background for this view
5286 super.onDraw(canvas);
5288 final int compoundPaddingLeft = getCompoundPaddingLeft();
5289 final int compoundPaddingTop = getCompoundPaddingTop();
5290 final int compoundPaddingRight = getCompoundPaddingRight();
5291 final int compoundPaddingBottom = getCompoundPaddingBottom();
5292 final int scrollX = mScrollX;
5293 final int scrollY = mScrollY;
5294 final int right = mRight;
5295 final int left = mLeft;
5296 final int bottom = mBottom;
5297 final int top = mTop;
5298 final boolean isLayoutRtl = isLayoutRtl();
5299 final int offset = getHorizontalOffsetForDrawables();
5300 final int leftOffset = isLayoutRtl ? 0 : offset;
5301 final int rightOffset = isLayoutRtl ? offset : 0 ;
5303 final Drawables dr = mDrawables;
5306 * Compound, not extended, because the icon is not clipped
5307 * if the text height is smaller.
5310 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5311 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5313 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5314 // Make sure to update invalidateDrawable() when changing this code.
5315 if (dr.mDrawableLeft != null) {
5317 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5318 scrollY + compoundPaddingTop +
5319 (vspace - dr.mDrawableHeightLeft) / 2);
5320 dr.mDrawableLeft.draw(canvas);
5324 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5325 // Make sure to update invalidateDrawable() when changing this code.
5326 if (dr.mDrawableRight != null) {
5328 canvas.translate(scrollX + right - left - mPaddingRight
5329 - dr.mDrawableSizeRight - rightOffset,
5330 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5331 dr.mDrawableRight.draw(canvas);
5335 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5336 // Make sure to update invalidateDrawable() when changing this code.
5337 if (dr.mDrawableTop != null) {
5339 canvas.translate(scrollX + compoundPaddingLeft +
5340 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5341 dr.mDrawableTop.draw(canvas);
5345 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5346 // Make sure to update invalidateDrawable() when changing this code.
5347 if (dr.mDrawableBottom != null) {
5349 canvas.translate(scrollX + compoundPaddingLeft +
5350 (hspace - dr.mDrawableWidthBottom) / 2,
5351 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5352 dr.mDrawableBottom.draw(canvas);
5357 int color = mCurTextColor;
5359 if (mLayout == null) {
5363 Layout layout = mLayout;
5365 if (mHint != null && mText.length() == 0) {
5366 if (mHintTextColor != null) {
5367 color = mCurHintTextColor;
5370 layout = mHintLayout;
5373 mTextPaint.setColor(color);
5374 mTextPaint.drawableState = getDrawableState();
5377 /* Would be faster if we didn't have to do this. Can we chop the
5378 (displayable) text so that we don't need to do this ever?
5381 int extendedPaddingTop = getExtendedPaddingTop();
5382 int extendedPaddingBottom = getExtendedPaddingBottom();
5384 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5385 final int maxScrollY = mLayout.getHeight() - vspace;
5387 float clipLeft = compoundPaddingLeft + scrollX;
5388 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5389 float clipRight = right - left - getFudgedPaddingRight() + scrollX;
5390 float clipBottom = bottom - top + scrollY -
5391 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5393 if (mShadowRadius != 0) {
5394 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5395 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5397 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5398 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5401 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5403 int voffsetText = 0;
5404 int voffsetCursor = 0;
5406 // translate in by our padding
5407 /* shortcircuit calling getVerticaOffset() */
5408 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5409 voffsetText = getVerticalOffset(false);
5410 voffsetCursor = getVerticalOffset(true);
5412 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5414 final int layoutDirection = getLayoutDirection();
5415 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5416 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5417 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5418 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5419 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5420 final int width = mRight - mLeft;
5421 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5422 final float dx = mLayout.getLineRight(0) - (width - padding);
5423 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5426 if (mMarquee != null && mMarquee.isRunning()) {
5427 final float dx = -mMarquee.getScroll();
5428 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5432 final int cursorOffsetVertical = voffsetCursor - voffsetText;
5434 Path highlight = getUpdatedHighlightPath();
5435 if (mEditor != null) {
5436 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5438 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5441 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5442 final float dx = mMarquee.getGhostOffset();
5443 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5444 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5451 public void getFocusedRect(Rect r) {
5452 if (mLayout == null) {
5453 super.getFocusedRect(r);
5457 int selEnd = getSelectionEnd();
5459 super.getFocusedRect(r);
5463 int selStart = getSelectionStart();
5464 if (selStart < 0 || selStart >= selEnd) {
5465 int line = mLayout.getLineForOffset(selEnd);
5466 r.top = mLayout.getLineTop(line);
5467 r.bottom = mLayout.getLineBottom(line);
5468 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5469 r.right = r.left + 4;
5471 int lineStart = mLayout.getLineForOffset(selStart);
5472 int lineEnd = mLayout.getLineForOffset(selEnd);
5473 r.top = mLayout.getLineTop(lineStart);
5474 r.bottom = mLayout.getLineBottom(lineEnd);
5475 if (lineStart == lineEnd) {
5476 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5477 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5479 // Selection extends across multiple lines -- make the focused
5480 // rect cover the entire width.
5481 if (mHighlightPathBogus) {
5482 if (mHighlightPath == null) mHighlightPath = new Path();
5483 mHighlightPath.reset();
5484 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5485 mHighlightPathBogus = false;
5487 synchronized (TEMP_RECTF) {
5488 mHighlightPath.computeBounds(TEMP_RECTF, true);
5489 r.left = (int)TEMP_RECTF.left-1;
5490 r.right = (int)TEMP_RECTF.right+1;
5495 // Adjust for padding and gravity.
5496 int paddingLeft = getCompoundPaddingLeft();
5497 int paddingTop = getExtendedPaddingTop();
5498 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5499 paddingTop += getVerticalOffset(false);
5501 r.offset(paddingLeft, paddingTop);
5502 int paddingBottom = getExtendedPaddingBottom();
5503 r.bottom += paddingBottom;
5507 * Return the number of lines of text, or 0 if the internal Layout has not
5510 public int getLineCount() {
5511 return mLayout != null ? mLayout.getLineCount() : 0;
5515 * Return the baseline for the specified line (0...getLineCount() - 1)
5516 * If bounds is not null, return the top, left, right, bottom extents
5517 * of the specified line in it. If the internal Layout has not been built,
5518 * return 0 and set bounds to (0, 0, 0, 0)
5519 * @param line which line to examine (0..getLineCount() - 1)
5520 * @param bounds Optional. If not null, it returns the extent of the line
5521 * @return the Y-coordinate of the baseline
5523 public int getLineBounds(int line, Rect bounds) {
5524 if (mLayout == null) {
5525 if (bounds != null) {
5526 bounds.set(0, 0, 0, 0);
5531 int baseline = mLayout.getLineBounds(line, bounds);
5533 int voffset = getExtendedPaddingTop();
5534 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5535 voffset += getVerticalOffset(true);
5537 if (bounds != null) {
5538 bounds.offset(getCompoundPaddingLeft(), voffset);
5540 return baseline + voffset;
5545 public int getBaseline() {
5546 if (mLayout == null) {
5547 return super.getBaseline();
5551 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5552 voffset = getVerticalOffset(true);
5555 if (isLayoutModeOptical(mParent)) {
5556 voffset -= getOpticalInsets().top;
5559 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5566 protected int getFadeTop(boolean offsetRequired) {
5567 if (mLayout == null) return 0;
5570 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5571 voffset = getVerticalOffset(true);
5574 if (offsetRequired) voffset += getTopPaddingOffset();
5576 return getExtendedPaddingTop() + voffset;
5583 protected int getFadeHeight(boolean offsetRequired) {
5584 return mLayout != null ? mLayout.getHeight() : 0;
5588 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5589 if (keyCode == KeyEvent.KEYCODE_BACK) {
5590 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
5592 if (isInSelectionMode) {
5593 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5594 KeyEvent.DispatcherState state = getKeyDispatcherState();
5595 if (state != null) {
5596 state.startTracking(event, this);
5599 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5600 KeyEvent.DispatcherState state = getKeyDispatcherState();
5601 if (state != null) {
5602 state.handleUpEvent(event);
5604 if (event.isTracking() && !event.isCanceled()) {
5605 stopSelectionActionMode();
5611 return super.onKeyPreIme(keyCode, event);
5615 public boolean onKeyDown(int keyCode, KeyEvent event) {
5616 int which = doKeyDown(keyCode, event, null);
5618 return super.onKeyDown(keyCode, event);
5625 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5626 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5628 int which = doKeyDown(keyCode, down, event);
5630 // Go through default dispatching.
5631 return super.onKeyMultiple(keyCode, repeatCount, event);
5634 // Consumed the whole thing.
5640 // We are going to dispatch the remaining events to either the input
5641 // or movement method. To do this, we will just send a repeated stream
5642 // of down and up events until we have done the complete repeatCount.
5643 // It would be nice if those interfaces had an onKeyMultiple() method,
5644 // but adding that is a more complicated change.
5645 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5647 // mEditor and mEditor.mInput are not null from doKeyDown
5648 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5649 while (--repeatCount > 0) {
5650 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5651 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5653 hideErrorIfUnchanged();
5655 } else if (which == 2) {
5656 // mMovement is not null from doKeyDown
5657 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5658 while (--repeatCount > 0) {
5659 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5660 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5668 * Returns true if pressing ENTER in this field advances focus instead
5669 * of inserting the character. This is true mostly in single-line fields,
5670 * but also in mail addresses and subjects which will display on multiple
5671 * lines but where it doesn't make sense to insert newlines.
5673 private boolean shouldAdvanceFocusOnEnter() {
5674 if (getKeyListener() == null) {
5682 if (mEditor != null &&
5683 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5684 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5685 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5686 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5695 * Returns true if pressing TAB in this field advances focus instead
5696 * of inserting the character. Insert tabs only in multi-line editors.
5698 private boolean shouldAdvanceFocusOnTab() {
5699 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5700 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5701 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5702 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5703 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5710 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5715 // If this is the initial keydown, we don't want to prevent a movement away from this view.
5716 // While this shouldn't be necessary because any time we're preventing default movement we
5717 // should be restricting the focus to remain within this view, thus we'll also receive
5718 // the key up event, occasionally key up events will get dropped and we don't want to
5719 // prevent the user from traversing out of this on the next key down.
5720 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5721 mPreventDefaultMovement = false;
5725 case KeyEvent.KEYCODE_ENTER:
5726 if (event.hasNoModifiers()) {
5727 // When mInputContentType is set, we know that we are
5728 // running in a "modern" cupcake environment, so don't need
5729 // to worry about the application trying to capture
5730 // enter key events.
5731 if (mEditor != null && mEditor.mInputContentType != null) {
5732 // If there is an action listener, given them a
5733 // chance to consume the event.
5734 if (mEditor.mInputContentType.onEditorActionListener != null &&
5735 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
5736 this, EditorInfo.IME_NULL, event)) {
5737 mEditor.mInputContentType.enterDown = true;
5738 // We are consuming the enter key for them.
5743 // If our editor should move focus when enter is pressed, or
5744 // this is a generated event from an IME action button, then
5745 // don't let it be inserted into the text.
5746 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5747 || shouldAdvanceFocusOnEnter()) {
5748 if (hasOnClickListeners()) {
5756 case KeyEvent.KEYCODE_DPAD_CENTER:
5757 if (event.hasNoModifiers()) {
5758 if (shouldAdvanceFocusOnEnter()) {
5764 case KeyEvent.KEYCODE_TAB:
5765 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
5766 if (shouldAdvanceFocusOnTab()) {
5772 // Has to be done on key down (and not on key up) to correctly be intercepted.
5773 case KeyEvent.KEYCODE_BACK:
5774 if (mEditor != null && mEditor.mSelectionActionMode != null) {
5775 stopSelectionActionMode();
5781 if (mEditor != null && mEditor.mKeyListener != null) {
5782 boolean doDown = true;
5783 if (otherEvent != null) {
5786 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
5788 hideErrorIfUnchanged();
5793 } catch (AbstractMethodError e) {
5794 // onKeyOther was added after 1.0, so if it isn't
5795 // implemented we need to try to dispatch as a regular down.
5803 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
5806 hideErrorIfUnchanged();
5807 if (handled) return 1;
5811 // bug 650865: sometimes we get a key event before a layout.
5812 // don't try to move around if we don't know the layout.
5814 if (mMovement != null && mLayout != null) {
5815 boolean doDown = true;
5816 if (otherEvent != null) {
5818 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
5824 } catch (AbstractMethodError e) {
5825 // onKeyOther was added after 1.0, so if it isn't
5826 // implemented we need to try to dispatch as a regular down.
5830 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
5831 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5832 mPreventDefaultMovement = true;
5839 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
5843 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
5847 public void resetErrorChangedFlag() {
5849 * Keep track of what the error was before doing the input
5850 * so that if an input filter changed the error, we leave
5851 * that error showing. Otherwise, we take down whatever
5852 * error was showing when the user types something.
5854 if (mEditor != null) mEditor.mErrorWasChanged = false;
5860 public void hideErrorIfUnchanged() {
5861 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
5862 setError(null, null);
5867 public boolean onKeyUp(int keyCode, KeyEvent event) {
5869 return super.onKeyUp(keyCode, event);
5872 if (!KeyEvent.isModifierKey(keyCode)) {
5873 mPreventDefaultMovement = false;
5877 case KeyEvent.KEYCODE_DPAD_CENTER:
5878 if (event.hasNoModifiers()) {
5880 * If there is a click listener, just call through to
5881 * super, which will invoke it.
5883 * If there isn't a click listener, try to show the soft
5884 * input method. (It will also
5885 * call performClick(), but that won't do anything in
5888 if (!hasOnClickListeners()) {
5889 if (mMovement != null && mText instanceof Editable
5890 && mLayout != null && onCheckIsTextEditor()) {
5891 InputMethodManager imm = InputMethodManager.peekInstance();
5893 if (imm != null && getShowSoftInputOnFocus()) {
5894 imm.showSoftInput(this, 0);
5899 return super.onKeyUp(keyCode, event);
5901 case KeyEvent.KEYCODE_ENTER:
5902 if (event.hasNoModifiers()) {
5903 if (mEditor != null && mEditor.mInputContentType != null
5904 && mEditor.mInputContentType.onEditorActionListener != null
5905 && mEditor.mInputContentType.enterDown) {
5906 mEditor.mInputContentType.enterDown = false;
5907 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
5908 this, EditorInfo.IME_NULL, event)) {
5913 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
5914 || shouldAdvanceFocusOnEnter()) {
5916 * If there is a click listener, just call through to
5917 * super, which will invoke it.
5919 * If there isn't a click listener, try to advance focus,
5920 * but still call through to super, which will reset the
5921 * pressed state and longpress state. (It will also
5922 * call performClick(), but that won't do anything in
5925 if (!hasOnClickListeners()) {
5926 View v = focusSearch(FOCUS_DOWN);
5929 if (!v.requestFocus(FOCUS_DOWN)) {
5930 throw new IllegalStateException(
5931 "focus search returned a view " +
5932 "that wasn't able to take focus!");
5936 * Return true because we handled the key; super
5937 * will return false because there was no click
5940 super.onKeyUp(keyCode, event);
5942 } else if ((event.getFlags()
5943 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
5944 // No target for next focus, but make sure the IME
5945 // if this came from it.
5946 InputMethodManager imm = InputMethodManager.peekInstance();
5947 if (imm != null && imm.isActive(this)) {
5948 imm.hideSoftInputFromWindow(getWindowToken(), 0);
5953 return super.onKeyUp(keyCode, event);
5958 if (mEditor != null && mEditor.mKeyListener != null)
5959 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
5962 if (mMovement != null && mLayout != null)
5963 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
5966 return super.onKeyUp(keyCode, event);
5970 public boolean onCheckIsTextEditor() {
5971 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
5975 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
5976 if (onCheckIsTextEditor() && isEnabled()) {
5977 mEditor.createInputMethodStateIfNeeded();
5978 outAttrs.inputType = getInputType();
5979 if (mEditor.mInputContentType != null) {
5980 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
5981 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
5982 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
5983 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
5984 outAttrs.extras = mEditor.mInputContentType.extras;
5986 outAttrs.imeOptions = EditorInfo.IME_NULL;
5988 if (focusSearch(FOCUS_DOWN) != null) {
5989 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
5991 if (focusSearch(FOCUS_UP) != null) {
5992 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
5994 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
5995 == EditorInfo.IME_ACTION_UNSPECIFIED) {
5996 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
5997 // An action has not been set, but the enter key will move to
5998 // the next focus, so set the action to that.
5999 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6001 // An action has not been set, and there is no focus to move
6002 // to, so let's just supply a "done" action.
6003 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6005 if (!shouldAdvanceFocusOnEnter()) {
6006 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6009 if (isMultilineInputType(outAttrs.inputType)) {
6010 // Multi-line text editors should always show an enter key.
6011 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6013 outAttrs.hintText = mHint;
6014 if (mText instanceof Editable) {
6015 InputConnection ic = new EditableInputConnection(this);
6016 outAttrs.initialSelStart = getSelectionStart();
6017 outAttrs.initialSelEnd = getSelectionEnd();
6018 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6026 * If this TextView contains editable content, extract a portion of it
6027 * based on the information in <var>request</var> in to <var>outText</var>.
6028 * @return Returns true if the text was successfully extracted, else false.
6030 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6031 createEditorIfNeeded();
6032 return mEditor.extractText(request, outText);
6036 * This is used to remove all style-impacting spans from text before new
6037 * extracted text is being replaced into it, so that we don't have any
6038 * lingering spans applied during the replace.
6040 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6041 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6042 int i = spans.length;
6045 spannable.removeSpan(spans[i]);
6050 * Apply to this text view the given extracted text, as previously
6051 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6053 public void setExtractedText(ExtractedText text) {
6054 Editable content = getEditableText();
6055 if (text.text != null) {
6056 if (content == null) {
6057 setText(text.text, TextView.BufferType.EDITABLE);
6058 } else if (text.partialStartOffset < 0) {
6059 removeParcelableSpans(content, 0, content.length());
6060 content.replace(0, content.length(), text.text);
6062 final int N = content.length();
6063 int start = text.partialStartOffset;
6064 if (start > N) start = N;
6065 int end = text.partialEndOffset;
6066 if (end > N) end = N;
6067 removeParcelableSpans(content, start, end);
6068 content.replace(start, end, text.text);
6072 // Now set the selection position... make sure it is in range, to
6073 // avoid crashes. If this is a partial update, it is possible that
6074 // the underlying text may have changed, causing us problems here.
6075 // Also we just don't want to trust clients to do the right thing.
6076 Spannable sp = (Spannable)getText();
6077 final int N = sp.length();
6078 int start = text.selectionStart;
6079 if (start < 0) start = 0;
6080 else if (start > N) start = N;
6081 int end = text.selectionEnd;
6082 if (end < 0) end = 0;
6083 else if (end > N) end = N;
6084 Selection.setSelection(sp, start, end);
6086 // Finally, update the selection mode.
6087 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6088 MetaKeyKeyListener.startSelecting(this, sp);
6090 MetaKeyKeyListener.stopSelecting(this, sp);
6097 public void setExtracting(ExtractedTextRequest req) {
6098 if (mEditor.mInputMethodState != null) {
6099 mEditor.mInputMethodState.mExtractedTextRequest = req;
6101 // This would stop a possible selection mode, but no such mode is started in case
6102 // extracted mode will start. Some text is selected though, and will trigger an action mode
6103 // in the extracted view.
6104 mEditor.hideControllers();
6108 * Called by the framework in response to a text completion from
6109 * the current input method, provided by it calling
6110 * {@link InputConnection#commitCompletion
6111 * InputConnection.commitCompletion()}. The default implementation does
6112 * nothing; text views that are supporting auto-completion should override
6113 * this to do their desired behavior.
6115 * @param text The auto complete text the user has selected.
6117 public void onCommitCompletion(CompletionInfo text) {
6118 // intentionally empty
6122 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6123 * a dictionnary) from the current input method, provided by it calling
6124 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6125 * implementation flashes the background of the corrected word to provide feedback to the user.
6127 * @param info The auto correct info about the text that was corrected.
6129 public void onCommitCorrection(CorrectionInfo info) {
6130 if (mEditor != null) mEditor.onCommitCorrection(info);
6133 public void beginBatchEdit() {
6134 if (mEditor != null) mEditor.beginBatchEdit();
6137 public void endBatchEdit() {
6138 if (mEditor != null) mEditor.endBatchEdit();
6142 * Called by the framework in response to a request to begin a batch
6143 * of edit operations through a call to link {@link #beginBatchEdit()}.
6145 public void onBeginBatchEdit() {
6146 // intentionally empty
6150 * Called by the framework in response to a request to end a batch
6151 * of edit operations through a call to link {@link #endBatchEdit}.
6153 public void onEndBatchEdit() {
6154 // intentionally empty
6158 * Called by the framework in response to a private command from the
6159 * current method, provided by it calling
6160 * {@link InputConnection#performPrivateCommand
6161 * InputConnection.performPrivateCommand()}.
6163 * @param action The action name of the command.
6164 * @param data Any additional data for the command. This may be null.
6165 * @return Return true if you handled the command, else false.
6167 public boolean onPrivateIMECommand(String action, Bundle data) {
6171 private void nullLayouts() {
6172 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6173 mSavedLayout = (BoringLayout) mLayout;
6175 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6176 mSavedHintLayout = (BoringLayout) mHintLayout;
6179 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6181 mBoring = mHintBoring = null;
6183 // Since it depends on the value of mLayout
6184 if (mEditor != null) mEditor.prepareCursorControllers();
6188 * Make a new Layout based on the already-measured size of the view,
6189 * on the assumption that it was measured correctly at some point.
6191 private void assumeLayout() {
6192 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6198 int physicalWidth = width;
6200 if (mHorizontallyScrolling) {
6204 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6205 physicalWidth, false);
6208 private Layout.Alignment getLayoutAlignment() {
6209 Layout.Alignment alignment;
6210 switch (getTextAlignment()) {
6211 case TEXT_ALIGNMENT_GRAVITY:
6212 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6214 alignment = Layout.Alignment.ALIGN_NORMAL;
6217 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6220 alignment = Layout.Alignment.ALIGN_LEFT;
6223 alignment = Layout.Alignment.ALIGN_RIGHT;
6225 case Gravity.CENTER_HORIZONTAL:
6226 alignment = Layout.Alignment.ALIGN_CENTER;
6229 alignment = Layout.Alignment.ALIGN_NORMAL;
6233 case TEXT_ALIGNMENT_TEXT_START:
6234 alignment = Layout.Alignment.ALIGN_NORMAL;
6236 case TEXT_ALIGNMENT_TEXT_END:
6237 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6239 case TEXT_ALIGNMENT_CENTER:
6240 alignment = Layout.Alignment.ALIGN_CENTER;
6242 case TEXT_ALIGNMENT_VIEW_START:
6243 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6244 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6246 case TEXT_ALIGNMENT_VIEW_END:
6247 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6248 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6250 case TEXT_ALIGNMENT_INHERIT:
6251 // This should never happen as we have already resolved the text alignment
6252 // but better safe than sorry so we just fall through
6254 alignment = Layout.Alignment.ALIGN_NORMAL;
6261 * The width passed in is now the desired layout width,
6262 * not the full view width with padding.
6265 protected void makeNewLayout(int wantWidth, int hintWidth,
6266 BoringLayout.Metrics boring,
6267 BoringLayout.Metrics hintBoring,
6268 int ellipsisWidth, boolean bringIntoView) {
6271 // Update "old" cached values
6272 mOldMaximum = mMaximum;
6273 mOldMaxMode = mMaxMode;
6275 mHighlightPathBogus = true;
6277 if (wantWidth < 0) {
6280 if (hintWidth < 0) {
6284 Layout.Alignment alignment = getLayoutAlignment();
6285 final boolean testDirChange = mSingleLine && mLayout != null &&
6286 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6287 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6289 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6290 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6291 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6292 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6293 TruncateAt effectiveEllipsize = mEllipsize;
6294 if (mEllipsize == TruncateAt.MARQUEE &&
6295 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6296 effectiveEllipsize = TruncateAt.END_SMALL;
6299 if (mTextDir == null) {
6300 mTextDir = getTextDirectionHeuristic();
6303 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6304 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6305 if (switchEllipsize) {
6306 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6307 TruncateAt.END : TruncateAt.MARQUEE;
6308 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6309 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6312 shouldEllipsize = mEllipsize != null;
6315 if (mHint != null) {
6316 if (shouldEllipsize) hintWidth = wantWidth;
6318 if (hintBoring == UNKNOWN_BORING) {
6319 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6321 if (hintBoring != null) {
6322 mHintBoring = hintBoring;
6326 if (hintBoring != null) {
6327 if (hintBoring.width <= hintWidth &&
6328 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6329 if (mSavedHintLayout != null) {
6330 mHintLayout = mSavedHintLayout.
6331 replaceOrMake(mHint, mTextPaint,
6332 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6333 hintBoring, mIncludePad);
6335 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6336 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6337 hintBoring, mIncludePad);
6340 mSavedHintLayout = (BoringLayout) mHintLayout;
6341 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6342 if (mSavedHintLayout != null) {
6343 mHintLayout = mSavedHintLayout.
6344 replaceOrMake(mHint, mTextPaint,
6345 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6346 hintBoring, mIncludePad, mEllipsize,
6349 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6350 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6351 hintBoring, mIncludePad, mEllipsize,
6354 } else if (shouldEllipsize) {
6355 mHintLayout = new StaticLayout(mHint,
6357 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6358 mSpacingAdd, mIncludePad, mEllipsize,
6359 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6361 mHintLayout = new StaticLayout(mHint, mTextPaint,
6362 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6365 } else if (shouldEllipsize) {
6366 mHintLayout = new StaticLayout(mHint,
6368 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult,
6369 mSpacingAdd, mIncludePad, mEllipsize,
6370 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6372 mHintLayout = new StaticLayout(mHint, mTextPaint,
6373 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6378 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6379 registerForPreDraw();
6382 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6383 if (!compressText(ellipsisWidth)) {
6384 final int height = mLayoutParams.height;
6385 // If the size of the view does not depend on the size of the text, try to
6386 // start the marquee immediately
6387 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6390 // Defer the start of the marquee until we know our width (see setFrame())
6391 mRestartMarquee = true;
6396 // CursorControllers need a non-null mLayout
6397 if (mEditor != null) mEditor.prepareCursorControllers();
6400 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6401 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6403 Layout result = null;
6404 if (mText instanceof Spannable) {
6405 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6406 alignment, mTextDir, mSpacingMult,
6407 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
6410 if (boring == UNKNOWN_BORING) {
6411 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6412 if (boring != null) {
6417 if (boring != null) {
6418 if (boring.width <= wantWidth &&
6419 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6420 if (useSaved && mSavedLayout != null) {
6421 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6422 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6423 boring, mIncludePad);
6425 result = BoringLayout.make(mTransformed, mTextPaint,
6426 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6427 boring, mIncludePad);
6431 mSavedLayout = (BoringLayout) result;
6433 } else if (shouldEllipsize && boring.width <= wantWidth) {
6434 if (useSaved && mSavedLayout != null) {
6435 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6436 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6437 boring, mIncludePad, effectiveEllipsize,
6440 result = BoringLayout.make(mTransformed, mTextPaint,
6441 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6442 boring, mIncludePad, effectiveEllipsize,
6445 } else if (shouldEllipsize) {
6446 result = new StaticLayout(mTransformed,
6447 0, mTransformed.length(),
6448 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6449 mSpacingAdd, mIncludePad, effectiveEllipsize,
6450 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6452 result = new StaticLayout(mTransformed, mTextPaint,
6453 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6456 } else if (shouldEllipsize) {
6457 result = new StaticLayout(mTransformed,
6458 0, mTransformed.length(),
6459 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult,
6460 mSpacingAdd, mIncludePad, effectiveEllipsize,
6461 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6463 result = new StaticLayout(mTransformed, mTextPaint,
6464 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd,
6471 private boolean compressText(float width) {
6472 if (isHardwareAccelerated()) return false;
6474 // Only compress the text if it hasn't been compressed by the previous pass
6475 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6476 mTextPaint.getTextScaleX() == 1.0f) {
6477 final float textWidth = mLayout.getLineWidth(0);
6478 final float overflow = (textWidth + 1.0f - width) / width;
6479 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6480 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6481 post(new Runnable() {
6493 private static int desired(Layout layout) {
6494 int n = layout.getLineCount();
6495 CharSequence text = layout.getText();
6498 // if any line was wrapped, we can't use it.
6499 // but it's ok for the last line not to have a newline
6501 for (int i = 0; i < n - 1; i++) {
6502 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6506 for (int i = 0; i < n; i++) {
6507 max = Math.max(max, layout.getLineWidth(i));
6510 return (int) FloatMath.ceil(max);
6514 * Set whether the TextView includes extra top and bottom padding to make
6515 * room for accents that go above the normal ascent and descent.
6516 * The default is true.
6518 * @see #getIncludeFontPadding()
6520 * @attr ref android.R.styleable#TextView_includeFontPadding
6522 public void setIncludeFontPadding(boolean includepad) {
6523 if (mIncludePad != includepad) {
6524 mIncludePad = includepad;
6526 if (mLayout != null) {
6535 * Gets whether the TextView includes extra top and bottom padding to make
6536 * room for accents that go above the normal ascent and descent.
6538 * @see #setIncludeFontPadding(boolean)
6540 * @attr ref android.R.styleable#TextView_includeFontPadding
6542 public boolean getIncludeFontPadding() {
6546 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6549 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6550 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6551 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6552 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6553 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6558 BoringLayout.Metrics boring = UNKNOWN_BORING;
6559 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6561 if (mTextDir == null) {
6562 mTextDir = getTextDirectionHeuristic();
6566 boolean fromexisting = false;
6568 if (widthMode == MeasureSpec.EXACTLY) {
6569 // Parent has told us how big to be. So be it.
6572 if (mLayout != null && mEllipsize == null) {
6573 des = desired(mLayout);
6577 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6578 if (boring != null) {
6582 fromexisting = true;
6585 if (boring == null || boring == UNKNOWN_BORING) {
6587 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6591 width = boring.width;
6594 final Drawables dr = mDrawables;
6596 width = Math.max(width, dr.mDrawableWidthTop);
6597 width = Math.max(width, dr.mDrawableWidthBottom);
6600 if (mHint != null) {
6604 if (mHintLayout != null && mEllipsize == null) {
6605 hintDes = desired(mHintLayout);
6609 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
6610 if (hintBoring != null) {
6611 mHintBoring = hintBoring;
6615 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6617 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
6619 hintWidth = hintDes;
6621 hintWidth = hintBoring.width;
6624 if (hintWidth > width) {
6629 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6631 if (mMaxWidthMode == EMS) {
6632 width = Math.min(width, mMaxWidth * getLineHeight());
6634 width = Math.min(width, mMaxWidth);
6637 if (mMinWidthMode == EMS) {
6638 width = Math.max(width, mMinWidth * getLineHeight());
6640 width = Math.max(width, mMinWidth);
6643 // Check against our minimum width
6644 width = Math.max(width, getSuggestedMinimumWidth());
6646 if (widthMode == MeasureSpec.AT_MOST) {
6647 width = Math.min(widthSize, width);
6651 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6652 int unpaddedWidth = want;
6654 if (mHorizontallyScrolling) want = VERY_WIDE;
6656 int hintWant = want;
6657 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6659 if (mLayout == null) {
6660 makeNewLayout(want, hintWant, boring, hintBoring,
6661 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6663 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6664 (hintWidth != hintWant) ||
6665 (mLayout.getEllipsizedWidth() !=
6666 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6668 final boolean widthChanged = (mHint == null) &&
6669 (mEllipsize == null) &&
6670 (want > mLayout.getWidth()) &&
6671 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6673 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6675 if (layoutChanged || maximumChanged) {
6676 if (!maximumChanged && widthChanged) {
6677 mLayout.increaseWidthTo(want);
6679 makeNewLayout(want, hintWant, boring, hintBoring,
6680 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6683 // Nothing has changed
6687 if (heightMode == MeasureSpec.EXACTLY) {
6688 // Parent has told us how big to be. So be it.
6689 height = heightSize;
6690 mDesiredHeightAtMeasure = -1;
6692 int desired = getDesiredHeight();
6695 mDesiredHeightAtMeasure = desired;
6697 if (heightMode == MeasureSpec.AT_MOST) {
6698 height = Math.min(desired, heightSize);
6702 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6703 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6704 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6708 * We didn't let makeNewLayout() register to bring the cursor into view,
6709 * so do it here if there is any possibility that it is needed.
6711 if (mMovement != null ||
6712 mLayout.getWidth() > unpaddedWidth ||
6713 mLayout.getHeight() > unpaddedHeight) {
6714 registerForPreDraw();
6719 setMeasuredDimension(width, height);
6722 private int getDesiredHeight() {
6724 getDesiredHeight(mLayout, true),
6725 getDesiredHeight(mHintLayout, mEllipsize != null));
6728 private int getDesiredHeight(Layout layout, boolean cap) {
6729 if (layout == null) {
6733 int linecount = layout.getLineCount();
6734 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
6735 int desired = layout.getLineTop(linecount);
6737 final Drawables dr = mDrawables;
6739 desired = Math.max(desired, dr.mDrawableHeightLeft);
6740 desired = Math.max(desired, dr.mDrawableHeightRight);
6745 if (mMaxMode == LINES) {
6747 * Don't cap the hint to a certain number of lines.
6748 * (Do cap it, though, if we have a maximum pixel height.)
6751 if (linecount > mMaximum) {
6752 desired = layout.getLineTop(mMaximum);
6755 desired = Math.max(desired, dr.mDrawableHeightLeft);
6756 desired = Math.max(desired, dr.mDrawableHeightRight);
6760 linecount = mMaximum;
6764 desired = Math.min(desired, mMaximum);
6767 if (mMinMode == LINES) {
6768 if (linecount < mMinimum) {
6769 desired += getLineHeight() * (mMinimum - linecount);
6772 desired = Math.max(desired, mMinimum);
6775 // Check against our minimum height
6776 desired = Math.max(desired, getSuggestedMinimumHeight());
6782 * Check whether a change to the existing text layout requires a
6785 private void checkForResize() {
6786 boolean sizeChanged = false;
6788 if (mLayout != null) {
6789 // Check if our width changed
6790 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
6795 // Check if our height changed
6796 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
6797 int desiredHeight = getDesiredHeight();
6799 if (desiredHeight != this.getHeight()) {
6802 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
6803 if (mDesiredHeightAtMeasure >= 0) {
6804 int desiredHeight = getDesiredHeight();
6806 if (desiredHeight != mDesiredHeightAtMeasure) {
6815 // caller will have already invalidated
6820 * Check whether entirely new text requires a new view layout
6821 * or merely a new text layout.
6823 private void checkForRelayout() {
6824 // If we have a fixed width, we can just swap in a new text layout
6825 // if the text height stays the same or if the view height is fixed.
6827 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
6828 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
6829 (mHint == null || mHintLayout != null) &&
6830 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
6831 // Static width, so try making a new text layout.
6833 int oldht = mLayout.getHeight();
6834 int want = mLayout.getWidth();
6835 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
6838 * No need to bring the text into view, since the size is not
6839 * changing (unless we do the requestLayout(), in which case it
6840 * will happen at measure).
6842 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
6843 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
6846 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
6847 // In a fixed-height view, so use our new text layout.
6848 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
6849 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
6854 // Dynamic height, but height has stayed the same,
6855 // so use our new text layout.
6856 if (mLayout.getHeight() == oldht &&
6857 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
6863 // We lose: the height has changed and we have a dynamic height.
6864 // Request a new view layout using our new text layout.
6868 // Dynamic width, so we have no choice but to request a new
6869 // view layout with a new text layout.
6877 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
6878 super.onLayout(changed, left, top, right, bottom);
6879 if (mDeferScroll >= 0) {
6880 int curs = mDeferScroll;
6882 bringPointIntoView(Math.min(curs, mText.length()));
6886 private boolean isShowingHint() {
6887 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
6891 * Returns true if anything changed.
6893 private boolean bringTextIntoView() {
6894 Layout layout = isShowingHint() ? mHintLayout : mLayout;
6896 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6897 line = layout.getLineCount() - 1;
6900 Layout.Alignment a = layout.getParagraphAlignment(line);
6901 int dir = layout.getParagraphDirection(line);
6902 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6903 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
6904 int ht = layout.getHeight();
6906 int scrollx, scrolly;
6908 // Convert to left, center, or right alignment.
6909 if (a == Layout.Alignment.ALIGN_NORMAL) {
6910 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
6911 Layout.Alignment.ALIGN_RIGHT;
6912 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
6913 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
6914 Layout.Alignment.ALIGN_LEFT;
6917 if (a == Layout.Alignment.ALIGN_CENTER) {
6919 * Keep centered if possible, or, if it is too wide to fit,
6920 * keep leading edge in view.
6923 int left = (int) FloatMath.floor(layout.getLineLeft(line));
6924 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6926 if (right - left < hspace) {
6927 scrollx = (right + left) / 2 - hspace / 2;
6930 scrollx = right - hspace;
6935 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
6936 int right = (int) FloatMath.ceil(layout.getLineRight(line));
6937 scrollx = right - hspace;
6938 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
6939 scrollx = (int) FloatMath.floor(layout.getLineLeft(line));
6945 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
6946 scrolly = ht - vspace;
6952 if (scrollx != mScrollX || scrolly != mScrollY) {
6953 scrollTo(scrollx, scrolly);
6961 * Move the point, specified by the offset, into the view if it is needed.
6962 * This has to be called after layout. Returns true if anything changed.
6964 public boolean bringPointIntoView(int offset) {
6965 if (isLayoutRequested()) {
6966 mDeferScroll = offset;
6969 boolean changed = false;
6971 Layout layout = isShowingHint() ? mHintLayout: mLayout;
6973 if (layout == null) return changed;
6975 int line = layout.getLineForOffset(offset);
6979 switch (layout.getParagraphAlignment(line)) {
6987 grav = layout.getParagraphDirection(line);
6989 case ALIGN_OPPOSITE:
6990 grav = -layout.getParagraphDirection(line);
6998 // We only want to clamp the cursor to fit within the layout width
6999 // in left-to-right modes, because in a right to left alignment,
7000 // we want to scroll to keep the line-right on the screen, as other
7001 // lines are likely to have text flush with the right margin, which
7002 // we want to keep visible.
7003 // A better long-term solution would probably be to measure both
7004 // the full line and a blank-trimmed version, and, for example, use
7005 // the latter measurement for centering and right alignment, but for
7006 // the time being we only implement the cursor clamping in left to
7007 // right where it is most likely to be annoying.
7008 final boolean clamped = grav > 0;
7009 // FIXME: Is it okay to truncate this, or should we round?
7010 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7011 final int top = layout.getLineTop(line);
7012 final int bottom = layout.getLineTop(line + 1);
7014 int left = (int) FloatMath.floor(layout.getLineLeft(line));
7015 int right = (int) FloatMath.ceil(layout.getLineRight(line));
7016 int ht = layout.getHeight();
7018 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7019 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7020 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7021 // If cursor has been clamped, make sure we don't scroll.
7022 right = Math.max(x, left + hspace);
7025 int hslack = (bottom - top) / 2;
7026 int vslack = hslack;
7028 if (vslack > vspace / 4)
7029 vslack = vspace / 4;
7030 if (hslack > hspace / 4)
7031 hslack = hspace / 4;
7036 if (top - vs < vslack)
7038 if (bottom - vs > vspace - vslack)
7039 vs = bottom - (vspace - vslack);
7040 if (ht - vs < vspace)
7046 if (x - hs < hslack) {
7049 if (x - hs > hspace - hslack) {
7050 hs = x - (hspace - hslack);
7057 if (right - hs < hspace)
7058 hs = right - hspace;
7059 } else if (grav > 0) {
7060 if (right - hs < hspace)
7061 hs = right - hspace;
7064 } else /* grav == 0 */ {
7065 if (right - left <= hspace) {
7067 * If the entire text fits, center it exactly.
7069 hs = left - (hspace - (right - left)) / 2;
7070 } else if (x > right - hslack) {
7072 * If we are near the right edge, keep the right edge
7073 * at the edge of the view.
7075 hs = right - hspace;
7076 } else if (x < left + hslack) {
7078 * If we are near the left edge, keep the left edge
7079 * at the edge of the view.
7082 } else if (left > hs) {
7084 * Is there whitespace visible at the left? Fix it if so.
7087 } else if (right < hs + hspace) {
7089 * Is there whitespace visible at the right? Fix it if so.
7091 hs = right - hspace;
7094 * Otherwise, float as needed.
7096 if (x - hs < hslack) {
7099 if (x - hs > hspace - hslack) {
7100 hs = x - (hspace - hslack);
7105 if (hs != mScrollX || vs != mScrollY) {
7106 if (mScroller == null) {
7109 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7110 int dx = hs - mScrollX;
7111 int dy = vs - mScrollY;
7113 if (duration > ANIMATED_SCROLL_GAP) {
7114 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7115 awakenScrollBars(mScroller.getDuration());
7118 if (!mScroller.isFinished()) {
7119 mScroller.abortAnimation();
7125 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7132 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7133 // requestRectangleOnScreen() is in terms of content coordinates.
7135 // The offsets here are to ensure the rectangle we are using is
7136 // within our view bounds, in case the cursor is on the far left
7137 // or right. If it isn't withing the bounds, then this request
7139 if (mTempRect == null) mTempRect = new Rect();
7140 mTempRect.set(x - 2, top, x + 2, bottom);
7141 getInterestingRect(mTempRect, line);
7142 mTempRect.offset(mScrollX, mScrollY);
7144 if (requestRectangleOnScreen(mTempRect)) {
7153 * Move the cursor, if needed, so that it is at an offset that is visible
7154 * to the user. This will not move the cursor if it represents more than
7155 * one character (a selection range). This will only work if the
7156 * TextView contains spannable text; otherwise it will do nothing.
7158 * @return True if the cursor was actually moved, false otherwise.
7160 public boolean moveCursorToVisibleOffset() {
7161 if (!(mText instanceof Spannable)) {
7164 int start = getSelectionStart();
7165 int end = getSelectionEnd();
7170 // First: make sure the line is visible on screen:
7172 int line = mLayout.getLineForOffset(start);
7174 final int top = mLayout.getLineTop(line);
7175 final int bottom = mLayout.getLineTop(line + 1);
7176 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7177 int vslack = (bottom - top) / 2;
7178 if (vslack > vspace / 4)
7179 vslack = vspace / 4;
7180 final int vs = mScrollY;
7182 if (top < (vs+vslack)) {
7183 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7184 } else if (bottom > (vspace+vs-vslack)) {
7185 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7188 // Next: make sure the character is visible on screen:
7190 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7191 final int hs = mScrollX;
7192 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7193 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7195 // line might contain bidirectional text
7196 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7197 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7199 int newStart = start;
7200 if (newStart < lowChar) {
7202 } else if (newStart > highChar) {
7203 newStart = highChar;
7206 if (newStart != start) {
7207 Selection.setSelection((Spannable)mText, newStart);
7215 public void computeScroll() {
7216 if (mScroller != null) {
7217 if (mScroller.computeScrollOffset()) {
7218 mScrollX = mScroller.getCurrX();
7219 mScrollY = mScroller.getCurrY();
7220 invalidateParentCaches();
7221 postInvalidate(); // So we draw again
7226 private void getInterestingRect(Rect r, int line) {
7227 convertFromViewportToContentCoordinates(r);
7229 // Rectangle can can be expanded on first and last line to take
7230 // padding into account.
7231 // TODO Take left/right padding into account too?
7232 if (line == 0) r.top -= getExtendedPaddingTop();
7233 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7236 private void convertFromViewportToContentCoordinates(Rect r) {
7237 final int horizontalOffset = viewportToContentHorizontalOffset();
7238 r.left += horizontalOffset;
7239 r.right += horizontalOffset;
7241 final int verticalOffset = viewportToContentVerticalOffset();
7242 r.top += verticalOffset;
7243 r.bottom += verticalOffset;
7246 int viewportToContentHorizontalOffset() {
7247 return getCompoundPaddingLeft() - mScrollX;
7250 int viewportToContentVerticalOffset() {
7251 int offset = getExtendedPaddingTop() - mScrollY;
7252 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7253 offset += getVerticalOffset(false);
7259 public void debug(int depth) {
7262 String output = debugIndent(depth);
7263 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7264 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7267 if (mText != null) {
7269 output += "mText=\"" + mText + "\" ";
7270 if (mLayout != null) {
7271 output += "mLayout width=" + mLayout.getWidth()
7272 + " height=" + mLayout.getHeight();
7275 output += "mText=NULL";
7277 Log.d(VIEW_LOG_TAG, output);
7281 * Convenience for {@link Selection#getSelectionStart}.
7283 @ViewDebug.ExportedProperty(category = "text")
7284 public int getSelectionStart() {
7285 return Selection.getSelectionStart(getText());
7289 * Convenience for {@link Selection#getSelectionEnd}.
7291 @ViewDebug.ExportedProperty(category = "text")
7292 public int getSelectionEnd() {
7293 return Selection.getSelectionEnd(getText());
7297 * Return true iff there is a selection inside this text view.
7299 public boolean hasSelection() {
7300 final int selectionStart = getSelectionStart();
7301 final int selectionEnd = getSelectionEnd();
7303 return selectionStart >= 0 && selectionStart != selectionEnd;
7307 * Sets the properties of this field (lines, horizontally scrolling,
7308 * transformation method) to be for a single-line input.
7310 * @attr ref android.R.styleable#TextView_singleLine
7312 public void setSingleLine() {
7313 setSingleLine(true);
7317 * Sets the properties of this field to transform input to ALL CAPS
7318 * display. This may use a "small caps" formatting if available.
7319 * This setting will be ignored if this field is editable or selectable.
7321 * This call replaces the current transformation method. Disabling this
7322 * will not necessarily restore the previous behavior from before this
7325 * @see #setTransformationMethod(TransformationMethod)
7326 * @attr ref android.R.styleable#TextView_textAllCaps
7328 public void setAllCaps(boolean allCaps) {
7330 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7332 setTransformationMethod(null);
7337 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7338 * transformation method) to be for a single-line input; if false, restores these to the default
7341 * Note that the default conditions are not necessarily those that were in effect prior this
7342 * method, and you may want to reset these properties to your custom values.
7344 * @attr ref android.R.styleable#TextView_singleLine
7346 @android.view.RemotableViewMethod
7347 public void setSingleLine(boolean singleLine) {
7348 // Could be used, but may break backward compatibility.
7349 // if (mSingleLine == singleLine) return;
7350 setInputTypeSingleLine(singleLine);
7351 applySingleLine(singleLine, true, true);
7355 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7358 private void setInputTypeSingleLine(boolean singleLine) {
7359 if (mEditor != null &&
7360 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7362 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7364 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7369 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7370 boolean changeMaxLines) {
7371 mSingleLine = singleLine;
7374 setHorizontallyScrolling(true);
7375 if (applyTransformation) {
7376 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7379 if (changeMaxLines) {
7380 setMaxLines(Integer.MAX_VALUE);
7382 setHorizontallyScrolling(false);
7383 if (applyTransformation) {
7384 setTransformationMethod(null);
7390 * Causes words in the text that are longer than the view is wide
7391 * to be ellipsized instead of broken in the middle. You may also
7392 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7393 * to constrain the text to a single line. Use <code>null</code>
7394 * to turn off ellipsizing.
7396 * If {@link #setMaxLines} has been used to set two or more lines,
7397 * only {@link android.text.TextUtils.TruncateAt#END} and
7398 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7399 * (other ellipsizing types will not do anything).
7401 * @attr ref android.R.styleable#TextView_ellipsize
7403 public void setEllipsize(TextUtils.TruncateAt where) {
7404 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7405 if (mEllipsize != where) {
7408 if (mLayout != null) {
7417 * Sets how many times to repeat the marquee animation. Only applied if the
7418 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7420 * @see #getMarqueeRepeatLimit()
7422 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7424 public void setMarqueeRepeatLimit(int marqueeLimit) {
7425 mMarqueeRepeatLimit = marqueeLimit;
7429 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7430 * TextView has marquee enabled.
7432 * @return the number of times the marquee animation is repeated. -1 if the animation
7433 * repeats indefinitely
7435 * @see #setMarqueeRepeatLimit(int)
7437 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7439 public int getMarqueeRepeatLimit() {
7440 return mMarqueeRepeatLimit;
7444 * Returns where, if anywhere, words that are longer than the view
7445 * is wide should be ellipsized.
7447 @ViewDebug.ExportedProperty
7448 public TextUtils.TruncateAt getEllipsize() {
7453 * Set the TextView so that when it takes focus, all the text is
7456 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7458 @android.view.RemotableViewMethod
7459 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7460 createEditorIfNeeded();
7461 mEditor.mSelectAllOnFocus = selectAllOnFocus;
7463 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7464 setText(mText, BufferType.SPANNABLE);
7469 * Set whether the cursor is visible. The default is true. Note that this property only
7470 * makes sense for editable TextView.
7472 * @see #isCursorVisible()
7474 * @attr ref android.R.styleable#TextView_cursorVisible
7476 @android.view.RemotableViewMethod
7477 public void setCursorVisible(boolean visible) {
7478 if (visible && mEditor == null) return; // visible is the default value with no edit data
7479 createEditorIfNeeded();
7480 if (mEditor.mCursorVisible != visible) {
7481 mEditor.mCursorVisible = visible;
7484 mEditor.makeBlink();
7486 // InsertionPointCursorController depends on mCursorVisible
7487 mEditor.prepareCursorControllers();
7492 * @return whether or not the cursor is visible (assuming this TextView is editable)
7494 * @see #setCursorVisible(boolean)
7496 * @attr ref android.R.styleable#TextView_cursorVisible
7498 public boolean isCursorVisible() {
7499 // true is the default value
7500 return mEditor == null ? true : mEditor.mCursorVisible;
7503 private boolean canMarquee() {
7504 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7505 return width > 0 && (mLayout.getLineWidth(0) > width ||
7506 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7507 mSavedMarqueeModeLayout.getLineWidth(0) > width));
7510 private void startMarquee() {
7511 // Do not ellipsize EditText
7512 if (getKeyListener() != null) return;
7514 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7518 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7519 getLineCount() == 1 && canMarquee()) {
7521 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7522 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7523 final Layout tmp = mLayout;
7524 mLayout = mSavedMarqueeModeLayout;
7525 mSavedMarqueeModeLayout = tmp;
7526 setHorizontalFadingEdgeEnabled(true);
7531 if (mMarquee == null) mMarquee = new Marquee(this);
7532 mMarquee.start(mMarqueeRepeatLimit);
7536 private void stopMarquee() {
7537 if (mMarquee != null && !mMarquee.isStopped()) {
7541 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7542 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7543 final Layout tmp = mSavedMarqueeModeLayout;
7544 mSavedMarqueeModeLayout = mLayout;
7546 setHorizontalFadingEdgeEnabled(false);
7552 private void startStopMarquee(boolean start) {
7553 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7563 * This method is called when the text is changed, in case any subclasses
7564 * would like to know.
7566 * Within <code>text</code>, the <code>lengthAfter</code> characters
7567 * beginning at <code>start</code> have just replaced old text that had
7568 * length <code>lengthBefore</code>. It is an error to attempt to make
7569 * changes to <code>text</code> from this callback.
7571 * @param text The text the TextView is displaying
7572 * @param start The offset of the start of the range of the text that was
7574 * @param lengthBefore The length of the former text that has been replaced
7575 * @param lengthAfter The length of the replacement modified text
7577 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7578 // intentionally empty, template pattern method can be overridden by subclasses
7582 * This method is called when the selection has changed, in case any
7583 * subclasses would like to know.
7585 * @param selStart The new selection start location.
7586 * @param selEnd The new selection end location.
7588 protected void onSelectionChanged(int selStart, int selEnd) {
7589 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7593 * Adds a TextWatcher to the list of those whose methods are called
7594 * whenever this TextView's text changes.
7596 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7597 * not called after {@link #setText} calls. Now, doing {@link #setText}
7598 * if there are any text changed listeners forces the buffer type to
7599 * Editable if it would not otherwise be and does call this method.
7601 public void addTextChangedListener(TextWatcher watcher) {
7602 if (mListeners == null) {
7603 mListeners = new ArrayList<TextWatcher>();
7606 mListeners.add(watcher);
7610 * Removes the specified TextWatcher from the list of those whose
7611 * methods are called
7612 * whenever this TextView's text changes.
7614 public void removeTextChangedListener(TextWatcher watcher) {
7615 if (mListeners != null) {
7616 int i = mListeners.indexOf(watcher);
7619 mListeners.remove(i);
7624 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7625 if (mListeners != null) {
7626 final ArrayList<TextWatcher> list = mListeners;
7627 final int count = list.size();
7628 for (int i = 0; i < count; i++) {
7629 list.get(i).beforeTextChanged(text, start, before, after);
7633 // The spans that are inside or intersect the modified region no longer make sense
7634 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7635 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
7638 // Removes all spans that are inside or actually overlap the start..end range
7639 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
7640 if (!(mText instanceof Editable)) return;
7641 Editable text = (Editable) mText;
7643 T[] spans = text.getSpans(start, end, type);
7644 final int length = spans.length;
7645 for (int i = 0; i < length; i++) {
7646 final int spanStart = text.getSpanStart(spans[i]);
7647 final int spanEnd = text.getSpanEnd(spans[i]);
7648 if (spanEnd == start || spanStart == end) break;
7649 text.removeSpan(spans[i]);
7653 void removeAdjacentSuggestionSpans(final int pos) {
7654 if (!(mText instanceof Editable)) return;
7655 final Editable text = (Editable) mText;
7657 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7658 final int length = spans.length;
7659 for (int i = 0; i < length; i++) {
7660 final int spanStart = text.getSpanStart(spans[i]);
7661 final int spanEnd = text.getSpanEnd(spans[i]);
7662 if (spanEnd == pos || spanStart == pos) {
7663 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7664 text.removeSpan(spans[i]);
7671 * Not private so it can be called from an inner class without going
7674 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7675 if (mListeners != null) {
7676 final ArrayList<TextWatcher> list = mListeners;
7677 final int count = list.size();
7678 for (int i = 0; i < count; i++) {
7679 list.get(i).onTextChanged(text, start, before, after);
7683 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
7687 * Not private so it can be called from an inner class without going
7690 void sendAfterTextChanged(Editable text) {
7691 if (mListeners != null) {
7692 final ArrayList<TextWatcher> list = mListeners;
7693 final int count = list.size();
7694 for (int i = 0; i < count; i++) {
7695 list.get(i).afterTextChanged(text);
7698 hideErrorIfUnchanged();
7701 void updateAfterEdit() {
7703 int curs = getSelectionStart();
7705 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7706 registerForPreDraw();
7712 mHighlightPathBogus = true;
7713 if (mEditor != null) mEditor.makeBlink();
7714 bringPointIntoView(curs);
7719 * Not private so it can be called from an inner class without going
7722 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
7723 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
7724 if (ims == null || ims.mBatchEditNesting == 0) {
7728 ims.mContentChanged = true;
7729 if (ims.mChangedStart < 0) {
7730 ims.mChangedStart = start;
7731 ims.mChangedEnd = start+before;
7733 ims.mChangedStart = Math.min(ims.mChangedStart, start);
7734 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
7736 ims.mChangedDelta += after-before;
7738 resetErrorChangedFlag();
7739 sendOnTextChanged(buffer, start, before, after);
7740 onTextChanged(buffer, start, before, after);
7744 * Not private so it can be called from an inner class without going
7747 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
7748 // XXX Make the start and end move together if this ends up
7749 // spending too much time invalidating.
7751 boolean selChanged = false;
7752 int newSelStart=-1, newSelEnd=-1;
7754 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
7756 if (what == Selection.SELECTION_END) {
7758 newSelEnd = newStart;
7760 if (oldStart >= 0 || newStart >= 0) {
7761 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
7763 registerForPreDraw();
7764 if (mEditor != null) mEditor.makeBlink();
7768 if (what == Selection.SELECTION_START) {
7770 newSelStart = newStart;
7772 if (oldStart >= 0 || newStart >= 0) {
7773 int end = Selection.getSelectionEnd(buf);
7774 invalidateCursor(end, oldStart, newStart);
7779 mHighlightPathBogus = true;
7780 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
7782 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
7783 if (newSelStart < 0) {
7784 newSelStart = Selection.getSelectionStart(buf);
7786 if (newSelEnd < 0) {
7787 newSelEnd = Selection.getSelectionEnd(buf);
7789 onSelectionChanged(newSelStart, newSelEnd);
7793 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
7794 what instanceof CharacterStyle) {
7795 if (ims == null || ims.mBatchEditNesting == 0) {
7797 mHighlightPathBogus = true;
7800 ims.mContentChanged = true;
7802 if (mEditor != null) {
7803 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
7804 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
7808 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
7809 mHighlightPathBogus = true;
7810 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
7811 ims.mSelectionModeChanged = true;
7814 if (Selection.getSelectionStart(buf) >= 0) {
7815 if (ims == null || ims.mBatchEditNesting == 0) {
7818 ims.mCursorChanged = true;
7823 if (what instanceof ParcelableSpan) {
7824 // If this is a span that can be sent to a remote process,
7825 // the current extract editor would be interested in it.
7826 if (ims != null && ims.mExtractedTextRequest != null) {
7827 if (ims.mBatchEditNesting != 0) {
7828 if (oldStart >= 0) {
7829 if (ims.mChangedStart > oldStart) {
7830 ims.mChangedStart = oldStart;
7832 if (ims.mChangedStart > oldEnd) {
7833 ims.mChangedStart = oldEnd;
7836 if (newStart >= 0) {
7837 if (ims.mChangedStart > newStart) {
7838 ims.mChangedStart = newStart;
7840 if (ims.mChangedStart > newEnd) {
7841 ims.mChangedStart = newEnd;
7845 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
7846 + oldStart + "-" + oldEnd + ","
7847 + newStart + "-" + newEnd + " " + what);
7848 ims.mContentChanged = true;
7853 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
7854 what instanceof SpellCheckSpan) {
7855 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
7863 public void dispatchFinishTemporaryDetach() {
7864 mDispatchTemporaryDetach = true;
7865 super.dispatchFinishTemporaryDetach();
7866 mDispatchTemporaryDetach = false;
7870 public void onStartTemporaryDetach() {
7871 super.onStartTemporaryDetach();
7872 // Only track when onStartTemporaryDetach() is called directly,
7873 // usually because this instance is an editable field in a list
7874 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
7876 // Tell the editor that we are temporarily detached. It can use this to preserve
7877 // selection state as needed.
7878 if (mEditor != null) mEditor.mTemporaryDetach = true;
7882 public void onFinishTemporaryDetach() {
7883 super.onFinishTemporaryDetach();
7884 // Only track when onStartTemporaryDetach() is called directly,
7885 // usually because this instance is an editable field in a list
7886 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
7887 if (mEditor != null) mEditor.mTemporaryDetach = false;
7891 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
7892 if (mTemporaryDetach) {
7893 // If we are temporarily in the detach state, then do nothing.
7894 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7898 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
7901 if (mText instanceof Spannable) {
7902 Spannable sp = (Spannable) mText;
7903 MetaKeyKeyListener.resetMetaState(sp);
7907 startStopMarquee(focused);
7909 if (mTransformation != null) {
7910 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
7913 super.onFocusChanged(focused, direction, previouslyFocusedRect);
7917 public void onWindowFocusChanged(boolean hasWindowFocus) {
7918 super.onWindowFocusChanged(hasWindowFocus);
7920 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
7922 startStopMarquee(hasWindowFocus);
7926 protected void onVisibilityChanged(View changedView, int visibility) {
7927 super.onVisibilityChanged(changedView, visibility);
7928 if (mEditor != null && visibility != VISIBLE) {
7929 mEditor.hideControllers();
7934 * Use {@link BaseInputConnection#removeComposingSpans
7935 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
7936 * state from this text view.
7938 public void clearComposingText() {
7939 if (mText instanceof Spannable) {
7940 BaseInputConnection.removeComposingSpans((Spannable)mText);
7945 public void setSelected(boolean selected) {
7946 boolean wasSelected = isSelected();
7948 super.setSelected(selected);
7950 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7960 public boolean onTouchEvent(MotionEvent event) {
7961 final int action = event.getActionMasked();
7963 if (mEditor != null) mEditor.onTouchEvent(event);
7965 final boolean superResult = super.onTouchEvent(event);
7968 * Don't handle the release after a long press, because it will
7969 * move the selection away from whatever the menu action was
7972 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
7973 mEditor.mDiscardNextActionUp = false;
7977 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
7978 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
7980 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
7981 && mText instanceof Spannable && mLayout != null) {
7982 boolean handled = false;
7984 if (mMovement != null) {
7985 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
7988 final boolean textIsSelectable = isTextSelectable();
7989 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
7990 // The LinkMovementMethod which should handle taps on links has not been installed
7991 // on non editable text that support text selection.
7992 // We reproduce its behavior here to open links for these.
7993 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
7994 getSelectionEnd(), ClickableSpan.class);
7996 if (links.length > 0) {
7997 links[0].onClick(this);
8002 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8003 // Show the IME, except when selecting in read-only text.
8004 final InputMethodManager imm = InputMethodManager.peekInstance();
8006 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8007 handled |= imm != null && imm.showSoftInput(this, 0);
8010 // The above condition ensures that the mEditor is not null
8011 mEditor.onTouchUpEvent(event);
8025 public boolean onGenericMotionEvent(MotionEvent event) {
8026 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8028 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8031 } catch (AbstractMethodError ex) {
8032 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8033 // Ignore its absence in case third party applications implemented the
8034 // interface directly.
8037 return super.onGenericMotionEvent(event);
8041 * @return True iff this TextView contains a text that can be edited, or if this is
8042 * a selectable TextView.
8044 boolean isTextEditable() {
8045 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8049 * Returns true, only while processing a touch gesture, if the initial
8050 * touch down event caused focus to move to the text view and as a result
8051 * its selection changed. Only valid while processing the touch gesture
8052 * of interest, in an editable text view.
8054 public boolean didTouchFocusSelect() {
8055 return mEditor != null && mEditor.mTouchFocusSelected;
8059 public void cancelLongPress() {
8060 super.cancelLongPress();
8061 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8065 public boolean onTrackballEvent(MotionEvent event) {
8066 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8067 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8072 return super.onTrackballEvent(event);
8075 public void setScroller(Scroller s) {
8080 protected float getLeftFadingEdgeStrength() {
8081 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8082 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8083 if (mMarquee != null && !mMarquee.isStopped()) {
8084 final Marquee marquee = mMarquee;
8085 if (marquee.shouldDrawLeftFade()) {
8086 final float scroll = marquee.getScroll();
8087 return scroll / getHorizontalFadingEdgeLength();
8091 } else if (getLineCount() == 1) {
8092 final int layoutDirection = getLayoutDirection();
8093 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8094 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8098 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8099 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8100 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8101 case Gravity.CENTER_HORIZONTAL:
8102 case Gravity.FILL_HORIZONTAL:
8103 final int textDirection = mLayout.getParagraphDirection(0);
8104 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8107 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8108 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8109 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8114 return super.getLeftFadingEdgeStrength();
8118 protected float getRightFadingEdgeStrength() {
8119 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8120 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8121 if (mMarquee != null && !mMarquee.isStopped()) {
8122 final Marquee marquee = mMarquee;
8123 final float maxFadeScroll = marquee.getMaxFadeScroll();
8124 final float scroll = marquee.getScroll();
8125 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
8126 } else if (getLineCount() == 1) {
8127 final int layoutDirection = getLayoutDirection();
8128 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8129 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8131 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8132 getCompoundPaddingRight();
8133 final float lineWidth = mLayout.getLineWidth(0);
8134 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8137 case Gravity.CENTER_HORIZONTAL:
8138 case Gravity.FILL_HORIZONTAL:
8139 final int textDirection = mLayout.getParagraphDirection(0);
8140 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8143 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8144 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8145 getHorizontalFadingEdgeLength();
8150 return super.getRightFadingEdgeStrength();
8154 protected int computeHorizontalScrollRange() {
8155 if (mLayout != null) {
8156 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8157 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8160 return super.computeHorizontalScrollRange();
8164 protected int computeVerticalScrollRange() {
8165 if (mLayout != null)
8166 return mLayout.getHeight();
8168 return super.computeVerticalScrollRange();
8172 protected int computeVerticalScrollExtent() {
8173 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8177 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8178 super.findViewsWithText(outViews, searched, flags);
8179 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8180 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8181 String searchedLowerCase = searched.toString().toLowerCase();
8182 String textLowerCase = mText.toString().toLowerCase();
8183 if (textLowerCase.contains(searchedLowerCase)) {
8189 public enum BufferType {
8190 NORMAL, SPANNABLE, EDITABLE,
8194 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8195 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8196 * if TextView_textColor was not set directly.
8200 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8201 if (attrs == null) {
8202 // Preserve behavior prior to removal of this API.
8203 throw new NullPointerException();
8206 // It's not safe to use this method from apps. The parameter 'attrs'
8207 // must have been obtained using the TextView filter array which is not
8208 // available to the SDK. As such, we grab a default TypedArray with the
8209 // right filter instead here.
8210 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8211 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8212 if (colors == null) {
8213 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8215 final TypedArray appearance = context.obtainStyledAttributes(
8216 ap, R.styleable.TextAppearance);
8217 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8218 appearance.recycle();
8227 * Returns the default color from the TextView_textColor attribute from the
8228 * AttributeSet, if set, or the default color from the
8229 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8230 * TextView_textColor was not set directly.
8234 public static int getTextColor(Context context, TypedArray attrs, int def) {
8235 final ColorStateList colors = getTextColors(context, attrs);
8236 if (colors == null) {
8239 return colors.getDefaultColor();
8244 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8245 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK;
8246 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) {
8248 case KeyEvent.KEYCODE_A:
8249 if (canSelectText()) {
8250 return onTextContextMenuItem(ID_SELECT_ALL);
8253 case KeyEvent.KEYCODE_X:
8255 return onTextContextMenuItem(ID_CUT);
8258 case KeyEvent.KEYCODE_C:
8260 return onTextContextMenuItem(ID_COPY);
8263 case KeyEvent.KEYCODE_V:
8265 return onTextContextMenuItem(ID_PASTE);
8270 return super.onKeyShortcut(keyCode, event);
8274 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8275 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8276 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8279 private boolean canSelectText() {
8280 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8284 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8285 * The text must be spannable and the movement method must allow for arbitary selection.
8287 * See also {@link #canSelectText()}.
8289 boolean textCanBeSelected() {
8290 // prepareCursorController() relies on this method.
8291 // If you change this condition, make sure prepareCursorController is called anywhere
8292 // the value of this condition might be changed.
8293 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8294 return isTextEditable() ||
8295 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8298 private Locale getTextServicesLocale(boolean allowNullLocale) {
8299 // Start fetching the text services locale asynchronously.
8300 updateTextServicesLocaleAsync();
8301 // If !allowNullLocale and there is no cached text services locale, just return the default
8303 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8304 : mCurrentSpellCheckerLocaleCache;
8308 * This is a temporary method. Future versions may support multi-locale text.
8309 * Caveat: This method may not return the latest text services locale, but this should be
8310 * acceptable and it's more important to make this method asynchronous.
8312 * @return The locale that should be used for a word iterator
8313 * in this TextView, based on the current spell checker settings,
8314 * the current IME's locale, or the system default locale.
8315 * Please note that a word iterator in this TextView is different from another word iterator
8316 * used by SpellChecker.java of TextView. This method should be used for the former.
8319 // TODO: Support multi-locale
8320 // TODO: Update the text services locale immediately after the keyboard locale is switched
8321 // by catching intent of keyboard switch event
8322 public Locale getTextServicesLocale() {
8323 return getTextServicesLocale(false /* allowNullLocale */);
8327 * This is a temporary method. Future versions may support multi-locale text.
8328 * Caveat: This method may not return the latest spell checker locale, but this should be
8329 * acceptable and it's more important to make this method asynchronous.
8331 * @return The locale that should be used for a spell checker in this TextView,
8332 * based on the current spell checker settings, the current IME's locale, or the system default
8336 public Locale getSpellCheckerLocale() {
8337 return getTextServicesLocale(true /* allowNullLocale */);
8340 private void updateTextServicesLocaleAsync() {
8341 // AsyncTask.execute() uses a serial executor which means we don't have
8342 // to lock around updateTextServicesLocaleLocked() to prevent it from
8343 // being executed n times in parallel.
8344 AsyncTask.execute(new Runnable() {
8347 updateTextServicesLocaleLocked();
8352 private void updateTextServicesLocaleLocked() {
8353 final TextServicesManager textServicesManager = (TextServicesManager)
8354 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8355 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8356 final Locale locale;
8357 if (subtype != null) {
8358 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
8362 mCurrentSpellCheckerLocaleCache = locale;
8365 void onLocaleChanged() {
8366 // Will be re-created on demand in getWordIterator with the proper new locale
8367 mEditor.mWordIterator = null;
8371 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8372 * Made available to achieve a consistent behavior.
8375 public WordIterator getWordIterator() {
8376 if (mEditor != null) {
8377 return mEditor.getWordIterator();
8384 public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
8385 super.onPopulateAccessibilityEvent(event);
8387 final boolean isPassword = hasPasswordTransformationMethod();
8388 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8389 final CharSequence text = getTextForAccessibility();
8390 if (!TextUtils.isEmpty(text)) {
8391 event.getText().add(text);
8397 * @return true if the user has explicitly allowed accessibility services
8398 * to speak passwords.
8400 private boolean shouldSpeakPasswordsForAccessibility() {
8401 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8402 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8403 UserHandle.USER_CURRENT_OR_SELF) == 1);
8407 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
8408 super.onInitializeAccessibilityEvent(event);
8410 event.setClassName(TextView.class.getName());
8411 final boolean isPassword = hasPasswordTransformationMethod();
8412 event.setPassword(isPassword);
8414 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8415 event.setFromIndex(Selection.getSelectionStart(mText));
8416 event.setToIndex(Selection.getSelectionEnd(mText));
8417 event.setItemCount(mText.length());
8422 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
8423 super.onInitializeAccessibilityNodeInfo(info);
8425 info.setClassName(TextView.class.getName());
8426 final boolean isPassword = hasPasswordTransformationMethod();
8427 info.setPassword(isPassword);
8429 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8430 info.setText(getTextForAccessibility());
8433 if (mBufferType == BufferType.EDITABLE) {
8434 info.setEditable(true);
8437 if (mEditor != null) {
8438 info.setInputType(mEditor.mInputType);
8440 if (mEditor.mError != null) {
8441 info.setContentInvalid(true);
8442 info.setError(mEditor.mError);
8446 if (!TextUtils.isEmpty(mText)) {
8447 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8448 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8449 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8450 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8451 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8452 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8453 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8457 if (canSelectText()) {
8458 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8461 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8464 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8467 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8471 // Check for known input filter types.
8472 final int numFilters = mFilters.length;
8473 for (int i = 0; i < numFilters; i++) {
8474 final InputFilter filter = mFilters[i];
8475 if (filter instanceof InputFilter.LengthFilter) {
8476 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
8480 if (!isSingleLine()) {
8481 info.setMultiLine(true);
8486 * Performs an accessibility action after it has been offered to the
8492 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
8494 case AccessibilityNodeInfo.ACTION_CLICK: {
8495 boolean handled = false;
8497 // Simulate View.onTouchEvent for an ACTION_UP event.
8498 if (isClickable() || isLongClickable()) {
8499 if (isFocusable() && !isFocused()) {
8507 // Simulate TextView.onTouchEvent for an ACTION_UP event.
8508 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8509 && mText instanceof Spannable && mLayout != null
8510 && (isTextEditable() || isTextSelectable()) && isFocused()) {
8511 // Show the IME, except when selecting in read-only text.
8512 final InputMethodManager imm = InputMethodManager.peekInstance();
8514 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
8515 handled |= imm.showSoftInput(this, 0);
8521 case AccessibilityNodeInfo.ACTION_COPY: {
8522 if (isFocused() && canCopy()) {
8523 if (onTextContextMenuItem(ID_COPY)) {
8528 case AccessibilityNodeInfo.ACTION_PASTE: {
8529 if (isFocused() && canPaste()) {
8530 if (onTextContextMenuItem(ID_PASTE)) {
8535 case AccessibilityNodeInfo.ACTION_CUT: {
8536 if (isFocused() && canCut()) {
8537 if (onTextContextMenuItem(ID_CUT)) {
8542 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8543 if (isFocused() && canSelectText()) {
8544 ensureIterableTextForAccessibilitySelectable();
8545 CharSequence text = getIterableTextForAccessibility();
8549 final int start = (arguments != null) ? arguments.getInt(
8550 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8551 final int end = (arguments != null) ? arguments.getInt(
8552 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8553 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8554 // No arguments clears the selection.
8555 if (start == end && end == -1) {
8556 Selection.removeSelection((Spannable) text);
8559 if (start >= 0 && start <= end && end <= text.length()) {
8560 Selection.setSelection((Spannable) text, start, end);
8561 // Make sure selection mode is engaged.
8562 if (mEditor != null) {
8563 mEditor.startSelectionActionMode();
8570 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
8571 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
8572 ensureIterableTextForAccessibilitySelectable();
8573 return super.performAccessibilityActionInternal(action, arguments);
8576 return super.performAccessibilityActionInternal(action, arguments);
8582 public void sendAccessibilityEvent(int eventType) {
8583 // Do not send scroll events since first they are not interesting for
8584 // accessibility and second such events a generated too frequently.
8585 // For details see the implementation of bringTextIntoView().
8586 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8589 super.sendAccessibilityEvent(eventType);
8593 * Gets the text reported for accessibility purposes.
8595 * @return The accessibility text.
8599 public CharSequence getTextForAccessibility() {
8600 CharSequence text = getText();
8601 if (TextUtils.isEmpty(text)) {
8607 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
8608 int fromIndex, int removedCount, int addedCount) {
8609 AccessibilityEvent event =
8610 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
8611 event.setFromIndex(fromIndex);
8612 event.setRemovedCount(removedCount);
8613 event.setAddedCount(addedCount);
8614 event.setBeforeText(beforeText);
8615 sendAccessibilityEventUnchecked(event);
8619 * Returns whether this text view is a current input method target. The
8620 * default implementation just checks with {@link InputMethodManager}.
8622 public boolean isInputMethodTarget() {
8623 InputMethodManager imm = InputMethodManager.peekInstance();
8624 return imm != null && imm.isActive(this);
8627 static final int ID_SELECT_ALL = android.R.id.selectAll;
8628 static final int ID_CUT = android.R.id.cut;
8629 static final int ID_COPY = android.R.id.copy;
8630 static final int ID_PASTE = android.R.id.paste;
8633 * Called when a context menu option for the text view is selected. Currently
8634 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
8635 * {@link android.R.id#copy} or {@link android.R.id#paste}.
8637 * @return true if the context menu item action was performed.
8639 public boolean onTextContextMenuItem(int id) {
8641 int max = mText.length();
8644 final int selStart = getSelectionStart();
8645 final int selEnd = getSelectionEnd();
8647 min = Math.max(0, Math.min(selStart, selEnd));
8648 max = Math.max(0, Math.max(selStart, selEnd));
8653 // This does not enter text selection mode. Text is highlighted, so that it can be
8654 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
8663 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8664 deleteText_internal(min, max);
8665 stopSelectionActionMode();
8669 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
8670 stopSelectionActionMode();
8676 CharSequence getTransformedText(int start, int end) {
8677 return removeSuggestionSpans(mTransformed.subSequence(start, end));
8681 public boolean performLongClick() {
8682 boolean handled = false;
8684 if (super.performLongClick()) {
8688 if (mEditor != null) {
8689 handled |= mEditor.performLongClick(handled);
8693 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
8694 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
8701 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
8702 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
8703 if (mEditor != null) {
8704 mEditor.onScrollChanged();
8709 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
8710 * by the IME or by the spell checker as the user types. This is done by adding
8711 * {@link SuggestionSpan}s to the text.
8713 * When suggestions are enabled (default), this list of suggestions will be displayed when the
8714 * user asks for them on these parts of the text. This value depends on the inputType of this
8717 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
8719 * In addition, the type variation must be one of
8720 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
8721 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
8722 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
8723 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
8724 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
8726 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
8728 * @return true if the suggestions popup window is enabled, based on the inputType.
8730 public boolean isSuggestionsEnabled() {
8731 if (mEditor == null) return false;
8732 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
8735 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
8737 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
8738 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
8739 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
8740 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
8741 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
8742 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
8746 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
8747 * selection is initiated in this View.
8749 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
8750 * Paste actions, depending on what this View supports.
8752 * A custom implementation can add new entries in the default menu in its
8753 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
8754 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and
8755 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy}
8756 * or {@link android.R.id#paste} ids as parameters.
8758 * Returning false from
8759 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
8760 * the action mode from being started.
8762 * Action click events should be handled by the custom implementation of
8763 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
8765 * Note that text selection mode is not started when a TextView receives focus and the
8766 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
8767 * that case, to allow for quick replacement.
8769 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
8770 createEditorIfNeeded();
8771 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
8775 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
8777 * @return The current custom selection callback.
8779 public ActionMode.Callback getCustomSelectionActionModeCallback() {
8780 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
8786 protected void stopSelectionActionMode() {
8787 mEditor.stopSelectionActionMode();
8791 if (hasPasswordTransformationMethod()) {
8795 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
8796 mEditor.mKeyListener != null) {
8804 if (hasPasswordTransformationMethod()) {
8808 if (mText.length() > 0 && hasSelection() && mEditor != null) {
8815 boolean canPaste() {
8816 return (mText instanceof Editable &&
8817 mEditor != null && mEditor.mKeyListener != null &&
8818 getSelectionStart() >= 0 &&
8819 getSelectionEnd() >= 0 &&
8820 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
8824 boolean selectAllText() {
8825 final int length = mText.length();
8826 Selection.setSelection((Spannable) mText, 0, length);
8831 * Paste clipboard content between min and max positions.
8833 private void paste(int min, int max) {
8834 ClipboardManager clipboard =
8835 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
8836 ClipData clip = clipboard.getPrimaryClip();
8838 boolean didFirst = false;
8839 for (int i=0; i<clip.getItemCount(); i++) {
8840 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext());
8841 if (paste != null) {
8843 Selection.setSelection((Spannable) mText, max);
8844 ((Editable) mText).replace(min, max, paste);
8847 ((Editable) mText).insert(getSelectionEnd(), "\n");
8848 ((Editable) mText).insert(getSelectionEnd(), paste);
8852 stopSelectionActionMode();
8853 LAST_CUT_OR_COPY_TIME = 0;
8857 private void setPrimaryClip(ClipData clip) {
8858 ClipboardManager clipboard = (ClipboardManager) getContext().
8859 getSystemService(Context.CLIPBOARD_SERVICE);
8860 clipboard.setPrimaryClip(clip);
8861 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
8865 * Get the character offset closest to the specified absolute position. A typical use case is to
8866 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
8868 * @param x The horizontal absolute position of a point on screen
8869 * @param y The vertical absolute position of a point on screen
8870 * @return the character offset for the character whose position is closest to the specified
8871 * position. Returns -1 if there is no layout.
8873 public int getOffsetForPosition(float x, float y) {
8874 if (getLayout() == null) return -1;
8875 final int line = getLineAtCoordinate(y);
8876 final int offset = getOffsetAtCoordinate(line, x);
8880 float convertToLocalHorizontalCoordinate(float x) {
8881 x -= getTotalPaddingLeft();
8882 // Clamp the position to inside of the view.
8883 x = Math.max(0.0f, x);
8884 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
8889 int getLineAtCoordinate(float y) {
8890 y -= getTotalPaddingTop();
8891 // Clamp the position to inside of the view.
8892 y = Math.max(0.0f, y);
8893 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
8895 return getLayout().getLineForVertical((int) y);
8898 private int getOffsetAtCoordinate(int line, float x) {
8899 x = convertToLocalHorizontalCoordinate(x);
8900 return getLayout().getOffsetForHorizontal(line, x);
8904 public boolean onDragEvent(DragEvent event) {
8905 switch (event.getAction()) {
8906 case DragEvent.ACTION_DRAG_STARTED:
8907 return mEditor != null && mEditor.hasInsertionController();
8909 case DragEvent.ACTION_DRAG_ENTERED:
8910 TextView.this.requestFocus();
8913 case DragEvent.ACTION_DRAG_LOCATION:
8914 final int offset = getOffsetForPosition(event.getX(), event.getY());
8915 Selection.setSelection((Spannable)mText, offset);
8918 case DragEvent.ACTION_DROP:
8919 if (mEditor != null) mEditor.onDrop(event);
8922 case DragEvent.ACTION_DRAG_ENDED:
8923 case DragEvent.ACTION_DRAG_EXITED:
8929 boolean isInBatchEditMode() {
8930 if (mEditor == null) return false;
8931 final Editor.InputMethodState ims = mEditor.mInputMethodState;
8933 return ims.mBatchEditNesting > 0;
8935 return mEditor.mInBatchEditControllers;
8939 public void onRtlPropertiesChanged(int layoutDirection) {
8940 super.onRtlPropertiesChanged(layoutDirection);
8942 mTextDir = getTextDirectionHeuristic();
8944 if (mLayout != null) {
8949 TextDirectionHeuristic getTextDirectionHeuristic() {
8950 if (hasPasswordTransformationMethod()) {
8951 // passwords fields should be LTR
8952 return TextDirectionHeuristics.LTR;
8955 // Always need to resolve layout direction first
8956 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
8958 // Now, we can select the heuristic
8959 switch (getTextDirection()) {
8961 case TEXT_DIRECTION_FIRST_STRONG:
8962 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
8963 TextDirectionHeuristics.FIRSTSTRONG_LTR);
8964 case TEXT_DIRECTION_ANY_RTL:
8965 return TextDirectionHeuristics.ANYRTL_LTR;
8966 case TEXT_DIRECTION_LTR:
8967 return TextDirectionHeuristics.LTR;
8968 case TEXT_DIRECTION_RTL:
8969 return TextDirectionHeuristics.RTL;
8970 case TEXT_DIRECTION_LOCALE:
8971 return TextDirectionHeuristics.LOCALE;
8979 public void onResolveDrawables(int layoutDirection) {
8980 // No need to resolve twice
8981 if (mLastLayoutDirection == layoutDirection) {
8984 mLastLayoutDirection = layoutDirection;
8986 // Resolve drawables
8987 if (mDrawables != null) {
8988 mDrawables.resolveWithLayoutDirection(layoutDirection);
8995 protected void resetResolvedDrawables() {
8996 super.resetResolvedDrawables();
8997 mLastLayoutDirection = -1;
9003 protected void viewClicked(InputMethodManager imm) {
9005 imm.viewClicked(this);
9010 * Deletes the range of text [start, end[.
9013 protected void deleteText_internal(int start, int end) {
9014 ((Editable) mText).delete(start, end);
9018 * Replaces the range of text [start, end[ by replacement text
9021 protected void replaceText_internal(int start, int end, CharSequence text) {
9022 ((Editable) mText).replace(start, end, text);
9026 * Sets a span on the specified range of text
9029 protected void setSpan_internal(Object span, int start, int end, int flags) {
9030 ((Editable) mText).setSpan(span, start, end, flags);
9034 * Moves the cursor to the specified offset position in text
9037 protected void setCursorPosition_internal(int start, int end) {
9038 Selection.setSelection(((Editable) mText), start, end);
9042 * An Editor should be created as soon as any of the editable-specific fields (grouped
9043 * inside the Editor object) is assigned to a non-default value.
9044 * This method will create the Editor if needed.
9046 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9047 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9048 * Editor for backward compatibility, as soon as one of these fields is assigned.
9050 * Also note that for performance reasons, the mEditor is created when needed, but not
9051 * reset when no more edit-specific fields are needed.
9053 private void createEditorIfNeeded() {
9054 if (mEditor == null) {
9055 mEditor = new Editor(this);
9063 public CharSequence getIterableTextForAccessibility() {
9067 private void ensureIterableTextForAccessibilitySelectable() {
9068 if (!(mText instanceof Spannable)) {
9069 setText(mText, BufferType.SPANNABLE);
9077 public TextSegmentIterator getIteratorForGranularity(int granularity) {
9078 switch (granularity) {
9079 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9080 Spannable text = (Spannable) getIterableTextForAccessibility();
9081 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9082 AccessibilityIterators.LineTextSegmentIterator iterator =
9083 AccessibilityIterators.LineTextSegmentIterator.getInstance();
9084 iterator.initialize(text, getLayout());
9088 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9089 Spannable text = (Spannable) getIterableTextForAccessibility();
9090 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9091 AccessibilityIterators.PageTextSegmentIterator iterator =
9092 AccessibilityIterators.PageTextSegmentIterator.getInstance();
9093 iterator.initialize(this);
9098 return super.getIteratorForGranularity(granularity);
9105 public int getAccessibilitySelectionStart() {
9106 return getSelectionStart();
9112 public boolean isAccessibilitySelectionExtendable() {
9120 public int getAccessibilitySelectionEnd() {
9121 return getSelectionEnd();
9128 public void setAccessibilitySelection(int start, int end) {
9129 if (getAccessibilitySelectionStart() == start
9130 && getAccessibilitySelectionEnd() == end) {
9133 // Hide all selection controllers used for adjusting selection
9134 // since we are doing so explicitlty by other means and these
9135 // controllers interact with how selection behaves.
9136 if (mEditor != null) {
9137 mEditor.hideControllers();
9139 CharSequence text = getIterableTextForAccessibility();
9140 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
9141 Selection.setSelection((Spannable) text, start, end);
9143 Selection.removeSelection((Spannable) text);
9148 * User interface state that is stored by TextView for implementing
9149 * {@link View#onSaveInstanceState}.
9151 public static class SavedState extends BaseSavedState {
9155 boolean frozenWithFocus;
9158 SavedState(Parcelable superState) {
9163 public void writeToParcel(Parcel out, int flags) {
9164 super.writeToParcel(out, flags);
9165 out.writeInt(selStart);
9166 out.writeInt(selEnd);
9167 out.writeInt(frozenWithFocus ? 1 : 0);
9168 TextUtils.writeToParcel(text, out, flags);
9170 if (error == null) {
9174 TextUtils.writeToParcel(error, out, flags);
9179 public String toString() {
9180 String str = "TextView.SavedState{"
9181 + Integer.toHexString(System.identityHashCode(this))
9182 + " start=" + selStart + " end=" + selEnd;
9184 str += " text=" + text;
9189 @SuppressWarnings("hiding")
9190 public static final Parcelable.Creator<SavedState> CREATOR
9191 = new Parcelable.Creator<SavedState>() {
9192 public SavedState createFromParcel(Parcel in) {
9193 return new SavedState(in);
9196 public SavedState[] newArray(int size) {
9197 return new SavedState[size];
9201 private SavedState(Parcel in) {
9203 selStart = in.readInt();
9204 selEnd = in.readInt();
9205 frozenWithFocus = (in.readInt() != 0);
9206 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9208 if (in.readInt() != 0) {
9209 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9214 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
9215 private char[] mChars;
9216 private int mStart, mLength;
9218 public CharWrapper(char[] chars, int start, int len) {
9224 /* package */ void set(char[] chars, int start, int len) {
9230 public int length() {
9234 public char charAt(int off) {
9235 return mChars[off + mStart];
9239 public String toString() {
9240 return new String(mChars, mStart, mLength);
9243 public CharSequence subSequence(int start, int end) {
9244 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9245 throw new IndexOutOfBoundsException(start + ", " + end);
9248 return new String(mChars, start + mStart, end - start);
9251 public void getChars(int start, int end, char[] buf, int off) {
9252 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9253 throw new IndexOutOfBoundsException(start + ", " + end);
9256 System.arraycopy(mChars, start + mStart, buf, off, end - start);
9259 public void drawText(Canvas c, int start, int end,
9260 float x, float y, Paint p) {
9261 c.drawText(mChars, start + mStart, end - start, x, y, p);
9264 public void drawTextRun(Canvas c, int start, int end,
9265 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
9266 int count = end - start;
9267 int contextCount = contextEnd - contextStart;
9268 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
9269 contextCount, x, y, isRtl, p);
9272 public float measureText(int start, int end, Paint p) {
9273 return p.measureText(mChars, start + mStart, end - start);
9276 public int getTextWidths(int start, int end, float[] widths, Paint p) {
9277 return p.getTextWidths(mChars, start + mStart, end - start, widths);
9280 public float getTextRunAdvances(int start, int end, int contextStart,
9281 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
9283 int count = end - start;
9284 int contextCount = contextEnd - contextStart;
9285 return p.getTextRunAdvances(mChars, start + mStart, count,
9286 contextStart + mStart, contextCount, isRtl, advances,
9290 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
9291 int offset, int cursorOpt, Paint p) {
9292 int contextCount = contextEnd - contextStart;
9293 return p.getTextRunCursor(mChars, contextStart + mStart,
9294 contextCount, dir, offset + mStart, cursorOpt);
9298 private static final class Marquee {
9299 // TODO: Add an option to configure this
9300 private static final float MARQUEE_DELTA_MAX = 0.07f;
9301 private static final int MARQUEE_DELAY = 1200;
9302 private static final int MARQUEE_RESTART_DELAY = 1200;
9303 private static final int MARQUEE_DP_PER_SECOND = 30;
9305 private static final byte MARQUEE_STOPPED = 0x0;
9306 private static final byte MARQUEE_STARTING = 0x1;
9307 private static final byte MARQUEE_RUNNING = 0x2;
9309 private final WeakReference<TextView> mView;
9310 private final Choreographer mChoreographer;
9312 private byte mStatus = MARQUEE_STOPPED;
9313 private final float mPixelsPerSecond;
9314 private float mMaxScroll;
9315 private float mMaxFadeScroll;
9316 private float mGhostStart;
9317 private float mGhostOffset;
9318 private float mFadeStop;
9319 private int mRepeatLimit;
9321 private float mScroll;
9322 private long mLastAnimationMs;
9324 Marquee(TextView v) {
9325 final float density = v.getContext().getResources().getDisplayMetrics().density;
9326 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
9327 mView = new WeakReference<TextView>(v);
9328 mChoreographer = Choreographer.getInstance();
9331 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
9333 public void doFrame(long frameTimeNanos) {
9338 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
9340 public void doFrame(long frameTimeNanos) {
9341 mStatus = MARQUEE_RUNNING;
9342 mLastAnimationMs = mChoreographer.getFrameTime();
9347 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
9349 public void doFrame(long frameTimeNanos) {
9350 if (mStatus == MARQUEE_RUNNING) {
9351 if (mRepeatLimit >= 0) {
9354 start(mRepeatLimit);
9360 if (mStatus != MARQUEE_RUNNING) {
9364 mChoreographer.removeFrameCallback(mTickCallback);
9366 final TextView textView = mView.get();
9367 if (textView != null && (textView.isFocused() || textView.isSelected())) {
9368 long currentMs = mChoreographer.getFrameTime();
9369 long deltaMs = currentMs - mLastAnimationMs;
9370 mLastAnimationMs = currentMs;
9371 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
9373 if (mScroll > mMaxScroll) {
9374 mScroll = mMaxScroll;
9375 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
9377 mChoreographer.postFrameCallback(mTickCallback);
9379 textView.invalidate();
9384 mStatus = MARQUEE_STOPPED;
9385 mChoreographer.removeFrameCallback(mStartCallback);
9386 mChoreographer.removeFrameCallback(mRestartCallback);
9387 mChoreographer.removeFrameCallback(mTickCallback);
9391 private void resetScroll() {
9393 final TextView textView = mView.get();
9394 if (textView != null) textView.invalidate();
9397 void start(int repeatLimit) {
9398 if (repeatLimit == 0) {
9402 mRepeatLimit = repeatLimit;
9403 final TextView textView = mView.get();
9404 if (textView != null && textView.mLayout != null) {
9405 mStatus = MARQUEE_STARTING;
9407 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9408 textView.getCompoundPaddingRight();
9409 final float lineWidth = textView.mLayout.getLineWidth(0);
9410 final float gap = textWidth / 3.0f;
9411 mGhostStart = lineWidth - textWidth + gap;
9412 mMaxScroll = mGhostStart + textWidth;
9413 mGhostOffset = lineWidth + gap;
9414 mFadeStop = lineWidth + textWidth / 6.0f;
9415 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9417 textView.invalidate();
9418 mChoreographer.postFrameCallback(mStartCallback);
9422 float getGhostOffset() {
9423 return mGhostOffset;
9430 float getMaxFadeScroll() {
9431 return mMaxFadeScroll;
9434 boolean shouldDrawLeftFade() {
9435 return mScroll <= mFadeStop;
9438 boolean shouldDrawGhost() {
9439 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
9442 boolean isRunning() {
9443 return mStatus == MARQUEE_RUNNING;
9446 boolean isStopped() {
9447 return mStatus == MARQUEE_STOPPED;
9451 private class ChangeWatcher implements TextWatcher, SpanWatcher {
9453 private CharSequence mBeforeText;
9455 public void beforeTextChanged(CharSequence buffer, int start,
9456 int before, int after) {
9457 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
9458 + " before=" + before + " after=" + after + ": " + buffer);
9460 if (AccessibilityManager.getInstance(mContext).isEnabled()
9461 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
9462 || shouldSpeakPasswordsForAccessibility())) {
9463 mBeforeText = buffer.toString();
9466 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
9469 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
9470 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
9471 + " before=" + before + " after=" + after + ": " + buffer);
9472 TextView.this.handleTextChanged(buffer, start, before, after);
9474 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
9475 (isFocused() || isSelected() && isShown())) {
9476 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
9481 public void afterTextChanged(Editable buffer) {
9482 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
9483 TextView.this.sendAfterTextChanged(buffer);
9485 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
9486 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
9490 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
9491 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
9492 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
9493 TextView.this.spanChange(buf, what, s, st, e, en);
9496 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
9497 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
9498 + " what=" + what + ": " + buf);
9499 TextView.this.spanChange(buf, what, -1, s, -1, e);
9502 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
9503 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
9504 + " what=" + what + ": " + buf);
9505 TextView.this.spanChange(buf, what, s, -1, e, -1);