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.ColorInt;
21 import android.annotation.DrawableRes;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.annotation.StringRes;
25 import android.annotation.StyleRes;
26 import android.annotation.XmlRes;
27 import android.app.Activity;
28 import android.app.assist.AssistStructure;
29 import android.content.ClipData;
30 import android.content.ClipboardManager;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.UndoManager;
34 import android.content.res.ColorStateList;
35 import android.content.res.CompatibilityInfo;
36 import android.content.res.Configuration;
37 import android.content.res.Resources;
38 import android.content.res.TypedArray;
39 import android.content.res.XmlResourceParser;
40 import android.graphics.Canvas;
41 import android.graphics.Insets;
42 import android.graphics.Paint;
43 import android.graphics.Path;
44 import android.graphics.PorterDuff;
45 import android.graphics.Rect;
46 import android.graphics.RectF;
47 import android.graphics.Typeface;
48 import android.graphics.drawable.Drawable;
49 import android.inputmethodservice.ExtractEditText;
50 import android.os.AsyncTask;
51 import android.os.Bundle;
52 import android.os.Parcel;
53 import android.os.Parcelable;
54 import android.os.ParcelableParcel;
55 import android.os.SystemClock;
56 import android.os.UserHandle;
57 import android.provider.Settings;
58 import android.text.BoringLayout;
59 import android.text.DynamicLayout;
60 import android.text.Editable;
61 import android.text.GetChars;
62 import android.text.GraphicsOperations;
63 import android.text.InputFilter;
64 import android.text.InputType;
65 import android.text.Layout;
66 import android.text.ParcelableSpan;
67 import android.text.Selection;
68 import android.text.SpanWatcher;
69 import android.text.Spannable;
70 import android.text.SpannableString;
71 import android.text.SpannableStringBuilder;
72 import android.text.Spanned;
73 import android.text.SpannedString;
74 import android.text.StaticLayout;
75 import android.text.TextDirectionHeuristic;
76 import android.text.TextDirectionHeuristics;
77 import android.text.TextPaint;
78 import android.text.TextUtils;
79 import android.text.TextUtils.TruncateAt;
80 import android.text.TextWatcher;
81 import android.text.method.AllCapsTransformationMethod;
82 import android.text.method.ArrowKeyMovementMethod;
83 import android.text.method.DateKeyListener;
84 import android.text.method.DateTimeKeyListener;
85 import android.text.method.DialerKeyListener;
86 import android.text.method.DigitsKeyListener;
87 import android.text.method.KeyListener;
88 import android.text.method.LinkMovementMethod;
89 import android.text.method.MetaKeyKeyListener;
90 import android.text.method.MovementMethod;
91 import android.text.method.PasswordTransformationMethod;
92 import android.text.method.SingleLineTransformationMethod;
93 import android.text.method.TextKeyListener;
94 import android.text.method.TimeKeyListener;
95 import android.text.method.TransformationMethod;
96 import android.text.method.TransformationMethod2;
97 import android.text.method.WordIterator;
98 import android.text.style.CharacterStyle;
99 import android.text.style.ClickableSpan;
100 import android.text.style.ParagraphStyle;
101 import android.text.style.SpellCheckSpan;
102 import android.text.style.SuggestionSpan;
103 import android.text.style.URLSpan;
104 import android.text.style.UpdateAppearance;
105 import android.text.util.Linkify;
106 import android.util.AttributeSet;
107 import android.util.Log;
108 import android.util.TypedValue;
109 import android.view.AccessibilityIterators.TextSegmentIterator;
110 import android.view.ActionMode;
111 import android.view.Choreographer;
112 import android.view.DragEvent;
113 import android.view.Gravity;
114 import android.view.HapticFeedbackConstants;
115 import android.view.KeyCharacterMap;
116 import android.view.KeyEvent;
117 import android.view.MotionEvent;
118 import android.view.View;
119 import android.view.ViewStructure;
120 import android.view.ViewConfiguration;
121 import android.view.ViewDebug;
122 import android.view.ViewGroup.LayoutParams;
123 import android.view.ViewRootImpl;
124 import android.view.ViewTreeObserver;
125 import android.view.ViewHierarchyEncoder;
126 import android.view.accessibility.AccessibilityEvent;
127 import android.view.accessibility.AccessibilityManager;
128 import android.view.accessibility.AccessibilityNodeInfo;
129 import android.view.animation.AnimationUtils;
130 import android.view.inputmethod.BaseInputConnection;
131 import android.view.inputmethod.CompletionInfo;
132 import android.view.inputmethod.CorrectionInfo;
133 import android.view.inputmethod.EditorInfo;
134 import android.view.inputmethod.ExtractedText;
135 import android.view.inputmethod.ExtractedTextRequest;
136 import android.view.inputmethod.InputConnection;
137 import android.view.inputmethod.InputMethodManager;
138 import android.view.textservice.SpellCheckerSubtype;
139 import android.view.textservice.TextServicesManager;
140 import android.widget.RemoteViews.RemoteView;
142 import com.android.internal.util.FastMath;
143 import com.android.internal.widget.EditableInputConnection;
145 import org.xmlpull.v1.XmlPullParserException;
147 import java.io.IOException;
148 import java.lang.ref.WeakReference;
149 import java.util.ArrayList;
150 import java.util.Locale;
152 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
155 * Displays text to the user and optionally allows them to edit it. A TextView
156 * is a complete text editor, however the basic class is configured to not
157 * allow editing; see {@link EditText} for a subclass that configures the text
161 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the
162 * XML attribute {@link android.R.styleable#TextView_textIsSelectable
163 * android:textIsSelectable} to "true" or call
164 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag
165 * allows users to make selection gestures in the TextView, which in turn triggers the system's
166 * built-in copy/paste controls.
168 * <b>XML attributes</b>
170 * See {@link android.R.styleable#TextView TextView Attributes},
171 * {@link android.R.styleable#View View Attributes}
173 * @attr ref android.R.styleable#TextView_text
174 * @attr ref android.R.styleable#TextView_bufferType
175 * @attr ref android.R.styleable#TextView_hint
176 * @attr ref android.R.styleable#TextView_textColor
177 * @attr ref android.R.styleable#TextView_textColorHighlight
178 * @attr ref android.R.styleable#TextView_textColorHint
179 * @attr ref android.R.styleable#TextView_textAppearance
180 * @attr ref android.R.styleable#TextView_textColorLink
181 * @attr ref android.R.styleable#TextView_textSize
182 * @attr ref android.R.styleable#TextView_textScaleX
183 * @attr ref android.R.styleable#TextView_fontFamily
184 * @attr ref android.R.styleable#TextView_typeface
185 * @attr ref android.R.styleable#TextView_textStyle
186 * @attr ref android.R.styleable#TextView_cursorVisible
187 * @attr ref android.R.styleable#TextView_maxLines
188 * @attr ref android.R.styleable#TextView_maxHeight
189 * @attr ref android.R.styleable#TextView_lines
190 * @attr ref android.R.styleable#TextView_height
191 * @attr ref android.R.styleable#TextView_minLines
192 * @attr ref android.R.styleable#TextView_minHeight
193 * @attr ref android.R.styleable#TextView_maxEms
194 * @attr ref android.R.styleable#TextView_maxWidth
195 * @attr ref android.R.styleable#TextView_ems
196 * @attr ref android.R.styleable#TextView_width
197 * @attr ref android.R.styleable#TextView_minEms
198 * @attr ref android.R.styleable#TextView_minWidth
199 * @attr ref android.R.styleable#TextView_gravity
200 * @attr ref android.R.styleable#TextView_scrollHorizontally
201 * @attr ref android.R.styleable#TextView_password
202 * @attr ref android.R.styleable#TextView_singleLine
203 * @attr ref android.R.styleable#TextView_selectAllOnFocus
204 * @attr ref android.R.styleable#TextView_includeFontPadding
205 * @attr ref android.R.styleable#TextView_maxLength
206 * @attr ref android.R.styleable#TextView_shadowColor
207 * @attr ref android.R.styleable#TextView_shadowDx
208 * @attr ref android.R.styleable#TextView_shadowDy
209 * @attr ref android.R.styleable#TextView_shadowRadius
210 * @attr ref android.R.styleable#TextView_autoLink
211 * @attr ref android.R.styleable#TextView_linksClickable
212 * @attr ref android.R.styleable#TextView_numeric
213 * @attr ref android.R.styleable#TextView_digits
214 * @attr ref android.R.styleable#TextView_phoneNumber
215 * @attr ref android.R.styleable#TextView_inputMethod
216 * @attr ref android.R.styleable#TextView_capitalize
217 * @attr ref android.R.styleable#TextView_autoText
218 * @attr ref android.R.styleable#TextView_editable
219 * @attr ref android.R.styleable#TextView_freezesText
220 * @attr ref android.R.styleable#TextView_ellipsize
221 * @attr ref android.R.styleable#TextView_drawableTop
222 * @attr ref android.R.styleable#TextView_drawableBottom
223 * @attr ref android.R.styleable#TextView_drawableRight
224 * @attr ref android.R.styleable#TextView_drawableLeft
225 * @attr ref android.R.styleable#TextView_drawableStart
226 * @attr ref android.R.styleable#TextView_drawableEnd
227 * @attr ref android.R.styleable#TextView_drawablePadding
228 * @attr ref android.R.styleable#TextView_drawableTint
229 * @attr ref android.R.styleable#TextView_drawableTintMode
230 * @attr ref android.R.styleable#TextView_lineSpacingExtra
231 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
232 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
233 * @attr ref android.R.styleable#TextView_inputType
234 * @attr ref android.R.styleable#TextView_imeOptions
235 * @attr ref android.R.styleable#TextView_privateImeOptions
236 * @attr ref android.R.styleable#TextView_imeActionLabel
237 * @attr ref android.R.styleable#TextView_imeActionId
238 * @attr ref android.R.styleable#TextView_editorExtras
239 * @attr ref android.R.styleable#TextView_elegantTextHeight
240 * @attr ref android.R.styleable#TextView_letterSpacing
241 * @attr ref android.R.styleable#TextView_fontFeatureSettings
242 * @attr ref android.R.styleable#TextView_breakStrategy
243 * @attr ref android.R.styleable#TextView_hyphenationFrequency
246 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
247 static final String LOG_TAG = "TextView";
248 static final boolean DEBUG_EXTRACT = false;
250 // Enum for the "typeface" XML parameter.
251 // TODO: How can we get this from the XML instead of hardcoding it here?
252 private static final int SANS = 1;
253 private static final int SERIF = 2;
254 private static final int MONOSPACE = 3;
256 // Bitfield for the "numeric" XML parameter.
257 // TODO: How can we get this from the XML instead of hardcoding it here?
258 private static final int SIGNED = 2;
259 private static final int DECIMAL = 4;
262 * Draw marquee text with fading edges as usual
264 private static final int MARQUEE_FADE_NORMAL = 0;
267 * Draw marquee text as ellipsize end while inactive instead of with the fade.
268 * (Useful for devices where the fade can be expensive if overdone)
270 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
273 * Draw marquee text with fading edges because it is currently active/animating.
275 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
277 private static final int LINES = 1;
278 private static final int EMS = LINES;
279 private static final int PIXELS = 2;
281 private static final RectF TEMP_RECTF = new RectF();
283 // XXX should be much larger
284 private static final int VERY_WIDE = 1024*1024;
285 private static final int ANIMATED_SCROLL_GAP = 250;
287 private static final InputFilter[] NO_FILTERS = new InputFilter[0];
288 private static final Spanned EMPTY_SPANNED = new SpannedString("");
290 private static final int CHANGE_WATCHER_PRIORITY = 100;
292 // New state used to change background based on whether this TextView is multiline.
293 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
295 // Accessibility action to share selected text.
296 private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
298 // System wide time for last cut, copy or text changed action.
299 static long sLastCutCopyOrTextChangedTime;
304 static final int PROCESS_TEXT_REQUEST_CODE = 100;
306 private ColorStateList mTextColor;
307 private ColorStateList mHintTextColor;
308 private ColorStateList mLinkTextColor;
309 @ViewDebug.ExportedProperty(category = "text")
310 private int mCurTextColor;
311 private int mCurHintTextColor;
312 private boolean mFreezesText;
313 private boolean mDispatchTemporaryDetach;
315 /** Whether this view is temporarily detached from the parent view. */
316 boolean mTemporaryDetach;
318 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
319 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
321 private float mShadowRadius, mShadowDx, mShadowDy;
322 private int mShadowColor;
324 private boolean mPreDrawRegistered;
325 private boolean mPreDrawListenerDetached;
327 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
328 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
329 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
330 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
331 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
332 private boolean mPreventDefaultMovement;
334 private TextUtils.TruncateAt mEllipsize;
336 static class Drawables {
337 static final int LEFT = 0;
338 static final int TOP = 1;
339 static final int RIGHT = 2;
340 static final int BOTTOM = 3;
342 static final int DRAWABLE_NONE = -1;
343 static final int DRAWABLE_RIGHT = 0;
344 static final int DRAWABLE_LEFT = 1;
346 final Rect mCompoundRect = new Rect();
348 final Drawable[] mShowing = new Drawable[4];
350 ColorStateList mTintList;
351 PorterDuff.Mode mTintMode;
353 boolean mHasTintMode;
355 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
356 Drawable mDrawableLeftInitial, mDrawableRightInitial;
358 boolean mIsRtlCompatibilityMode;
361 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
362 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
364 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
365 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
367 int mDrawablePadding;
369 int mDrawableSaved = DRAWABLE_NONE;
371 public Drawables(Context context) {
372 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
373 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
374 !context.getApplicationInfo().hasRtlSupport());
378 public void resolveWithLayoutDirection(int layoutDirection) {
379 // First reset "left" and "right" drawables to their initial values
380 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
381 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
383 if (mIsRtlCompatibilityMode) {
384 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
385 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
386 mShowing[Drawables.LEFT] = mDrawableStart;
387 mDrawableSizeLeft = mDrawableSizeStart;
388 mDrawableHeightLeft = mDrawableHeightStart;
390 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
391 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
392 mShowing[Drawables.RIGHT] = mDrawableEnd;
393 mDrawableSizeRight = mDrawableSizeEnd;
394 mDrawableHeightRight = mDrawableHeightEnd;
397 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
398 // drawable if and only if they have been defined
399 switch(layoutDirection) {
400 case LAYOUT_DIRECTION_RTL:
402 mShowing[Drawables.RIGHT] = mDrawableStart;
403 mDrawableSizeRight = mDrawableSizeStart;
404 mDrawableHeightRight = mDrawableHeightStart;
406 mShowing[Drawables.LEFT] = mDrawableEnd;
407 mDrawableSizeLeft = mDrawableSizeEnd;
408 mDrawableHeightLeft = mDrawableHeightEnd;
412 case LAYOUT_DIRECTION_LTR:
415 mShowing[Drawables.LEFT] = mDrawableStart;
416 mDrawableSizeLeft = mDrawableSizeStart;
417 mDrawableHeightLeft = mDrawableHeightStart;
419 mShowing[Drawables.RIGHT] = mDrawableEnd;
420 mDrawableSizeRight = mDrawableSizeEnd;
421 mDrawableHeightRight = mDrawableHeightEnd;
426 applyErrorDrawableIfNeeded(layoutDirection);
427 updateDrawablesLayoutDirection(layoutDirection);
430 private void updateDrawablesLayoutDirection(int layoutDirection) {
431 for (Drawable dr : mShowing) {
433 dr.setLayoutDirection(layoutDirection);
438 public void setErrorDrawable(Drawable dr, TextView tv) {
439 if (mDrawableError != dr && mDrawableError != null) {
440 mDrawableError.setCallback(null);
444 if (mDrawableError != null) {
445 final Rect compoundRect = mCompoundRect;
446 final int[] state = tv.getDrawableState();
448 mDrawableError.setState(state);
449 mDrawableError.copyBounds(compoundRect);
450 mDrawableError.setCallback(tv);
451 mDrawableSizeError = compoundRect.width();
452 mDrawableHeightError = compoundRect.height();
454 mDrawableSizeError = mDrawableHeightError = 0;
458 private void applyErrorDrawableIfNeeded(int layoutDirection) {
459 // first restore the initial state if needed
460 switch (mDrawableSaved) {
462 mShowing[Drawables.LEFT] = mDrawableTemp;
463 mDrawableSizeLeft = mDrawableSizeTemp;
464 mDrawableHeightLeft = mDrawableHeightTemp;
467 mShowing[Drawables.RIGHT] = mDrawableTemp;
468 mDrawableSizeRight = mDrawableSizeTemp;
469 mDrawableHeightRight = mDrawableHeightTemp;
474 // then, if needed, assign the Error drawable to the correct location
475 if (mDrawableError != null) {
476 switch(layoutDirection) {
477 case LAYOUT_DIRECTION_RTL:
478 mDrawableSaved = DRAWABLE_LEFT;
480 mDrawableTemp = mShowing[Drawables.LEFT];
481 mDrawableSizeTemp = mDrawableSizeLeft;
482 mDrawableHeightTemp = mDrawableHeightLeft;
484 mShowing[Drawables.LEFT] = mDrawableError;
485 mDrawableSizeLeft = mDrawableSizeError;
486 mDrawableHeightLeft = mDrawableHeightError;
488 case LAYOUT_DIRECTION_LTR:
490 mDrawableSaved = DRAWABLE_RIGHT;
492 mDrawableTemp = mShowing[Drawables.RIGHT];
493 mDrawableSizeTemp = mDrawableSizeRight;
494 mDrawableHeightTemp = mDrawableHeightRight;
496 mShowing[Drawables.RIGHT] = mDrawableError;
497 mDrawableSizeRight = mDrawableSizeError;
498 mDrawableHeightRight = mDrawableHeightError;
505 Drawables mDrawables;
507 private CharWrapper mCharWrapper;
509 private Marquee mMarquee;
510 private boolean mRestartMarquee;
512 private int mMarqueeRepeatLimit = 3;
514 private int mLastLayoutDirection = -1;
517 * On some devices the fading edges add a performance penalty if used
518 * extensively in the same layout. This mode indicates how the marquee
519 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
521 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
524 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
525 * the layout that should be used when the mode switches.
527 private Layout mSavedMarqueeModeLayout;
529 @ViewDebug.ExportedProperty(category = "text")
530 private CharSequence mText;
531 private CharSequence mTransformed;
532 private BufferType mBufferType = BufferType.NORMAL;
534 private CharSequence mHint;
535 private Layout mHintLayout;
537 private MovementMethod mMovement;
539 private TransformationMethod mTransformation;
540 private boolean mAllowTransformationLengthChange;
541 private ChangeWatcher mChangeWatcher;
543 private ArrayList<TextWatcher> mListeners;
545 // display attributes
546 private final TextPaint mTextPaint;
547 private boolean mUserSetTextScaleX;
548 private Layout mLayout;
549 private boolean mLocaleChanged = false;
551 @ViewDebug.ExportedProperty(category = "text")
552 private int mGravity = Gravity.TOP | Gravity.START;
553 private boolean mHorizontallyScrolling;
555 private int mAutoLinkMask;
556 private boolean mLinksClickable = true;
558 private float mSpacingMult = 1.0f;
559 private float mSpacingAdd = 0.0f;
561 private int mBreakStrategy;
562 private int mHyphenationFrequency;
564 private int mMaximum = Integer.MAX_VALUE;
565 private int mMaxMode = LINES;
566 private int mMinimum = 0;
567 private int mMinMode = LINES;
569 private int mOldMaximum = mMaximum;
570 private int mOldMaxMode = mMaxMode;
572 private int mMaxWidth = Integer.MAX_VALUE;
573 private int mMaxWidthMode = PIXELS;
574 private int mMinWidth = 0;
575 private int mMinWidthMode = PIXELS;
577 private boolean mSingleLine;
578 private int mDesiredHeightAtMeasure = -1;
579 private boolean mIncludePad = true;
580 private int mDeferScroll = -1;
582 // tmp primitives, so we don't alloc them on each draw
583 private Rect mTempRect;
584 private long mLastScroll;
585 private Scroller mScroller;
587 private BoringLayout.Metrics mBoring, mHintBoring;
588 private BoringLayout mSavedLayout, mSavedHintLayout;
590 private TextDirectionHeuristic mTextDir;
592 private InputFilter[] mFilters = NO_FILTERS;
594 private volatile Locale mCurrentSpellCheckerLocaleCache;
596 // It is possible to have a selection even when mEditor is null (programmatically set, like when
597 // a link is pressed). These highlight-related fields do not go in mEditor.
598 int mHighlightColor = 0x6633B5E5;
599 private Path mHighlightPath;
600 private final Paint mHighlightPaint;
601 private boolean mHighlightPathBogus = true;
603 private boolean mFirstTouch = false;
604 private long mLastTouchUpTime = 0;
606 // Although these fields are specific to editable text, they are not added to Editor because
607 // they are defined by the TextView's style and are theme-dependent.
608 int mCursorDrawableRes;
609 // These four fields, could be moved to Editor, since we know their default values and we
610 // could condition the creation of the Editor to a non standard value. This is however
611 // brittle since the hardcoded values here (such as
612 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
613 // default style is modified.
614 int mTextSelectHandleLeftRes;
615 int mTextSelectHandleRightRes;
616 int mTextSelectHandleRes;
617 int mTextEditSuggestionItemLayout;
620 * EditText specific data, created on demand when one of the Editor fields is used.
621 * See {@link #createEditorIfNeeded()}.
623 private Editor mEditor;
626 * Kick-start the font cache for the zygote process (to pay the cost of
627 * initializing freetype for our default font only once).
630 Paint p = new Paint();
631 p.setAntiAlias(true);
632 // We don't care about the result, just the side-effect of measuring.
637 * Interface definition for a callback to be invoked when an action is
638 * performed on the editor.
640 public interface OnEditorActionListener {
642 * Called when an action is being performed.
644 * @param v The view that was clicked.
645 * @param actionId Identifier of the action. This will be either the
646 * identifier you supplied, or {@link EditorInfo#IME_NULL
647 * EditorInfo.IME_NULL} if being called due to the enter key
649 * @param event If triggered by an enter key, this is the event;
650 * otherwise, this is null.
651 * @return Return true if you have consumed the action, else false.
653 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
656 public TextView(Context context) {
660 public TextView(Context context, @Nullable AttributeSet attrs) {
661 this(context, attrs, com.android.internal.R.attr.textViewStyle);
664 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
665 this(context, attrs, defStyleAttr, 0);
668 @SuppressWarnings("deprecation")
670 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
671 super(context, attrs, defStyleAttr, defStyleRes);
675 final Resources res = getResources();
676 final CompatibilityInfo compat = res.getCompatibilityInfo();
678 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
679 mTextPaint.density = res.getDisplayMetrics().density;
680 mTextPaint.setCompatibilityScaling(compat.applicationScale);
682 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
683 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
685 mMovement = getDefaultMovementMethod();
687 mTransformation = null;
689 int textColorHighlight = 0;
690 ColorStateList textColor = null;
691 ColorStateList textColorHint = null;
692 ColorStateList textColorLink = null;
694 String fontFamily = null;
695 boolean fontFamilyExplicit = false;
696 int typefaceIndex = -1;
698 boolean allCaps = false;
700 float dx = 0, dy = 0, r = 0;
701 boolean elegant = false;
702 float letterSpacing = 0;
703 String fontFeatureSettings = null;
704 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
705 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
707 final Resources.Theme theme = context.getTheme();
710 * Look the appearance up without checking first if it exists because
711 * almost every TextView has one and it greatly simplifies the logic
712 * to be able to parse the appearance first and then let specific tags
713 * for this View override it.
715 TypedArray a = theme.obtainStyledAttributes(attrs,
716 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
717 TypedArray appearance = null;
718 int ap = a.getResourceId(
719 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
722 appearance = theme.obtainStyledAttributes(
723 ap, com.android.internal.R.styleable.TextAppearance);
725 if (appearance != null) {
726 int n = appearance.getIndexCount();
727 for (int i = 0; i < n; i++) {
728 int attr = appearance.getIndex(i);
731 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
732 textColorHighlight = appearance.getColor(attr, textColorHighlight);
735 case com.android.internal.R.styleable.TextAppearance_textColor:
736 textColor = appearance.getColorStateList(attr);
739 case com.android.internal.R.styleable.TextAppearance_textColorHint:
740 textColorHint = appearance.getColorStateList(attr);
743 case com.android.internal.R.styleable.TextAppearance_textColorLink:
744 textColorLink = appearance.getColorStateList(attr);
747 case com.android.internal.R.styleable.TextAppearance_textSize:
748 textSize = appearance.getDimensionPixelSize(attr, textSize);
751 case com.android.internal.R.styleable.TextAppearance_typeface:
752 typefaceIndex = appearance.getInt(attr, -1);
755 case com.android.internal.R.styleable.TextAppearance_fontFamily:
756 fontFamily = appearance.getString(attr);
759 case com.android.internal.R.styleable.TextAppearance_textStyle:
760 styleIndex = appearance.getInt(attr, -1);
763 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
764 allCaps = appearance.getBoolean(attr, false);
767 case com.android.internal.R.styleable.TextAppearance_shadowColor:
768 shadowcolor = appearance.getInt(attr, 0);
771 case com.android.internal.R.styleable.TextAppearance_shadowDx:
772 dx = appearance.getFloat(attr, 0);
775 case com.android.internal.R.styleable.TextAppearance_shadowDy:
776 dy = appearance.getFloat(attr, 0);
779 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
780 r = appearance.getFloat(attr, 0);
783 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
784 elegant = appearance.getBoolean(attr, false);
787 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
788 letterSpacing = appearance.getFloat(attr, 0);
791 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
792 fontFeatureSettings = appearance.getString(attr);
797 appearance.recycle();
800 boolean editable = getDefaultEditable();
801 CharSequence inputMethod = null;
803 CharSequence digits = null;
804 boolean phone = false;
805 boolean autotext = false;
808 boolean selectallonfocus = false;
809 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
810 drawableBottom = null, drawableStart = null, drawableEnd = null;
811 ColorStateList drawableTint = null;
812 PorterDuff.Mode drawableTintMode = null;
813 int drawablePadding = 0;
815 boolean singleLine = false;
817 CharSequence text = "";
818 CharSequence hint = null;
819 boolean password = false;
820 int inputType = EditorInfo.TYPE_NULL;
822 a = theme.obtainStyledAttributes(
823 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
825 int n = a.getIndexCount();
826 for (int i = 0; i < n; i++) {
827 int attr = a.getIndex(i);
830 case com.android.internal.R.styleable.TextView_editable:
831 editable = a.getBoolean(attr, editable);
834 case com.android.internal.R.styleable.TextView_inputMethod:
835 inputMethod = a.getText(attr);
838 case com.android.internal.R.styleable.TextView_numeric:
839 numeric = a.getInt(attr, numeric);
842 case com.android.internal.R.styleable.TextView_digits:
843 digits = a.getText(attr);
846 case com.android.internal.R.styleable.TextView_phoneNumber:
847 phone = a.getBoolean(attr, phone);
850 case com.android.internal.R.styleable.TextView_autoText:
851 autotext = a.getBoolean(attr, autotext);
854 case com.android.internal.R.styleable.TextView_capitalize:
855 autocap = a.getInt(attr, autocap);
858 case com.android.internal.R.styleable.TextView_bufferType:
859 buffertype = a.getInt(attr, buffertype);
862 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
863 selectallonfocus = a.getBoolean(attr, selectallonfocus);
866 case com.android.internal.R.styleable.TextView_autoLink:
867 mAutoLinkMask = a.getInt(attr, 0);
870 case com.android.internal.R.styleable.TextView_linksClickable:
871 mLinksClickable = a.getBoolean(attr, true);
874 case com.android.internal.R.styleable.TextView_drawableLeft:
875 drawableLeft = a.getDrawable(attr);
878 case com.android.internal.R.styleable.TextView_drawableTop:
879 drawableTop = a.getDrawable(attr);
882 case com.android.internal.R.styleable.TextView_drawableRight:
883 drawableRight = a.getDrawable(attr);
886 case com.android.internal.R.styleable.TextView_drawableBottom:
887 drawableBottom = a.getDrawable(attr);
890 case com.android.internal.R.styleable.TextView_drawableStart:
891 drawableStart = a.getDrawable(attr);
894 case com.android.internal.R.styleable.TextView_drawableEnd:
895 drawableEnd = a.getDrawable(attr);
898 case com.android.internal.R.styleable.TextView_drawableTint:
899 drawableTint = a.getColorStateList(attr);
902 case com.android.internal.R.styleable.TextView_drawableTintMode:
903 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
906 case com.android.internal.R.styleable.TextView_drawablePadding:
907 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
910 case com.android.internal.R.styleable.TextView_maxLines:
911 setMaxLines(a.getInt(attr, -1));
914 case com.android.internal.R.styleable.TextView_maxHeight:
915 setMaxHeight(a.getDimensionPixelSize(attr, -1));
918 case com.android.internal.R.styleable.TextView_lines:
919 setLines(a.getInt(attr, -1));
922 case com.android.internal.R.styleable.TextView_height:
923 setHeight(a.getDimensionPixelSize(attr, -1));
926 case com.android.internal.R.styleable.TextView_minLines:
927 setMinLines(a.getInt(attr, -1));
930 case com.android.internal.R.styleable.TextView_minHeight:
931 setMinHeight(a.getDimensionPixelSize(attr, -1));
934 case com.android.internal.R.styleable.TextView_maxEms:
935 setMaxEms(a.getInt(attr, -1));
938 case com.android.internal.R.styleable.TextView_maxWidth:
939 setMaxWidth(a.getDimensionPixelSize(attr, -1));
942 case com.android.internal.R.styleable.TextView_ems:
943 setEms(a.getInt(attr, -1));
946 case com.android.internal.R.styleable.TextView_width:
947 setWidth(a.getDimensionPixelSize(attr, -1));
950 case com.android.internal.R.styleable.TextView_minEms:
951 setMinEms(a.getInt(attr, -1));
954 case com.android.internal.R.styleable.TextView_minWidth:
955 setMinWidth(a.getDimensionPixelSize(attr, -1));
958 case com.android.internal.R.styleable.TextView_gravity:
959 setGravity(a.getInt(attr, -1));
962 case com.android.internal.R.styleable.TextView_hint:
963 hint = a.getText(attr);
966 case com.android.internal.R.styleable.TextView_text:
967 text = a.getText(attr);
970 case com.android.internal.R.styleable.TextView_scrollHorizontally:
971 if (a.getBoolean(attr, false)) {
972 setHorizontallyScrolling(true);
976 case com.android.internal.R.styleable.TextView_singleLine:
977 singleLine = a.getBoolean(attr, singleLine);
980 case com.android.internal.R.styleable.TextView_ellipsize:
981 ellipsize = a.getInt(attr, ellipsize);
984 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
985 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
988 case com.android.internal.R.styleable.TextView_includeFontPadding:
989 if (!a.getBoolean(attr, true)) {
990 setIncludeFontPadding(false);
994 case com.android.internal.R.styleable.TextView_cursorVisible:
995 if (!a.getBoolean(attr, true)) {
996 setCursorVisible(false);
1000 case com.android.internal.R.styleable.TextView_maxLength:
1001 maxlength = a.getInt(attr, -1);
1004 case com.android.internal.R.styleable.TextView_textScaleX:
1005 setTextScaleX(a.getFloat(attr, 1.0f));
1008 case com.android.internal.R.styleable.TextView_freezesText:
1009 mFreezesText = a.getBoolean(attr, false);
1012 case com.android.internal.R.styleable.TextView_shadowColor:
1013 shadowcolor = a.getInt(attr, 0);
1016 case com.android.internal.R.styleable.TextView_shadowDx:
1017 dx = a.getFloat(attr, 0);
1020 case com.android.internal.R.styleable.TextView_shadowDy:
1021 dy = a.getFloat(attr, 0);
1024 case com.android.internal.R.styleable.TextView_shadowRadius:
1025 r = a.getFloat(attr, 0);
1028 case com.android.internal.R.styleable.TextView_enabled:
1029 setEnabled(a.getBoolean(attr, isEnabled()));
1032 case com.android.internal.R.styleable.TextView_textColorHighlight:
1033 textColorHighlight = a.getColor(attr, textColorHighlight);
1036 case com.android.internal.R.styleable.TextView_textColor:
1037 textColor = a.getColorStateList(attr);
1040 case com.android.internal.R.styleable.TextView_textColorHint:
1041 textColorHint = a.getColorStateList(attr);
1044 case com.android.internal.R.styleable.TextView_textColorLink:
1045 textColorLink = a.getColorStateList(attr);
1048 case com.android.internal.R.styleable.TextView_textSize:
1049 textSize = a.getDimensionPixelSize(attr, textSize);
1052 case com.android.internal.R.styleable.TextView_typeface:
1053 typefaceIndex = a.getInt(attr, typefaceIndex);
1056 case com.android.internal.R.styleable.TextView_textStyle:
1057 styleIndex = a.getInt(attr, styleIndex);
1060 case com.android.internal.R.styleable.TextView_fontFamily:
1061 fontFamily = a.getString(attr);
1062 fontFamilyExplicit = true;
1065 case com.android.internal.R.styleable.TextView_password:
1066 password = a.getBoolean(attr, password);
1069 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1070 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1073 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1074 mSpacingMult = a.getFloat(attr, mSpacingMult);
1077 case com.android.internal.R.styleable.TextView_inputType:
1078 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1081 case com.android.internal.R.styleable.TextView_allowUndo:
1082 createEditorIfNeeded();
1083 mEditor.mAllowUndo = a.getBoolean(attr, true);
1086 case com.android.internal.R.styleable.TextView_imeOptions:
1087 createEditorIfNeeded();
1088 mEditor.createInputContentTypeIfNeeded();
1089 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1090 mEditor.mInputContentType.imeOptions);
1093 case com.android.internal.R.styleable.TextView_imeActionLabel:
1094 createEditorIfNeeded();
1095 mEditor.createInputContentTypeIfNeeded();
1096 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1099 case com.android.internal.R.styleable.TextView_imeActionId:
1100 createEditorIfNeeded();
1101 mEditor.createInputContentTypeIfNeeded();
1102 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1103 mEditor.mInputContentType.imeActionId);
1106 case com.android.internal.R.styleable.TextView_privateImeOptions:
1107 setPrivateImeOptions(a.getString(attr));
1110 case com.android.internal.R.styleable.TextView_editorExtras:
1112 setInputExtras(a.getResourceId(attr, 0));
1113 } catch (XmlPullParserException e) {
1114 Log.w(LOG_TAG, "Failure reading input extras", e);
1115 } catch (IOException e) {
1116 Log.w(LOG_TAG, "Failure reading input extras", e);
1120 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1121 mCursorDrawableRes = a.getResourceId(attr, 0);
1124 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1125 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1128 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1129 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1132 case com.android.internal.R.styleable.TextView_textSelectHandle:
1133 mTextSelectHandleRes = a.getResourceId(attr, 0);
1136 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1137 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1140 case com.android.internal.R.styleable.TextView_textIsSelectable:
1141 setTextIsSelectable(a.getBoolean(attr, false));
1144 case com.android.internal.R.styleable.TextView_textAllCaps:
1145 allCaps = a.getBoolean(attr, false);
1148 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1149 elegant = a.getBoolean(attr, false);
1152 case com.android.internal.R.styleable.TextView_letterSpacing:
1153 letterSpacing = a.getFloat(attr, 0);
1156 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1157 fontFeatureSettings = a.getString(attr);
1160 case com.android.internal.R.styleable.TextView_breakStrategy:
1161 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1164 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1165 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1171 BufferType bufferType = BufferType.EDITABLE;
1173 final int variation =
1174 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1175 final boolean passwordInputType = variation
1176 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1177 final boolean webPasswordInputType = variation
1178 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1179 final boolean numberPasswordInputType = variation
1180 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1182 if (inputMethod != null) {
1186 c = Class.forName(inputMethod.toString());
1187 } catch (ClassNotFoundException ex) {
1188 throw new RuntimeException(ex);
1192 createEditorIfNeeded();
1193 mEditor.mKeyListener = (KeyListener) c.newInstance();
1194 } catch (InstantiationException ex) {
1195 throw new RuntimeException(ex);
1196 } catch (IllegalAccessException ex) {
1197 throw new RuntimeException(ex);
1200 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1202 : mEditor.mKeyListener.getInputType();
1203 } catch (IncompatibleClassChangeError e) {
1204 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1206 } else if (digits != null) {
1207 createEditorIfNeeded();
1208 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1209 // If no input type was specified, we will default to generic
1210 // text, since we can't tell the IME about the set of digits
1211 // that was selected.
1212 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1213 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1214 } else if (inputType != EditorInfo.TYPE_NULL) {
1215 setInputType(inputType, true);
1216 // If set, the input type overrides what was set using the deprecated singleLine flag.
1217 singleLine = !isMultilineInputType(inputType);
1219 createEditorIfNeeded();
1220 mEditor.mKeyListener = DialerKeyListener.getInstance();
1221 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1222 } else if (numeric != 0) {
1223 createEditorIfNeeded();
1224 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
1225 (numeric & DECIMAL) != 0);
1226 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1227 if ((numeric & SIGNED) != 0) {
1228 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1230 if ((numeric & DECIMAL) != 0) {
1231 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1233 mEditor.mInputType = inputType;
1234 } else if (autotext || autocap != -1) {
1235 TextKeyListener.Capitalize cap;
1237 inputType = EditorInfo.TYPE_CLASS_TEXT;
1241 cap = TextKeyListener.Capitalize.SENTENCES;
1242 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1246 cap = TextKeyListener.Capitalize.WORDS;
1247 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1251 cap = TextKeyListener.Capitalize.CHARACTERS;
1252 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1256 cap = TextKeyListener.Capitalize.NONE;
1260 createEditorIfNeeded();
1261 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1262 mEditor.mInputType = inputType;
1263 } else if (isTextSelectable()) {
1264 // Prevent text changes from keyboard.
1265 if (mEditor != null) {
1266 mEditor.mKeyListener = null;
1267 mEditor.mInputType = EditorInfo.TYPE_NULL;
1269 bufferType = BufferType.SPANNABLE;
1270 // So that selection can be changed using arrow keys and touch is handled.
1271 setMovementMethod(ArrowKeyMovementMethod.getInstance());
1272 } else if (editable) {
1273 createEditorIfNeeded();
1274 mEditor.mKeyListener = TextKeyListener.getInstance();
1275 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1277 if (mEditor != null) mEditor.mKeyListener = null;
1279 switch (buffertype) {
1281 bufferType = BufferType.NORMAL;
1284 bufferType = BufferType.SPANNABLE;
1287 bufferType = BufferType.EDITABLE;
1292 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1293 webPasswordInputType, numberPasswordInputType);
1295 if (selectallonfocus) {
1296 createEditorIfNeeded();
1297 mEditor.mSelectAllOnFocus = true;
1299 if (bufferType == BufferType.NORMAL)
1300 bufferType = BufferType.SPANNABLE;
1303 // Set up the tint (if needed) before setting the drawables so that it
1304 // gets applied correctly.
1305 if (drawableTint != null || drawableTintMode != null) {
1306 if (mDrawables == null) {
1307 mDrawables = new Drawables(context);
1309 if (drawableTint != null) {
1310 mDrawables.mTintList = drawableTint;
1311 mDrawables.mHasTint = true;
1313 if (drawableTintMode != null) {
1314 mDrawables.mTintMode = drawableTintMode;
1315 mDrawables.mHasTintMode = true;
1319 // This call will save the initial left/right drawables
1320 setCompoundDrawablesWithIntrinsicBounds(
1321 drawableLeft, drawableTop, drawableRight, drawableBottom);
1322 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1323 setCompoundDrawablePadding(drawablePadding);
1325 // Same as setSingleLine(), but make sure the transformation method and the maximum number
1326 // of lines of height are unchanged for multi-line TextViews.
1327 setInputTypeSingleLine(singleLine);
1328 applySingleLine(singleLine, singleLine, singleLine);
1330 if (singleLine && getKeyListener() == null && ellipsize < 0) {
1331 ellipsize = 3; // END
1334 switch (ellipsize) {
1336 setEllipsize(TextUtils.TruncateAt.START);
1339 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1342 setEllipsize(TextUtils.TruncateAt.END);
1345 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1346 setHorizontalFadingEdgeEnabled(true);
1347 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1349 setHorizontalFadingEdgeEnabled(false);
1350 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1352 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1356 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1357 setHintTextColor(textColorHint);
1358 setLinkTextColor(textColorLink);
1359 if (textColorHighlight != 0) {
1360 setHighlightColor(textColorHighlight);
1362 setRawTextSize(textSize);
1363 setElegantTextHeight(elegant);
1364 setLetterSpacing(letterSpacing);
1365 setFontFeatureSettings(fontFeatureSettings);
1368 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1371 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1372 setTransformationMethod(PasswordTransformationMethod.getInstance());
1373 typefaceIndex = MONOSPACE;
1374 } else if (mEditor != null &&
1375 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1376 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1377 typefaceIndex = MONOSPACE;
1380 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1383 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
1385 if (shadowcolor != 0) {
1386 setShadowLayer(r, dx, dy, shadowcolor);
1389 if (maxlength >= 0) {
1390 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1392 setFilters(NO_FILTERS);
1395 setText(text, bufferType);
1396 if (hint != null) setHint(hint);
1399 * Views are not normally focusable unless specified to be.
1400 * However, TextViews that have input or movement methods *are*
1401 * focusable by default.
1403 a = context.obtainStyledAttributes(
1404 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1406 boolean focusable = mMovement != null || getKeyListener() != null;
1407 boolean clickable = focusable || isClickable();
1408 boolean longClickable = focusable || isLongClickable();
1410 n = a.getIndexCount();
1411 for (int i = 0; i < n; i++) {
1412 int attr = a.getIndex(i);
1415 case com.android.internal.R.styleable.View_focusable:
1416 focusable = a.getBoolean(attr, focusable);
1419 case com.android.internal.R.styleable.View_clickable:
1420 clickable = a.getBoolean(attr, clickable);
1423 case com.android.internal.R.styleable.View_longClickable:
1424 longClickable = a.getBoolean(attr, longClickable);
1430 setFocusable(focusable);
1431 setClickable(clickable);
1432 setLongClickable(longClickable);
1434 if (mEditor != null) mEditor.prepareCursorControllers();
1436 // If not explicitly specified this view is important for accessibility.
1437 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1438 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1442 private int[] parseDimensionArray(TypedArray dimens) {
1443 if (dimens == null) {
1446 int[] result = new int[dimens.length()];
1447 for (int i = 0; i < result.length; i++) {
1448 result[i] = dimens.getDimensionPixelSize(i, 0);
1457 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1458 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
1459 if (resultCode == Activity.RESULT_OK && data != null) {
1460 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1461 if (result != null) {
1462 if (isTextEditable()) {
1463 replaceSelectionWithText(result);
1465 if (result.length() > 0) {
1466 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1472 if (mEditor.hasSelectionController()) {
1473 mEditor.startSelectionActionMode();
1478 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
1480 if (familyName != null) {
1481 tf = Typeface.create(familyName, styleIndex);
1487 switch (typefaceIndex) {
1489 tf = Typeface.SANS_SERIF;
1493 tf = Typeface.SERIF;
1497 tf = Typeface.MONOSPACE;
1501 setTypeface(tf, styleIndex);
1504 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1505 boolean hasRelativeDrawables = (start != null) || (end != null);
1506 if (hasRelativeDrawables) {
1507 Drawables dr = mDrawables;
1509 mDrawables = dr = new Drawables(getContext());
1511 mDrawables.mOverride = true;
1512 final Rect compoundRect = dr.mCompoundRect;
1513 int[] state = getDrawableState();
1514 if (start != null) {
1515 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1516 start.setState(state);
1517 start.copyBounds(compoundRect);
1518 start.setCallback(this);
1520 dr.mDrawableStart = start;
1521 dr.mDrawableSizeStart = compoundRect.width();
1522 dr.mDrawableHeightStart = compoundRect.height();
1524 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1527 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1528 end.setState(state);
1529 end.copyBounds(compoundRect);
1530 end.setCallback(this);
1532 dr.mDrawableEnd = end;
1533 dr.mDrawableSizeEnd = compoundRect.width();
1534 dr.mDrawableHeightEnd = compoundRect.height();
1536 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1538 resetResolvedDrawables();
1540 applyCompoundDrawableTint();
1545 public void setEnabled(boolean enabled) {
1546 if (enabled == isEnabled()) {
1551 // Hide the soft input if the currently active TextView is disabled
1552 InputMethodManager imm = InputMethodManager.peekInstance();
1553 if (imm != null && imm.isActive(this)) {
1554 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1558 super.setEnabled(enabled);
1561 // Make sure IME is updated with current editor info.
1562 InputMethodManager imm = InputMethodManager.peekInstance();
1563 if (imm != null) imm.restartInput(this);
1566 // Will change text color
1567 if (mEditor != null) {
1568 mEditor.invalidateTextDisplayList();
1569 mEditor.prepareCursorControllers();
1571 // start or stop the cursor blinking as appropriate
1572 mEditor.makeBlink();
1577 * Sets the typeface and style in which the text should be displayed,
1578 * and turns on the fake bold and italic bits in the Paint if the
1579 * Typeface that you provided does not have all the bits in the
1580 * style that you specified.
1582 * @attr ref android.R.styleable#TextView_typeface
1583 * @attr ref android.R.styleable#TextView_textStyle
1585 public void setTypeface(Typeface tf, int style) {
1588 tf = Typeface.defaultFromStyle(style);
1590 tf = Typeface.create(tf, style);
1594 // now compute what (if any) algorithmic styling is needed
1595 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1596 int need = style & ~typefaceStyle;
1597 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1598 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1600 mTextPaint.setFakeBoldText(false);
1601 mTextPaint.setTextSkewX(0);
1607 * Subclasses override this to specify that they have a KeyListener
1608 * by default even if not specifically called for in the XML options.
1610 protected boolean getDefaultEditable() {
1615 * Subclasses override this to specify a default movement method.
1617 protected MovementMethod getDefaultMovementMethod() {
1622 * Return the text the TextView is displaying. If setText() was called with
1623 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1624 * the return value from this method to Spannable or Editable, respectively.
1626 * Note: The content of the return value should not be modified. If you want
1627 * a modifiable one, you should make your own copy first.
1629 * @attr ref android.R.styleable#TextView_text
1631 @ViewDebug.CapturedViewProperty
1632 public CharSequence getText() {
1637 * Returns the length, in characters, of the text managed by this TextView
1639 public int length() {
1640 return mText.length();
1644 * Return the text the TextView is displaying as an Editable object. If
1645 * the text is not editable, null is returned.
1649 public Editable getEditableText() {
1650 return (mText instanceof Editable) ? (Editable)mText : null;
1654 * @return the height of one standard line in pixels. Note that markup
1655 * within the text can cause individual lines to be taller or shorter
1656 * than this height, and the layout may contain additional first-
1657 * or last-line padding.
1659 public int getLineHeight() {
1660 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
1664 * @return the Layout that is currently being used to display the text.
1665 * This can be null if the text or width has recently changes.
1667 public final Layout getLayout() {
1672 * @return the Layout that is currently being used to display the hint text.
1675 final Layout getHintLayout() {
1680 * Retrieve the {@link android.content.UndoManager} that is currently associated
1681 * with this TextView. By default there is no associated UndoManager, so null
1682 * is returned. One can be associated with the TextView through
1683 * {@link #setUndoManager(android.content.UndoManager, String)}
1687 public final UndoManager getUndoManager() {
1688 // TODO: Consider supporting a global undo manager.
1689 throw new UnsupportedOperationException("not implemented");
1693 * Associate an {@link android.content.UndoManager} with this TextView. Once
1694 * done, all edit operations on the TextView will result in appropriate
1695 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1698 * @param undoManager The {@link android.content.UndoManager} to associate with
1699 * this TextView, or null to clear any existing association.
1700 * @param tag String tag identifying this particular TextView owner in the
1701 * UndoManager. This is used to keep the correct association with the
1702 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
1706 public final void setUndoManager(UndoManager undoManager, String tag) {
1707 // TODO: Consider supporting a global undo manager. An implementation will need to:
1708 // * createEditorIfNeeded()
1709 // * Promote to BufferType.EDITABLE if needed.
1710 // * Update the UndoManager and UndoOwner.
1711 // Likewise it will need to be able to restore the default UndoManager.
1712 throw new UnsupportedOperationException("not implemented");
1716 * @return the current key listener for this TextView.
1717 * This will frequently be null for non-EditText TextViews.
1719 * @attr ref android.R.styleable#TextView_numeric
1720 * @attr ref android.R.styleable#TextView_digits
1721 * @attr ref android.R.styleable#TextView_phoneNumber
1722 * @attr ref android.R.styleable#TextView_inputMethod
1723 * @attr ref android.R.styleable#TextView_capitalize
1724 * @attr ref android.R.styleable#TextView_autoText
1726 public final KeyListener getKeyListener() {
1727 return mEditor == null ? null : mEditor.mKeyListener;
1731 * Sets the key listener to be used with this TextView. This can be null
1732 * to disallow user input. Note that this method has significant and
1733 * subtle interactions with soft keyboards and other input method:
1734 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1735 * for important details. Calling this method will replace the current
1736 * content type of the text view with the content type returned by the
1739 * Be warned that if you want a TextView with a key listener or movement
1740 * method not to be focusable, or if you want a TextView without a
1741 * key listener or movement method to be focusable, you must call
1742 * {@link #setFocusable} again after calling this to get the focusability
1743 * back the way you want it.
1745 * @attr ref android.R.styleable#TextView_numeric
1746 * @attr ref android.R.styleable#TextView_digits
1747 * @attr ref android.R.styleable#TextView_phoneNumber
1748 * @attr ref android.R.styleable#TextView_inputMethod
1749 * @attr ref android.R.styleable#TextView_capitalize
1750 * @attr ref android.R.styleable#TextView_autoText
1752 public void setKeyListener(KeyListener input) {
1753 setKeyListenerOnly(input);
1754 fixFocusableAndClickableSettings();
1756 if (input != null) {
1757 createEditorIfNeeded();
1759 mEditor.mInputType = mEditor.mKeyListener.getInputType();
1760 } catch (IncompatibleClassChangeError e) {
1761 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1763 // Change inputType, without affecting transformation.
1764 // No need to applySingleLine since mSingleLine is unchanged.
1765 setInputTypeSingleLine(mSingleLine);
1767 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
1770 InputMethodManager imm = InputMethodManager.peekInstance();
1771 if (imm != null) imm.restartInput(this);
1774 private void setKeyListenerOnly(KeyListener input) {
1775 if (mEditor == null && input == null) return; // null is the default value
1777 createEditorIfNeeded();
1778 if (mEditor.mKeyListener != input) {
1779 mEditor.mKeyListener = input;
1780 if (input != null && !(mText instanceof Editable)) {
1784 setFilters((Editable) mText, mFilters);
1789 * @return the movement method being used for this TextView.
1790 * This will frequently be null for non-EditText TextViews.
1792 public final MovementMethod getMovementMethod() {
1797 * Sets the movement method (arrow key handler) to be used for
1798 * this TextView. This can be null to disallow using the arrow keys
1799 * to move the cursor or scroll the view.
1801 * Be warned that if you want a TextView with a key listener or movement
1802 * method not to be focusable, or if you want a TextView without a
1803 * key listener or movement method to be focusable, you must call
1804 * {@link #setFocusable} again after calling this to get the focusability
1805 * back the way you want it.
1807 public final void setMovementMethod(MovementMethod movement) {
1808 if (mMovement != movement) {
1809 mMovement = movement;
1811 if (movement != null && !(mText instanceof Spannable)) {
1815 fixFocusableAndClickableSettings();
1817 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1819 if (mEditor != null) mEditor.prepareCursorControllers();
1823 private void fixFocusableAndClickableSettings() {
1824 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
1827 setLongClickable(true);
1829 setFocusable(false);
1830 setClickable(false);
1831 setLongClickable(false);
1836 * @return the current transformation method for this TextView.
1837 * This will frequently be null except for single-line and password
1840 * @attr ref android.R.styleable#TextView_password
1841 * @attr ref android.R.styleable#TextView_singleLine
1843 public final TransformationMethod getTransformationMethod() {
1844 return mTransformation;
1848 * Sets the transformation that is applied to the text that this
1849 * TextView is displaying.
1851 * @attr ref android.R.styleable#TextView_password
1852 * @attr ref android.R.styleable#TextView_singleLine
1854 public final void setTransformationMethod(TransformationMethod method) {
1855 if (method == mTransformation) {
1856 // Avoid the setText() below if the transformation is
1860 if (mTransformation != null) {
1861 if (mText instanceof Spannable) {
1862 ((Spannable) mText).removeSpan(mTransformation);
1866 mTransformation = method;
1868 if (method instanceof TransformationMethod2) {
1869 TransformationMethod2 method2 = (TransformationMethod2) method;
1870 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
1871 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1873 mAllowTransformationLengthChange = false;
1878 if (hasPasswordTransformationMethod()) {
1879 notifyViewAccessibilityStateChangedIfNeeded(
1880 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
1885 * Returns the top padding of the view, plus space for the top
1888 public int getCompoundPaddingTop() {
1889 final Drawables dr = mDrawables;
1890 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
1893 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1898 * Returns the bottom padding of the view, plus space for the bottom
1901 public int getCompoundPaddingBottom() {
1902 final Drawables dr = mDrawables;
1903 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
1904 return mPaddingBottom;
1906 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1911 * Returns the left padding of the view, plus space for the left
1914 public int getCompoundPaddingLeft() {
1915 final Drawables dr = mDrawables;
1916 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
1917 return mPaddingLeft;
1919 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1924 * Returns the right padding of the view, plus space for the right
1927 public int getCompoundPaddingRight() {
1928 final Drawables dr = mDrawables;
1929 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
1930 return mPaddingRight;
1932 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1937 * Returns the start padding of the view, plus space for the start
1940 public int getCompoundPaddingStart() {
1942 switch(getLayoutDirection()) {
1944 case LAYOUT_DIRECTION_LTR:
1945 return getCompoundPaddingLeft();
1946 case LAYOUT_DIRECTION_RTL:
1947 return getCompoundPaddingRight();
1952 * Returns the end padding of the view, plus space for the end
1955 public int getCompoundPaddingEnd() {
1957 switch(getLayoutDirection()) {
1959 case LAYOUT_DIRECTION_LTR:
1960 return getCompoundPaddingRight();
1961 case LAYOUT_DIRECTION_RTL:
1962 return getCompoundPaddingLeft();
1967 * Returns the extended top padding of the view, including both the
1968 * top Drawable if any and any extra space to keep more than maxLines
1969 * of text from showing. It is only valid to call this after measuring.
1971 public int getExtendedPaddingTop() {
1972 if (mMaxMode != LINES) {
1973 return getCompoundPaddingTop();
1976 if (mLayout == null) {
1980 if (mLayout.getLineCount() <= mMaximum) {
1981 return getCompoundPaddingTop();
1984 int top = getCompoundPaddingTop();
1985 int bottom = getCompoundPaddingBottom();
1986 int viewht = getHeight() - top - bottom;
1987 int layoutht = mLayout.getLineTop(mMaximum);
1989 if (layoutht >= viewht) {
1993 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
1994 if (gravity == Gravity.TOP) {
1996 } else if (gravity == Gravity.BOTTOM) {
1997 return top + viewht - layoutht;
1998 } else { // (gravity == Gravity.CENTER_VERTICAL)
1999 return top + (viewht - layoutht) / 2;
2004 * Returns the extended bottom padding of the view, including both the
2005 * bottom Drawable if any and any extra space to keep more than maxLines
2006 * of text from showing. It is only valid to call this after measuring.
2008 public int getExtendedPaddingBottom() {
2009 if (mMaxMode != LINES) {
2010 return getCompoundPaddingBottom();
2013 if (mLayout == null) {
2017 if (mLayout.getLineCount() <= mMaximum) {
2018 return getCompoundPaddingBottom();
2021 int top = getCompoundPaddingTop();
2022 int bottom = getCompoundPaddingBottom();
2023 int viewht = getHeight() - top - bottom;
2024 int layoutht = mLayout.getLineTop(mMaximum);
2026 if (layoutht >= viewht) {
2030 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2031 if (gravity == Gravity.TOP) {
2032 return bottom + viewht - layoutht;
2033 } else if (gravity == Gravity.BOTTOM) {
2035 } else { // (gravity == Gravity.CENTER_VERTICAL)
2036 return bottom + (viewht - layoutht) / 2;
2041 * Returns the total left padding of the view, including the left
2044 public int getTotalPaddingLeft() {
2045 return getCompoundPaddingLeft();
2049 * Returns the total right padding of the view, including the right
2052 public int getTotalPaddingRight() {
2053 return getCompoundPaddingRight();
2057 * Returns the total start padding of the view, including the start
2060 public int getTotalPaddingStart() {
2061 return getCompoundPaddingStart();
2065 * Returns the total end padding of the view, including the end
2068 public int getTotalPaddingEnd() {
2069 return getCompoundPaddingEnd();
2073 * Returns the total top padding of the view, including the top
2074 * Drawable if any, the extra space to keep more than maxLines
2075 * from showing, and the vertical offset for gravity, if any.
2077 public int getTotalPaddingTop() {
2078 return getExtendedPaddingTop() + getVerticalOffset(true);
2082 * Returns the total bottom padding of the view, including the bottom
2083 * Drawable if any, the extra space to keep more than maxLines
2084 * from showing, and the vertical offset for gravity, if any.
2086 public int getTotalPaddingBottom() {
2087 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2091 * Sets the Drawables (if any) to appear to the left of, above, to the
2092 * right of, and below the text. Use {@code null} if you do not want a
2093 * Drawable there. The Drawables must already have had
2094 * {@link Drawable#setBounds} called.
2096 * Calling this method will overwrite any Drawables previously set using
2097 * {@link #setCompoundDrawablesRelative} or related methods.
2099 * @attr ref android.R.styleable#TextView_drawableLeft
2100 * @attr ref android.R.styleable#TextView_drawableTop
2101 * @attr ref android.R.styleable#TextView_drawableRight
2102 * @attr ref android.R.styleable#TextView_drawableBottom
2104 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2105 @Nullable Drawable right, @Nullable Drawable bottom) {
2106 Drawables dr = mDrawables;
2108 // We're switching to absolute, discard relative.
2110 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2111 dr.mDrawableStart = null;
2112 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2113 dr.mDrawableEnd = null;
2114 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2115 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2118 final boolean drawables = left != null || top != null || right != null || bottom != null;
2120 // Clearing drawables... can we free the data structure?
2122 if (dr.mDrawablePadding == 0) {
2125 // We need to retain the last set padding, so just clear
2126 // out all of the fields in the existing structure.
2127 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2128 if (dr.mShowing[i] != null) {
2129 dr.mShowing[i].setCallback(null);
2131 dr.mShowing[i] = null;
2133 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2134 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2135 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2136 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2141 mDrawables = dr = new Drawables(getContext());
2144 mDrawables.mOverride = false;
2146 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2147 dr.mShowing[Drawables.LEFT].setCallback(null);
2149 dr.mShowing[Drawables.LEFT] = left;
2151 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2152 dr.mShowing[Drawables.TOP].setCallback(null);
2154 dr.mShowing[Drawables.TOP] = top;
2156 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2157 dr.mShowing[Drawables.RIGHT].setCallback(null);
2159 dr.mShowing[Drawables.RIGHT] = right;
2161 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2162 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2164 dr.mShowing[Drawables.BOTTOM] = bottom;
2166 final Rect compoundRect = dr.mCompoundRect;
2169 state = getDrawableState();
2172 left.setState(state);
2173 left.copyBounds(compoundRect);
2174 left.setCallback(this);
2175 dr.mDrawableSizeLeft = compoundRect.width();
2176 dr.mDrawableHeightLeft = compoundRect.height();
2178 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2181 if (right != null) {
2182 right.setState(state);
2183 right.copyBounds(compoundRect);
2184 right.setCallback(this);
2185 dr.mDrawableSizeRight = compoundRect.width();
2186 dr.mDrawableHeightRight = compoundRect.height();
2188 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2192 top.setState(state);
2193 top.copyBounds(compoundRect);
2194 top.setCallback(this);
2195 dr.mDrawableSizeTop = compoundRect.height();
2196 dr.mDrawableWidthTop = compoundRect.width();
2198 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2201 if (bottom != null) {
2202 bottom.setState(state);
2203 bottom.copyBounds(compoundRect);
2204 bottom.setCallback(this);
2205 dr.mDrawableSizeBottom = compoundRect.height();
2206 dr.mDrawableWidthBottom = compoundRect.width();
2208 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2212 // Save initial left/right drawables
2214 dr.mDrawableLeftInitial = left;
2215 dr.mDrawableRightInitial = right;
2218 resetResolvedDrawables();
2220 applyCompoundDrawableTint();
2226 * Sets the Drawables (if any) to appear to the left of, above, to the
2227 * right of, and below the text. Use 0 if you do not want a Drawable there.
2228 * The Drawables' bounds will be set to their intrinsic bounds.
2230 * Calling this method will overwrite any Drawables previously set using
2231 * {@link #setCompoundDrawablesRelative} or related methods.
2233 * @param left Resource identifier of the left Drawable.
2234 * @param top Resource identifier of the top Drawable.
2235 * @param right Resource identifier of the right Drawable.
2236 * @param bottom Resource identifier of the bottom Drawable.
2238 * @attr ref android.R.styleable#TextView_drawableLeft
2239 * @attr ref android.R.styleable#TextView_drawableTop
2240 * @attr ref android.R.styleable#TextView_drawableRight
2241 * @attr ref android.R.styleable#TextView_drawableBottom
2243 @android.view.RemotableViewMethod
2244 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2245 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2246 final Context context = getContext();
2247 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2248 top != 0 ? context.getDrawable(top) : null,
2249 right != 0 ? context.getDrawable(right) : null,
2250 bottom != 0 ? context.getDrawable(bottom) : null);
2254 * Sets the Drawables (if any) to appear to the left of, above, to the
2255 * right of, and below the text. Use {@code null} if you do not want a
2256 * Drawable there. The Drawables' bounds will be set to their intrinsic
2259 * Calling this method will overwrite any Drawables previously set using
2260 * {@link #setCompoundDrawablesRelative} or related methods.
2262 * @attr ref android.R.styleable#TextView_drawableLeft
2263 * @attr ref android.R.styleable#TextView_drawableTop
2264 * @attr ref android.R.styleable#TextView_drawableRight
2265 * @attr ref android.R.styleable#TextView_drawableBottom
2267 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2268 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2271 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2273 if (right != null) {
2274 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2277 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2279 if (bottom != null) {
2280 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2282 setCompoundDrawables(left, top, right, bottom);
2286 * Sets the Drawables (if any) to appear to the start of, above, to the end
2287 * of, and below the text. Use {@code null} if you do not want a Drawable
2288 * there. The Drawables must already have had {@link Drawable#setBounds}
2291 * Calling this method will overwrite any Drawables previously set using
2292 * {@link #setCompoundDrawables} or related methods.
2294 * @attr ref android.R.styleable#TextView_drawableStart
2295 * @attr ref android.R.styleable#TextView_drawableTop
2296 * @attr ref android.R.styleable#TextView_drawableEnd
2297 * @attr ref android.R.styleable#TextView_drawableBottom
2299 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2300 @Nullable Drawable end, @Nullable Drawable bottom) {
2301 Drawables dr = mDrawables;
2303 // We're switching to relative, discard absolute.
2305 if (dr.mShowing[Drawables.LEFT] != null) {
2306 dr.mShowing[Drawables.LEFT].setCallback(null);
2308 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2309 if (dr.mShowing[Drawables.RIGHT] != null) {
2310 dr.mShowing[Drawables.RIGHT].setCallback(null);
2312 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2313 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2314 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2317 final boolean drawables = start != null || top != null
2318 || end != null || bottom != null;
2321 // Clearing drawables... can we free the data structure?
2323 if (dr.mDrawablePadding == 0) {
2326 // We need to retain the last set padding, so just clear
2327 // out all of the fields in the existing structure.
2328 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2329 dr.mDrawableStart = null;
2330 if (dr.mShowing[Drawables.TOP] != null) {
2331 dr.mShowing[Drawables.TOP].setCallback(null);
2333 dr.mShowing[Drawables.TOP] = null;
2334 if (dr.mDrawableEnd != null) {
2335 dr.mDrawableEnd.setCallback(null);
2337 dr.mDrawableEnd = null;
2338 if (dr.mShowing[Drawables.BOTTOM] != null) {
2339 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2341 dr.mShowing[Drawables.BOTTOM] = null;
2342 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2343 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2344 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2345 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2350 mDrawables = dr = new Drawables(getContext());
2353 mDrawables.mOverride = true;
2355 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2356 dr.mDrawableStart.setCallback(null);
2358 dr.mDrawableStart = start;
2360 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2361 dr.mShowing[Drawables.TOP].setCallback(null);
2363 dr.mShowing[Drawables.TOP] = top;
2365 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2366 dr.mDrawableEnd.setCallback(null);
2368 dr.mDrawableEnd = end;
2370 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2371 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2373 dr.mShowing[Drawables.BOTTOM] = bottom;
2375 final Rect compoundRect = dr.mCompoundRect;
2378 state = getDrawableState();
2380 if (start != null) {
2381 start.setState(state);
2382 start.copyBounds(compoundRect);
2383 start.setCallback(this);
2384 dr.mDrawableSizeStart = compoundRect.width();
2385 dr.mDrawableHeightStart = compoundRect.height();
2387 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2391 end.setState(state);
2392 end.copyBounds(compoundRect);
2393 end.setCallback(this);
2394 dr.mDrawableSizeEnd = compoundRect.width();
2395 dr.mDrawableHeightEnd = compoundRect.height();
2397 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2401 top.setState(state);
2402 top.copyBounds(compoundRect);
2403 top.setCallback(this);
2404 dr.mDrawableSizeTop = compoundRect.height();
2405 dr.mDrawableWidthTop = compoundRect.width();
2407 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2410 if (bottom != null) {
2411 bottom.setState(state);
2412 bottom.copyBounds(compoundRect);
2413 bottom.setCallback(this);
2414 dr.mDrawableSizeBottom = compoundRect.height();
2415 dr.mDrawableWidthBottom = compoundRect.width();
2417 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2421 resetResolvedDrawables();
2428 * Sets the Drawables (if any) to appear to the start of, above, to the end
2429 * of, and below the text. Use 0 if you do not want a Drawable there. The
2430 * Drawables' bounds will be set to their intrinsic bounds.
2432 * Calling this method will overwrite any Drawables previously set using
2433 * {@link #setCompoundDrawables} or related methods.
2435 * @param start Resource identifier of the start Drawable.
2436 * @param top Resource identifier of the top Drawable.
2437 * @param end Resource identifier of the end Drawable.
2438 * @param bottom Resource identifier of the bottom Drawable.
2440 * @attr ref android.R.styleable#TextView_drawableStart
2441 * @attr ref android.R.styleable#TextView_drawableTop
2442 * @attr ref android.R.styleable#TextView_drawableEnd
2443 * @attr ref android.R.styleable#TextView_drawableBottom
2445 @android.view.RemotableViewMethod
2446 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2447 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
2448 final Context context = getContext();
2449 setCompoundDrawablesRelativeWithIntrinsicBounds(
2450 start != 0 ? context.getDrawable(start) : null,
2451 top != 0 ? context.getDrawable(top) : null,
2452 end != 0 ? context.getDrawable(end) : null,
2453 bottom != 0 ? context.getDrawable(bottom) : null);
2457 * Sets the Drawables (if any) to appear to the start of, above, to the end
2458 * of, and below the text. Use {@code null} if you do not want a Drawable
2459 * there. The Drawables' bounds will be set to their intrinsic bounds.
2461 * Calling this method will overwrite any Drawables previously set using
2462 * {@link #setCompoundDrawables} or related methods.
2464 * @attr ref android.R.styleable#TextView_drawableStart
2465 * @attr ref android.R.styleable#TextView_drawableTop
2466 * @attr ref android.R.styleable#TextView_drawableEnd
2467 * @attr ref android.R.styleable#TextView_drawableBottom
2469 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2470 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
2472 if (start != null) {
2473 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2476 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2479 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2481 if (bottom != null) {
2482 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2484 setCompoundDrawablesRelative(start, top, end, bottom);
2488 * Returns drawables for the left, top, right, and bottom borders.
2490 * @attr ref android.R.styleable#TextView_drawableLeft
2491 * @attr ref android.R.styleable#TextView_drawableTop
2492 * @attr ref android.R.styleable#TextView_drawableRight
2493 * @attr ref android.R.styleable#TextView_drawableBottom
2496 public Drawable[] getCompoundDrawables() {
2497 final Drawables dr = mDrawables;
2499 return dr.mShowing.clone();
2501 return new Drawable[] { null, null, null, null };
2506 * Returns drawables for the start, top, end, and bottom borders.
2508 * @attr ref android.R.styleable#TextView_drawableStart
2509 * @attr ref android.R.styleable#TextView_drawableTop
2510 * @attr ref android.R.styleable#TextView_drawableEnd
2511 * @attr ref android.R.styleable#TextView_drawableBottom
2514 public Drawable[] getCompoundDrawablesRelative() {
2515 final Drawables dr = mDrawables;
2517 return new Drawable[] {
2518 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2519 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
2522 return new Drawable[] { null, null, null, null };
2527 * Sets the size of the padding between the compound drawables and
2530 * @attr ref android.R.styleable#TextView_drawablePadding
2532 @android.view.RemotableViewMethod
2533 public void setCompoundDrawablePadding(int pad) {
2534 Drawables dr = mDrawables;
2537 dr.mDrawablePadding = pad;
2541 mDrawables = dr = new Drawables(getContext());
2543 dr.mDrawablePadding = pad;
2551 * Returns the padding between the compound drawables and the text.
2553 * @attr ref android.R.styleable#TextView_drawablePadding
2555 public int getCompoundDrawablePadding() {
2556 final Drawables dr = mDrawables;
2557 return dr != null ? dr.mDrawablePadding : 0;
2561 * Applies a tint to the compound drawables. Does not modify the
2562 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2564 * Subsequent calls to
2565 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2566 * and related methods will automatically mutate the drawables and apply
2567 * the specified tint and tint mode using
2568 * {@link Drawable#setTintList(ColorStateList)}.
2570 * @param tint the tint to apply, may be {@code null} to clear tint
2572 * @attr ref android.R.styleable#TextView_drawableTint
2573 * @see #getCompoundDrawableTintList()
2574 * @see Drawable#setTintList(ColorStateList)
2576 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2577 if (mDrawables == null) {
2578 mDrawables = new Drawables(getContext());
2580 mDrawables.mTintList = tint;
2581 mDrawables.mHasTint = true;
2583 applyCompoundDrawableTint();
2587 * @return the tint applied to the compound drawables
2588 * @attr ref android.R.styleable#TextView_drawableTint
2589 * @see #setCompoundDrawableTintList(ColorStateList)
2591 public ColorStateList getCompoundDrawableTintList() {
2592 return mDrawables != null ? mDrawables.mTintList : null;
2596 * Specifies the blending mode used to apply the tint specified by
2597 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2598 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2600 * @param tintMode the blending mode used to apply the tint, may be
2601 * {@code null} to clear tint
2602 * @attr ref android.R.styleable#TextView_drawableTintMode
2603 * @see #setCompoundDrawableTintList(ColorStateList)
2604 * @see Drawable#setTintMode(PorterDuff.Mode)
2606 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2607 if (mDrawables == null) {
2608 mDrawables = new Drawables(getContext());
2610 mDrawables.mTintMode = tintMode;
2611 mDrawables.mHasTintMode = true;
2613 applyCompoundDrawableTint();
2617 * Returns the blending mode used to apply the tint to the compound
2618 * drawables, if specified.
2620 * @return the blending mode used to apply the tint to the compound
2622 * @attr ref android.R.styleable#TextView_drawableTintMode
2623 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2625 public PorterDuff.Mode getCompoundDrawableTintMode() {
2626 return mDrawables != null ? mDrawables.mTintMode : null;
2629 private void applyCompoundDrawableTint() {
2630 if (mDrawables == null) {
2634 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2635 final ColorStateList tintList = mDrawables.mTintList;
2636 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2637 final boolean hasTint = mDrawables.mHasTint;
2638 final boolean hasTintMode = mDrawables.mHasTintMode;
2639 final int[] state = getDrawableState();
2641 for (Drawable dr : mDrawables.mShowing) {
2646 if (dr == mDrawables.mDrawableError) {
2647 // From a developer's perspective, the error drawable isn't
2648 // a compound drawable. Don't apply the generic compound
2649 // drawable tint to it.
2656 dr.setTintList(tintList);
2660 dr.setTintMode(tintMode);
2663 // The drawable (or one of its children) may not have been
2664 // stateful before applying the tint, so let's try again.
2665 if (dr.isStateful()) {
2673 public void setPadding(int left, int top, int right, int bottom) {
2674 if (left != mPaddingLeft ||
2675 right != mPaddingRight ||
2676 top != mPaddingTop ||
2677 bottom != mPaddingBottom) {
2681 // the super call will requestLayout()
2682 super.setPadding(left, top, right, bottom);
2687 public void setPaddingRelative(int start, int top, int end, int bottom) {
2688 if (start != getPaddingStart() ||
2689 end != getPaddingEnd() ||
2690 top != mPaddingTop ||
2691 bottom != mPaddingBottom) {
2695 // the super call will requestLayout()
2696 super.setPaddingRelative(start, top, end, bottom);
2701 * Gets the autolink mask of the text. See {@link
2702 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2705 * @attr ref android.R.styleable#TextView_autoLink
2707 public final int getAutoLinkMask() {
2708 return mAutoLinkMask;
2712 * Sets the text appearance from the specified style resource.
2714 * Use a framework-defined {@code TextAppearance} style like
2715 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2716 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2717 * set of attributes that can be used in a custom style.
2719 * @param resId the resource identifier of the style to apply
2720 * @attr ref android.R.styleable#TextView_textAppearance
2722 @SuppressWarnings("deprecation")
2723 public void setTextAppearance(@StyleRes int resId) {
2724 setTextAppearance(mContext, resId);
2728 * Sets the text color, size, style, hint color, and highlight color
2729 * from the specified TextAppearance resource.
2731 * @deprecated Use {@link #setTextAppearance(int)} instead.
2734 public void setTextAppearance(Context context, @StyleRes int resId) {
2735 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
2737 final int textColorHighlight = ta.getColor(
2738 R.styleable.TextAppearance_textColorHighlight, 0);
2739 if (textColorHighlight != 0) {
2740 setHighlightColor(textColorHighlight);
2743 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2744 if (textColor != null) {
2745 setTextColor(textColor);
2748 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2749 if (textSize != 0) {
2750 setRawTextSize(textSize);
2753 final ColorStateList textColorHint = ta.getColorStateList(
2754 R.styleable.TextAppearance_textColorHint);
2755 if (textColorHint != null) {
2756 setHintTextColor(textColorHint);
2759 final ColorStateList textColorLink = ta.getColorStateList(
2760 R.styleable.TextAppearance_textColorLink);
2761 if (textColorLink != null) {
2762 setLinkTextColor(textColorLink);
2765 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2766 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2767 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2768 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
2770 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2771 if (shadowColor != 0) {
2772 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2773 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2774 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2775 setShadowLayer(r, dx, dy, shadowColor);
2778 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
2779 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2782 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2783 setElegantTextHeight(ta.getBoolean(
2784 R.styleable.TextAppearance_elegantTextHeight, false));
2787 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2788 setLetterSpacing(ta.getFloat(
2789 R.styleable.TextAppearance_letterSpacing, 0));
2792 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2793 setFontFeatureSettings(ta.getString(
2794 R.styleable.TextAppearance_fontFeatureSettings));
2801 * Get the default {@link Locale} of the text in this TextView.
2802 * @return the default {@link Locale} of the text in this TextView.
2804 public Locale getTextLocale() {
2805 return mTextPaint.getTextLocale();
2809 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2810 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2811 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2813 * @param locale the {@link Locale} for drawing text, must not be null.
2815 * @see Paint#setTextLocale
2817 public void setTextLocale(Locale locale) {
2818 mLocaleChanged = true;
2819 mTextPaint.setTextLocale(locale);
2823 protected void onConfigurationChanged(Configuration newConfig) {
2824 super.onConfigurationChanged(newConfig);
2825 if (!mLocaleChanged) {
2826 mTextPaint.setTextLocale(Locale.getDefault());
2831 * @return the size (in pixels) of the default text size in this TextView.
2833 @ViewDebug.ExportedProperty(category = "text")
2834 public float getTextSize() {
2835 return mTextPaint.getTextSize();
2839 * @return the size (in scaled pixels) of thee default text size in this TextView.
2842 @ViewDebug.ExportedProperty(category = "text")
2843 public float getScaledTextSize() {
2844 return mTextPaint.getTextSize() / mTextPaint.density;
2848 @ViewDebug.ExportedProperty(category = "text", mapping = {
2849 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2850 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2851 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2852 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2854 public int getTypefaceStyle() {
2855 Typeface typeface = mTextPaint.getTypeface();
2856 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
2860 * Set the default text size to the given value, interpreted as "scaled
2861 * pixel" units. This size is adjusted based on the current density and
2862 * user font size preference.
2864 * @param size The scaled pixel size.
2866 * @attr ref android.R.styleable#TextView_textSize
2868 @android.view.RemotableViewMethod
2869 public void setTextSize(float size) {
2870 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2874 * Set the default text size to a given unit and value. See {@link
2875 * TypedValue} for the possible dimension units.
2877 * @param unit The desired dimension unit.
2878 * @param size The desired size in the given units.
2880 * @attr ref android.R.styleable#TextView_textSize
2882 public void setTextSize(int unit, float size) {
2883 Context c = getContext();
2887 r = Resources.getSystem();
2889 r = c.getResources();
2891 setRawTextSize(TypedValue.applyDimension(
2892 unit, size, r.getDisplayMetrics()));
2895 private void setRawTextSize(float size) {
2896 if (size != mTextPaint.getTextSize()) {
2897 mTextPaint.setTextSize(size);
2899 if (mLayout != null) {
2908 * @return the extent by which text is currently being stretched
2909 * horizontally. This will usually be 1.
2911 public float getTextScaleX() {
2912 return mTextPaint.getTextScaleX();
2916 * Sets the extent by which text should be stretched horizontally.
2918 * @attr ref android.R.styleable#TextView_textScaleX
2920 @android.view.RemotableViewMethod
2921 public void setTextScaleX(float size) {
2922 if (size != mTextPaint.getTextScaleX()) {
2923 mUserSetTextScaleX = true;
2924 mTextPaint.setTextScaleX(size);
2926 if (mLayout != null) {
2935 * Sets the typeface and style in which the text should be displayed.
2936 * Note that not all Typeface families actually have bold and italic
2937 * variants, so you may need to use
2938 * {@link #setTypeface(Typeface, int)} to get the appearance
2939 * that you actually want.
2941 * @see #getTypeface()
2943 * @attr ref android.R.styleable#TextView_fontFamily
2944 * @attr ref android.R.styleable#TextView_typeface
2945 * @attr ref android.R.styleable#TextView_textStyle
2947 public void setTypeface(Typeface tf) {
2948 if (mTextPaint.getTypeface() != tf) {
2949 mTextPaint.setTypeface(tf);
2951 if (mLayout != null) {
2960 * @return the current typeface and style in which the text is being
2963 * @see #setTypeface(Typeface)
2965 * @attr ref android.R.styleable#TextView_fontFamily
2966 * @attr ref android.R.styleable#TextView_typeface
2967 * @attr ref android.R.styleable#TextView_textStyle
2969 public Typeface getTypeface() {
2970 return mTextPaint.getTypeface();
2974 * Set the TextView's elegant height metrics flag. This setting selects font
2975 * variants that have not been compacted to fit Latin-based vertical
2976 * metrics, and also increases top and bottom bounds to provide more space.
2978 * @param elegant set the paint's elegant metrics flag.
2980 * @attr ref android.R.styleable#TextView_elegantTextHeight
2982 public void setElegantTextHeight(boolean elegant) {
2983 mTextPaint.setElegantTextHeight(elegant);
2987 * @return the extent by which text is currently being letter-spaced.
2988 * This will normally be 0.
2990 * @see #setLetterSpacing(float)
2991 * @see Paint#setLetterSpacing
2993 public float getLetterSpacing() {
2994 return mTextPaint.getLetterSpacing();
2998 * Sets text letter-spacing. The value is in 'EM' units. Typical values
2999 * for slight expansion will be around 0.05. Negative values tighten text.
3001 * @see #getLetterSpacing()
3002 * @see Paint#getLetterSpacing
3004 * @attr ref android.R.styleable#TextView_letterSpacing
3006 @android.view.RemotableViewMethod
3007 public void setLetterSpacing(float letterSpacing) {
3008 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3009 mTextPaint.setLetterSpacing(letterSpacing);
3011 if (mLayout != null) {
3020 * @return the currently set font feature settings. Default is null.
3022 * @see #setFontFeatureSettings(String)
3023 * @see Paint#setFontFeatureSettings
3026 public String getFontFeatureSettings() {
3027 return mTextPaint.getFontFeatureSettings();
3031 * Sets the break strategy for breaking paragraphs into lines. The default value for
3032 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3033 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3034 * text "dancing" when being edited.
3036 * @attr ref android.R.styleable#TextView_breakStrategy
3037 * @see #getBreakStrategy()
3039 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3040 mBreakStrategy = breakStrategy;
3041 if (mLayout != null) {
3049 * @return the currently set break strategy.
3051 * @attr ref android.R.styleable#TextView_breakStrategy
3052 * @see #setBreakStrategy(int)
3054 @Layout.BreakStrategy
3055 public int getBreakStrategy() {
3056 return mBreakStrategy;
3060 * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
3061 * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3063 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3064 * @see #getHyphenationFrequency()
3066 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3067 mHyphenationFrequency = hyphenationFrequency;
3068 if (mLayout != null) {
3076 * @return the currently set hyphenation frequency.
3078 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3079 * @see #setHyphenationFrequency(int)
3081 @Layout.HyphenationFrequency
3082 public int getHyphenationFrequency() {
3083 return mHyphenationFrequency;
3087 * Sets font feature settings. The format is the same as the CSS
3088 * font-feature-settings attribute:
3089 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
3091 * @param fontFeatureSettings font feature settings represented as CSS compatible string
3092 * @see #getFontFeatureSettings()
3093 * @see Paint#getFontFeatureSettings
3095 * @attr ref android.R.styleable#TextView_fontFeatureSettings
3097 @android.view.RemotableViewMethod
3098 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3099 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3100 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3102 if (mLayout != null) {
3112 * Sets the text color for all the states (normal, selected,
3113 * focused) to be this color.
3115 * @see #setTextColor(ColorStateList)
3116 * @see #getTextColors()
3118 * @attr ref android.R.styleable#TextView_textColor
3120 @android.view.RemotableViewMethod
3121 public void setTextColor(@ColorInt int color) {
3122 mTextColor = ColorStateList.valueOf(color);
3127 * Sets the text color.
3129 * @see #setTextColor(int)
3130 * @see #getTextColors()
3131 * @see #setHintTextColor(ColorStateList)
3132 * @see #setLinkTextColor(ColorStateList)
3134 * @attr ref android.R.styleable#TextView_textColor
3136 public void setTextColor(ColorStateList colors) {
3137 if (colors == null) {
3138 throw new NullPointerException();
3141 mTextColor = colors;
3146 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
3148 * @see #setTextColor(ColorStateList)
3149 * @see #setTextColor(int)
3151 * @attr ref android.R.styleable#TextView_textColor
3153 public final ColorStateList getTextColors() {
3158 * <p>Return the current color selected for normal text.</p>
3160 * @return Returns the current text color.
3163 public final int getCurrentTextColor() {
3164 return mCurTextColor;
3168 * Sets the color used to display the selection highlight.
3170 * @attr ref android.R.styleable#TextView_textColorHighlight
3172 @android.view.RemotableViewMethod
3173 public void setHighlightColor(@ColorInt int color) {
3174 if (mHighlightColor != color) {
3175 mHighlightColor = color;
3181 * @return the color used to display the selection highlight
3183 * @see #setHighlightColor(int)
3185 * @attr ref android.R.styleable#TextView_textColorHighlight
3188 public int getHighlightColor() {
3189 return mHighlightColor;
3193 * Sets whether the soft input method will be made visible when this
3194 * TextView gets focused. The default is true.
3196 @android.view.RemotableViewMethod
3197 public final void setShowSoftInputOnFocus(boolean show) {
3198 createEditorIfNeeded();
3199 mEditor.mShowSoftInputOnFocus = show;
3203 * Returns whether the soft input method will be made visible when this
3204 * TextView gets focused. The default is true.
3206 public final boolean getShowSoftInputOnFocus() {
3207 // When there is no Editor, return default true value
3208 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3212 * Gives the text a shadow of the specified blur radius and color, the specified
3213 * distance from its drawn position.
3215 * The text shadow produced does not interact with the properties on view
3216 * that are responsible for real time shadows,
3217 * {@link View#getElevation() elevation} and
3218 * {@link View#getTranslationZ() translationZ}.
3220 * @see Paint#setShadowLayer(float, float, float, int)
3222 * @attr ref android.R.styleable#TextView_shadowColor
3223 * @attr ref android.R.styleable#TextView_shadowDx
3224 * @attr ref android.R.styleable#TextView_shadowDy
3225 * @attr ref android.R.styleable#TextView_shadowRadius
3227 public void setShadowLayer(float radius, float dx, float dy, int color) {
3228 mTextPaint.setShadowLayer(radius, dx, dy, color);
3230 mShadowRadius = radius;
3233 mShadowColor = color;
3235 // Will change text clip region
3236 if (mEditor != null) mEditor.invalidateTextDisplayList();
3241 * Gets the radius of the shadow layer.
3243 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3245 * @see #setShadowLayer(float, float, float, int)
3247 * @attr ref android.R.styleable#TextView_shadowRadius
3249 public float getShadowRadius() {
3250 return mShadowRadius;
3254 * @return the horizontal offset of the shadow layer
3256 * @see #setShadowLayer(float, float, float, int)
3258 * @attr ref android.R.styleable#TextView_shadowDx
3260 public float getShadowDx() {
3265 * @return the vertical offset of the shadow layer
3267 * @see #setShadowLayer(float, float, float, int)
3269 * @attr ref android.R.styleable#TextView_shadowDy
3271 public float getShadowDy() {
3276 * @return the color of the shadow layer
3278 * @see #setShadowLayer(float, float, float, int)
3280 * @attr ref android.R.styleable#TextView_shadowColor
3283 public int getShadowColor() {
3284 return mShadowColor;
3288 * @return the base paint used for the text. Please use this only to
3289 * consult the Paint's properties and not to change them.
3291 public TextPaint getPaint() {
3296 * Sets the autolink mask of the text. See {@link
3297 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3300 * @attr ref android.R.styleable#TextView_autoLink
3302 @android.view.RemotableViewMethod
3303 public final void setAutoLinkMask(int mask) {
3304 mAutoLinkMask = mask;
3308 * Sets whether the movement method will automatically be set to
3309 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3310 * set to nonzero and links are detected in {@link #setText}.
3311 * The default is true.
3313 * @attr ref android.R.styleable#TextView_linksClickable
3315 @android.view.RemotableViewMethod
3316 public final void setLinksClickable(boolean whether) {
3317 mLinksClickable = whether;
3321 * Returns whether the movement method will automatically be set to
3322 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3323 * set to nonzero and links are detected in {@link #setText}.
3324 * The default is true.
3326 * @attr ref android.R.styleable#TextView_linksClickable
3328 public final boolean getLinksClickable() {
3329 return mLinksClickable;
3333 * Returns the list of URLSpans attached to the text
3334 * (by {@link Linkify} or otherwise) if any. You can call
3335 * {@link URLSpan#getURL} on them to find where they link to
3336 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3337 * to find the region of the text they are attached to.
3339 public URLSpan[] getUrls() {
3340 if (mText instanceof Spanned) {
3341 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3343 return new URLSpan[0];
3348 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3351 * @see #setHintTextColor(ColorStateList)
3352 * @see #getHintTextColors()
3353 * @see #setTextColor(int)
3355 * @attr ref android.R.styleable#TextView_textColorHint
3357 @android.view.RemotableViewMethod
3358 public final void setHintTextColor(@ColorInt int color) {
3359 mHintTextColor = ColorStateList.valueOf(color);
3364 * Sets the color of the hint text.
3366 * @see #getHintTextColors()
3367 * @see #setHintTextColor(int)
3368 * @see #setTextColor(ColorStateList)
3369 * @see #setLinkTextColor(ColorStateList)
3371 * @attr ref android.R.styleable#TextView_textColorHint
3373 public final void setHintTextColor(ColorStateList colors) {
3374 mHintTextColor = colors;
3379 * @return the color of the hint text, for the different states of this TextView.
3381 * @see #setHintTextColor(ColorStateList)
3382 * @see #setHintTextColor(int)
3383 * @see #setTextColor(ColorStateList)
3384 * @see #setLinkTextColor(ColorStateList)
3386 * @attr ref android.R.styleable#TextView_textColorHint
3388 public final ColorStateList getHintTextColors() {
3389 return mHintTextColor;
3393 * <p>Return the current color selected to paint the hint text.</p>
3395 * @return Returns the current hint text color.
3398 public final int getCurrentHintTextColor() {
3399 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3403 * Sets the color of links in the text.
3405 * @see #setLinkTextColor(ColorStateList)
3406 * @see #getLinkTextColors()
3408 * @attr ref android.R.styleable#TextView_textColorLink
3410 @android.view.RemotableViewMethod
3411 public final void setLinkTextColor(@ColorInt int color) {
3412 mLinkTextColor = ColorStateList.valueOf(color);
3417 * Sets the color of links in the text.
3419 * @see #setLinkTextColor(int)
3420 * @see #getLinkTextColors()
3421 * @see #setTextColor(ColorStateList)
3422 * @see #setHintTextColor(ColorStateList)
3424 * @attr ref android.R.styleable#TextView_textColorLink
3426 public final void setLinkTextColor(ColorStateList colors) {
3427 mLinkTextColor = colors;
3432 * @return the list of colors used to paint the links in the text, for the different states of
3435 * @see #setLinkTextColor(ColorStateList)
3436 * @see #setLinkTextColor(int)
3438 * @attr ref android.R.styleable#TextView_textColorLink
3440 public final ColorStateList getLinkTextColors() {
3441 return mLinkTextColor;
3445 * Sets the horizontal alignment of the text and the
3446 * vertical gravity that will be used when there is extra space
3447 * in the TextView beyond what is required for the text itself.
3449 * @see android.view.Gravity
3450 * @attr ref android.R.styleable#TextView_gravity
3452 public void setGravity(int gravity) {
3453 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
3454 gravity |= Gravity.START;
3456 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3457 gravity |= Gravity.TOP;
3460 boolean newLayout = false;
3462 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3463 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
3467 if (gravity != mGravity) {
3473 if (mLayout != null && newLayout) {
3474 // XXX this is heavy-handed because no actual content changes.
3475 int want = mLayout.getWidth();
3476 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3478 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3479 mRight - mLeft - getCompoundPaddingLeft() -
3480 getCompoundPaddingRight(), true);
3485 * Returns the horizontal and vertical alignment of this TextView.
3487 * @see android.view.Gravity
3488 * @attr ref android.R.styleable#TextView_gravity
3490 public int getGravity() {
3495 * @return the flags on the Paint being used to display the text.
3496 * @see Paint#getFlags
3498 public int getPaintFlags() {
3499 return mTextPaint.getFlags();
3503 * Sets flags on the Paint being used to display the text and
3504 * reflows the text if they are different from the old flags.
3505 * @see Paint#setFlags
3507 @android.view.RemotableViewMethod
3508 public void setPaintFlags(int flags) {
3509 if (mTextPaint.getFlags() != flags) {
3510 mTextPaint.setFlags(flags);
3512 if (mLayout != null) {
3521 * Sets whether the text should be allowed to be wider than the
3522 * View is. If false, it will be wrapped to the width of the View.
3524 * @attr ref android.R.styleable#TextView_scrollHorizontally
3526 public void setHorizontallyScrolling(boolean whether) {
3527 if (mHorizontallyScrolling != whether) {
3528 mHorizontallyScrolling = whether;
3530 if (mLayout != null) {
3539 * Returns whether the text is allowed to be wider than the View is.
3540 * If false, the text will be wrapped to the width of the View.
3542 * @attr ref android.R.styleable#TextView_scrollHorizontally
3545 public boolean getHorizontallyScrolling() {
3546 return mHorizontallyScrolling;
3550 * Makes the TextView at least this many lines tall.
3552 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3553 * set this value to 1.
3555 * @see #getMinLines()
3557 * @attr ref android.R.styleable#TextView_minLines
3559 @android.view.RemotableViewMethod
3560 public void setMinLines(int minlines) {
3561 mMinimum = minlines;
3569 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3570 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3572 * @see #setMinLines(int)
3574 * @attr ref android.R.styleable#TextView_minLines
3576 public int getMinLines() {
3577 return mMinMode == LINES ? mMinimum : -1;
3581 * Makes the TextView at least this many pixels tall.
3583 * Setting this value overrides any other (minimum) number of lines setting.
3585 * @attr ref android.R.styleable#TextView_minHeight
3587 @android.view.RemotableViewMethod
3588 public void setMinHeight(int minHeight) {
3589 mMinimum = minHeight;
3597 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3598 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3600 * @see #setMinHeight(int)
3602 * @attr ref android.R.styleable#TextView_minHeight
3604 public int getMinHeight() {
3605 return mMinMode == PIXELS ? mMinimum : -1;
3609 * Makes the TextView at most this many lines tall.
3611 * Setting this value overrides any other (maximum) height setting.
3613 * @attr ref android.R.styleable#TextView_maxLines
3615 @android.view.RemotableViewMethod
3616 public void setMaxLines(int maxlines) {
3617 mMaximum = maxlines;
3625 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3626 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3628 * @see #setMaxLines(int)
3630 * @attr ref android.R.styleable#TextView_maxLines
3632 public int getMaxLines() {
3633 return mMaxMode == LINES ? mMaximum : -1;
3637 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3638 * {@link #setMaxLines(int)} method.
3640 * Setting this value overrides any other (maximum) number of lines setting.
3642 * @attr ref android.R.styleable#TextView_maxHeight
3644 @android.view.RemotableViewMethod
3645 public void setMaxHeight(int maxHeight) {
3646 mMaximum = maxHeight;
3654 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3655 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3657 * @see #setMaxHeight(int)
3659 * @attr ref android.R.styleable#TextView_maxHeight
3661 public int getMaxHeight() {
3662 return mMaxMode == PIXELS ? mMaximum : -1;
3666 * Makes the TextView exactly this many lines tall.
3668 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3669 * height setting. A single line TextView will set this value to 1.
3671 * @attr ref android.R.styleable#TextView_lines
3673 @android.view.RemotableViewMethod
3674 public void setLines(int lines) {
3675 mMaximum = mMinimum = lines;
3676 mMaxMode = mMinMode = LINES;
3683 * Makes the TextView exactly this many pixels tall.
3684 * You could do the same thing by specifying this number in the
3687 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3690 * @attr ref android.R.styleable#TextView_height
3692 @android.view.RemotableViewMethod
3693 public void setHeight(int pixels) {
3694 mMaximum = mMinimum = pixels;
3695 mMaxMode = mMinMode = PIXELS;
3702 * Makes the TextView at least this many ems wide
3704 * @attr ref android.R.styleable#TextView_minEms
3706 @android.view.RemotableViewMethod
3707 public void setMinEms(int minems) {
3709 mMinWidthMode = EMS;
3716 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3717 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3719 * @see #setMinEms(int)
3722 * @attr ref android.R.styleable#TextView_minEms
3724 public int getMinEms() {
3725 return mMinWidthMode == EMS ? mMinWidth : -1;
3729 * Makes the TextView at least this many pixels wide
3731 * @attr ref android.R.styleable#TextView_minWidth
3733 @android.view.RemotableViewMethod
3734 public void setMinWidth(int minpixels) {
3735 mMinWidth = minpixels;
3736 mMinWidthMode = PIXELS;
3743 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3744 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3746 * @see #setMinWidth(int)
3747 * @see #setWidth(int)
3749 * @attr ref android.R.styleable#TextView_minWidth
3751 public int getMinWidth() {
3752 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3756 * Makes the TextView at most this many ems wide
3758 * @attr ref android.R.styleable#TextView_maxEms
3760 @android.view.RemotableViewMethod
3761 public void setMaxEms(int maxems) {
3763 mMaxWidthMode = EMS;
3770 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3771 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3773 * @see #setMaxEms(int)
3776 * @attr ref android.R.styleable#TextView_maxEms
3778 public int getMaxEms() {
3779 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3783 * Makes the TextView at most this many pixels wide
3785 * @attr ref android.R.styleable#TextView_maxWidth
3787 @android.view.RemotableViewMethod
3788 public void setMaxWidth(int maxpixels) {
3789 mMaxWidth = maxpixels;
3790 mMaxWidthMode = PIXELS;
3797 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3798 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3800 * @see #setMaxWidth(int)
3801 * @see #setWidth(int)
3803 * @attr ref android.R.styleable#TextView_maxWidth
3805 public int getMaxWidth() {
3806 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3810 * Makes the TextView exactly this many ems wide
3812 * @see #setMaxEms(int)
3813 * @see #setMinEms(int)
3817 * @attr ref android.R.styleable#TextView_ems
3819 @android.view.RemotableViewMethod
3820 public void setEms(int ems) {
3821 mMaxWidth = mMinWidth = ems;
3822 mMaxWidthMode = mMinWidthMode = EMS;
3829 * Makes the TextView exactly this many pixels wide.
3830 * You could do the same thing by specifying this number in the
3833 * @see #setMaxWidth(int)
3834 * @see #setMinWidth(int)
3835 * @see #getMinWidth()
3836 * @see #getMaxWidth()
3838 * @attr ref android.R.styleable#TextView_width
3840 @android.view.RemotableViewMethod
3841 public void setWidth(int pixels) {
3842 mMaxWidth = mMinWidth = pixels;
3843 mMaxWidthMode = mMinWidthMode = PIXELS;
3850 * Sets line spacing for this TextView. Each line will have its height
3851 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3853 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3854 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3856 public void setLineSpacing(float add, float mult) {
3857 if (mSpacingAdd != add || mSpacingMult != mult) {
3859 mSpacingMult = mult;
3861 if (mLayout != null) {
3870 * Gets the line spacing multiplier
3872 * @return the value by which each line's height is multiplied to get its actual height.
3874 * @see #setLineSpacing(float, float)
3875 * @see #getLineSpacingExtra()
3877 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3879 public float getLineSpacingMultiplier() {
3880 return mSpacingMult;
3884 * Gets the line spacing extra space
3886 * @return the extra space that is added to the height of each lines of this TextView.
3888 * @see #setLineSpacing(float, float)
3889 * @see #getLineSpacingMultiplier()
3891 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3893 public float getLineSpacingExtra() {
3898 * Convenience method: Append the specified text to the TextView's
3899 * display buffer, upgrading it to BufferType.EDITABLE if it was
3900 * not already editable.
3902 public final void append(CharSequence text) {
3903 append(text, 0, text.length());
3907 * Convenience method: Append the specified text slice to the TextView's
3908 * display buffer, upgrading it to BufferType.EDITABLE if it was
3909 * not already editable.
3911 public void append(CharSequence text, int start, int end) {
3912 if (!(mText instanceof Editable)) {
3913 setText(mText, BufferType.EDITABLE);
3916 ((Editable) mText).append(text, start, end);
3919 private void updateTextColors() {
3920 boolean inval = false;
3921 int color = mTextColor.getColorForState(getDrawableState(), 0);
3922 if (color != mCurTextColor) {
3923 mCurTextColor = color;
3926 if (mLinkTextColor != null) {
3927 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3928 if (color != mTextPaint.linkColor) {
3929 mTextPaint.linkColor = color;
3933 if (mHintTextColor != null) {
3934 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3935 if (color != mCurHintTextColor) {
3936 mCurHintTextColor = color;
3937 if (mText.length() == 0) {
3943 // Text needs to be redrawn with the new color
3944 if (mEditor != null) mEditor.invalidateTextDisplayList();
3950 protected void drawableStateChanged() {
3951 super.drawableStateChanged();
3952 if (mTextColor != null && mTextColor.isStateful()
3953 || (mHintTextColor != null && mHintTextColor.isStateful())
3954 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3958 if (mDrawables != null) {
3959 final int[] state = getDrawableState();
3960 for (Drawable dr : mDrawables.mShowing) {
3961 if (dr != null && dr.isStateful()) {
3969 public void drawableHotspotChanged(float x, float y) {
3970 super.drawableHotspotChanged(x, y);
3972 if (mDrawables != null) {
3973 final int[] state = getDrawableState();
3974 for (Drawable dr : mDrawables.mShowing) {
3975 if (dr != null && dr.isStateful()) {
3976 dr.setHotspot(x, y);
3983 public Parcelable onSaveInstanceState() {
3984 Parcelable superState = super.onSaveInstanceState();
3986 // Save state if we are forced to
3987 boolean save = mFreezesText;
3991 if (mText != null) {
3992 start = getSelectionStart();
3993 end = getSelectionEnd();
3994 if (start >= 0 || end >= 0) {
3995 // Or save state if there is a selection
4001 SavedState ss = new SavedState(superState);
4002 // XXX Should also save the current scroll position!
4003 ss.selStart = start;
4006 if (mText instanceof Spanned) {
4007 Spannable sp = new SpannableStringBuilder(mText);
4009 if (mEditor != null) {
4010 removeMisspelledSpans(sp);
4011 sp.removeSpan(mEditor.mSuggestionRangeSpan);
4016 ss.text = mText.toString();
4019 if (isFocused() && start >= 0 && end >= 0) {
4020 ss.frozenWithFocus = true;
4023 ss.error = getError();
4025 if (mEditor != null) {
4026 ss.editorState = mEditor.saveInstanceState();
4034 void removeMisspelledSpans(Spannable spannable) {
4035 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4036 SuggestionSpan.class);
4037 for (int i = 0; i < suggestionSpans.length; i++) {
4038 int flags = suggestionSpans[i].getFlags();
4039 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4040 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4041 spannable.removeSpan(suggestionSpans[i]);
4047 public void onRestoreInstanceState(Parcelable state) {
4048 if (!(state instanceof SavedState)) {
4049 super.onRestoreInstanceState(state);
4053 SavedState ss = (SavedState)state;
4054 super.onRestoreInstanceState(ss.getSuperState());
4056 // XXX restore buffer type too, as well as lots of other stuff
4057 if (ss.text != null) {
4061 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4062 if (mText instanceof Spannable) {
4063 int len = mText.length();
4065 if (ss.selStart > len || ss.selEnd > len) {
4066 String restored = "";
4068 if (ss.text != null) {
4069 restored = "(restored) ";
4072 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
4073 "/" + ss.selEnd + " out of range for " + restored +
4076 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
4078 if (ss.frozenWithFocus) {
4079 createEditorIfNeeded();
4080 mEditor.mFrozenWithFocus = true;
4086 if (ss.error != null) {
4087 final CharSequence error = ss.error;
4088 // Display the error later, after the first layout pass
4089 post(new Runnable() {
4091 if (mEditor == null || !mEditor.mErrorWasChanged) {
4098 if (ss.editorState != null) {
4099 createEditorIfNeeded();
4100 mEditor.restoreInstanceState(ss.editorState);
4105 * Control whether this text view saves its entire text contents when
4106 * freezing to an icicle, in addition to dynamic state such as cursor
4107 * position. By default this is false, not saving the text. Set to true
4108 * if the text in the text view is not being saved somewhere else in
4109 * persistent storage (such as in a content provider) so that if the
4110 * view is later thawed the user will not lose their data.
4112 * @param freezesText Controls whether a frozen icicle should include the
4113 * entire text data: true to include it, false to not.
4115 * @attr ref android.R.styleable#TextView_freezesText
4117 @android.view.RemotableViewMethod
4118 public void setFreezesText(boolean freezesText) {
4119 mFreezesText = freezesText;
4123 * Return whether this text view is including its entire text contents
4124 * in frozen icicles.
4126 * @return Returns true if text is included, false if it isn't.
4128 * @see #setFreezesText
4130 public boolean getFreezesText() {
4131 return mFreezesText;
4134 ///////////////////////////////////////////////////////////////////////////
4137 * Sets the Factory used to create new Editables.
4139 public final void setEditableFactory(Editable.Factory factory) {
4140 mEditableFactory = factory;
4145 * Sets the Factory used to create new Spannables.
4147 public final void setSpannableFactory(Spannable.Factory factory) {
4148 mSpannableFactory = factory;
4153 * Sets the string value of the TextView. TextView <em>does not</em> accept
4154 * HTML-like formatting, which you can do with text strings in XML resource files.
4155 * To style your strings, attach android.text.style.* objects to a
4156 * {@link android.text.SpannableString SpannableString}, or see the
4157 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
4158 * Available Resource Types</a> documentation for an example of setting
4159 * formatted text in the XML resource file.
4161 * @attr ref android.R.styleable#TextView_text
4163 @android.view.RemotableViewMethod
4164 public final void setText(CharSequence text) {
4165 setText(text, mBufferType);
4169 * Like {@link #setText(CharSequence)},
4170 * except that the cursor position (if any) is retained in the new text.
4172 * @param text The new text to place in the text view.
4174 * @see #setText(CharSequence)
4176 @android.view.RemotableViewMethod
4177 public final void setTextKeepState(CharSequence text) {
4178 setTextKeepState(text, mBufferType);
4182 * Sets the text that this TextView is to display (see
4183 * {@link #setText(CharSequence)}) and also sets whether it is stored
4184 * in a styleable/spannable buffer and whether it is editable.
4186 * @attr ref android.R.styleable#TextView_text
4187 * @attr ref android.R.styleable#TextView_bufferType
4189 public void setText(CharSequence text, BufferType type) {
4190 setText(text, type, true, 0);
4192 if (mCharWrapper != null) {
4193 mCharWrapper.mChars = null;
4197 private void setText(CharSequence text, BufferType type,
4198 boolean notifyBefore, int oldlen) {
4203 // If suggestions are not enabled, remove the suggestion spans from the text
4204 if (!isSuggestionsEnabled()) {
4205 text = removeSuggestionSpans(text);
4208 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4210 if (text instanceof Spanned &&
4211 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
4212 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4213 setHorizontalFadingEdgeEnabled(true);
4214 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4216 setHorizontalFadingEdgeEnabled(false);
4217 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4219 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4222 int n = mFilters.length;
4223 for (int i = 0; i < n; i++) {
4224 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
4231 if (mText != null) {
4232 oldlen = mText.length();
4233 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4235 sendBeforeTextChanged("", 0, 0, text.length());
4239 boolean needEditableForNotification = false;
4241 if (mListeners != null && mListeners.size() != 0) {
4242 needEditableForNotification = true;
4245 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4246 needEditableForNotification) {
4247 createEditorIfNeeded();
4248 mEditor.forgetUndoRedo();
4249 Editable t = mEditableFactory.newEditable(text);
4251 setFilters(t, mFilters);
4252 InputMethodManager imm = InputMethodManager.peekInstance();
4253 if (imm != null) imm.restartInput(this);
4254 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4255 text = mSpannableFactory.newSpannable(text);
4256 } else if (!(text instanceof CharWrapper)) {
4257 text = TextUtils.stringOrSpannedString(text);
4260 if (mAutoLinkMask != 0) {
4263 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4264 s2 = (Spannable) text;
4266 s2 = mSpannableFactory.newSpannable(text);
4269 if (Linkify.addLinks(s2, mAutoLinkMask)) {
4271 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4274 * We must go ahead and set the text before changing the
4275 * movement method, because setMovementMethod() may call
4276 * setText() again to try to upgrade the buffer type.
4280 // Do not change the movement method for text that support text selection as it
4281 // would prevent an arbitrary cursor displacement.
4282 if (mLinksClickable && !textCanBeSelected()) {
4283 setMovementMethod(LinkMovementMethod.getInstance());
4291 if (mTransformation == null) {
4292 mTransformed = text;
4294 mTransformed = mTransformation.getTransformation(text, this);
4297 final int textLength = text.length();
4299 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
4300 Spannable sp = (Spannable) text;
4302 // Remove any ChangeWatchers that might have come from other TextViews.
4303 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4304 final int count = watchers.length;
4305 for (int i = 0; i < count; i++) {
4306 sp.removeSpan(watchers[i]);
4309 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
4311 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
4312 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
4314 if (mEditor != null) mEditor.addSpanWatchers(sp);
4316 if (mTransformation != null) {
4317 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4320 if (mMovement != null) {
4321 mMovement.initialize(this, (Spannable) text);
4324 * Initializing the movement method will have set the
4325 * selection, so reset mSelectionMoved to keep that from
4326 * interfering with the normal on-focus selection-setting.
4328 if (mEditor != null) mEditor.mSelectionMoved = false;
4332 if (mLayout != null) {
4336 sendOnTextChanged(text, 0, oldlen, textLength);
4337 onTextChanged(text, 0, oldlen, textLength);
4339 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4341 if (needEditableForNotification) {
4342 sendAfterTextChanged((Editable) text);
4345 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
4346 if (mEditor != null) mEditor.prepareCursorControllers();
4350 * Sets the TextView to display the specified slice of the specified
4351 * char array. You must promise that you will not change the contents
4352 * of the array except for right before another call to setText(),
4353 * since the TextView has no way to know that the text
4354 * has changed and that it needs to invalidate and re-layout.
4356 public final void setText(char[] text, int start, int len) {
4359 if (start < 0 || len < 0 || start + len > text.length) {
4360 throw new IndexOutOfBoundsException(start + ", " + len);
4364 * We must do the before-notification here ourselves because if
4365 * the old text is a CharWrapper we destroy it before calling
4366 * into the normal path.
4368 if (mText != null) {
4369 oldlen = mText.length();
4370 sendBeforeTextChanged(mText, 0, oldlen, len);
4372 sendBeforeTextChanged("", 0, 0, len);
4375 if (mCharWrapper == null) {
4376 mCharWrapper = new CharWrapper(text, start, len);
4378 mCharWrapper.set(text, start, len);
4381 setText(mCharWrapper, mBufferType, false, oldlen);
4385 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4386 * except that the cursor position (if any) is retained in the new text.
4388 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4390 public final void setTextKeepState(CharSequence text, BufferType type) {
4391 int start = getSelectionStart();
4392 int end = getSelectionEnd();
4393 int len = text.length();
4395 setText(text, type);
4397 if (start >= 0 || end >= 0) {
4398 if (mText instanceof Spannable) {
4399 Selection.setSelection((Spannable) mText,
4400 Math.max(0, Math.min(start, len)),
4401 Math.max(0, Math.min(end, len)));
4406 @android.view.RemotableViewMethod
4407 public final void setText(@StringRes int resid) {
4408 setText(getContext().getResources().getText(resid));
4411 public final void setText(@StringRes int resid, BufferType type) {
4412 setText(getContext().getResources().getText(resid), type);
4416 * Sets the text to be displayed when the text of the TextView is empty.
4417 * Null means to use the normal empty text. The hint does not currently
4418 * participate in determining the size of the view.
4420 * @attr ref android.R.styleable#TextView_hint
4422 @android.view.RemotableViewMethod
4423 public final void setHint(CharSequence hint) {
4424 mHint = TextUtils.stringOrSpannedString(hint);
4426 if (mLayout != null) {
4430 if (mText.length() == 0) {
4434 // Invalidate display list if hint is currently used
4435 if (mEditor != null && mText.length() == 0 && mHint != null) {
4436 mEditor.invalidateTextDisplayList();
4441 * Sets the text to be displayed when the text of the TextView is empty,
4444 * @attr ref android.R.styleable#TextView_hint
4446 @android.view.RemotableViewMethod
4447 public final void setHint(@StringRes int resid) {
4448 setHint(getContext().getResources().getText(resid));
4452 * Returns the hint that is displayed when the text of the TextView
4455 * @attr ref android.R.styleable#TextView_hint
4457 @ViewDebug.CapturedViewProperty
4458 public CharSequence getHint() {
4462 boolean isSingleLine() {
4466 private static boolean isMultilineInputType(int type) {
4467 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4468 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4472 * Removes the suggestion spans.
4474 CharSequence removeSuggestionSpans(CharSequence text) {
4475 if (text instanceof Spanned) {
4476 Spannable spannable;
4477 if (text instanceof Spannable) {
4478 spannable = (Spannable) text;
4480 spannable = new SpannableString(text);
4484 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4485 for (int i = 0; i < spans.length; i++) {
4486 spannable.removeSpan(spans[i]);
4493 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4494 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4495 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4496 * then a soft keyboard will not be displayed for this text view.
4498 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4499 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4502 * @see #getInputType()
4503 * @see #setRawInputType(int)
4504 * @see android.text.InputType
4505 * @attr ref android.R.styleable#TextView_inputType
4507 public void setInputType(int type) {
4508 final boolean wasPassword = isPasswordInputType(getInputType());
4509 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
4510 setInputType(type, false);
4511 final boolean isPassword = isPasswordInputType(type);
4512 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
4513 boolean forceUpdate = false;
4515 setTransformationMethod(PasswordTransformationMethod.getInstance());
4516 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4517 } else if (isVisiblePassword) {
4518 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4521 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4522 } else if (wasPassword || wasVisiblePassword) {
4523 // not in password mode, clean up typeface and transformation
4524 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
4525 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4530 boolean singleLine = !isMultilineInputType(type);
4532 // We need to update the single line mode if it has changed or we
4533 // were previously in password mode.
4534 if (mSingleLine != singleLine || forceUpdate) {
4535 // Change single line mode, but only change the transformation if
4536 // we are not in password mode.
4537 applySingleLine(singleLine, !isPassword, true);
4540 if (!isSuggestionsEnabled()) {
4541 mText = removeSuggestionSpans(mText);
4544 InputMethodManager imm = InputMethodManager.peekInstance();
4545 if (imm != null) imm.restartInput(this);
4549 * It would be better to rely on the input type for everything. A password inputType should have
4550 * a password transformation. We should hence use isPasswordInputType instead of this method.
4553 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4554 * would install the correct transformation).
4555 * - Refuse the installation of a non-password transformation in setTransformation if the input
4558 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4559 * is useful since it matches what the user can see (obfuscated text or not).
4561 * @return true if the current transformation method is of the password type.
4563 boolean hasPasswordTransformationMethod() {
4564 return mTransformation instanceof PasswordTransformationMethod;
4567 private static boolean isPasswordInputType(int inputType) {
4568 final int variation =
4569 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4571 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4573 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4575 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
4578 private static boolean isVisiblePasswordInputType(int inputType) {
4579 final int variation =
4580 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4582 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
4586 * Directly change the content type integer of the text view, without
4587 * modifying any other state.
4588 * @see #setInputType(int)
4589 * @see android.text.InputType
4590 * @attr ref android.R.styleable#TextView_inputType
4592 public void setRawInputType(int type) {
4593 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
4594 createEditorIfNeeded();
4595 mEditor.mInputType = type;
4598 private void setInputType(int type, boolean direct) {
4599 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4601 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
4602 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
4603 TextKeyListener.Capitalize cap;
4604 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4605 cap = TextKeyListener.Capitalize.CHARACTERS;
4606 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4607 cap = TextKeyListener.Capitalize.WORDS;
4608 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4609 cap = TextKeyListener.Capitalize.SENTENCES;
4611 cap = TextKeyListener.Capitalize.NONE;
4613 input = TextKeyListener.getInstance(autotext, cap);
4614 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4615 input = DigitsKeyListener.getInstance(
4616 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4617 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4618 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4619 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4620 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4621 input = DateKeyListener.getInstance();
4623 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4624 input = TimeKeyListener.getInstance();
4627 input = DateTimeKeyListener.getInstance();
4630 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4631 input = DialerKeyListener.getInstance();
4633 input = TextKeyListener.getInstance();
4635 setRawInputType(type);
4637 createEditorIfNeeded();
4638 mEditor.mKeyListener = input;
4640 setKeyListenerOnly(input);
4645 * Get the type of the editable content.
4647 * @see #setInputType(int)
4648 * @see android.text.InputType
4650 public int getInputType() {
4651 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
4655 * Change the editor type integer associated with the text view, which
4656 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4658 * @see #getImeOptions
4659 * @see android.view.inputmethod.EditorInfo
4660 * @attr ref android.R.styleable#TextView_imeOptions
4662 public void setImeOptions(int imeOptions) {
4663 createEditorIfNeeded();
4664 mEditor.createInputContentTypeIfNeeded();
4665 mEditor.mInputContentType.imeOptions = imeOptions;
4669 * Get the type of the IME editor.
4671 * @see #setImeOptions(int)
4672 * @see android.view.inputmethod.EditorInfo
4674 public int getImeOptions() {
4675 return mEditor != null && mEditor.mInputContentType != null
4676 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
4680 * Change the custom IME action associated with the text view, which
4681 * will be reported to an IME with {@link EditorInfo#actionLabel}
4682 * and {@link EditorInfo#actionId} when it has focus.
4683 * @see #getImeActionLabel
4684 * @see #getImeActionId
4685 * @see android.view.inputmethod.EditorInfo
4686 * @attr ref android.R.styleable#TextView_imeActionLabel
4687 * @attr ref android.R.styleable#TextView_imeActionId
4689 public void setImeActionLabel(CharSequence label, int actionId) {
4690 createEditorIfNeeded();
4691 mEditor.createInputContentTypeIfNeeded();
4692 mEditor.mInputContentType.imeActionLabel = label;
4693 mEditor.mInputContentType.imeActionId = actionId;
4697 * Get the IME action label previous set with {@link #setImeActionLabel}.
4699 * @see #setImeActionLabel
4700 * @see android.view.inputmethod.EditorInfo
4702 public CharSequence getImeActionLabel() {
4703 return mEditor != null && mEditor.mInputContentType != null
4704 ? mEditor.mInputContentType.imeActionLabel : null;
4708 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4710 * @see #setImeActionLabel
4711 * @see android.view.inputmethod.EditorInfo
4713 public int getImeActionId() {
4714 return mEditor != null && mEditor.mInputContentType != null
4715 ? mEditor.mInputContentType.imeActionId : 0;
4719 * Set a special listener to be called when an action is performed
4720 * on the text view. This will be called when the enter key is pressed,
4721 * or when an action supplied to the IME is selected by the user. Setting
4722 * this means that the normal hard key event will not insert a newline
4723 * into the text view, even if it is multi-line; holding down the ALT
4724 * modifier will, however, allow the user to insert a newline character.
4726 public void setOnEditorActionListener(OnEditorActionListener l) {
4727 createEditorIfNeeded();
4728 mEditor.createInputContentTypeIfNeeded();
4729 mEditor.mInputContentType.onEditorActionListener = l;
4733 * Called when an attached input method calls
4734 * {@link InputConnection#performEditorAction(int)
4735 * InputConnection.performEditorAction()}
4736 * for this text view. The default implementation will call your action
4737 * listener supplied to {@link #setOnEditorActionListener}, or perform
4738 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
4739 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4740 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
4741 * EditorInfo.IME_ACTION_DONE}.
4743 * <p>For backwards compatibility, if no IME options have been set and the
4744 * text view would not normally advance focus on enter, then
4745 * the NEXT and DONE actions received here will be turned into an enter
4746 * key down/up pair to go through the normal key handling.
4748 * @param actionCode The code of the action being performed.
4750 * @see #setOnEditorActionListener
4752 public void onEditorAction(int actionCode) {
4753 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
4755 if (ict.onEditorActionListener != null) {
4756 if (ict.onEditorActionListener.onEditorAction(this,
4757 actionCode, null)) {
4762 // This is the handling for some default action.
4763 // Note that for backwards compatibility we don't do this
4764 // default handling if explicit ime options have not been given,
4765 // instead turning this into the normal enter key codes that an
4766 // app may be expecting.
4767 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
4768 View v = focusSearch(FOCUS_FORWARD);
4770 if (!v.requestFocus(FOCUS_FORWARD)) {
4771 throw new IllegalStateException("focus search returned a view " +
4772 "that wasn't able to take focus!");
4777 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
4778 View v = focusSearch(FOCUS_BACKWARD);
4780 if (!v.requestFocus(FOCUS_BACKWARD)) {
4781 throw new IllegalStateException("focus search returned a view " +
4782 "that wasn't able to take focus!");
4787 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4788 InputMethodManager imm = InputMethodManager.peekInstance();
4789 if (imm != null && imm.isActive(this)) {
4790 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4796 ViewRootImpl viewRootImpl = getViewRootImpl();
4797 if (viewRootImpl != null) {
4798 long eventTime = SystemClock.uptimeMillis();
4799 viewRootImpl.dispatchKeyFromIme(
4800 new KeyEvent(eventTime, eventTime,
4801 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4802 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4803 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4804 | KeyEvent.FLAG_EDITOR_ACTION));
4805 viewRootImpl.dispatchKeyFromIme(
4806 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
4807 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4808 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4809 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4810 | KeyEvent.FLAG_EDITOR_ACTION));
4815 * Set the private content type of the text, which is the
4816 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4817 * field that will be filled in when creating an input connection.
4819 * @see #getPrivateImeOptions()
4820 * @see EditorInfo#privateImeOptions
4821 * @attr ref android.R.styleable#TextView_privateImeOptions
4823 public void setPrivateImeOptions(String type) {
4824 createEditorIfNeeded();
4825 mEditor.createInputContentTypeIfNeeded();
4826 mEditor.mInputContentType.privateImeOptions = type;
4830 * Get the private type of the content.
4832 * @see #setPrivateImeOptions(String)
4833 * @see EditorInfo#privateImeOptions
4835 public String getPrivateImeOptions() {
4836 return mEditor != null && mEditor.mInputContentType != null
4837 ? mEditor.mInputContentType.privateImeOptions : null;
4841 * Set the extra input data of the text, which is the
4842 * {@link EditorInfo#extras TextBoxAttribute.extras}
4843 * Bundle that will be filled in when creating an input connection. The
4844 * given integer is the resource ID of an XML resource holding an
4845 * {@link android.R.styleable#InputExtras <input-extras>} XML tree.
4847 * @see #getInputExtras(boolean)
4848 * @see EditorInfo#extras
4849 * @attr ref android.R.styleable#TextView_editorExtras
4851 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
4852 createEditorIfNeeded();
4853 XmlResourceParser parser = getResources().getXml(xmlResId);
4854 mEditor.createInputContentTypeIfNeeded();
4855 mEditor.mInputContentType.extras = new Bundle();
4856 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
4860 * Retrieve the input extras currently associated with the text view, which
4861 * can be viewed as well as modified.
4863 * @param create If true, the extras will be created if they don't already
4864 * exist. Otherwise, null will be returned if none have been created.
4865 * @see #setInputExtras(int)
4866 * @see EditorInfo#extras
4867 * @attr ref android.R.styleable#TextView_editorExtras
4869 public Bundle getInputExtras(boolean create) {
4870 if (mEditor == null && !create) return null;
4871 createEditorIfNeeded();
4872 if (mEditor.mInputContentType == null) {
4873 if (!create) return null;
4874 mEditor.createInputContentTypeIfNeeded();
4876 if (mEditor.mInputContentType.extras == null) {
4877 if (!create) return null;
4878 mEditor.mInputContentType.extras = new Bundle();
4880 return mEditor.mInputContentType.extras;
4884 * Returns the error message that was set to be displayed with
4885 * {@link #setError}, or <code>null</code> if no error was set
4886 * or if it the error was cleared by the widget after user input.
4888 public CharSequence getError() {
4889 return mEditor == null ? null : mEditor.mError;
4893 * Sets the right-hand compound drawable of the TextView to the "error"
4894 * icon and sets an error message that will be displayed in a popup when
4895 * the TextView has focus. The icon and error message will be reset to
4896 * null when any key events cause changes to the TextView's text. If the
4897 * <code>error</code> is <code>null</code>, the error message and icon
4900 @android.view.RemotableViewMethod
4901 public void setError(CharSequence error) {
4902 if (error == null) {
4903 setError(null, null);
4905 Drawable dr = getContext().getDrawable(
4906 com.android.internal.R.drawable.indicator_input_error);
4908 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4909 setError(error, dr);
4914 * Sets the right-hand compound drawable of the TextView to the specified
4915 * icon and sets an error message that will be displayed in a popup when
4916 * the TextView has focus. The icon and error message will be reset to
4917 * null when any key events cause changes to the TextView's text. The
4918 * drawable must already have had {@link Drawable#setBounds} set on it.
4919 * If the <code>error</code> is <code>null</code>, the error message will
4920 * be cleared (and you should provide a <code>null</code> icon as well).
4922 public void setError(CharSequence error, Drawable icon) {
4923 createEditorIfNeeded();
4924 mEditor.setError(error, icon);
4925 notifyViewAccessibilityStateChangedIfNeeded(
4926 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
4930 protected boolean setFrame(int l, int t, int r, int b) {
4931 boolean result = super.setFrame(l, t, r, b);
4933 if (mEditor != null) mEditor.setFrame();
4935 restartMarqueeIfNeeded();
4940 private void restartMarqueeIfNeeded() {
4941 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4942 mRestartMarquee = false;
4948 * Sets the list of input filters that will be used if the buffer is
4949 * Editable. Has no effect otherwise.
4951 * @attr ref android.R.styleable#TextView_maxLength
4953 public void setFilters(InputFilter[] filters) {
4954 if (filters == null) {
4955 throw new IllegalArgumentException();
4960 if (mText instanceof Editable) {
4961 setFilters((Editable) mText, filters);
4966 * Sets the list of input filters on the specified Editable,
4967 * and includes mInput in the list if it is an InputFilter.
4969 private void setFilters(Editable e, InputFilter[] filters) {
4970 if (mEditor != null) {
4971 final boolean undoFilter = mEditor.mUndoInputFilter != null;
4972 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4974 if (undoFilter) num++;
4975 if (keyFilter) num++;
4977 InputFilter[] nf = new InputFilter[filters.length + num];
4979 System.arraycopy(filters, 0, nf, 0, filters.length);
4982 nf[filters.length] = mEditor.mUndoInputFilter;
4986 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
4993 e.setFilters(filters);
4997 * Returns the current list of input filters.
4999 * @attr ref android.R.styleable#TextView_maxLength
5001 public InputFilter[] getFilters() {
5005 /////////////////////////////////////////////////////////////////////////
5007 private int getBoxHeight(Layout l) {
5008 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5009 int padding = (l == mHintLayout) ?
5010 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5011 getExtendedPaddingTop() + getExtendedPaddingBottom();
5012 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5015 int getVerticalOffset(boolean forceNormal) {
5017 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5020 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5024 if (gravity != Gravity.TOP) {
5025 int boxht = getBoxHeight(l);
5026 int textht = l.getHeight();
5028 if (textht < boxht) {
5029 if (gravity == Gravity.BOTTOM)
5030 voffset = boxht - textht;
5031 else // (gravity == Gravity.CENTER_VERTICAL)
5032 voffset = (boxht - textht) >> 1;
5038 private int getBottomVerticalOffset(boolean forceNormal) {
5040 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5043 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5047 if (gravity != Gravity.BOTTOM) {
5048 int boxht = getBoxHeight(l);
5049 int textht = l.getHeight();
5051 if (textht < boxht) {
5052 if (gravity == Gravity.TOP)
5053 voffset = boxht - textht;
5054 else // (gravity == Gravity.CENTER_VERTICAL)
5055 voffset = (boxht - textht) >> 1;
5061 void invalidateCursorPath() {
5062 if (mHighlightPathBogus) {
5065 final int horizontalPadding = getCompoundPaddingLeft();
5066 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5068 if (mEditor.mCursorCount == 0) {
5069 synchronized (TEMP_RECTF) {
5071 * The reason for this concern about the thickness of the
5072 * cursor and doing the floor/ceil on the coordinates is that
5073 * some EditTexts (notably textfields in the Browser) have
5074 * anti-aliased text where not all the characters are
5075 * necessarily at integer-multiple locations. This should
5076 * make sure the entire cursor gets invalidated instead of
5077 * sometimes missing half a pixel.
5079 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
5086 // mHighlightPath is guaranteed to be non null at that point.
5087 mHighlightPath.computeBounds(TEMP_RECTF, false);
5089 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5090 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5091 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5092 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
5095 for (int i = 0; i < mEditor.mCursorCount; i++) {
5096 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5097 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5098 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5104 void invalidateCursor() {
5105 int where = getSelectionEnd();
5107 invalidateCursor(where, where, where);
5110 private void invalidateCursor(int a, int b, int c) {
5111 if (a >= 0 || b >= 0 || c >= 0) {
5112 int start = Math.min(Math.min(a, b), c);
5113 int end = Math.max(Math.max(a, b), c);
5114 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
5119 * Invalidates the region of text enclosed between the start and end text offsets.
5121 void invalidateRegion(int start, int end, boolean invalidateCursor) {
5122 if (mLayout == null) {
5125 int lineStart = mLayout.getLineForOffset(start);
5126 int top = mLayout.getLineTop(lineStart);
5128 // This is ridiculous, but the descent from the line above
5129 // can hang down into the line we really want to redraw,
5130 // so we have to invalidate part of the line above to make
5131 // sure everything that needs to be redrawn really is.
5132 // (But not the whole line above, because that would cause
5133 // the same problem with the descenders on the line above it!)
5134 if (lineStart > 0) {
5135 top -= mLayout.getLineDescent(lineStart - 1);
5141 lineEnd = lineStart;
5143 lineEnd = mLayout.getLineForOffset(end);
5145 int bottom = mLayout.getLineBottom(lineEnd);
5147 // mEditor can be null in case selection is set programmatically.
5148 if (invalidateCursor && mEditor != null) {
5149 for (int i = 0; i < mEditor.mCursorCount; i++) {
5150 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5151 top = Math.min(top, bounds.top);
5152 bottom = Math.max(bottom, bounds.bottom);
5156 final int compoundPaddingLeft = getCompoundPaddingLeft();
5157 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5160 if (lineStart == lineEnd && !invalidateCursor) {
5161 left = (int) mLayout.getPrimaryHorizontal(start);
5162 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5163 left += compoundPaddingLeft;
5164 right += compoundPaddingLeft;
5166 // Rectangle bounding box when the region spans several lines
5167 left = compoundPaddingLeft;
5168 right = getWidth() - getCompoundPaddingRight();
5171 invalidate(mScrollX + left, verticalPadding + top,
5172 mScrollX + right, verticalPadding + bottom);
5176 private void registerForPreDraw() {
5177 if (!mPreDrawRegistered) {
5178 getViewTreeObserver().addOnPreDrawListener(this);
5179 mPreDrawRegistered = true;
5183 private void unregisterForPreDraw() {
5184 getViewTreeObserver().removeOnPreDrawListener(this);
5185 mPreDrawRegistered = false;
5186 mPreDrawListenerDetached = false;
5192 public boolean onPreDraw() {
5193 if (mLayout == null) {
5197 if (mMovement != null) {
5198 /* This code also provides auto-scrolling when a cursor is moved using a
5199 * CursorController (insertion point or selection limits).
5200 * For selection, ensure start or end is visible depending on controller's state.
5202 int curs = getSelectionEnd();
5203 // Do not create the controller if it is not already created.
5204 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5205 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
5206 curs = getSelectionStart();
5210 * TODO: This should really only keep the end in view if
5211 * it already was before the text changed. I'm not sure
5212 * of a good way to tell from here if it was.
5214 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5215 curs = mText.length();
5219 bringPointIntoView(curs);
5222 bringTextIntoView();
5225 // This has to be checked here since:
5226 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5227 // a screen rotation) since layout is not yet initialized at that point.
5228 if (mEditor != null && mEditor.mCreatedWithASelection) {
5229 mEditor.startSelectionActionMode();
5230 mEditor.mCreatedWithASelection = false;
5233 // Phone specific code (there is no ExtractEditText on tablets).
5234 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
5235 // not be set. Do the test here instead.
5236 if (isInExtractedMode() && hasSelection() && mEditor != null
5237 && mEditor.mTextActionMode == null && isShown() && hasWindowFocus()) {
5238 mEditor.startSelectionActionMode();
5241 unregisterForPreDraw();
5247 protected void onAttachedToWindow() {
5248 super.onAttachedToWindow();
5250 mTemporaryDetach = false;
5252 if (mEditor != null) mEditor.onAttachedToWindow();
5254 if (mPreDrawListenerDetached) {
5255 getViewTreeObserver().addOnPreDrawListener(this);
5256 mPreDrawListenerDetached = false;
5262 protected void onDetachedFromWindowInternal() {
5263 if (mPreDrawRegistered) {
5264 getViewTreeObserver().removeOnPreDrawListener(this);
5265 mPreDrawListenerDetached = true;
5268 resetResolvedDrawables();
5270 if (mEditor != null) mEditor.onDetachedFromWindow();
5272 super.onDetachedFromWindowInternal();
5276 public void onScreenStateChanged(int screenState) {
5277 super.onScreenStateChanged(screenState);
5278 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5282 protected boolean isPaddingOffsetRequired() {
5283 return mShadowRadius != 0 || mDrawables != null;
5287 protected int getLeftPaddingOffset() {
5288 return getCompoundPaddingLeft() - mPaddingLeft +
5289 (int) Math.min(0, mShadowDx - mShadowRadius);
5293 protected int getTopPaddingOffset() {
5294 return (int) Math.min(0, mShadowDy - mShadowRadius);
5298 protected int getBottomPaddingOffset() {
5299 return (int) Math.max(0, mShadowDy + mShadowRadius);
5302 private int getFudgedPaddingRight() {
5303 // Add sufficient space for cursor and tone marks
5304 int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
5305 return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
5309 protected int getRightPaddingOffset() {
5310 return -(getFudgedPaddingRight() - mPaddingRight) +
5311 (int) Math.max(0, mShadowDx + mShadowRadius);
5315 protected boolean verifyDrawable(Drawable who) {
5316 final boolean verified = super.verifyDrawable(who);
5317 if (!verified && mDrawables != null) {
5318 for (Drawable dr : mDrawables.mShowing) {
5328 public void jumpDrawablesToCurrentState() {
5329 super.jumpDrawablesToCurrentState();
5330 if (mDrawables != null) {
5331 for (Drawable dr : mDrawables.mShowing) {
5333 dr.jumpToCurrentState();
5340 public void invalidateDrawable(Drawable drawable) {
5341 boolean handled = false;
5343 if (verifyDrawable(drawable)) {
5344 final Rect dirty = drawable.getBounds();
5345 int scrollX = mScrollX;
5346 int scrollY = mScrollY;
5348 // IMPORTANT: The coordinates below are based on the coordinates computed
5349 // for each compound drawable in onDraw(). Make sure to update each section
5351 final TextView.Drawables drawables = mDrawables;
5352 if (drawables != null) {
5353 if (drawable == drawables.mShowing[Drawables.LEFT]) {
5354 final int compoundPaddingTop = getCompoundPaddingTop();
5355 final int compoundPaddingBottom = getCompoundPaddingBottom();
5356 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5358 scrollX += mPaddingLeft;
5359 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5361 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
5362 final int compoundPaddingTop = getCompoundPaddingTop();
5363 final int compoundPaddingBottom = getCompoundPaddingBottom();
5364 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5366 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5367 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5369 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
5370 final int compoundPaddingLeft = getCompoundPaddingLeft();
5371 final int compoundPaddingRight = getCompoundPaddingRight();
5372 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5374 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5375 scrollY += mPaddingTop;
5377 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
5378 final int compoundPaddingLeft = getCompoundPaddingLeft();
5379 final int compoundPaddingRight = getCompoundPaddingRight();
5380 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5382 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5383 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5389 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5390 dirty.right + scrollX, dirty.bottom + scrollY);
5395 super.invalidateDrawable(drawable);
5400 public boolean hasOverlappingRendering() {
5401 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5402 return ((getBackground() != null && getBackground().getCurrent() != null)
5403 || mText instanceof Spannable || hasSelection()
5404 || isHorizontalFadingEdgeEnabled());
5409 * Returns the state of the {@code textIsSelectable} flag (See
5410 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5411 * to allow users to select and copy text in a non-editable TextView, the content of an
5412 * {@link EditText} can always be selected, independently of the value of this flag.
5415 * @return True if the text displayed in this TextView can be selected by the user.
5417 * @attr ref android.R.styleable#TextView_textIsSelectable
5419 public boolean isTextSelectable() {
5420 return mEditor == null ? false : mEditor.mTextIsSelectable;
5424 * Sets whether the content of this view is selectable by the user. The default is
5425 * {@code false}, meaning that the content is not selectable.
5427 * When you use a TextView to display a useful piece of information to the user (such as a
5428 * contact's address), make it selectable, so that the user can select and copy its
5429 * content. You can also use set the XML attribute
5430 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5432 * When you call this method to set the value of {@code textIsSelectable}, it sets
5433 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5434 * and {@code longClickable} to the same value. These flags correspond to the attributes
5435 * {@link android.R.styleable#View_focusable android:focusable},
5436 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5437 * {@link android.R.styleable#View_clickable android:clickable}, and
5438 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5439 * flags to a state you had set previously, call one or more of the following methods:
5440 * {@link #setFocusable(boolean) setFocusable()},
5441 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5442 * {@link #setClickable(boolean) setClickable()} or
5443 * {@link #setLongClickable(boolean) setLongClickable()}.
5445 * @param selectable Whether the content of this TextView should be selectable.
5447 public void setTextIsSelectable(boolean selectable) {
5448 if (!selectable && mEditor == null) return; // false is default value with no edit data
5450 createEditorIfNeeded();
5451 if (mEditor.mTextIsSelectable == selectable) return;
5453 mEditor.mTextIsSelectable = selectable;
5454 setFocusableInTouchMode(selectable);
5455 setFocusable(selectable);
5456 setClickable(selectable);
5457 setLongClickable(selectable);
5459 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5461 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5462 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5464 // Called by setText above, but safer in case of future code changes
5465 mEditor.prepareCursorControllers();
5469 protected int[] onCreateDrawableState(int extraSpace) {
5470 final int[] drawableState;
5473 drawableState = super.onCreateDrawableState(extraSpace);
5475 drawableState = super.onCreateDrawableState(extraSpace + 1);
5476 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5479 if (isTextSelectable()) {
5480 // Disable pressed state, which was introduced when TextView was made clickable.
5481 // Prevents text color change.
5482 // setClickable(false) would have a similar effect, but it also disables focus changes
5483 // and long press actions, which are both needed by text selection.
5484 final int length = drawableState.length;
5485 for (int i = 0; i < length; i++) {
5486 if (drawableState[i] == R.attr.state_pressed) {
5487 final int[] nonPressedState = new int[length - 1];
5488 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5489 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5490 return nonPressedState;
5495 return drawableState;
5498 private Path getUpdatedHighlightPath() {
5499 Path highlight = null;
5500 Paint highlightPaint = mHighlightPaint;
5502 final int selStart = getSelectionStart();
5503 final int selEnd = getSelectionEnd();
5504 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5505 if (selStart == selEnd) {
5506 if (mEditor != null && mEditor.isCursorVisible() &&
5507 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5508 (2 * Editor.BLINK) < Editor.BLINK) {
5509 if (mHighlightPathBogus) {
5510 if (mHighlightPath == null) mHighlightPath = new Path();
5511 mHighlightPath.reset();
5512 mLayout.getCursorPath(selStart, mHighlightPath, mText);
5513 mEditor.updateCursorsPositions();
5514 mHighlightPathBogus = false;
5517 // XXX should pass to skin instead of drawing directly
5518 highlightPaint.setColor(mCurTextColor);
5519 highlightPaint.setStyle(Paint.Style.STROKE);
5520 highlight = mHighlightPath;
5523 if (mHighlightPathBogus) {
5524 if (mHighlightPath == null) mHighlightPath = new Path();
5525 mHighlightPath.reset();
5526 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5527 mHighlightPathBogus = false;
5530 // XXX should pass to skin instead of drawing directly
5531 highlightPaint.setColor(mHighlightColor);
5532 highlightPaint.setStyle(Paint.Style.FILL);
5534 highlight = mHighlightPath;
5543 public int getHorizontalOffsetForDrawables() {
5548 protected void onDraw(Canvas canvas) {
5549 restartMarqueeIfNeeded();
5551 // Draw the background for this view
5552 super.onDraw(canvas);
5554 final int compoundPaddingLeft = getCompoundPaddingLeft();
5555 final int compoundPaddingTop = getCompoundPaddingTop();
5556 final int compoundPaddingRight = getCompoundPaddingRight();
5557 final int compoundPaddingBottom = getCompoundPaddingBottom();
5558 final int scrollX = mScrollX;
5559 final int scrollY = mScrollY;
5560 final int right = mRight;
5561 final int left = mLeft;
5562 final int bottom = mBottom;
5563 final int top = mTop;
5564 final boolean isLayoutRtl = isLayoutRtl();
5565 final int offset = getHorizontalOffsetForDrawables();
5566 final int leftOffset = isLayoutRtl ? 0 : offset;
5567 final int rightOffset = isLayoutRtl ? offset : 0 ;
5569 final Drawables dr = mDrawables;
5572 * Compound, not extended, because the icon is not clipped
5573 * if the text height is smaller.
5576 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5577 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5579 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5580 // Make sure to update invalidateDrawable() when changing this code.
5581 if (dr.mShowing[Drawables.LEFT] != null) {
5583 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5584 scrollY + compoundPaddingTop +
5585 (vspace - dr.mDrawableHeightLeft) / 2);
5586 dr.mShowing[Drawables.LEFT].draw(canvas);
5590 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5591 // Make sure to update invalidateDrawable() when changing this code.
5592 if (dr.mShowing[Drawables.RIGHT] != null) {
5594 canvas.translate(scrollX + right - left - mPaddingRight
5595 - dr.mDrawableSizeRight - rightOffset,
5596 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5597 dr.mShowing[Drawables.RIGHT].draw(canvas);
5601 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5602 // Make sure to update invalidateDrawable() when changing this code.
5603 if (dr.mShowing[Drawables.TOP] != null) {
5605 canvas.translate(scrollX + compoundPaddingLeft +
5606 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5607 dr.mShowing[Drawables.TOP].draw(canvas);
5611 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5612 // Make sure to update invalidateDrawable() when changing this code.
5613 if (dr.mShowing[Drawables.BOTTOM] != null) {
5615 canvas.translate(scrollX + compoundPaddingLeft +
5616 (hspace - dr.mDrawableWidthBottom) / 2,
5617 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5618 dr.mShowing[Drawables.BOTTOM].draw(canvas);
5623 int color = mCurTextColor;
5625 if (mLayout == null) {
5629 Layout layout = mLayout;
5631 if (mHint != null && mText.length() == 0) {
5632 if (mHintTextColor != null) {
5633 color = mCurHintTextColor;
5636 layout = mHintLayout;
5639 mTextPaint.setColor(color);
5640 mTextPaint.drawableState = getDrawableState();
5643 /* Would be faster if we didn't have to do this. Can we chop the
5644 (displayable) text so that we don't need to do this ever?
5647 int extendedPaddingTop = getExtendedPaddingTop();
5648 int extendedPaddingBottom = getExtendedPaddingBottom();
5650 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5651 final int maxScrollY = mLayout.getHeight() - vspace;
5653 float clipLeft = compoundPaddingLeft + scrollX;
5654 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5655 float clipRight = right - left - getFudgedPaddingRight() + scrollX;
5656 float clipBottom = bottom - top + scrollY -
5657 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5659 if (mShadowRadius != 0) {
5660 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5661 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5663 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5664 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5667 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5669 int voffsetText = 0;
5670 int voffsetCursor = 0;
5672 // translate in by our padding
5673 /* shortcircuit calling getVerticaOffset() */
5674 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5675 voffsetText = getVerticalOffset(false);
5676 voffsetCursor = getVerticalOffset(true);
5678 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5680 final int layoutDirection = getLayoutDirection();
5681 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5682 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5683 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5684 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5685 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5686 final int width = mRight - mLeft;
5687 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5688 final float dx = mLayout.getLineRight(0) - (width - padding);
5689 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5692 if (mMarquee != null && mMarquee.isRunning()) {
5693 final float dx = -mMarquee.getScroll();
5694 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5698 final int cursorOffsetVertical = voffsetCursor - voffsetText;
5700 Path highlight = getUpdatedHighlightPath();
5701 if (mEditor != null) {
5702 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5704 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5707 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5708 final float dx = mMarquee.getGhostOffset();
5709 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5710 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5717 public void getFocusedRect(Rect r) {
5718 if (mLayout == null) {
5719 super.getFocusedRect(r);
5723 int selEnd = getSelectionEnd();
5725 super.getFocusedRect(r);
5729 int selStart = getSelectionStart();
5730 if (selStart < 0 || selStart >= selEnd) {
5731 int line = mLayout.getLineForOffset(selEnd);
5732 r.top = mLayout.getLineTop(line);
5733 r.bottom = mLayout.getLineBottom(line);
5734 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5735 r.right = r.left + 4;
5737 int lineStart = mLayout.getLineForOffset(selStart);
5738 int lineEnd = mLayout.getLineForOffset(selEnd);
5739 r.top = mLayout.getLineTop(lineStart);
5740 r.bottom = mLayout.getLineBottom(lineEnd);
5741 if (lineStart == lineEnd) {
5742 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5743 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5745 // Selection extends across multiple lines -- make the focused
5746 // rect cover the entire width.
5747 if (mHighlightPathBogus) {
5748 if (mHighlightPath == null) mHighlightPath = new Path();
5749 mHighlightPath.reset();
5750 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5751 mHighlightPathBogus = false;
5753 synchronized (TEMP_RECTF) {
5754 mHighlightPath.computeBounds(TEMP_RECTF, true);
5755 r.left = (int)TEMP_RECTF.left-1;
5756 r.right = (int)TEMP_RECTF.right+1;
5761 // Adjust for padding and gravity.
5762 int paddingLeft = getCompoundPaddingLeft();
5763 int paddingTop = getExtendedPaddingTop();
5764 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5765 paddingTop += getVerticalOffset(false);
5767 r.offset(paddingLeft, paddingTop);
5768 int paddingBottom = getExtendedPaddingBottom();
5769 r.bottom += paddingBottom;
5773 * Return the number of lines of text, or 0 if the internal Layout has not
5776 public int getLineCount() {
5777 return mLayout != null ? mLayout.getLineCount() : 0;
5781 * Return the baseline for the specified line (0...getLineCount() - 1)
5782 * If bounds is not null, return the top, left, right, bottom extents
5783 * of the specified line in it. If the internal Layout has not been built,
5784 * return 0 and set bounds to (0, 0, 0, 0)
5785 * @param line which line to examine (0..getLineCount() - 1)
5786 * @param bounds Optional. If not null, it returns the extent of the line
5787 * @return the Y-coordinate of the baseline
5789 public int getLineBounds(int line, Rect bounds) {
5790 if (mLayout == null) {
5791 if (bounds != null) {
5792 bounds.set(0, 0, 0, 0);
5797 int baseline = mLayout.getLineBounds(line, bounds);
5799 int voffset = getExtendedPaddingTop();
5800 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5801 voffset += getVerticalOffset(true);
5803 if (bounds != null) {
5804 bounds.offset(getCompoundPaddingLeft(), voffset);
5806 return baseline + voffset;
5811 public int getBaseline() {
5812 if (mLayout == null) {
5813 return super.getBaseline();
5817 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5818 voffset = getVerticalOffset(true);
5821 if (isLayoutModeOptical(mParent)) {
5822 voffset -= getOpticalInsets().top;
5825 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0);
5832 protected int getFadeTop(boolean offsetRequired) {
5833 if (mLayout == null) return 0;
5836 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5837 voffset = getVerticalOffset(true);
5840 if (offsetRequired) voffset += getTopPaddingOffset();
5842 return getExtendedPaddingTop() + voffset;
5849 protected int getFadeHeight(boolean offsetRequired) {
5850 return mLayout != null ? mLayout.getHeight() : 0;
5854 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5855 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
5856 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
5857 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
5858 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
5861 return super.onKeyPreIme(keyCode, event);
5867 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
5868 // Do nothing unless mEditor is in text action mode.
5869 if (mEditor == null || mEditor.mTextActionMode == null) {
5873 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5874 KeyEvent.DispatcherState state = getKeyDispatcherState();
5875 if (state != null) {
5876 state.startTracking(event, this);
5879 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5880 KeyEvent.DispatcherState state = getKeyDispatcherState();
5881 if (state != null) {
5882 state.handleUpEvent(event);
5884 if (event.isTracking() && !event.isCanceled()) {
5885 stopTextActionMode();
5893 public boolean onKeyDown(int keyCode, KeyEvent event) {
5894 int which = doKeyDown(keyCode, event, null);
5896 return super.onKeyDown(keyCode, event);
5903 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5904 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5906 int which = doKeyDown(keyCode, down, event);
5908 // Go through default dispatching.
5909 return super.onKeyMultiple(keyCode, repeatCount, event);
5912 // Consumed the whole thing.
5918 // We are going to dispatch the remaining events to either the input
5919 // or movement method. To do this, we will just send a repeated stream
5920 // of down and up events until we have done the complete repeatCount.
5921 // It would be nice if those interfaces had an onKeyMultiple() method,
5922 // but adding that is a more complicated change.
5923 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5925 // mEditor and mEditor.mInput are not null from doKeyDown
5926 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5927 while (--repeatCount > 0) {
5928 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5929 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5931 hideErrorIfUnchanged();
5933 } else if (which == 2) {
5934 // mMovement is not null from doKeyDown
5935 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5936 while (--repeatCount > 0) {
5937 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5938 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5946 * Returns true if pressing ENTER in this field advances focus instead
5947 * of inserting the character. This is true mostly in single-line fields,
5948 * but also in mail addresses and subjects which will display on multiple
5949 * lines but where it doesn't make sense to insert newlines.
5951 private boolean shouldAdvanceFocusOnEnter() {
5952 if (getKeyListener() == null) {
5960 if (mEditor != null &&
5961 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5962 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5963 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
5964 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5973 * Returns true if pressing TAB in this field advances focus instead
5974 * of inserting the character. Insert tabs only in multi-line editors.
5976 private boolean shouldAdvanceFocusOnTab() {
5977 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5978 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5979 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5980 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5981 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
5988 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
5993 // If this is the initial keydown, we don't want to prevent a movement away from this view.
5994 // While this shouldn't be necessary because any time we're preventing default movement we
5995 // should be restricting the focus to remain within this view, thus we'll also receive
5996 // the key up event, occasionally key up events will get dropped and we don't want to
5997 // prevent the user from traversing out of this on the next key down.
5998 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
5999 mPreventDefaultMovement = false;
6003 case KeyEvent.KEYCODE_ENTER:
6004 if (event.hasNoModifiers()) {
6005 // When mInputContentType is set, we know that we are
6006 // running in a "modern" cupcake environment, so don't need
6007 // to worry about the application trying to capture
6008 // enter key events.
6009 if (mEditor != null && mEditor.mInputContentType != null) {
6010 // If there is an action listener, given them a
6011 // chance to consume the event.
6012 if (mEditor.mInputContentType.onEditorActionListener != null &&
6013 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6014 this, EditorInfo.IME_NULL, event)) {
6015 mEditor.mInputContentType.enterDown = true;
6016 // We are consuming the enter key for them.
6021 // If our editor should move focus when enter is pressed, or
6022 // this is a generated event from an IME action button, then
6023 // don't let it be inserted into the text.
6024 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6025 || shouldAdvanceFocusOnEnter()) {
6026 if (hasOnClickListeners()) {
6034 case KeyEvent.KEYCODE_DPAD_CENTER:
6035 if (event.hasNoModifiers()) {
6036 if (shouldAdvanceFocusOnEnter()) {
6042 case KeyEvent.KEYCODE_TAB:
6043 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6044 if (shouldAdvanceFocusOnTab()) {
6050 // Has to be done on key down (and not on key up) to correctly be intercepted.
6051 case KeyEvent.KEYCODE_BACK:
6052 if (mEditor != null && mEditor.mTextActionMode != null) {
6053 stopTextActionMode();
6059 if (mEditor != null && mEditor.mKeyListener != null) {
6060 boolean doDown = true;
6061 if (otherEvent != null) {
6064 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6066 hideErrorIfUnchanged();
6071 } catch (AbstractMethodError e) {
6072 // onKeyOther was added after 1.0, so if it isn't
6073 // implemented we need to try to dispatch as a regular down.
6081 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6084 hideErrorIfUnchanged();
6085 if (handled) return 1;
6089 // bug 650865: sometimes we get a key event before a layout.
6090 // don't try to move around if we don't know the layout.
6092 if (mMovement != null && mLayout != null) {
6093 boolean doDown = true;
6094 if (otherEvent != null) {
6096 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6102 } catch (AbstractMethodError e) {
6103 // onKeyOther was added after 1.0, so if it isn't
6104 // implemented we need to try to dispatch as a regular down.
6108 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6109 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6110 mPreventDefaultMovement = true;
6117 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
6121 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6125 public void resetErrorChangedFlag() {
6127 * Keep track of what the error was before doing the input
6128 * so that if an input filter changed the error, we leave
6129 * that error showing. Otherwise, we take down whatever
6130 * error was showing when the user types something.
6132 if (mEditor != null) mEditor.mErrorWasChanged = false;
6138 public void hideErrorIfUnchanged() {
6139 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
6140 setError(null, null);
6145 public boolean onKeyUp(int keyCode, KeyEvent event) {
6147 return super.onKeyUp(keyCode, event);
6150 if (!KeyEvent.isModifierKey(keyCode)) {
6151 mPreventDefaultMovement = false;
6155 case KeyEvent.KEYCODE_DPAD_CENTER:
6156 if (event.hasNoModifiers()) {
6158 * If there is a click listener, just call through to
6159 * super, which will invoke it.
6161 * If there isn't a click listener, try to show the soft
6162 * input method. (It will also
6163 * call performClick(), but that won't do anything in
6166 if (!hasOnClickListeners()) {
6167 if (mMovement != null && mText instanceof Editable
6168 && mLayout != null && onCheckIsTextEditor()) {
6169 InputMethodManager imm = InputMethodManager.peekInstance();
6171 if (imm != null && getShowSoftInputOnFocus()) {
6172 imm.showSoftInput(this, 0);
6177 return super.onKeyUp(keyCode, event);
6179 case KeyEvent.KEYCODE_ENTER:
6180 if (event.hasNoModifiers()) {
6181 if (mEditor != null && mEditor.mInputContentType != null
6182 && mEditor.mInputContentType.onEditorActionListener != null
6183 && mEditor.mInputContentType.enterDown) {
6184 mEditor.mInputContentType.enterDown = false;
6185 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6186 this, EditorInfo.IME_NULL, event)) {
6191 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6192 || shouldAdvanceFocusOnEnter()) {
6194 * If there is a click listener, just call through to
6195 * super, which will invoke it.
6197 * If there isn't a click listener, try to advance focus,
6198 * but still call through to super, which will reset the
6199 * pressed state and longpress state. (It will also
6200 * call performClick(), but that won't do anything in
6203 if (!hasOnClickListeners()) {
6204 View v = focusSearch(FOCUS_DOWN);
6207 if (!v.requestFocus(FOCUS_DOWN)) {
6208 throw new IllegalStateException(
6209 "focus search returned a view " +
6210 "that wasn't able to take focus!");
6214 * Return true because we handled the key; super
6215 * will return false because there was no click
6218 super.onKeyUp(keyCode, event);
6220 } else if ((event.getFlags()
6221 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6222 // No target for next focus, but make sure the IME
6223 // if this came from it.
6224 InputMethodManager imm = InputMethodManager.peekInstance();
6225 if (imm != null && imm.isActive(this)) {
6226 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6231 return super.onKeyUp(keyCode, event);
6236 if (mEditor != null && mEditor.mKeyListener != null)
6237 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
6240 if (mMovement != null && mLayout != null)
6241 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6244 return super.onKeyUp(keyCode, event);
6248 public boolean onCheckIsTextEditor() {
6249 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
6253 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6254 if (onCheckIsTextEditor() && isEnabled()) {
6255 mEditor.createInputMethodStateIfNeeded();
6256 outAttrs.inputType = getInputType();
6257 if (mEditor.mInputContentType != null) {
6258 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6259 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6260 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6261 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6262 outAttrs.extras = mEditor.mInputContentType.extras;
6264 outAttrs.imeOptions = EditorInfo.IME_NULL;
6266 if (focusSearch(FOCUS_DOWN) != null) {
6267 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6269 if (focusSearch(FOCUS_UP) != null) {
6270 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6272 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6273 == EditorInfo.IME_ACTION_UNSPECIFIED) {
6274 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
6275 // An action has not been set, but the enter key will move to
6276 // the next focus, so set the action to that.
6277 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6279 // An action has not been set, and there is no focus to move
6280 // to, so let's just supply a "done" action.
6281 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6283 if (!shouldAdvanceFocusOnEnter()) {
6284 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6287 if (isMultilineInputType(outAttrs.inputType)) {
6288 // Multi-line text editors should always show an enter key.
6289 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6291 outAttrs.hintText = mHint;
6292 if (mText instanceof Editable) {
6293 InputConnection ic = new EditableInputConnection(this);
6294 outAttrs.initialSelStart = getSelectionStart();
6295 outAttrs.initialSelEnd = getSelectionEnd();
6296 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6304 * If this TextView contains editable content, extract a portion of it
6305 * based on the information in <var>request</var> in to <var>outText</var>.
6306 * @return Returns true if the text was successfully extracted, else false.
6308 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6309 createEditorIfNeeded();
6310 return mEditor.extractText(request, outText);
6314 * This is used to remove all style-impacting spans from text before new
6315 * extracted text is being replaced into it, so that we don't have any
6316 * lingering spans applied during the replace.
6318 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6319 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6320 int i = spans.length;
6323 spannable.removeSpan(spans[i]);
6328 * Apply to this text view the given extracted text, as previously
6329 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6331 public void setExtractedText(ExtractedText text) {
6332 Editable content = getEditableText();
6333 if (text.text != null) {
6334 if (content == null) {
6335 setText(text.text, TextView.BufferType.EDITABLE);
6336 } else if (text.partialStartOffset < 0) {
6337 removeParcelableSpans(content, 0, content.length());
6338 content.replace(0, content.length(), text.text);
6340 final int N = content.length();
6341 int start = text.partialStartOffset;
6342 if (start > N) start = N;
6343 int end = text.partialEndOffset;
6344 if (end > N) end = N;
6345 removeParcelableSpans(content, start, end);
6346 content.replace(start, end, text.text);
6350 // Now set the selection position... make sure it is in range, to
6351 // avoid crashes. If this is a partial update, it is possible that
6352 // the underlying text may have changed, causing us problems here.
6353 // Also we just don't want to trust clients to do the right thing.
6354 Spannable sp = (Spannable)getText();
6355 final int N = sp.length();
6356 int start = text.selectionStart;
6357 if (start < 0) start = 0;
6358 else if (start > N) start = N;
6359 int end = text.selectionEnd;
6360 if (end < 0) end = 0;
6361 else if (end > N) end = N;
6362 Selection.setSelection(sp, start, end);
6364 // Finally, update the selection mode.
6365 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6366 MetaKeyKeyListener.startSelecting(this, sp);
6368 MetaKeyKeyListener.stopSelecting(this, sp);
6375 public void setExtracting(ExtractedTextRequest req) {
6376 if (mEditor.mInputMethodState != null) {
6377 mEditor.mInputMethodState.mExtractedTextRequest = req;
6379 // This would stop a possible selection mode, but no such mode is started in case
6380 // extracted mode will start. Some text is selected though, and will trigger an action mode
6381 // in the extracted view.
6382 mEditor.hideCursorAndSpanControllers();
6383 stopTextActionMode();
6387 * Called by the framework in response to a text completion from
6388 * the current input method, provided by it calling
6389 * {@link InputConnection#commitCompletion
6390 * InputConnection.commitCompletion()}. The default implementation does
6391 * nothing; text views that are supporting auto-completion should override
6392 * this to do their desired behavior.
6394 * @param text The auto complete text the user has selected.
6396 public void onCommitCompletion(CompletionInfo text) {
6397 // intentionally empty
6401 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6402 * a dictionnary) from the current input method, provided by it calling
6403 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6404 * implementation flashes the background of the corrected word to provide feedback to the user.
6406 * @param info The auto correct info about the text that was corrected.
6408 public void onCommitCorrection(CorrectionInfo info) {
6409 if (mEditor != null) mEditor.onCommitCorrection(info);
6412 public void beginBatchEdit() {
6413 if (mEditor != null) mEditor.beginBatchEdit();
6416 public void endBatchEdit() {
6417 if (mEditor != null) mEditor.endBatchEdit();
6421 * Called by the framework in response to a request to begin a batch
6422 * of edit operations through a call to link {@link #beginBatchEdit()}.
6424 public void onBeginBatchEdit() {
6425 // intentionally empty
6429 * Called by the framework in response to a request to end a batch
6430 * of edit operations through a call to link {@link #endBatchEdit}.
6432 public void onEndBatchEdit() {
6433 // intentionally empty
6437 * Called by the framework in response to a private command from the
6438 * current method, provided by it calling
6439 * {@link InputConnection#performPrivateCommand
6440 * InputConnection.performPrivateCommand()}.
6442 * @param action The action name of the command.
6443 * @param data Any additional data for the command. This may be null.
6444 * @return Return true if you handled the command, else false.
6446 public boolean onPrivateIMECommand(String action, Bundle data) {
6450 private void nullLayouts() {
6451 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6452 mSavedLayout = (BoringLayout) mLayout;
6454 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6455 mSavedHintLayout = (BoringLayout) mHintLayout;
6458 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6460 mBoring = mHintBoring = null;
6462 // Since it depends on the value of mLayout
6463 if (mEditor != null) mEditor.prepareCursorControllers();
6467 * Make a new Layout based on the already-measured size of the view,
6468 * on the assumption that it was measured correctly at some point.
6470 private void assumeLayout() {
6471 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6477 int physicalWidth = width;
6479 if (mHorizontallyScrolling) {
6483 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6484 physicalWidth, false);
6487 private Layout.Alignment getLayoutAlignment() {
6488 Layout.Alignment alignment;
6489 switch (getTextAlignment()) {
6490 case TEXT_ALIGNMENT_GRAVITY:
6491 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6493 alignment = Layout.Alignment.ALIGN_NORMAL;
6496 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6499 alignment = Layout.Alignment.ALIGN_LEFT;
6502 alignment = Layout.Alignment.ALIGN_RIGHT;
6504 case Gravity.CENTER_HORIZONTAL:
6505 alignment = Layout.Alignment.ALIGN_CENTER;
6508 alignment = Layout.Alignment.ALIGN_NORMAL;
6512 case TEXT_ALIGNMENT_TEXT_START:
6513 alignment = Layout.Alignment.ALIGN_NORMAL;
6515 case TEXT_ALIGNMENT_TEXT_END:
6516 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6518 case TEXT_ALIGNMENT_CENTER:
6519 alignment = Layout.Alignment.ALIGN_CENTER;
6521 case TEXT_ALIGNMENT_VIEW_START:
6522 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6523 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6525 case TEXT_ALIGNMENT_VIEW_END:
6526 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6527 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6529 case TEXT_ALIGNMENT_INHERIT:
6530 // This should never happen as we have already resolved the text alignment
6531 // but better safe than sorry so we just fall through
6533 alignment = Layout.Alignment.ALIGN_NORMAL;
6540 * The width passed in is now the desired layout width,
6541 * not the full view width with padding.
6544 protected void makeNewLayout(int wantWidth, int hintWidth,
6545 BoringLayout.Metrics boring,
6546 BoringLayout.Metrics hintBoring,
6547 int ellipsisWidth, boolean bringIntoView) {
6550 // Update "old" cached values
6551 mOldMaximum = mMaximum;
6552 mOldMaxMode = mMaxMode;
6554 mHighlightPathBogus = true;
6556 if (wantWidth < 0) {
6559 if (hintWidth < 0) {
6563 Layout.Alignment alignment = getLayoutAlignment();
6564 final boolean testDirChange = mSingleLine && mLayout != null &&
6565 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6566 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6568 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6569 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6570 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6571 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6572 TruncateAt effectiveEllipsize = mEllipsize;
6573 if (mEllipsize == TruncateAt.MARQUEE &&
6574 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6575 effectiveEllipsize = TruncateAt.END_SMALL;
6578 if (mTextDir == null) {
6579 mTextDir = getTextDirectionHeuristic();
6582 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6583 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6584 if (switchEllipsize) {
6585 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6586 TruncateAt.END : TruncateAt.MARQUEE;
6587 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6588 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6591 shouldEllipsize = mEllipsize != null;
6594 if (mHint != null) {
6595 if (shouldEllipsize) hintWidth = wantWidth;
6597 if (hintBoring == UNKNOWN_BORING) {
6598 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6600 if (hintBoring != null) {
6601 mHintBoring = hintBoring;
6605 if (hintBoring != null) {
6606 if (hintBoring.width <= hintWidth &&
6607 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6608 if (mSavedHintLayout != null) {
6609 mHintLayout = mSavedHintLayout.
6610 replaceOrMake(mHint, mTextPaint,
6611 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6612 hintBoring, mIncludePad);
6614 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6615 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6616 hintBoring, mIncludePad);
6619 mSavedHintLayout = (BoringLayout) mHintLayout;
6620 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6621 if (mSavedHintLayout != null) {
6622 mHintLayout = mSavedHintLayout.
6623 replaceOrMake(mHint, mTextPaint,
6624 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6625 hintBoring, mIncludePad, mEllipsize,
6628 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6629 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6630 hintBoring, mIncludePad, mEllipsize,
6635 // TODO: code duplication with makeSingleLayout()
6636 if (mHintLayout == null) {
6637 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6638 mHint.length(), mTextPaint, hintWidth)
6639 .setAlignment(alignment)
6640 .setTextDirection(mTextDir)
6641 .setLineSpacing(mSpacingAdd, mSpacingMult)
6642 .setIncludePad(mIncludePad)
6643 .setBreakStrategy(mBreakStrategy)
6644 .setHyphenationFrequency(mHyphenationFrequency);
6645 if (shouldEllipsize) {
6646 builder.setEllipsize(mEllipsize)
6647 .setEllipsizedWidth(ellipsisWidth)
6648 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6650 mHintLayout = builder.build();
6654 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6655 registerForPreDraw();
6658 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6659 if (!compressText(ellipsisWidth)) {
6660 final int height = mLayoutParams.height;
6661 // If the size of the view does not depend on the size of the text, try to
6662 // start the marquee immediately
6663 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6666 // Defer the start of the marquee until we know our width (see setFrame())
6667 mRestartMarquee = true;
6672 // CursorControllers need a non-null mLayout
6673 if (mEditor != null) mEditor.prepareCursorControllers();
6676 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6677 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6679 Layout result = null;
6680 if (mText instanceof Spannable) {
6681 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6682 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6683 mBreakStrategy, mHyphenationFrequency,
6684 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
6686 if (boring == UNKNOWN_BORING) {
6687 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6688 if (boring != null) {
6693 if (boring != null) {
6694 if (boring.width <= wantWidth &&
6695 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6696 if (useSaved && mSavedLayout != null) {
6697 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6698 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6699 boring, mIncludePad);
6701 result = BoringLayout.make(mTransformed, mTextPaint,
6702 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6703 boring, mIncludePad);
6707 mSavedLayout = (BoringLayout) result;
6709 } else if (shouldEllipsize && boring.width <= wantWidth) {
6710 if (useSaved && mSavedLayout != null) {
6711 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6712 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6713 boring, mIncludePad, effectiveEllipsize,
6716 result = BoringLayout.make(mTransformed, mTextPaint,
6717 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6718 boring, mIncludePad, effectiveEllipsize,
6724 if (result == null) {
6725 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6726 0, mTransformed.length(), mTextPaint, wantWidth)
6727 .setAlignment(alignment)
6728 .setTextDirection(mTextDir)
6729 .setLineSpacing(mSpacingAdd, mSpacingMult)
6730 .setIncludePad(mIncludePad)
6731 .setBreakStrategy(mBreakStrategy)
6732 .setHyphenationFrequency(mHyphenationFrequency);
6733 if (shouldEllipsize) {
6734 builder.setEllipsize(effectiveEllipsize)
6735 .setEllipsizedWidth(ellipsisWidth)
6736 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6738 // TODO: explore always setting maxLines
6739 result = builder.build();
6744 private boolean compressText(float width) {
6745 if (isHardwareAccelerated()) return false;
6747 // Only compress the text if it hasn't been compressed by the previous pass
6748 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6749 mTextPaint.getTextScaleX() == 1.0f) {
6750 final float textWidth = mLayout.getLineWidth(0);
6751 final float overflow = (textWidth + 1.0f - width) / width;
6752 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6753 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6754 post(new Runnable() {
6766 private static int desired(Layout layout) {
6767 int n = layout.getLineCount();
6768 CharSequence text = layout.getText();
6771 // if any line was wrapped, we can't use it.
6772 // but it's ok for the last line not to have a newline
6774 for (int i = 0; i < n - 1; i++) {
6775 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6779 for (int i = 0; i < n; i++) {
6780 max = Math.max(max, layout.getLineWidth(i));
6783 return (int) Math.ceil(max);
6787 * Set whether the TextView includes extra top and bottom padding to make
6788 * room for accents that go above the normal ascent and descent.
6789 * The default is true.
6791 * @see #getIncludeFontPadding()
6793 * @attr ref android.R.styleable#TextView_includeFontPadding
6795 public void setIncludeFontPadding(boolean includepad) {
6796 if (mIncludePad != includepad) {
6797 mIncludePad = includepad;
6799 if (mLayout != null) {
6808 * Gets whether the TextView includes extra top and bottom padding to make
6809 * room for accents that go above the normal ascent and descent.
6811 * @see #setIncludeFontPadding(boolean)
6813 * @attr ref android.R.styleable#TextView_includeFontPadding
6815 public boolean getIncludeFontPadding() {
6819 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6822 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6823 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6824 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6825 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6826 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6831 BoringLayout.Metrics boring = UNKNOWN_BORING;
6832 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6834 if (mTextDir == null) {
6835 mTextDir = getTextDirectionHeuristic();
6839 boolean fromexisting = false;
6841 if (widthMode == MeasureSpec.EXACTLY) {
6842 // Parent has told us how big to be. So be it.
6845 if (mLayout != null && mEllipsize == null) {
6846 des = desired(mLayout);
6850 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6851 if (boring != null) {
6855 fromexisting = true;
6858 if (boring == null || boring == UNKNOWN_BORING) {
6860 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6864 width = boring.width;
6867 final Drawables dr = mDrawables;
6869 width = Math.max(width, dr.mDrawableWidthTop);
6870 width = Math.max(width, dr.mDrawableWidthBottom);
6873 if (mHint != null) {
6877 if (mHintLayout != null && mEllipsize == null) {
6878 hintDes = desired(mHintLayout);
6882 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
6883 if (hintBoring != null) {
6884 mHintBoring = hintBoring;
6888 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6890 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
6892 hintWidth = hintDes;
6894 hintWidth = hintBoring.width;
6897 if (hintWidth > width) {
6902 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6904 if (mMaxWidthMode == EMS) {
6905 width = Math.min(width, mMaxWidth * getLineHeight());
6907 width = Math.min(width, mMaxWidth);
6910 if (mMinWidthMode == EMS) {
6911 width = Math.max(width, mMinWidth * getLineHeight());
6913 width = Math.max(width, mMinWidth);
6916 // Check against our minimum width
6917 width = Math.max(width, getSuggestedMinimumWidth());
6919 if (widthMode == MeasureSpec.AT_MOST) {
6920 width = Math.min(widthSize, width);
6924 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6925 int unpaddedWidth = want;
6927 if (mHorizontallyScrolling) want = VERY_WIDE;
6929 int hintWant = want;
6930 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6932 if (mLayout == null) {
6933 makeNewLayout(want, hintWant, boring, hintBoring,
6934 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6936 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6937 (hintWidth != hintWant) ||
6938 (mLayout.getEllipsizedWidth() !=
6939 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6941 final boolean widthChanged = (mHint == null) &&
6942 (mEllipsize == null) &&
6943 (want > mLayout.getWidth()) &&
6944 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6946 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6948 if (layoutChanged || maximumChanged) {
6949 if (!maximumChanged && widthChanged) {
6950 mLayout.increaseWidthTo(want);
6952 makeNewLayout(want, hintWant, boring, hintBoring,
6953 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6956 // Nothing has changed
6960 if (heightMode == MeasureSpec.EXACTLY) {
6961 // Parent has told us how big to be. So be it.
6962 height = heightSize;
6963 mDesiredHeightAtMeasure = -1;
6965 int desired = getDesiredHeight();
6968 mDesiredHeightAtMeasure = desired;
6970 if (heightMode == MeasureSpec.AT_MOST) {
6971 height = Math.min(desired, heightSize);
6975 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
6976 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
6977 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
6981 * We didn't let makeNewLayout() register to bring the cursor into view,
6982 * so do it here if there is any possibility that it is needed.
6984 if (mMovement != null ||
6985 mLayout.getWidth() > unpaddedWidth ||
6986 mLayout.getHeight() > unpaddedHeight) {
6987 registerForPreDraw();
6992 setMeasuredDimension(width, height);
6995 private int getDesiredHeight() {
6997 getDesiredHeight(mLayout, true),
6998 getDesiredHeight(mHintLayout, mEllipsize != null));
7001 private int getDesiredHeight(Layout layout, boolean cap) {
7002 if (layout == null) {
7006 int linecount = layout.getLineCount();
7007 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7008 int desired = layout.getLineTop(linecount);
7010 final Drawables dr = mDrawables;
7012 desired = Math.max(desired, dr.mDrawableHeightLeft);
7013 desired = Math.max(desired, dr.mDrawableHeightRight);
7018 if (mMaxMode == LINES) {
7020 * Don't cap the hint to a certain number of lines.
7021 * (Do cap it, though, if we have a maximum pixel height.)
7024 if (linecount > mMaximum) {
7025 desired = layout.getLineTop(mMaximum);
7028 desired = Math.max(desired, dr.mDrawableHeightLeft);
7029 desired = Math.max(desired, dr.mDrawableHeightRight);
7033 linecount = mMaximum;
7037 desired = Math.min(desired, mMaximum);
7040 if (mMinMode == LINES) {
7041 if (linecount < mMinimum) {
7042 desired += getLineHeight() * (mMinimum - linecount);
7045 desired = Math.max(desired, mMinimum);
7048 // Check against our minimum height
7049 desired = Math.max(desired, getSuggestedMinimumHeight());
7055 * Check whether a change to the existing text layout requires a
7058 private void checkForResize() {
7059 boolean sizeChanged = false;
7061 if (mLayout != null) {
7062 // Check if our width changed
7063 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7068 // Check if our height changed
7069 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7070 int desiredHeight = getDesiredHeight();
7072 if (desiredHeight != this.getHeight()) {
7075 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7076 if (mDesiredHeightAtMeasure >= 0) {
7077 int desiredHeight = getDesiredHeight();
7079 if (desiredHeight != mDesiredHeightAtMeasure) {
7088 // caller will have already invalidated
7093 * Check whether entirely new text requires a new view layout
7094 * or merely a new text layout.
7096 private void checkForRelayout() {
7097 // If we have a fixed width, we can just swap in a new text layout
7098 // if the text height stays the same or if the view height is fixed.
7100 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7101 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7102 (mHint == null || mHintLayout != null) &&
7103 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7104 // Static width, so try making a new text layout.
7106 int oldht = mLayout.getHeight();
7107 int want = mLayout.getWidth();
7108 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7111 * No need to bring the text into view, since the size is not
7112 * changing (unless we do the requestLayout(), in which case it
7113 * will happen at measure).
7115 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7116 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7119 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7120 // In a fixed-height view, so use our new text layout.
7121 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7122 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7127 // Dynamic height, but height has stayed the same,
7128 // so use our new text layout.
7129 if (mLayout.getHeight() == oldht &&
7130 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7136 // We lose: the height has changed and we have a dynamic height.
7137 // Request a new view layout using our new text layout.
7141 // Dynamic width, so we have no choice but to request a new
7142 // view layout with a new text layout.
7150 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7151 super.onLayout(changed, left, top, right, bottom);
7152 if (mDeferScroll >= 0) {
7153 int curs = mDeferScroll;
7155 bringPointIntoView(Math.min(curs, mText.length()));
7159 private boolean isShowingHint() {
7160 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7164 * Returns true if anything changed.
7166 private boolean bringTextIntoView() {
7167 Layout layout = isShowingHint() ? mHintLayout : mLayout;
7169 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7170 line = layout.getLineCount() - 1;
7173 Layout.Alignment a = layout.getParagraphAlignment(line);
7174 int dir = layout.getParagraphDirection(line);
7175 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7176 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7177 int ht = layout.getHeight();
7179 int scrollx, scrolly;
7181 // Convert to left, center, or right alignment.
7182 if (a == Layout.Alignment.ALIGN_NORMAL) {
7183 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7184 Layout.Alignment.ALIGN_RIGHT;
7185 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7186 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7187 Layout.Alignment.ALIGN_LEFT;
7190 if (a == Layout.Alignment.ALIGN_CENTER) {
7192 * Keep centered if possible, or, if it is too wide to fit,
7193 * keep leading edge in view.
7196 int left = (int) Math.floor(layout.getLineLeft(line));
7197 int right = (int) Math.ceil(layout.getLineRight(line));
7199 if (right - left < hspace) {
7200 scrollx = (right + left) / 2 - hspace / 2;
7203 scrollx = right - hspace;
7208 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7209 int right = (int) Math.ceil(layout.getLineRight(line));
7210 scrollx = right - hspace;
7211 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7212 scrollx = (int) Math.floor(layout.getLineLeft(line));
7218 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7219 scrolly = ht - vspace;
7225 if (scrollx != mScrollX || scrolly != mScrollY) {
7226 scrollTo(scrollx, scrolly);
7234 * Move the point, specified by the offset, into the view if it is needed.
7235 * This has to be called after layout. Returns true if anything changed.
7237 public boolean bringPointIntoView(int offset) {
7238 if (isLayoutRequested()) {
7239 mDeferScroll = offset;
7242 boolean changed = false;
7244 Layout layout = isShowingHint() ? mHintLayout: mLayout;
7246 if (layout == null) return changed;
7248 int line = layout.getLineForOffset(offset);
7252 switch (layout.getParagraphAlignment(line)) {
7260 grav = layout.getParagraphDirection(line);
7262 case ALIGN_OPPOSITE:
7263 grav = -layout.getParagraphDirection(line);
7271 // We only want to clamp the cursor to fit within the layout width
7272 // in left-to-right modes, because in a right to left alignment,
7273 // we want to scroll to keep the line-right on the screen, as other
7274 // lines are likely to have text flush with the right margin, which
7275 // we want to keep visible.
7276 // A better long-term solution would probably be to measure both
7277 // the full line and a blank-trimmed version, and, for example, use
7278 // the latter measurement for centering and right alignment, but for
7279 // the time being we only implement the cursor clamping in left to
7280 // right where it is most likely to be annoying.
7281 final boolean clamped = grav > 0;
7282 // FIXME: Is it okay to truncate this, or should we round?
7283 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7284 final int top = layout.getLineTop(line);
7285 final int bottom = layout.getLineTop(line + 1);
7287 int left = (int) Math.floor(layout.getLineLeft(line));
7288 int right = (int) Math.ceil(layout.getLineRight(line));
7289 int ht = layout.getHeight();
7291 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7292 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7293 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7294 // If cursor has been clamped, make sure we don't scroll.
7295 right = Math.max(x, left + hspace);
7298 int hslack = (bottom - top) / 2;
7299 int vslack = hslack;
7301 if (vslack > vspace / 4)
7302 vslack = vspace / 4;
7303 if (hslack > hspace / 4)
7304 hslack = hspace / 4;
7309 if (top - vs < vslack)
7311 if (bottom - vs > vspace - vslack)
7312 vs = bottom - (vspace - vslack);
7313 if (ht - vs < vspace)
7319 if (x - hs < hslack) {
7322 if (x - hs > hspace - hslack) {
7323 hs = x - (hspace - hslack);
7330 if (right - hs < hspace)
7331 hs = right - hspace;
7332 } else if (grav > 0) {
7333 if (right - hs < hspace)
7334 hs = right - hspace;
7337 } else /* grav == 0 */ {
7338 if (right - left <= hspace) {
7340 * If the entire text fits, center it exactly.
7342 hs = left - (hspace - (right - left)) / 2;
7343 } else if (x > right - hslack) {
7345 * If we are near the right edge, keep the right edge
7346 * at the edge of the view.
7348 hs = right - hspace;
7349 } else if (x < left + hslack) {
7351 * If we are near the left edge, keep the left edge
7352 * at the edge of the view.
7355 } else if (left > hs) {
7357 * Is there whitespace visible at the left? Fix it if so.
7360 } else if (right < hs + hspace) {
7362 * Is there whitespace visible at the right? Fix it if so.
7364 hs = right - hspace;
7367 * Otherwise, float as needed.
7369 if (x - hs < hslack) {
7372 if (x - hs > hspace - hslack) {
7373 hs = x - (hspace - hslack);
7378 if (hs != mScrollX || vs != mScrollY) {
7379 if (mScroller == null) {
7382 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7383 int dx = hs - mScrollX;
7384 int dy = vs - mScrollY;
7386 if (duration > ANIMATED_SCROLL_GAP) {
7387 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7388 awakenScrollBars(mScroller.getDuration());
7391 if (!mScroller.isFinished()) {
7392 mScroller.abortAnimation();
7398 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7405 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7406 // requestRectangleOnScreen() is in terms of content coordinates.
7408 // The offsets here are to ensure the rectangle we are using is
7409 // within our view bounds, in case the cursor is on the far left
7410 // or right. If it isn't withing the bounds, then this request
7412 if (mTempRect == null) mTempRect = new Rect();
7413 mTempRect.set(x - 2, top, x + 2, bottom);
7414 getInterestingRect(mTempRect, line);
7415 mTempRect.offset(mScrollX, mScrollY);
7417 if (requestRectangleOnScreen(mTempRect)) {
7426 * Move the cursor, if needed, so that it is at an offset that is visible
7427 * to the user. This will not move the cursor if it represents more than
7428 * one character (a selection range). This will only work if the
7429 * TextView contains spannable text; otherwise it will do nothing.
7431 * @return True if the cursor was actually moved, false otherwise.
7433 public boolean moveCursorToVisibleOffset() {
7434 if (!(mText instanceof Spannable)) {
7437 int start = getSelectionStart();
7438 int end = getSelectionEnd();
7443 // First: make sure the line is visible on screen:
7445 int line = mLayout.getLineForOffset(start);
7447 final int top = mLayout.getLineTop(line);
7448 final int bottom = mLayout.getLineTop(line + 1);
7449 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7450 int vslack = (bottom - top) / 2;
7451 if (vslack > vspace / 4)
7452 vslack = vspace / 4;
7453 final int vs = mScrollY;
7455 if (top < (vs+vslack)) {
7456 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7457 } else if (bottom > (vspace+vs-vslack)) {
7458 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7461 // Next: make sure the character is visible on screen:
7463 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7464 final int hs = mScrollX;
7465 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7466 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7468 // line might contain bidirectional text
7469 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7470 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7472 int newStart = start;
7473 if (newStart < lowChar) {
7475 } else if (newStart > highChar) {
7476 newStart = highChar;
7479 if (newStart != start) {
7480 Selection.setSelection((Spannable)mText, newStart);
7488 public void computeScroll() {
7489 if (mScroller != null) {
7490 if (mScroller.computeScrollOffset()) {
7491 mScrollX = mScroller.getCurrX();
7492 mScrollY = mScroller.getCurrY();
7493 invalidateParentCaches();
7494 postInvalidate(); // So we draw again
7499 private void getInterestingRect(Rect r, int line) {
7500 convertFromViewportToContentCoordinates(r);
7502 // Rectangle can can be expanded on first and last line to take
7503 // padding into account.
7504 // TODO Take left/right padding into account too?
7505 if (line == 0) r.top -= getExtendedPaddingTop();
7506 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7509 private void convertFromViewportToContentCoordinates(Rect r) {
7510 final int horizontalOffset = viewportToContentHorizontalOffset();
7511 r.left += horizontalOffset;
7512 r.right += horizontalOffset;
7514 final int verticalOffset = viewportToContentVerticalOffset();
7515 r.top += verticalOffset;
7516 r.bottom += verticalOffset;
7519 int viewportToContentHorizontalOffset() {
7520 return getCompoundPaddingLeft() - mScrollX;
7523 int viewportToContentVerticalOffset() {
7524 int offset = getExtendedPaddingTop() - mScrollY;
7525 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7526 offset += getVerticalOffset(false);
7532 public void debug(int depth) {
7535 String output = debugIndent(depth);
7536 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7537 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7540 if (mText != null) {
7542 output += "mText=\"" + mText + "\" ";
7543 if (mLayout != null) {
7544 output += "mLayout width=" + mLayout.getWidth()
7545 + " height=" + mLayout.getHeight();
7548 output += "mText=NULL";
7550 Log.d(VIEW_LOG_TAG, output);
7554 * Convenience for {@link Selection#getSelectionStart}.
7556 @ViewDebug.ExportedProperty(category = "text")
7557 public int getSelectionStart() {
7558 return Selection.getSelectionStart(getText());
7562 * Convenience for {@link Selection#getSelectionEnd}.
7564 @ViewDebug.ExportedProperty(category = "text")
7565 public int getSelectionEnd() {
7566 return Selection.getSelectionEnd(getText());
7570 * Return true iff there is a selection inside this text view.
7572 public boolean hasSelection() {
7573 final int selectionStart = getSelectionStart();
7574 final int selectionEnd = getSelectionEnd();
7576 return selectionStart >= 0 && selectionStart != selectionEnd;
7579 String getSelectedText() {
7580 if (hasSelection()) {
7581 return String.valueOf(mText.subSequence(getSelectionStart(), getSelectionEnd()));
7587 * Sets the properties of this field (lines, horizontally scrolling,
7588 * transformation method) to be for a single-line input.
7590 * @attr ref android.R.styleable#TextView_singleLine
7592 public void setSingleLine() {
7593 setSingleLine(true);
7597 * Sets the properties of this field to transform input to ALL CAPS
7598 * display. This may use a "small caps" formatting if available.
7599 * This setting will be ignored if this field is editable or selectable.
7601 * This call replaces the current transformation method. Disabling this
7602 * will not necessarily restore the previous behavior from before this
7605 * @see #setTransformationMethod(TransformationMethod)
7606 * @attr ref android.R.styleable#TextView_textAllCaps
7608 public void setAllCaps(boolean allCaps) {
7610 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7612 setTransformationMethod(null);
7617 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7618 * transformation method) to be for a single-line input; if false, restores these to the default
7621 * Note that the default conditions are not necessarily those that were in effect prior this
7622 * method, and you may want to reset these properties to your custom values.
7624 * @attr ref android.R.styleable#TextView_singleLine
7626 @android.view.RemotableViewMethod
7627 public void setSingleLine(boolean singleLine) {
7628 // Could be used, but may break backward compatibility.
7629 // if (mSingleLine == singleLine) return;
7630 setInputTypeSingleLine(singleLine);
7631 applySingleLine(singleLine, true, true);
7635 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7638 private void setInputTypeSingleLine(boolean singleLine) {
7639 if (mEditor != null &&
7640 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7642 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7644 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7649 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7650 boolean changeMaxLines) {
7651 mSingleLine = singleLine;
7654 setHorizontallyScrolling(true);
7655 if (applyTransformation) {
7656 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7659 if (changeMaxLines) {
7660 setMaxLines(Integer.MAX_VALUE);
7662 setHorizontallyScrolling(false);
7663 if (applyTransformation) {
7664 setTransformationMethod(null);
7670 * Causes words in the text that are longer than the view is wide
7671 * to be ellipsized instead of broken in the middle. You may also
7672 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7673 * to constrain the text to a single line. Use <code>null</code>
7674 * to turn off ellipsizing.
7676 * If {@link #setMaxLines} has been used to set two or more lines,
7677 * only {@link android.text.TextUtils.TruncateAt#END} and
7678 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7679 * (other ellipsizing types will not do anything).
7681 * @attr ref android.R.styleable#TextView_ellipsize
7683 public void setEllipsize(TextUtils.TruncateAt where) {
7684 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7685 if (mEllipsize != where) {
7688 if (mLayout != null) {
7697 * Sets how many times to repeat the marquee animation. Only applied if the
7698 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7700 * @see #getMarqueeRepeatLimit()
7702 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7704 public void setMarqueeRepeatLimit(int marqueeLimit) {
7705 mMarqueeRepeatLimit = marqueeLimit;
7709 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7710 * TextView has marquee enabled.
7712 * @return the number of times the marquee animation is repeated. -1 if the animation
7713 * repeats indefinitely
7715 * @see #setMarqueeRepeatLimit(int)
7717 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7719 public int getMarqueeRepeatLimit() {
7720 return mMarqueeRepeatLimit;
7724 * Returns where, if anywhere, words that are longer than the view
7725 * is wide should be ellipsized.
7727 @ViewDebug.ExportedProperty
7728 public TextUtils.TruncateAt getEllipsize() {
7733 * Set the TextView so that when it takes focus, all the text is
7736 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7738 @android.view.RemotableViewMethod
7739 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7740 createEditorIfNeeded();
7741 mEditor.mSelectAllOnFocus = selectAllOnFocus;
7743 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7744 setText(mText, BufferType.SPANNABLE);
7749 * Set whether the cursor is visible. The default is true. Note that this property only
7750 * makes sense for editable TextView.
7752 * @see #isCursorVisible()
7754 * @attr ref android.R.styleable#TextView_cursorVisible
7756 @android.view.RemotableViewMethod
7757 public void setCursorVisible(boolean visible) {
7758 if (visible && mEditor == null) return; // visible is the default value with no edit data
7759 createEditorIfNeeded();
7760 if (mEditor.mCursorVisible != visible) {
7761 mEditor.mCursorVisible = visible;
7764 mEditor.makeBlink();
7766 // InsertionPointCursorController depends on mCursorVisible
7767 mEditor.prepareCursorControllers();
7772 * @return whether or not the cursor is visible (assuming this TextView is editable)
7774 * @see #setCursorVisible(boolean)
7776 * @attr ref android.R.styleable#TextView_cursorVisible
7778 public boolean isCursorVisible() {
7779 // true is the default value
7780 return mEditor == null ? true : mEditor.mCursorVisible;
7783 private boolean canMarquee() {
7784 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7785 return width > 0 && (mLayout.getLineWidth(0) > width ||
7786 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7787 mSavedMarqueeModeLayout.getLineWidth(0) > width));
7790 private void startMarquee() {
7791 // Do not ellipsize EditText
7792 if (getKeyListener() != null) return;
7794 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7798 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7799 getLineCount() == 1 && canMarquee()) {
7801 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7802 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7803 final Layout tmp = mLayout;
7804 mLayout = mSavedMarqueeModeLayout;
7805 mSavedMarqueeModeLayout = tmp;
7806 setHorizontalFadingEdgeEnabled(true);
7811 if (mMarquee == null) mMarquee = new Marquee(this);
7812 mMarquee.start(mMarqueeRepeatLimit);
7816 private void stopMarquee() {
7817 if (mMarquee != null && !mMarquee.isStopped()) {
7821 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7822 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7823 final Layout tmp = mSavedMarqueeModeLayout;
7824 mSavedMarqueeModeLayout = mLayout;
7826 setHorizontalFadingEdgeEnabled(false);
7832 private void startStopMarquee(boolean start) {
7833 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7843 * This method is called when the text is changed, in case any subclasses
7844 * would like to know.
7846 * Within <code>text</code>, the <code>lengthAfter</code> characters
7847 * beginning at <code>start</code> have just replaced old text that had
7848 * length <code>lengthBefore</code>. It is an error to attempt to make
7849 * changes to <code>text</code> from this callback.
7851 * @param text The text the TextView is displaying
7852 * @param start The offset of the start of the range of the text that was
7854 * @param lengthBefore The length of the former text that has been replaced
7855 * @param lengthAfter The length of the replacement modified text
7857 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7858 // intentionally empty, template pattern method can be overridden by subclasses
7862 * This method is called when the selection has changed, in case any
7863 * subclasses would like to know.
7865 * @param selStart The new selection start location.
7866 * @param selEnd The new selection end location.
7868 protected void onSelectionChanged(int selStart, int selEnd) {
7869 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7873 * Adds a TextWatcher to the list of those whose methods are called
7874 * whenever this TextView's text changes.
7876 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7877 * not called after {@link #setText} calls. Now, doing {@link #setText}
7878 * if there are any text changed listeners forces the buffer type to
7879 * Editable if it would not otherwise be and does call this method.
7881 public void addTextChangedListener(TextWatcher watcher) {
7882 if (mListeners == null) {
7883 mListeners = new ArrayList<TextWatcher>();
7886 mListeners.add(watcher);
7890 * Removes the specified TextWatcher from the list of those whose
7891 * methods are called
7892 * whenever this TextView's text changes.
7894 public void removeTextChangedListener(TextWatcher watcher) {
7895 if (mListeners != null) {
7896 int i = mListeners.indexOf(watcher);
7899 mListeners.remove(i);
7904 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7905 if (mListeners != null) {
7906 final ArrayList<TextWatcher> list = mListeners;
7907 final int count = list.size();
7908 for (int i = 0; i < count; i++) {
7909 list.get(i).beforeTextChanged(text, start, before, after);
7913 // The spans that are inside or intersect the modified region no longer make sense
7914 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7915 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
7918 // Removes all spans that are inside or actually overlap the start..end range
7919 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
7920 if (!(mText instanceof Editable)) return;
7921 Editable text = (Editable) mText;
7923 T[] spans = text.getSpans(start, end, type);
7924 final int length = spans.length;
7925 for (int i = 0; i < length; i++) {
7926 final int spanStart = text.getSpanStart(spans[i]);
7927 final int spanEnd = text.getSpanEnd(spans[i]);
7928 if (spanEnd == start || spanStart == end) break;
7929 text.removeSpan(spans[i]);
7933 void removeAdjacentSuggestionSpans(final int pos) {
7934 if (!(mText instanceof Editable)) return;
7935 final Editable text = (Editable) mText;
7937 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7938 final int length = spans.length;
7939 for (int i = 0; i < length; i++) {
7940 final int spanStart = text.getSpanStart(spans[i]);
7941 final int spanEnd = text.getSpanEnd(spans[i]);
7942 if (spanEnd == pos || spanStart == pos) {
7943 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7944 text.removeSpan(spans[i]);
7951 * Not private so it can be called from an inner class without going
7954 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7955 if (mListeners != null) {
7956 final ArrayList<TextWatcher> list = mListeners;
7957 final int count = list.size();
7958 for (int i = 0; i < count; i++) {
7959 list.get(i).onTextChanged(text, start, before, after);
7963 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
7967 * Not private so it can be called from an inner class without going
7970 void sendAfterTextChanged(Editable text) {
7971 if (mListeners != null) {
7972 final ArrayList<TextWatcher> list = mListeners;
7973 final int count = list.size();
7974 for (int i = 0; i < count; i++) {
7975 list.get(i).afterTextChanged(text);
7978 hideErrorIfUnchanged();
7981 void updateAfterEdit() {
7983 int curs = getSelectionStart();
7985 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7986 registerForPreDraw();
7992 mHighlightPathBogus = true;
7993 if (mEditor != null) mEditor.makeBlink();
7994 bringPointIntoView(curs);
7999 * Not private so it can be called from an inner class without going
8002 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8003 sLastCutCopyOrTextChangedTime = 0;
8005 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8006 if (ims == null || ims.mBatchEditNesting == 0) {
8010 ims.mContentChanged = true;
8011 if (ims.mChangedStart < 0) {
8012 ims.mChangedStart = start;
8013 ims.mChangedEnd = start+before;
8015 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8016 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8018 ims.mChangedDelta += after-before;
8020 resetErrorChangedFlag();
8021 sendOnTextChanged(buffer, start, before, after);
8022 onTextChanged(buffer, start, before, after);
8026 * Not private so it can be called from an inner class without going
8029 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8030 // XXX Make the start and end move together if this ends up
8031 // spending too much time invalidating.
8033 boolean selChanged = false;
8034 int newSelStart=-1, newSelEnd=-1;
8036 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8038 if (what == Selection.SELECTION_END) {
8040 newSelEnd = newStart;
8042 if (oldStart >= 0 || newStart >= 0) {
8043 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8045 registerForPreDraw();
8046 if (mEditor != null) mEditor.makeBlink();
8050 if (what == Selection.SELECTION_START) {
8052 newSelStart = newStart;
8054 if (oldStart >= 0 || newStart >= 0) {
8055 int end = Selection.getSelectionEnd(buf);
8056 invalidateCursor(end, oldStart, newStart);
8061 mHighlightPathBogus = true;
8062 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8064 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8065 if (newSelStart < 0) {
8066 newSelStart = Selection.getSelectionStart(buf);
8068 if (newSelEnd < 0) {
8069 newSelEnd = Selection.getSelectionEnd(buf);
8071 onSelectionChanged(newSelStart, newSelEnd);
8075 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8076 what instanceof CharacterStyle) {
8077 if (ims == null || ims.mBatchEditNesting == 0) {
8079 mHighlightPathBogus = true;
8082 ims.mContentChanged = true;
8084 if (mEditor != null) {
8085 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8086 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8090 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8091 mHighlightPathBogus = true;
8092 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8093 ims.mSelectionModeChanged = true;
8096 if (Selection.getSelectionStart(buf) >= 0) {
8097 if (ims == null || ims.mBatchEditNesting == 0) {
8100 ims.mCursorChanged = true;
8105 if (what instanceof ParcelableSpan) {
8106 // If this is a span that can be sent to a remote process,
8107 // the current extract editor would be interested in it.
8108 if (ims != null && ims.mExtractedTextRequest != null) {
8109 if (ims.mBatchEditNesting != 0) {
8110 if (oldStart >= 0) {
8111 if (ims.mChangedStart > oldStart) {
8112 ims.mChangedStart = oldStart;
8114 if (ims.mChangedStart > oldEnd) {
8115 ims.mChangedStart = oldEnd;
8118 if (newStart >= 0) {
8119 if (ims.mChangedStart > newStart) {
8120 ims.mChangedStart = newStart;
8122 if (ims.mChangedStart > newEnd) {
8123 ims.mChangedStart = newEnd;
8127 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
8128 + oldStart + "-" + oldEnd + ","
8129 + newStart + "-" + newEnd + " " + what);
8130 ims.mContentChanged = true;
8135 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8136 what instanceof SpellCheckSpan) {
8137 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8145 public void dispatchFinishTemporaryDetach() {
8146 mDispatchTemporaryDetach = true;
8147 super.dispatchFinishTemporaryDetach();
8148 mDispatchTemporaryDetach = false;
8152 public void onStartTemporaryDetach() {
8153 super.onStartTemporaryDetach();
8154 // Only track when onStartTemporaryDetach() is called directly,
8155 // usually because this instance is an editable field in a list
8156 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
8158 // Tell the editor that we are temporarily detached. It can use this to preserve
8159 // selection state as needed.
8160 if (mEditor != null) mEditor.mTemporaryDetach = true;
8164 public void onFinishTemporaryDetach() {
8165 super.onFinishTemporaryDetach();
8166 // Only track when onStartTemporaryDetach() is called directly,
8167 // usually because this instance is an editable field in a list
8168 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
8169 if (mEditor != null) mEditor.mTemporaryDetach = false;
8173 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8174 if (mTemporaryDetach) {
8175 // If we are temporarily in the detach state, then do nothing.
8176 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8180 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8183 if (mText instanceof Spannable) {
8184 Spannable sp = (Spannable) mText;
8185 MetaKeyKeyListener.resetMetaState(sp);
8189 startStopMarquee(focused);
8191 if (mTransformation != null) {
8192 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8195 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8199 public void onWindowFocusChanged(boolean hasWindowFocus) {
8200 super.onWindowFocusChanged(hasWindowFocus);
8202 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8204 startStopMarquee(hasWindowFocus);
8208 protected void onVisibilityChanged(View changedView, int visibility) {
8209 super.onVisibilityChanged(changedView, visibility);
8210 if (mEditor != null && visibility != VISIBLE) {
8211 mEditor.hideCursorAndSpanControllers();
8212 stopTextActionMode();
8217 * Use {@link BaseInputConnection#removeComposingSpans
8218 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8219 * state from this text view.
8221 public void clearComposingText() {
8222 if (mText instanceof Spannable) {
8223 BaseInputConnection.removeComposingSpans((Spannable)mText);
8228 public void setSelected(boolean selected) {
8229 boolean wasSelected = isSelected();
8231 super.setSelected(selected);
8233 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8243 public boolean onTouchEvent(MotionEvent event) {
8244 final int action = event.getActionMasked();
8246 if (mEditor != null && action == MotionEvent.ACTION_DOWN) {
8247 // Detect double tap and inform the Editor.
8248 if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
8249 ViewConfiguration.getDoubleTapTimeout()) {
8250 mEditor.mDoubleTap = true;
8251 mFirstTouch = false;
8253 mEditor.mDoubleTap = false;
8258 if (action == MotionEvent.ACTION_UP) {
8259 mLastTouchUpTime = SystemClock.uptimeMillis();
8262 if (mEditor != null) {
8263 mEditor.onTouchEvent(event);
8265 if (mEditor.mSelectionModifierCursorController != null &&
8266 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8271 final boolean superResult = super.onTouchEvent(event);
8274 * Don't handle the release after a long press, because it will move the selection away from
8275 * whatever the menu action was trying to affect. If the long press should have triggered an
8276 * insertion action mode, we can now actually show it.
8278 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8279 mEditor.mDiscardNextActionUp = false;
8281 if (mEditor.mIsInsertionActionModeStartPending) {
8282 mEditor.startInsertionActionMode();
8283 mEditor.mIsInsertionActionModeStartPending = false;
8288 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8289 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8291 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8292 && mText instanceof Spannable && mLayout != null) {
8293 boolean handled = false;
8295 if (mMovement != null) {
8296 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8299 final boolean textIsSelectable = isTextSelectable();
8300 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
8301 // The LinkMovementMethod which should handle taps on links has not been installed
8302 // on non editable text that support text selection.
8303 // We reproduce its behavior here to open links for these.
8304 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8305 getSelectionEnd(), ClickableSpan.class);
8307 if (links.length > 0) {
8308 links[0].onClick(this);
8313 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8314 // Show the IME, except when selecting in read-only text.
8315 final InputMethodManager imm = InputMethodManager.peekInstance();
8317 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8318 handled |= imm != null && imm.showSoftInput(this, 0);
8321 // The above condition ensures that the mEditor is not null
8322 mEditor.onTouchUpEvent(event);
8336 public boolean onGenericMotionEvent(MotionEvent event) {
8337 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8339 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8342 } catch (AbstractMethodError ex) {
8343 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8344 // Ignore its absence in case third party applications implemented the
8345 // interface directly.
8348 return super.onGenericMotionEvent(event);
8352 * @return True iff this TextView contains a text that can be edited, or if this is
8353 * a selectable TextView.
8355 boolean isTextEditable() {
8356 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8360 * Returns true, only while processing a touch gesture, if the initial
8361 * touch down event caused focus to move to the text view and as a result
8362 * its selection changed. Only valid while processing the touch gesture
8363 * of interest, in an editable text view.
8365 public boolean didTouchFocusSelect() {
8366 return mEditor != null && mEditor.mTouchFocusSelected;
8370 public void cancelLongPress() {
8371 super.cancelLongPress();
8372 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8376 public boolean onTrackballEvent(MotionEvent event) {
8377 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8378 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8383 return super.onTrackballEvent(event);
8386 public void setScroller(Scroller s) {
8391 protected float getLeftFadingEdgeStrength() {
8392 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8393 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8394 if (mMarquee != null && !mMarquee.isStopped()) {
8395 final Marquee marquee = mMarquee;
8396 if (marquee.shouldDrawLeftFade()) {
8397 final float scroll = marquee.getScroll();
8398 return scroll / getHorizontalFadingEdgeLength();
8402 } else if (getLineCount() == 1) {
8403 final int layoutDirection = getLayoutDirection();
8404 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8405 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8409 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8410 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8411 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8412 case Gravity.CENTER_HORIZONTAL:
8413 case Gravity.FILL_HORIZONTAL:
8414 final int textDirection = mLayout.getParagraphDirection(0);
8415 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8418 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8419 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8420 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8425 return super.getLeftFadingEdgeStrength();
8429 protected float getRightFadingEdgeStrength() {
8430 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8431 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8432 if (mMarquee != null && !mMarquee.isStopped()) {
8433 final Marquee marquee = mMarquee;
8434 final float maxFadeScroll = marquee.getMaxFadeScroll();
8435 final float scroll = marquee.getScroll();
8436 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
8437 } else if (getLineCount() == 1) {
8438 final int layoutDirection = getLayoutDirection();
8439 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8440 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8442 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8443 getCompoundPaddingRight();
8444 final float lineWidth = mLayout.getLineWidth(0);
8445 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8448 case Gravity.CENTER_HORIZONTAL:
8449 case Gravity.FILL_HORIZONTAL:
8450 final int textDirection = mLayout.getParagraphDirection(0);
8451 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8454 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8455 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8456 getHorizontalFadingEdgeLength();
8461 return super.getRightFadingEdgeStrength();
8465 protected int computeHorizontalScrollRange() {
8466 if (mLayout != null) {
8467 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8468 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8471 return super.computeHorizontalScrollRange();
8475 protected int computeVerticalScrollRange() {
8476 if (mLayout != null)
8477 return mLayout.getHeight();
8479 return super.computeVerticalScrollRange();
8483 protected int computeVerticalScrollExtent() {
8484 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8488 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8489 super.findViewsWithText(outViews, searched, flags);
8490 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8491 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8492 String searchedLowerCase = searched.toString().toLowerCase();
8493 String textLowerCase = mText.toString().toLowerCase();
8494 if (textLowerCase.contains(searchedLowerCase)) {
8500 public enum BufferType {
8501 NORMAL, SPANNABLE, EDITABLE,
8505 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8506 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8507 * if TextView_textColor was not set directly.
8511 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8512 if (attrs == null) {
8513 // Preserve behavior prior to removal of this API.
8514 throw new NullPointerException();
8517 // It's not safe to use this method from apps. The parameter 'attrs'
8518 // must have been obtained using the TextView filter array which is not
8519 // available to the SDK. As such, we grab a default TypedArray with the
8520 // right filter instead here.
8521 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8522 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8523 if (colors == null) {
8524 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8526 final TypedArray appearance = context.obtainStyledAttributes(
8527 ap, R.styleable.TextAppearance);
8528 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8529 appearance.recycle();
8538 * Returns the default color from the TextView_textColor attribute from the
8539 * AttributeSet, if set, or the default color from the
8540 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8541 * TextView_textColor was not set directly.
8545 public static int getTextColor(Context context, TypedArray attrs, int def) {
8546 final ColorStateList colors = getTextColors(context, attrs);
8547 if (colors == null) {
8550 return colors.getDefaultColor();
8555 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8556 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8557 // Handle Ctrl-only shortcuts.
8559 case KeyEvent.KEYCODE_A:
8560 if (canSelectText()) {
8561 return onTextContextMenuItem(ID_SELECT_ALL);
8564 case KeyEvent.KEYCODE_Z:
8566 return onTextContextMenuItem(ID_UNDO);
8569 case KeyEvent.KEYCODE_X:
8571 return onTextContextMenuItem(ID_CUT);
8574 case KeyEvent.KEYCODE_C:
8576 return onTextContextMenuItem(ID_COPY);
8579 case KeyEvent.KEYCODE_V:
8581 return onTextContextMenuItem(ID_PASTE);
8585 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8586 // Handle Ctrl-Shift shortcuts.
8588 case KeyEvent.KEYCODE_Z:
8590 return onTextContextMenuItem(ID_REDO);
8593 case KeyEvent.KEYCODE_V:
8595 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8599 return super.onKeyShortcut(keyCode, event);
8603 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8604 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8605 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8608 boolean canSelectText() {
8609 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8613 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8614 * The text must be spannable and the movement method must allow for arbitary selection.
8616 * See also {@link #canSelectText()}.
8618 boolean textCanBeSelected() {
8619 // prepareCursorController() relies on this method.
8620 // If you change this condition, make sure prepareCursorController is called anywhere
8621 // the value of this condition might be changed.
8622 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8623 return isTextEditable() ||
8624 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8627 private Locale getTextServicesLocale(boolean allowNullLocale) {
8628 // Start fetching the text services locale asynchronously.
8629 updateTextServicesLocaleAsync();
8630 // If !allowNullLocale and there is no cached text services locale, just return the default
8632 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8633 : mCurrentSpellCheckerLocaleCache;
8637 * This is a temporary method. Future versions may support multi-locale text.
8638 * Caveat: This method may not return the latest text services locale, but this should be
8639 * acceptable and it's more important to make this method asynchronous.
8641 * @return The locale that should be used for a word iterator
8642 * in this TextView, based on the current spell checker settings,
8643 * the current IME's locale, or the system default locale.
8644 * Please note that a word iterator in this TextView is different from another word iterator
8645 * used by SpellChecker.java of TextView. This method should be used for the former.
8648 // TODO: Support multi-locale
8649 // TODO: Update the text services locale immediately after the keyboard locale is switched
8650 // by catching intent of keyboard switch event
8651 public Locale getTextServicesLocale() {
8652 return getTextServicesLocale(false /* allowNullLocale */);
8656 * @return true if this TextView is specialized for showing and interacting with the extracted
8657 * text in a full-screen input method.
8660 public boolean isInExtractedMode() {
8665 * This is a temporary method. Future versions may support multi-locale text.
8666 * Caveat: This method may not return the latest spell checker locale, but this should be
8667 * acceptable and it's more important to make this method asynchronous.
8669 * @return The locale that should be used for a spell checker in this TextView,
8670 * based on the current spell checker settings, the current IME's locale, or the system default
8674 public Locale getSpellCheckerLocale() {
8675 return getTextServicesLocale(true /* allowNullLocale */);
8678 private void updateTextServicesLocaleAsync() {
8679 // AsyncTask.execute() uses a serial executor which means we don't have
8680 // to lock around updateTextServicesLocaleLocked() to prevent it from
8681 // being executed n times in parallel.
8682 AsyncTask.execute(new Runnable() {
8685 updateTextServicesLocaleLocked();
8690 private void updateTextServicesLocaleLocked() {
8691 final TextServicesManager textServicesManager = (TextServicesManager)
8692 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8693 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8694 final Locale locale;
8695 if (subtype != null) {
8696 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
8700 mCurrentSpellCheckerLocaleCache = locale;
8703 void onLocaleChanged() {
8704 // Will be re-created on demand in getWordIterator with the proper new locale
8705 mEditor.mWordIterator = null;
8709 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8710 * Made available to achieve a consistent behavior.
8713 public WordIterator getWordIterator() {
8714 if (mEditor != null) {
8715 return mEditor.getWordIterator();
8723 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8724 super.onPopulateAccessibilityEventInternal(event);
8726 final boolean isPassword = hasPasswordTransformationMethod();
8727 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8728 final CharSequence text = getTextForAccessibility();
8729 if (!TextUtils.isEmpty(text)) {
8730 event.getText().add(text);
8736 * @return true if the user has explicitly allowed accessibility services
8737 * to speak passwords.
8739 private boolean shouldSpeakPasswordsForAccessibility() {
8740 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8741 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8742 UserHandle.USER_CURRENT_OR_SELF) == 1);
8746 public CharSequence getAccessibilityClassName() {
8747 return TextView.class.getName();
8751 public void onProvideStructure(ViewStructure structure) {
8752 super.onProvideStructure(structure);
8753 final boolean isPassword = hasPasswordTransformationMethod()
8754 || isPasswordInputType(getInputType());
8756 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8758 // Extract style information that applies to the TextView as a whole.
8760 int typefaceStyle = getTypefaceStyle();
8761 if ((typefaceStyle & Typeface.BOLD) != 0) {
8762 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8764 if ((typefaceStyle & Typeface.ITALIC) != 0) {
8765 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
8768 // Global styles can also be set via TextView.setPaintFlags().
8769 int paintFlags = mTextPaint.getFlags();
8770 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
8771 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8773 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
8774 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
8776 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
8777 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
8780 // TextView does not have its own text background color. A background is either part
8781 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
8782 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
8783 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
8785 structure.setHint(getHint());
8790 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
8791 super.onInitializeAccessibilityEventInternal(event);
8793 final boolean isPassword = hasPasswordTransformationMethod();
8794 event.setPassword(isPassword);
8796 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8797 event.setFromIndex(Selection.getSelectionStart(mText));
8798 event.setToIndex(Selection.getSelectionEnd(mText));
8799 event.setItemCount(mText.length());
8805 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
8806 super.onInitializeAccessibilityNodeInfoInternal(info);
8808 final boolean isPassword = hasPasswordTransformationMethod();
8809 info.setPassword(isPassword);
8811 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8812 info.setText(getTextForAccessibility());
8815 if (mBufferType == BufferType.EDITABLE) {
8816 info.setEditable(true);
8819 if (mEditor != null) {
8820 info.setInputType(mEditor.mInputType);
8822 if (mEditor.mError != null) {
8823 info.setContentInvalid(true);
8824 info.setError(mEditor.mError);
8828 if (!TextUtils.isEmpty(mText)) {
8829 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8830 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8831 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8832 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8833 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8834 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8835 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8839 if (canSelectText()) {
8840 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8843 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8846 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8849 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8852 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
8853 ACCESSIBILITY_ACTION_SHARE,
8854 getResources().getString(com.android.internal.R.string.share)));
8858 // Check for known input filter types.
8859 final int numFilters = mFilters.length;
8860 for (int i = 0; i < numFilters; i++) {
8861 final InputFilter filter = mFilters[i];
8862 if (filter instanceof InputFilter.LengthFilter) {
8863 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
8867 if (!isSingleLine()) {
8868 info.setMultiLine(true);
8873 * Performs an accessibility action after it has been offered to the
8879 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
8881 case AccessibilityNodeInfo.ACTION_CLICK: {
8882 boolean handled = false;
8884 // Simulate View.onTouchEvent for an ACTION_UP event.
8885 if (isClickable() || isLongClickable()) {
8886 if (isFocusable() && !isFocused()) {
8894 // Simulate TextView.onTouchEvent for an ACTION_UP event.
8895 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8896 && mText instanceof Spannable && mLayout != null
8897 && (isTextEditable() || isTextSelectable()) && isFocused()) {
8898 // Show the IME, except when selecting in read-only text.
8899 final InputMethodManager imm = InputMethodManager.peekInstance();
8901 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
8902 handled |= imm.showSoftInput(this, 0);
8908 case AccessibilityNodeInfo.ACTION_COPY: {
8909 if (isFocused() && canCopy()) {
8910 if (onTextContextMenuItem(ID_COPY)) {
8915 case AccessibilityNodeInfo.ACTION_PASTE: {
8916 if (isFocused() && canPaste()) {
8917 if (onTextContextMenuItem(ID_PASTE)) {
8922 case AccessibilityNodeInfo.ACTION_CUT: {
8923 if (isFocused() && canCut()) {
8924 if (onTextContextMenuItem(ID_CUT)) {
8929 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
8930 if (isFocused() && canSelectText()) {
8931 ensureIterableTextForAccessibilitySelectable();
8932 CharSequence text = getIterableTextForAccessibility();
8936 final int start = (arguments != null) ? arguments.getInt(
8937 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
8938 final int end = (arguments != null) ? arguments.getInt(
8939 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
8940 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
8941 // No arguments clears the selection.
8942 if (start == end && end == -1) {
8943 Selection.removeSelection((Spannable) text);
8946 if (start >= 0 && start <= end && end <= text.length()) {
8947 Selection.setSelection((Spannable) text, start, end);
8948 // Make sure selection mode is engaged.
8949 if (mEditor != null) {
8950 mEditor.startSelectionActionMode();
8957 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
8958 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
8959 ensureIterableTextForAccessibilitySelectable();
8960 return super.performAccessibilityActionInternal(action, arguments);
8962 case ACCESSIBILITY_ACTION_SHARE: {
8963 if (isFocused() && canShare()) {
8964 if (onTextContextMenuItem(ID_SHARE)) {
8970 return super.performAccessibilityActionInternal(action, arguments);
8977 public void sendAccessibilityEventInternal(int eventType) {
8978 // Do not send scroll events since first they are not interesting for
8979 // accessibility and second such events a generated too frequently.
8980 // For details see the implementation of bringTextIntoView().
8981 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
8984 super.sendAccessibilityEventInternal(eventType);
8988 * Gets the text reported for accessibility purposes.
8990 * @return The accessibility text.
8994 public CharSequence getTextForAccessibility() {
8995 CharSequence text = getText();
8996 if (TextUtils.isEmpty(text)) {
9002 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9003 int fromIndex, int removedCount, int addedCount) {
9004 AccessibilityEvent event =
9005 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9006 event.setFromIndex(fromIndex);
9007 event.setRemovedCount(removedCount);
9008 event.setAddedCount(addedCount);
9009 event.setBeforeText(beforeText);
9010 sendAccessibilityEventUnchecked(event);
9014 * Returns whether this text view is a current input method target. The
9015 * default implementation just checks with {@link InputMethodManager}.
9017 public boolean isInputMethodTarget() {
9018 InputMethodManager imm = InputMethodManager.peekInstance();
9019 return imm != null && imm.isActive(this);
9022 static final int ID_SELECT_ALL = android.R.id.selectAll;
9023 static final int ID_UNDO = android.R.id.undo;
9024 static final int ID_REDO = android.R.id.redo;
9025 static final int ID_CUT = android.R.id.cut;
9026 static final int ID_COPY = android.R.id.copy;
9027 static final int ID_PASTE = android.R.id.paste;
9028 static final int ID_SHARE = android.R.id.shareText;
9029 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9030 static final int ID_REPLACE = android.R.id.replaceText;
9033 * Called when a context menu option for the text view is selected. Currently
9034 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9035 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9037 * @return true if the context menu item action was performed.
9039 public boolean onTextContextMenuItem(int id) {
9041 int max = mText.length();
9044 final int selStart = getSelectionStart();
9045 final int selEnd = getSelectionEnd();
9047 min = Math.max(0, Math.min(selStart, selEnd));
9048 max = Math.max(0, Math.max(selStart, selEnd));
9053 // This starts an action mode if triggered from another action mode. Text is
9054 // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
9055 // true even if text is empty.
9056 boolean shouldRestartActionMode =
9057 mEditor != null && mEditor.mTextActionMode != null;
9058 stopTextActionMode();
9060 if (shouldRestartActionMode) {
9061 mEditor.startSelectionActionMode();
9066 if (mEditor != null) {
9069 return true; // Returns true even if nothing was undone.
9072 if (mEditor != null) {
9075 return true; // Returns true even if nothing was undone.
9078 paste(min, max, true /* withFormatting */);
9081 case ID_PASTE_AS_PLAIN_TEXT:
9082 paste(min, max, false /* withFormatting */);
9086 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9087 deleteText_internal(min, max);
9088 stopTextActionMode();
9092 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9093 stopTextActionMode();
9097 if (mEditor != null) {
9103 shareSelectedText();
9109 CharSequence getTransformedText(int start, int end) {
9110 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9114 public boolean performLongClick() {
9115 boolean handled = false;
9117 if (super.performLongClick()) {
9121 if (mEditor != null) {
9122 handled |= mEditor.performLongClick(handled);
9126 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9127 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
9134 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9135 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9136 if (mEditor != null) {
9137 mEditor.onScrollChanged();
9142 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9143 * by the IME or by the spell checker as the user types. This is done by adding
9144 * {@link SuggestionSpan}s to the text.
9146 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9147 * user asks for them on these parts of the text. This value depends on the inputType of this
9150 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9152 * In addition, the type variation must be one of
9153 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9154 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9155 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9156 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9157 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9159 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9161 * @return true if the suggestions popup window is enabled, based on the inputType.
9163 public boolean isSuggestionsEnabled() {
9164 if (mEditor == null) return false;
9165 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9168 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9170 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9171 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9172 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9173 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9174 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9175 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9179 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9180 * selection is initiated in this View.
9182 * The standard implementation populates the menu with a subset of Select All, Cut, Copy,
9183 * Paste, Replace and Share actions, depending on what this View supports.
9185 * A custom implementation can add new entries in the default menu in its
9186 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
9187 * default actions can also be removed from the menu using
9188 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9189 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9190 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
9192 * Returning false from
9193 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
9194 * the action mode from being started.
9196 * Action click events should be handled by the custom implementation of
9197 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
9199 * Note that text selection mode is not started when a TextView receives focus and the
9200 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9201 * that case, to allow for quick replacement.
9203 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9204 createEditorIfNeeded();
9205 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
9209 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9211 * @return The current custom selection callback.
9213 public ActionMode.Callback getCustomSelectionActionModeCallback() {
9214 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
9218 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9219 * insertion is initiated in this View.
9220 * The standard implementation populates the menu with a subset of Select All,
9221 * Paste and Replace actions, depending on what this View supports.
9223 * <p>A custom implementation can add new entries in the default menu in its
9224 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9225 * android.view.Menu)} method. The default actions can also be removed from the menu using
9226 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9227 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
9229 * <p>Returning false from
9230 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9231 * android.view.Menu)} will prevent the action mode from being started.</p>
9233 * <p>Action click events should be handled by the custom implementation of
9234 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9235 * android.view.MenuItem)}.</p>
9237 * <p>Note that text insertion mode is not started when a TextView receives focus and the
9238 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
9240 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9241 createEditorIfNeeded();
9242 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9246 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9248 * @return The current custom insertion callback.
9250 public ActionMode.Callback getCustomInsertionActionModeCallback() {
9251 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9257 protected void stopTextActionMode() {
9258 if (mEditor != null) {
9259 mEditor.stopTextActionMode();
9264 return mEditor != null && mEditor.canUndo();
9268 return mEditor != null && mEditor.canRedo();
9272 if (hasPasswordTransformationMethod()) {
9276 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9277 mEditor.mKeyListener != null) {
9285 if (hasPasswordTransformationMethod()) {
9289 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9296 boolean canShare() {
9300 boolean canPaste() {
9301 return (mText instanceof Editable &&
9302 mEditor != null && mEditor.mKeyListener != null &&
9303 getSelectionStart() >= 0 &&
9304 getSelectionEnd() >= 0 &&
9305 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9309 boolean canProcessText() {
9310 if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9311 || hasPasswordTransformationMethod()) {
9315 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9322 boolean canSelectAllText() {
9323 return canSelectText() && !hasPasswordTransformationMethod()
9324 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
9327 boolean selectAllText() {
9328 // Need to hide insert point cursor controller before settings selection, otherwise insert
9329 // point cursor controller obtains cursor update event and update cursor with cancelling
9331 if (mEditor != null) {
9332 mEditor.hideInsertionPointCursorController();
9334 final int length = mText.length();
9335 Selection.setSelection((Spannable) mText, 0, length);
9339 void replaceSelectionWithText(CharSequence text) {
9340 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9344 * Paste clipboard content between min and max positions.
9346 private void paste(int min, int max, boolean withFormatting) {
9347 ClipboardManager clipboard =
9348 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9349 ClipData clip = clipboard.getPrimaryClip();
9351 boolean didFirst = false;
9352 for (int i=0; i<clip.getItemCount(); i++) {
9353 final CharSequence paste;
9354 if (withFormatting) {
9355 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9357 // Get an item as text and remove all spans by toString().
9358 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9359 paste = (text instanceof Spanned) ? text.toString() : text;
9361 if (paste != null) {
9363 Selection.setSelection((Spannable) mText, max);
9364 ((Editable) mText).replace(min, max, paste);
9367 ((Editable) mText).insert(getSelectionEnd(), "\n");
9368 ((Editable) mText).insert(getSelectionEnd(), paste);
9372 stopTextActionMode();
9373 sLastCutCopyOrTextChangedTime = 0;
9377 private void shareSelectedText() {
9378 String selectedText = getSelectedText();
9379 if (selectedText != null && !selectedText.isEmpty()) {
9380 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9381 sharingIntent.setType("text/plain");
9382 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9383 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9384 getContext().startActivity(Intent.createChooser(sharingIntent, null));
9385 stopTextActionMode();
9389 private void setPrimaryClip(ClipData clip) {
9390 ClipboardManager clipboard = (ClipboardManager) getContext().
9391 getSystemService(Context.CLIPBOARD_SERVICE);
9392 clipboard.setPrimaryClip(clip);
9393 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
9397 * Get the character offset closest to the specified absolute position. A typical use case is to
9398 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9400 * @param x The horizontal absolute position of a point on screen
9401 * @param y The vertical absolute position of a point on screen
9402 * @return the character offset for the character whose position is closest to the specified
9403 * position. Returns -1 if there is no layout.
9405 public int getOffsetForPosition(float x, float y) {
9406 if (getLayout() == null) return -1;
9407 final int line = getLineAtCoordinate(y);
9408 final int offset = getOffsetAtCoordinate(line, x);
9412 float convertToLocalHorizontalCoordinate(float x) {
9413 x -= getTotalPaddingLeft();
9414 // Clamp the position to inside of the view.
9415 x = Math.max(0.0f, x);
9416 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9421 int getLineAtCoordinate(float y) {
9422 y -= getTotalPaddingTop();
9423 // Clamp the position to inside of the view.
9424 y = Math.max(0.0f, y);
9425 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9427 return getLayout().getLineForVertical((int) y);
9430 int getOffsetAtCoordinate(int line, float x) {
9431 x = convertToLocalHorizontalCoordinate(x);
9432 return getLayout().getOffsetForHorizontal(line, x);
9436 public boolean onDragEvent(DragEvent event) {
9437 switch (event.getAction()) {
9438 case DragEvent.ACTION_DRAG_STARTED:
9439 return mEditor != null && mEditor.hasInsertionController();
9441 case DragEvent.ACTION_DRAG_ENTERED:
9442 TextView.this.requestFocus();
9445 case DragEvent.ACTION_DRAG_LOCATION:
9446 final int offset = getOffsetForPosition(event.getX(), event.getY());
9447 Selection.setSelection((Spannable)mText, offset);
9450 case DragEvent.ACTION_DROP:
9451 if (mEditor != null) mEditor.onDrop(event);
9454 case DragEvent.ACTION_DRAG_ENDED:
9455 case DragEvent.ACTION_DRAG_EXITED:
9461 boolean isInBatchEditMode() {
9462 if (mEditor == null) return false;
9463 final Editor.InputMethodState ims = mEditor.mInputMethodState;
9465 return ims.mBatchEditNesting > 0;
9467 return mEditor.mInBatchEditControllers;
9471 public void onRtlPropertiesChanged(int layoutDirection) {
9472 super.onRtlPropertiesChanged(layoutDirection);
9474 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9475 if (mTextDir != newTextDir) {
9476 mTextDir = newTextDir;
9477 if (mLayout != null) {
9483 TextDirectionHeuristic getTextDirectionHeuristic() {
9484 if (hasPasswordTransformationMethod()) {
9485 // passwords fields should be LTR
9486 return TextDirectionHeuristics.LTR;
9489 // Always need to resolve layout direction first
9490 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
9492 // Now, we can select the heuristic
9493 switch (getTextDirection()) {
9495 case TEXT_DIRECTION_FIRST_STRONG:
9496 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
9497 TextDirectionHeuristics.FIRSTSTRONG_LTR);
9498 case TEXT_DIRECTION_ANY_RTL:
9499 return TextDirectionHeuristics.ANYRTL_LTR;
9500 case TEXT_DIRECTION_LTR:
9501 return TextDirectionHeuristics.LTR;
9502 case TEXT_DIRECTION_RTL:
9503 return TextDirectionHeuristics.RTL;
9504 case TEXT_DIRECTION_LOCALE:
9505 return TextDirectionHeuristics.LOCALE;
9506 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9507 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9508 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9509 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
9517 public void onResolveDrawables(int layoutDirection) {
9518 // No need to resolve twice
9519 if (mLastLayoutDirection == layoutDirection) {
9522 mLastLayoutDirection = layoutDirection;
9524 // Resolve drawables
9525 if (mDrawables != null) {
9526 mDrawables.resolveWithLayoutDirection(layoutDirection);
9533 protected void resetResolvedDrawables() {
9534 super.resetResolvedDrawables();
9535 mLastLayoutDirection = -1;
9541 protected void viewClicked(InputMethodManager imm) {
9543 imm.viewClicked(this);
9548 * Deletes the range of text [start, end[.
9551 protected void deleteText_internal(int start, int end) {
9552 ((Editable) mText).delete(start, end);
9556 * Replaces the range of text [start, end[ by replacement text
9559 protected void replaceText_internal(int start, int end, CharSequence text) {
9560 ((Editable) mText).replace(start, end, text);
9564 * Sets a span on the specified range of text
9567 protected void setSpan_internal(Object span, int start, int end, int flags) {
9568 ((Editable) mText).setSpan(span, start, end, flags);
9572 * Moves the cursor to the specified offset position in text
9575 protected void setCursorPosition_internal(int start, int end) {
9576 Selection.setSelection(((Editable) mText), start, end);
9580 * An Editor should be created as soon as any of the editable-specific fields (grouped
9581 * inside the Editor object) is assigned to a non-default value.
9582 * This method will create the Editor if needed.
9584 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9585 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9586 * Editor for backward compatibility, as soon as one of these fields is assigned.
9588 * Also note that for performance reasons, the mEditor is created when needed, but not
9589 * reset when no more edit-specific fields are needed.
9591 private void createEditorIfNeeded() {
9592 if (mEditor == null) {
9593 mEditor = new Editor(this);
9601 public CharSequence getIterableTextForAccessibility() {
9605 private void ensureIterableTextForAccessibilitySelectable() {
9606 if (!(mText instanceof Spannable)) {
9607 setText(mText, BufferType.SPANNABLE);
9615 public TextSegmentIterator getIteratorForGranularity(int granularity) {
9616 switch (granularity) {
9617 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9618 Spannable text = (Spannable) getIterableTextForAccessibility();
9619 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9620 AccessibilityIterators.LineTextSegmentIterator iterator =
9621 AccessibilityIterators.LineTextSegmentIterator.getInstance();
9622 iterator.initialize(text, getLayout());
9626 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9627 Spannable text = (Spannable) getIterableTextForAccessibility();
9628 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9629 AccessibilityIterators.PageTextSegmentIterator iterator =
9630 AccessibilityIterators.PageTextSegmentIterator.getInstance();
9631 iterator.initialize(this);
9636 return super.getIteratorForGranularity(granularity);
9643 public int getAccessibilitySelectionStart() {
9644 return getSelectionStart();
9650 public boolean isAccessibilitySelectionExtendable() {
9658 public int getAccessibilitySelectionEnd() {
9659 return getSelectionEnd();
9666 public void setAccessibilitySelection(int start, int end) {
9667 if (getAccessibilitySelectionStart() == start
9668 && getAccessibilitySelectionEnd() == end) {
9671 // Hide all selection controllers used for adjusting selection
9672 // since we are doing so explicitlty by other means and these
9673 // controllers interact with how selection behaves.
9674 if (mEditor != null) {
9675 mEditor.hideCursorAndSpanControllers();
9676 mEditor.stopTextActionMode();
9678 CharSequence text = getIterableTextForAccessibility();
9679 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
9680 Selection.setSelection((Spannable) text, start, end);
9682 Selection.removeSelection((Spannable) text);
9688 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
9689 super.encodeProperties(stream);
9691 TruncateAt ellipsize = getEllipsize();
9692 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
9693 stream.addProperty("text:textSize", getTextSize());
9694 stream.addProperty("text:scaledTextSize", getScaledTextSize());
9695 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
9696 stream.addProperty("text:selectionStart", getSelectionStart());
9697 stream.addProperty("text:selectionEnd", getSelectionEnd());
9698 stream.addProperty("text:curTextColor", mCurTextColor);
9699 stream.addProperty("text:text", mText == null ? null : mText.toString());
9700 stream.addProperty("text:gravity", mGravity);
9704 * User interface state that is stored by TextView for implementing
9705 * {@link View#onSaveInstanceState}.
9707 public static class SavedState extends BaseSavedState {
9711 boolean frozenWithFocus;
9713 ParcelableParcel editorState; // Optional state from Editor.
9715 SavedState(Parcelable superState) {
9720 public void writeToParcel(Parcel out, int flags) {
9721 super.writeToParcel(out, flags);
9722 out.writeInt(selStart);
9723 out.writeInt(selEnd);
9724 out.writeInt(frozenWithFocus ? 1 : 0);
9725 TextUtils.writeToParcel(text, out, flags);
9727 if (error == null) {
9731 TextUtils.writeToParcel(error, out, flags);
9734 if (editorState == null) {
9738 editorState.writeToParcel(out, flags);
9743 public String toString() {
9744 String str = "TextView.SavedState{"
9745 + Integer.toHexString(System.identityHashCode(this))
9746 + " start=" + selStart + " end=" + selEnd;
9748 str += " text=" + text;
9753 @SuppressWarnings("hiding")
9754 public static final Parcelable.Creator<SavedState> CREATOR
9755 = new Parcelable.Creator<SavedState>() {
9756 public SavedState createFromParcel(Parcel in) {
9757 return new SavedState(in);
9760 public SavedState[] newArray(int size) {
9761 return new SavedState[size];
9765 private SavedState(Parcel in) {
9767 selStart = in.readInt();
9768 selEnd = in.readInt();
9769 frozenWithFocus = (in.readInt() != 0);
9770 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9772 if (in.readInt() != 0) {
9773 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9776 if (in.readInt() != 0) {
9777 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
9782 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
9783 private char[] mChars;
9784 private int mStart, mLength;
9786 public CharWrapper(char[] chars, int start, int len) {
9792 /* package */ void set(char[] chars, int start, int len) {
9798 public int length() {
9802 public char charAt(int off) {
9803 return mChars[off + mStart];
9807 public String toString() {
9808 return new String(mChars, mStart, mLength);
9811 public CharSequence subSequence(int start, int end) {
9812 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9813 throw new IndexOutOfBoundsException(start + ", " + end);
9816 return new String(mChars, start + mStart, end - start);
9819 public void getChars(int start, int end, char[] buf, int off) {
9820 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9821 throw new IndexOutOfBoundsException(start + ", " + end);
9824 System.arraycopy(mChars, start + mStart, buf, off, end - start);
9827 public void drawText(Canvas c, int start, int end,
9828 float x, float y, Paint p) {
9829 c.drawText(mChars, start + mStart, end - start, x, y, p);
9832 public void drawTextRun(Canvas c, int start, int end,
9833 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
9834 int count = end - start;
9835 int contextCount = contextEnd - contextStart;
9836 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
9837 contextCount, x, y, isRtl, p);
9840 public float measureText(int start, int end, Paint p) {
9841 return p.measureText(mChars, start + mStart, end - start);
9844 public int getTextWidths(int start, int end, float[] widths, Paint p) {
9845 return p.getTextWidths(mChars, start + mStart, end - start, widths);
9848 public float getTextRunAdvances(int start, int end, int contextStart,
9849 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
9851 int count = end - start;
9852 int contextCount = contextEnd - contextStart;
9853 return p.getTextRunAdvances(mChars, start + mStart, count,
9854 contextStart + mStart, contextCount, isRtl, advances,
9858 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
9859 int offset, int cursorOpt, Paint p) {
9860 int contextCount = contextEnd - contextStart;
9861 return p.getTextRunCursor(mChars, contextStart + mStart,
9862 contextCount, dir, offset + mStart, cursorOpt);
9866 private static final class Marquee {
9867 // TODO: Add an option to configure this
9868 private static final float MARQUEE_DELTA_MAX = 0.07f;
9869 private static final int MARQUEE_DELAY = 1200;
9870 private static final int MARQUEE_DP_PER_SECOND = 30;
9872 private static final byte MARQUEE_STOPPED = 0x0;
9873 private static final byte MARQUEE_STARTING = 0x1;
9874 private static final byte MARQUEE_RUNNING = 0x2;
9876 private final WeakReference<TextView> mView;
9877 private final Choreographer mChoreographer;
9879 private byte mStatus = MARQUEE_STOPPED;
9880 private final float mPixelsPerSecond;
9881 private float mMaxScroll;
9882 private float mMaxFadeScroll;
9883 private float mGhostStart;
9884 private float mGhostOffset;
9885 private float mFadeStop;
9886 private int mRepeatLimit;
9888 private float mScroll;
9889 private long mLastAnimationMs;
9891 Marquee(TextView v) {
9892 final float density = v.getContext().getResources().getDisplayMetrics().density;
9893 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
9894 mView = new WeakReference<TextView>(v);
9895 mChoreographer = Choreographer.getInstance();
9898 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
9900 public void doFrame(long frameTimeNanos) {
9905 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
9907 public void doFrame(long frameTimeNanos) {
9908 mStatus = MARQUEE_RUNNING;
9909 mLastAnimationMs = mChoreographer.getFrameTime();
9914 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
9916 public void doFrame(long frameTimeNanos) {
9917 if (mStatus == MARQUEE_RUNNING) {
9918 if (mRepeatLimit >= 0) {
9921 start(mRepeatLimit);
9927 if (mStatus != MARQUEE_RUNNING) {
9931 mChoreographer.removeFrameCallback(mTickCallback);
9933 final TextView textView = mView.get();
9934 if (textView != null && (textView.isFocused() || textView.isSelected())) {
9935 long currentMs = mChoreographer.getFrameTime();
9936 long deltaMs = currentMs - mLastAnimationMs;
9937 mLastAnimationMs = currentMs;
9938 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
9940 if (mScroll > mMaxScroll) {
9941 mScroll = mMaxScroll;
9942 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
9944 mChoreographer.postFrameCallback(mTickCallback);
9946 textView.invalidate();
9951 mStatus = MARQUEE_STOPPED;
9952 mChoreographer.removeFrameCallback(mStartCallback);
9953 mChoreographer.removeFrameCallback(mRestartCallback);
9954 mChoreographer.removeFrameCallback(mTickCallback);
9958 private void resetScroll() {
9960 final TextView textView = mView.get();
9961 if (textView != null) textView.invalidate();
9964 void start(int repeatLimit) {
9965 if (repeatLimit == 0) {
9969 mRepeatLimit = repeatLimit;
9970 final TextView textView = mView.get();
9971 if (textView != null && textView.mLayout != null) {
9972 mStatus = MARQUEE_STARTING;
9974 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
9975 textView.getCompoundPaddingRight();
9976 final float lineWidth = textView.mLayout.getLineWidth(0);
9977 final float gap = textWidth / 3.0f;
9978 mGhostStart = lineWidth - textWidth + gap;
9979 mMaxScroll = mGhostStart + textWidth;
9980 mGhostOffset = lineWidth + gap;
9981 mFadeStop = lineWidth + textWidth / 6.0f;
9982 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
9984 textView.invalidate();
9985 mChoreographer.postFrameCallback(mStartCallback);
9989 float getGhostOffset() {
9990 return mGhostOffset;
9997 float getMaxFadeScroll() {
9998 return mMaxFadeScroll;
10001 boolean shouldDrawLeftFade() {
10002 return mScroll <= mFadeStop;
10005 boolean shouldDrawGhost() {
10006 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10009 boolean isRunning() {
10010 return mStatus == MARQUEE_RUNNING;
10013 boolean isStopped() {
10014 return mStatus == MARQUEE_STOPPED;
10018 private class ChangeWatcher implements TextWatcher, SpanWatcher {
10020 private CharSequence mBeforeText;
10022 public void beforeTextChanged(CharSequence buffer, int start,
10023 int before, int after) {
10024 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10025 + " before=" + before + " after=" + after + ": " + buffer);
10027 if (AccessibilityManager.getInstance(mContext).isEnabled()
10028 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10029 || shouldSpeakPasswordsForAccessibility())) {
10030 mBeforeText = buffer.toString();
10033 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10036 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10037 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10038 + " before=" + before + " after=" + after + ": " + buffer);
10039 TextView.this.handleTextChanged(buffer, start, before, after);
10041 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10042 (isFocused() || isSelected() && isShown())) {
10043 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10044 mBeforeText = null;
10048 public void afterTextChanged(Editable buffer) {
10049 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10050 TextView.this.sendAfterTextChanged(buffer);
10052 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10053 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10057 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
10058 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10059 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10060 TextView.this.spanChange(buf, what, s, st, e, en);
10063 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10064 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10065 + " what=" + what + ": " + buf);
10066 TextView.this.spanChange(buf, what, -1, s, -1, e);
10069 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10070 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10071 + " what=" + what + ": " + buf);
10072 TextView.this.spanChange(buf, what, s, -1, e, -1);