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.os.AsyncTask;
50 import android.os.Bundle;
51 import android.os.Parcel;
52 import android.os.Parcelable;
53 import android.os.ParcelableParcel;
54 import android.os.SystemClock;
55 import android.os.UserHandle;
56 import android.provider.Settings;
57 import android.text.BoringLayout;
58 import android.text.DynamicLayout;
59 import android.text.Editable;
60 import android.text.GetChars;
61 import android.text.GraphicsOperations;
62 import android.text.InputFilter;
63 import android.text.InputType;
64 import android.text.Layout;
65 import android.text.ParcelableSpan;
66 import android.text.Selection;
67 import android.text.SpanWatcher;
68 import android.text.Spannable;
69 import android.text.SpannableString;
70 import android.text.SpannableStringBuilder;
71 import android.text.Spanned;
72 import android.text.SpannedString;
73 import android.text.StaticLayout;
74 import android.text.TextDirectionHeuristic;
75 import android.text.TextDirectionHeuristics;
76 import android.text.TextPaint;
77 import android.text.TextUtils;
78 import android.text.TextUtils.TruncateAt;
79 import android.text.TextWatcher;
80 import android.text.method.AllCapsTransformationMethod;
81 import android.text.method.ArrowKeyMovementMethod;
82 import android.text.method.DateKeyListener;
83 import android.text.method.DateTimeKeyListener;
84 import android.text.method.DialerKeyListener;
85 import android.text.method.DigitsKeyListener;
86 import android.text.method.KeyListener;
87 import android.text.method.LinkMovementMethod;
88 import android.text.method.MetaKeyKeyListener;
89 import android.text.method.MovementMethod;
90 import android.text.method.PasswordTransformationMethod;
91 import android.text.method.SingleLineTransformationMethod;
92 import android.text.method.TextKeyListener;
93 import android.text.method.TimeKeyListener;
94 import android.text.method.TransformationMethod;
95 import android.text.method.TransformationMethod2;
96 import android.text.method.WordIterator;
97 import android.text.style.CharacterStyle;
98 import android.text.style.ClickableSpan;
99 import android.text.style.ParagraphStyle;
100 import android.text.style.SpellCheckSpan;
101 import android.text.style.SuggestionSpan;
102 import android.text.style.URLSpan;
103 import android.text.style.UpdateAppearance;
104 import android.text.util.Linkify;
105 import android.util.AttributeSet;
106 import android.util.Log;
107 import android.util.TypedValue;
108 import android.view.AccessibilityIterators.TextSegmentIterator;
109 import android.view.ActionMode;
110 import android.view.Choreographer;
111 import android.view.DragEvent;
112 import android.view.Gravity;
113 import android.view.HapticFeedbackConstants;
114 import android.view.KeyCharacterMap;
115 import android.view.KeyEvent;
116 import android.view.MotionEvent;
117 import android.view.View;
118 import android.view.ViewParent;
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;
301 // Accessibility action start id for "process text" actions.
302 static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
307 static final int PROCESS_TEXT_REQUEST_CODE = 100;
309 // System wide time for last cut, copy or text changed action.
310 static long sLastCutCopyOrTextChangedTime;
312 private ColorStateList mTextColor;
313 private ColorStateList mHintTextColor;
314 private ColorStateList mLinkTextColor;
315 @ViewDebug.ExportedProperty(category = "text")
316 private int mCurTextColor;
317 private int mCurHintTextColor;
318 private boolean mFreezesText;
319 private boolean mDispatchTemporaryDetach;
321 /** Whether this view is temporarily detached from the parent view. */
322 boolean mTemporaryDetach;
324 private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
325 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
327 private float mShadowRadius, mShadowDx, mShadowDy;
328 private int mShadowColor;
330 private boolean mPreDrawRegistered;
331 private boolean mPreDrawListenerDetached;
333 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is
334 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse
335 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views
336 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by
337 // the user holding the movement key down) then we shouldn't prevent the focus from changing.
338 private boolean mPreventDefaultMovement;
340 private TextUtils.TruncateAt mEllipsize;
342 static class Drawables {
343 static final int LEFT = 0;
344 static final int TOP = 1;
345 static final int RIGHT = 2;
346 static final int BOTTOM = 3;
348 static final int DRAWABLE_NONE = -1;
349 static final int DRAWABLE_RIGHT = 0;
350 static final int DRAWABLE_LEFT = 1;
352 final Rect mCompoundRect = new Rect();
354 final Drawable[] mShowing = new Drawable[4];
356 ColorStateList mTintList;
357 PorterDuff.Mode mTintMode;
359 boolean mHasTintMode;
361 Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp;
362 Drawable mDrawableLeftInitial, mDrawableRightInitial;
364 boolean mIsRtlCompatibilityMode;
367 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight,
368 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp;
370 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight,
371 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp;
373 int mDrawablePadding;
375 int mDrawableSaved = DRAWABLE_NONE;
377 public Drawables(Context context) {
378 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
379 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 ||
380 !context.getApplicationInfo().hasRtlSupport());
384 public void resolveWithLayoutDirection(int layoutDirection) {
385 // First reset "left" and "right" drawables to their initial values
386 mShowing[Drawables.LEFT] = mDrawableLeftInitial;
387 mShowing[Drawables.RIGHT] = mDrawableRightInitial;
389 if (mIsRtlCompatibilityMode) {
390 // Use "start" drawable as "left" drawable if the "left" drawable was not defined
391 if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) {
392 mShowing[Drawables.LEFT] = mDrawableStart;
393 mDrawableSizeLeft = mDrawableSizeStart;
394 mDrawableHeightLeft = mDrawableHeightStart;
396 // Use "end" drawable as "right" drawable if the "right" drawable was not defined
397 if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) {
398 mShowing[Drawables.RIGHT] = mDrawableEnd;
399 mDrawableSizeRight = mDrawableSizeEnd;
400 mDrawableHeightRight = mDrawableHeightEnd;
403 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right"
404 // drawable if and only if they have been defined
405 switch(layoutDirection) {
406 case LAYOUT_DIRECTION_RTL:
408 mShowing[Drawables.RIGHT] = mDrawableStart;
409 mDrawableSizeRight = mDrawableSizeStart;
410 mDrawableHeightRight = mDrawableHeightStart;
412 mShowing[Drawables.LEFT] = mDrawableEnd;
413 mDrawableSizeLeft = mDrawableSizeEnd;
414 mDrawableHeightLeft = mDrawableHeightEnd;
418 case LAYOUT_DIRECTION_LTR:
421 mShowing[Drawables.LEFT] = mDrawableStart;
422 mDrawableSizeLeft = mDrawableSizeStart;
423 mDrawableHeightLeft = mDrawableHeightStart;
425 mShowing[Drawables.RIGHT] = mDrawableEnd;
426 mDrawableSizeRight = mDrawableSizeEnd;
427 mDrawableHeightRight = mDrawableHeightEnd;
432 applyErrorDrawableIfNeeded(layoutDirection);
433 updateDrawablesLayoutDirection(layoutDirection);
436 private void updateDrawablesLayoutDirection(int layoutDirection) {
437 for (Drawable dr : mShowing) {
439 dr.setLayoutDirection(layoutDirection);
444 public void setErrorDrawable(Drawable dr, TextView tv) {
445 if (mDrawableError != dr && mDrawableError != null) {
446 mDrawableError.setCallback(null);
450 if (mDrawableError != null) {
451 final Rect compoundRect = mCompoundRect;
452 final int[] state = tv.getDrawableState();
454 mDrawableError.setState(state);
455 mDrawableError.copyBounds(compoundRect);
456 mDrawableError.setCallback(tv);
457 mDrawableSizeError = compoundRect.width();
458 mDrawableHeightError = compoundRect.height();
460 mDrawableSizeError = mDrawableHeightError = 0;
464 private void applyErrorDrawableIfNeeded(int layoutDirection) {
465 // first restore the initial state if needed
466 switch (mDrawableSaved) {
468 mShowing[Drawables.LEFT] = mDrawableTemp;
469 mDrawableSizeLeft = mDrawableSizeTemp;
470 mDrawableHeightLeft = mDrawableHeightTemp;
473 mShowing[Drawables.RIGHT] = mDrawableTemp;
474 mDrawableSizeRight = mDrawableSizeTemp;
475 mDrawableHeightRight = mDrawableHeightTemp;
480 // then, if needed, assign the Error drawable to the correct location
481 if (mDrawableError != null) {
482 switch(layoutDirection) {
483 case LAYOUT_DIRECTION_RTL:
484 mDrawableSaved = DRAWABLE_LEFT;
486 mDrawableTemp = mShowing[Drawables.LEFT];
487 mDrawableSizeTemp = mDrawableSizeLeft;
488 mDrawableHeightTemp = mDrawableHeightLeft;
490 mShowing[Drawables.LEFT] = mDrawableError;
491 mDrawableSizeLeft = mDrawableSizeError;
492 mDrawableHeightLeft = mDrawableHeightError;
494 case LAYOUT_DIRECTION_LTR:
496 mDrawableSaved = DRAWABLE_RIGHT;
498 mDrawableTemp = mShowing[Drawables.RIGHT];
499 mDrawableSizeTemp = mDrawableSizeRight;
500 mDrawableHeightTemp = mDrawableHeightRight;
502 mShowing[Drawables.RIGHT] = mDrawableError;
503 mDrawableSizeRight = mDrawableSizeError;
504 mDrawableHeightRight = mDrawableHeightError;
511 Drawables mDrawables;
513 private CharWrapper mCharWrapper;
515 private Marquee mMarquee;
516 private boolean mRestartMarquee;
518 private int mMarqueeRepeatLimit = 3;
520 private int mLastLayoutDirection = -1;
523 * On some devices the fading edges add a performance penalty if used
524 * extensively in the same layout. This mode indicates how the marquee
525 * is currently being shown, if applicable. (mEllipsize will == MARQUEE)
527 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
530 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores
531 * the layout that should be used when the mode switches.
533 private Layout mSavedMarqueeModeLayout;
535 @ViewDebug.ExportedProperty(category = "text")
536 private CharSequence mText;
537 private CharSequence mTransformed;
538 private BufferType mBufferType = BufferType.NORMAL;
540 private CharSequence mHint;
541 private Layout mHintLayout;
543 private MovementMethod mMovement;
545 private TransformationMethod mTransformation;
546 private boolean mAllowTransformationLengthChange;
547 private ChangeWatcher mChangeWatcher;
549 private ArrayList<TextWatcher> mListeners;
551 // display attributes
552 private final TextPaint mTextPaint;
553 private boolean mUserSetTextScaleX;
554 private Layout mLayout;
555 private boolean mLocaleChanged = false;
557 @ViewDebug.ExportedProperty(category = "text")
558 private int mGravity = Gravity.TOP | Gravity.START;
559 private boolean mHorizontallyScrolling;
561 private int mAutoLinkMask;
562 private boolean mLinksClickable = true;
564 private float mSpacingMult = 1.0f;
565 private float mSpacingAdd = 0.0f;
567 private int mBreakStrategy;
568 private int mHyphenationFrequency;
570 private int mMaximum = Integer.MAX_VALUE;
571 private int mMaxMode = LINES;
572 private int mMinimum = 0;
573 private int mMinMode = LINES;
575 private int mOldMaximum = mMaximum;
576 private int mOldMaxMode = mMaxMode;
578 private int mMaxWidth = Integer.MAX_VALUE;
579 private int mMaxWidthMode = PIXELS;
580 private int mMinWidth = 0;
581 private int mMinWidthMode = PIXELS;
583 private boolean mSingleLine;
584 private int mDesiredHeightAtMeasure = -1;
585 private boolean mIncludePad = true;
586 private int mDeferScroll = -1;
588 // tmp primitives, so we don't alloc them on each draw
589 private Rect mTempRect;
590 private long mLastScroll;
591 private Scroller mScroller;
593 private BoringLayout.Metrics mBoring, mHintBoring;
594 private BoringLayout mSavedLayout, mSavedHintLayout;
596 private TextDirectionHeuristic mTextDir;
598 private InputFilter[] mFilters = NO_FILTERS;
600 private volatile Locale mCurrentSpellCheckerLocaleCache;
602 // It is possible to have a selection even when mEditor is null (programmatically set, like when
603 // a link is pressed). These highlight-related fields do not go in mEditor.
604 int mHighlightColor = 0x6633B5E5;
605 private Path mHighlightPath;
606 private final Paint mHighlightPaint;
607 private boolean mHighlightPathBogus = true;
609 private boolean mFirstTouch = false;
610 private long mLastTouchUpTime = 0;
612 // Although these fields are specific to editable text, they are not added to Editor because
613 // they are defined by the TextView's style and are theme-dependent.
614 int mCursorDrawableRes;
615 // These four fields, could be moved to Editor, since we know their default values and we
616 // could condition the creation of the Editor to a non standard value. This is however
617 // brittle since the hardcoded values here (such as
618 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
619 // default style is modified.
620 int mTextSelectHandleLeftRes;
621 int mTextSelectHandleRightRes;
622 int mTextSelectHandleRes;
623 int mTextEditSuggestionItemLayout;
626 * EditText specific data, created on demand when one of the Editor fields is used.
627 * See {@link #createEditorIfNeeded()}.
629 private Editor mEditor;
632 * Kick-start the font cache for the zygote process (to pay the cost of
633 * initializing freetype for our default font only once).
636 Paint p = new Paint();
637 p.setAntiAlias(true);
638 // We don't care about the result, just the side-effect of measuring.
643 * Interface definition for a callback to be invoked when an action is
644 * performed on the editor.
646 public interface OnEditorActionListener {
648 * Called when an action is being performed.
650 * @param v The view that was clicked.
651 * @param actionId Identifier of the action. This will be either the
652 * identifier you supplied, or {@link EditorInfo#IME_NULL
653 * EditorInfo.IME_NULL} if being called due to the enter key
655 * @param event If triggered by an enter key, this is the event;
656 * otherwise, this is null.
657 * @return Return true if you have consumed the action, else false.
659 boolean onEditorAction(TextView v, int actionId, KeyEvent event);
662 public TextView(Context context) {
666 public TextView(Context context, @Nullable AttributeSet attrs) {
667 this(context, attrs, com.android.internal.R.attr.textViewStyle);
670 public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
671 this(context, attrs, defStyleAttr, 0);
674 @SuppressWarnings("deprecation")
676 Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
677 super(context, attrs, defStyleAttr, defStyleRes);
681 final Resources res = getResources();
682 final CompatibilityInfo compat = res.getCompatibilityInfo();
684 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
685 mTextPaint.density = res.getDisplayMetrics().density;
686 mTextPaint.setCompatibilityScaling(compat.applicationScale);
688 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
689 mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
691 mMovement = getDefaultMovementMethod();
693 mTransformation = null;
695 int textColorHighlight = 0;
696 ColorStateList textColor = null;
697 ColorStateList textColorHint = null;
698 ColorStateList textColorLink = null;
700 String fontFamily = null;
701 boolean fontFamilyExplicit = false;
702 int typefaceIndex = -1;
704 boolean allCaps = false;
706 float dx = 0, dy = 0, r = 0;
707 boolean elegant = false;
708 float letterSpacing = 0;
709 String fontFeatureSettings = null;
710 mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
711 mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
713 final Resources.Theme theme = context.getTheme();
716 * Look the appearance up without checking first if it exists because
717 * almost every TextView has one and it greatly simplifies the logic
718 * to be able to parse the appearance first and then let specific tags
719 * for this View override it.
721 TypedArray a = theme.obtainStyledAttributes(attrs,
722 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes);
723 TypedArray appearance = null;
724 int ap = a.getResourceId(
725 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1);
728 appearance = theme.obtainStyledAttributes(
729 ap, com.android.internal.R.styleable.TextAppearance);
731 if (appearance != null) {
732 int n = appearance.getIndexCount();
733 for (int i = 0; i < n; i++) {
734 int attr = appearance.getIndex(i);
737 case com.android.internal.R.styleable.TextAppearance_textColorHighlight:
738 textColorHighlight = appearance.getColor(attr, textColorHighlight);
741 case com.android.internal.R.styleable.TextAppearance_textColor:
742 textColor = appearance.getColorStateList(attr);
745 case com.android.internal.R.styleable.TextAppearance_textColorHint:
746 textColorHint = appearance.getColorStateList(attr);
749 case com.android.internal.R.styleable.TextAppearance_textColorLink:
750 textColorLink = appearance.getColorStateList(attr);
753 case com.android.internal.R.styleable.TextAppearance_textSize:
754 textSize = appearance.getDimensionPixelSize(attr, textSize);
757 case com.android.internal.R.styleable.TextAppearance_typeface:
758 typefaceIndex = appearance.getInt(attr, -1);
761 case com.android.internal.R.styleable.TextAppearance_fontFamily:
762 fontFamily = appearance.getString(attr);
765 case com.android.internal.R.styleable.TextAppearance_textStyle:
766 styleIndex = appearance.getInt(attr, -1);
769 case com.android.internal.R.styleable.TextAppearance_textAllCaps:
770 allCaps = appearance.getBoolean(attr, false);
773 case com.android.internal.R.styleable.TextAppearance_shadowColor:
774 shadowcolor = appearance.getInt(attr, 0);
777 case com.android.internal.R.styleable.TextAppearance_shadowDx:
778 dx = appearance.getFloat(attr, 0);
781 case com.android.internal.R.styleable.TextAppearance_shadowDy:
782 dy = appearance.getFloat(attr, 0);
785 case com.android.internal.R.styleable.TextAppearance_shadowRadius:
786 r = appearance.getFloat(attr, 0);
789 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight:
790 elegant = appearance.getBoolean(attr, false);
793 case com.android.internal.R.styleable.TextAppearance_letterSpacing:
794 letterSpacing = appearance.getFloat(attr, 0);
797 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings:
798 fontFeatureSettings = appearance.getString(attr);
803 appearance.recycle();
806 boolean editable = getDefaultEditable();
807 CharSequence inputMethod = null;
809 CharSequence digits = null;
810 boolean phone = false;
811 boolean autotext = false;
814 boolean selectallonfocus = false;
815 Drawable drawableLeft = null, drawableTop = null, drawableRight = null,
816 drawableBottom = null, drawableStart = null, drawableEnd = null;
817 ColorStateList drawableTint = null;
818 PorterDuff.Mode drawableTintMode = null;
819 int drawablePadding = 0;
821 boolean singleLine = false;
823 CharSequence text = "";
824 CharSequence hint = null;
825 boolean password = false;
826 int inputType = EditorInfo.TYPE_NULL;
828 a = theme.obtainStyledAttributes(
829 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);
831 int n = a.getIndexCount();
832 for (int i = 0; i < n; i++) {
833 int attr = a.getIndex(i);
836 case com.android.internal.R.styleable.TextView_editable:
837 editable = a.getBoolean(attr, editable);
840 case com.android.internal.R.styleable.TextView_inputMethod:
841 inputMethod = a.getText(attr);
844 case com.android.internal.R.styleable.TextView_numeric:
845 numeric = a.getInt(attr, numeric);
848 case com.android.internal.R.styleable.TextView_digits:
849 digits = a.getText(attr);
852 case com.android.internal.R.styleable.TextView_phoneNumber:
853 phone = a.getBoolean(attr, phone);
856 case com.android.internal.R.styleable.TextView_autoText:
857 autotext = a.getBoolean(attr, autotext);
860 case com.android.internal.R.styleable.TextView_capitalize:
861 autocap = a.getInt(attr, autocap);
864 case com.android.internal.R.styleable.TextView_bufferType:
865 buffertype = a.getInt(attr, buffertype);
868 case com.android.internal.R.styleable.TextView_selectAllOnFocus:
869 selectallonfocus = a.getBoolean(attr, selectallonfocus);
872 case com.android.internal.R.styleable.TextView_autoLink:
873 mAutoLinkMask = a.getInt(attr, 0);
876 case com.android.internal.R.styleable.TextView_linksClickable:
877 mLinksClickable = a.getBoolean(attr, true);
880 case com.android.internal.R.styleable.TextView_drawableLeft:
881 drawableLeft = a.getDrawable(attr);
884 case com.android.internal.R.styleable.TextView_drawableTop:
885 drawableTop = a.getDrawable(attr);
888 case com.android.internal.R.styleable.TextView_drawableRight:
889 drawableRight = a.getDrawable(attr);
892 case com.android.internal.R.styleable.TextView_drawableBottom:
893 drawableBottom = a.getDrawable(attr);
896 case com.android.internal.R.styleable.TextView_drawableStart:
897 drawableStart = a.getDrawable(attr);
900 case com.android.internal.R.styleable.TextView_drawableEnd:
901 drawableEnd = a.getDrawable(attr);
904 case com.android.internal.R.styleable.TextView_drawableTint:
905 drawableTint = a.getColorStateList(attr);
908 case com.android.internal.R.styleable.TextView_drawableTintMode:
909 drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode);
912 case com.android.internal.R.styleable.TextView_drawablePadding:
913 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding);
916 case com.android.internal.R.styleable.TextView_maxLines:
917 setMaxLines(a.getInt(attr, -1));
920 case com.android.internal.R.styleable.TextView_maxHeight:
921 setMaxHeight(a.getDimensionPixelSize(attr, -1));
924 case com.android.internal.R.styleable.TextView_lines:
925 setLines(a.getInt(attr, -1));
928 case com.android.internal.R.styleable.TextView_height:
929 setHeight(a.getDimensionPixelSize(attr, -1));
932 case com.android.internal.R.styleable.TextView_minLines:
933 setMinLines(a.getInt(attr, -1));
936 case com.android.internal.R.styleable.TextView_minHeight:
937 setMinHeight(a.getDimensionPixelSize(attr, -1));
940 case com.android.internal.R.styleable.TextView_maxEms:
941 setMaxEms(a.getInt(attr, -1));
944 case com.android.internal.R.styleable.TextView_maxWidth:
945 setMaxWidth(a.getDimensionPixelSize(attr, -1));
948 case com.android.internal.R.styleable.TextView_ems:
949 setEms(a.getInt(attr, -1));
952 case com.android.internal.R.styleable.TextView_width:
953 setWidth(a.getDimensionPixelSize(attr, -1));
956 case com.android.internal.R.styleable.TextView_minEms:
957 setMinEms(a.getInt(attr, -1));
960 case com.android.internal.R.styleable.TextView_minWidth:
961 setMinWidth(a.getDimensionPixelSize(attr, -1));
964 case com.android.internal.R.styleable.TextView_gravity:
965 setGravity(a.getInt(attr, -1));
968 case com.android.internal.R.styleable.TextView_hint:
969 hint = a.getText(attr);
972 case com.android.internal.R.styleable.TextView_text:
973 text = a.getText(attr);
976 case com.android.internal.R.styleable.TextView_scrollHorizontally:
977 if (a.getBoolean(attr, false)) {
978 setHorizontallyScrolling(true);
982 case com.android.internal.R.styleable.TextView_singleLine:
983 singleLine = a.getBoolean(attr, singleLine);
986 case com.android.internal.R.styleable.TextView_ellipsize:
987 ellipsize = a.getInt(attr, ellipsize);
990 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit:
991 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit));
994 case com.android.internal.R.styleable.TextView_includeFontPadding:
995 if (!a.getBoolean(attr, true)) {
996 setIncludeFontPadding(false);
1000 case com.android.internal.R.styleable.TextView_cursorVisible:
1001 if (!a.getBoolean(attr, true)) {
1002 setCursorVisible(false);
1006 case com.android.internal.R.styleable.TextView_maxLength:
1007 maxlength = a.getInt(attr, -1);
1010 case com.android.internal.R.styleable.TextView_textScaleX:
1011 setTextScaleX(a.getFloat(attr, 1.0f));
1014 case com.android.internal.R.styleable.TextView_freezesText:
1015 mFreezesText = a.getBoolean(attr, false);
1018 case com.android.internal.R.styleable.TextView_shadowColor:
1019 shadowcolor = a.getInt(attr, 0);
1022 case com.android.internal.R.styleable.TextView_shadowDx:
1023 dx = a.getFloat(attr, 0);
1026 case com.android.internal.R.styleable.TextView_shadowDy:
1027 dy = a.getFloat(attr, 0);
1030 case com.android.internal.R.styleable.TextView_shadowRadius:
1031 r = a.getFloat(attr, 0);
1034 case com.android.internal.R.styleable.TextView_enabled:
1035 setEnabled(a.getBoolean(attr, isEnabled()));
1038 case com.android.internal.R.styleable.TextView_textColorHighlight:
1039 textColorHighlight = a.getColor(attr, textColorHighlight);
1042 case com.android.internal.R.styleable.TextView_textColor:
1043 textColor = a.getColorStateList(attr);
1046 case com.android.internal.R.styleable.TextView_textColorHint:
1047 textColorHint = a.getColorStateList(attr);
1050 case com.android.internal.R.styleable.TextView_textColorLink:
1051 textColorLink = a.getColorStateList(attr);
1054 case com.android.internal.R.styleable.TextView_textSize:
1055 textSize = a.getDimensionPixelSize(attr, textSize);
1058 case com.android.internal.R.styleable.TextView_typeface:
1059 typefaceIndex = a.getInt(attr, typefaceIndex);
1062 case com.android.internal.R.styleable.TextView_textStyle:
1063 styleIndex = a.getInt(attr, styleIndex);
1066 case com.android.internal.R.styleable.TextView_fontFamily:
1067 fontFamily = a.getString(attr);
1068 fontFamilyExplicit = true;
1071 case com.android.internal.R.styleable.TextView_password:
1072 password = a.getBoolean(attr, password);
1075 case com.android.internal.R.styleable.TextView_lineSpacingExtra:
1076 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd);
1079 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier:
1080 mSpacingMult = a.getFloat(attr, mSpacingMult);
1083 case com.android.internal.R.styleable.TextView_inputType:
1084 inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
1087 case com.android.internal.R.styleable.TextView_allowUndo:
1088 createEditorIfNeeded();
1089 mEditor.mAllowUndo = a.getBoolean(attr, true);
1092 case com.android.internal.R.styleable.TextView_imeOptions:
1093 createEditorIfNeeded();
1094 mEditor.createInputContentTypeIfNeeded();
1095 mEditor.mInputContentType.imeOptions = a.getInt(attr,
1096 mEditor.mInputContentType.imeOptions);
1099 case com.android.internal.R.styleable.TextView_imeActionLabel:
1100 createEditorIfNeeded();
1101 mEditor.createInputContentTypeIfNeeded();
1102 mEditor.mInputContentType.imeActionLabel = a.getText(attr);
1105 case com.android.internal.R.styleable.TextView_imeActionId:
1106 createEditorIfNeeded();
1107 mEditor.createInputContentTypeIfNeeded();
1108 mEditor.mInputContentType.imeActionId = a.getInt(attr,
1109 mEditor.mInputContentType.imeActionId);
1112 case com.android.internal.R.styleable.TextView_privateImeOptions:
1113 setPrivateImeOptions(a.getString(attr));
1116 case com.android.internal.R.styleable.TextView_editorExtras:
1118 setInputExtras(a.getResourceId(attr, 0));
1119 } catch (XmlPullParserException e) {
1120 Log.w(LOG_TAG, "Failure reading input extras", e);
1121 } catch (IOException e) {
1122 Log.w(LOG_TAG, "Failure reading input extras", e);
1126 case com.android.internal.R.styleable.TextView_textCursorDrawable:
1127 mCursorDrawableRes = a.getResourceId(attr, 0);
1130 case com.android.internal.R.styleable.TextView_textSelectHandleLeft:
1131 mTextSelectHandleLeftRes = a.getResourceId(attr, 0);
1134 case com.android.internal.R.styleable.TextView_textSelectHandleRight:
1135 mTextSelectHandleRightRes = a.getResourceId(attr, 0);
1138 case com.android.internal.R.styleable.TextView_textSelectHandle:
1139 mTextSelectHandleRes = a.getResourceId(attr, 0);
1142 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout:
1143 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0);
1146 case com.android.internal.R.styleable.TextView_textIsSelectable:
1147 setTextIsSelectable(a.getBoolean(attr, false));
1150 case com.android.internal.R.styleable.TextView_textAllCaps:
1151 allCaps = a.getBoolean(attr, false);
1154 case com.android.internal.R.styleable.TextView_elegantTextHeight:
1155 elegant = a.getBoolean(attr, false);
1158 case com.android.internal.R.styleable.TextView_letterSpacing:
1159 letterSpacing = a.getFloat(attr, 0);
1162 case com.android.internal.R.styleable.TextView_fontFeatureSettings:
1163 fontFeatureSettings = a.getString(attr);
1166 case com.android.internal.R.styleable.TextView_breakStrategy:
1167 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
1170 case com.android.internal.R.styleable.TextView_hyphenationFrequency:
1171 mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
1177 BufferType bufferType = BufferType.EDITABLE;
1179 final int variation =
1180 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
1181 final boolean passwordInputType = variation
1182 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
1183 final boolean webPasswordInputType = variation
1184 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD);
1185 final boolean numberPasswordInputType = variation
1186 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
1188 if (inputMethod != null) {
1192 c = Class.forName(inputMethod.toString());
1193 } catch (ClassNotFoundException ex) {
1194 throw new RuntimeException(ex);
1198 createEditorIfNeeded();
1199 mEditor.mKeyListener = (KeyListener) c.newInstance();
1200 } catch (InstantiationException ex) {
1201 throw new RuntimeException(ex);
1202 } catch (IllegalAccessException ex) {
1203 throw new RuntimeException(ex);
1206 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1208 : mEditor.mKeyListener.getInputType();
1209 } catch (IncompatibleClassChangeError e) {
1210 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1212 } else if (digits != null) {
1213 createEditorIfNeeded();
1214 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString());
1215 // If no input type was specified, we will default to generic
1216 // text, since we can't tell the IME about the set of digits
1217 // that was selected.
1218 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL
1219 ? inputType : EditorInfo.TYPE_CLASS_TEXT;
1220 } else if (inputType != EditorInfo.TYPE_NULL) {
1221 setInputType(inputType, true);
1222 // If set, the input type overrides what was set using the deprecated singleLine flag.
1223 singleLine = !isMultilineInputType(inputType);
1225 createEditorIfNeeded();
1226 mEditor.mKeyListener = DialerKeyListener.getInstance();
1227 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
1228 } else if (numeric != 0) {
1229 createEditorIfNeeded();
1230 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
1231 (numeric & DECIMAL) != 0);
1232 inputType = EditorInfo.TYPE_CLASS_NUMBER;
1233 if ((numeric & SIGNED) != 0) {
1234 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED;
1236 if ((numeric & DECIMAL) != 0) {
1237 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
1239 mEditor.mInputType = inputType;
1240 } else if (autotext || autocap != -1) {
1241 TextKeyListener.Capitalize cap;
1243 inputType = EditorInfo.TYPE_CLASS_TEXT;
1247 cap = TextKeyListener.Capitalize.SENTENCES;
1248 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
1252 cap = TextKeyListener.Capitalize.WORDS;
1253 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
1257 cap = TextKeyListener.Capitalize.CHARACTERS;
1258 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS;
1262 cap = TextKeyListener.Capitalize.NONE;
1266 createEditorIfNeeded();
1267 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap);
1268 mEditor.mInputType = inputType;
1269 } else if (isTextSelectable()) {
1270 // Prevent text changes from keyboard.
1271 if (mEditor != null) {
1272 mEditor.mKeyListener = null;
1273 mEditor.mInputType = EditorInfo.TYPE_NULL;
1275 bufferType = BufferType.SPANNABLE;
1276 // So that selection can be changed using arrow keys and touch is handled.
1277 setMovementMethod(ArrowKeyMovementMethod.getInstance());
1278 } else if (editable) {
1279 createEditorIfNeeded();
1280 mEditor.mKeyListener = TextKeyListener.getInstance();
1281 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1283 if (mEditor != null) mEditor.mKeyListener = null;
1285 switch (buffertype) {
1287 bufferType = BufferType.NORMAL;
1290 bufferType = BufferType.SPANNABLE;
1293 bufferType = BufferType.EDITABLE;
1298 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType,
1299 webPasswordInputType, numberPasswordInputType);
1301 if (selectallonfocus) {
1302 createEditorIfNeeded();
1303 mEditor.mSelectAllOnFocus = true;
1305 if (bufferType == BufferType.NORMAL)
1306 bufferType = BufferType.SPANNABLE;
1309 // Set up the tint (if needed) before setting the drawables so that it
1310 // gets applied correctly.
1311 if (drawableTint != null || drawableTintMode != null) {
1312 if (mDrawables == null) {
1313 mDrawables = new Drawables(context);
1315 if (drawableTint != null) {
1316 mDrawables.mTintList = drawableTint;
1317 mDrawables.mHasTint = true;
1319 if (drawableTintMode != null) {
1320 mDrawables.mTintMode = drawableTintMode;
1321 mDrawables.mHasTintMode = true;
1325 // This call will save the initial left/right drawables
1326 setCompoundDrawablesWithIntrinsicBounds(
1327 drawableLeft, drawableTop, drawableRight, drawableBottom);
1328 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd);
1329 setCompoundDrawablePadding(drawablePadding);
1331 // Same as setSingleLine(), but make sure the transformation method and the maximum number
1332 // of lines of height are unchanged for multi-line TextViews.
1333 setInputTypeSingleLine(singleLine);
1334 applySingleLine(singleLine, singleLine, singleLine);
1336 if (singleLine && getKeyListener() == null && ellipsize < 0) {
1337 ellipsize = 3; // END
1340 switch (ellipsize) {
1342 setEllipsize(TextUtils.TruncateAt.START);
1345 setEllipsize(TextUtils.TruncateAt.MIDDLE);
1348 setEllipsize(TextUtils.TruncateAt.END);
1351 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) {
1352 setHorizontalFadingEdgeEnabled(true);
1353 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
1355 setHorizontalFadingEdgeEnabled(false);
1356 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
1358 setEllipsize(TextUtils.TruncateAt.MARQUEE);
1362 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000));
1363 setHintTextColor(textColorHint);
1364 setLinkTextColor(textColorLink);
1365 if (textColorHighlight != 0) {
1366 setHighlightColor(textColorHighlight);
1368 setRawTextSize(textSize);
1369 setElegantTextHeight(elegant);
1370 setLetterSpacing(letterSpacing);
1371 setFontFeatureSettings(fontFeatureSettings);
1374 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
1377 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
1378 setTransformationMethod(PasswordTransformationMethod.getInstance());
1379 typefaceIndex = MONOSPACE;
1380 } else if (mEditor != null &&
1381 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
1382 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
1383 typefaceIndex = MONOSPACE;
1386 if (typefaceIndex != -1 && !fontFamilyExplicit) {
1389 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
1391 if (shadowcolor != 0) {
1392 setShadowLayer(r, dx, dy, shadowcolor);
1395 if (maxlength >= 0) {
1396 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) });
1398 setFilters(NO_FILTERS);
1401 setText(text, bufferType);
1402 if (hint != null) setHint(hint);
1405 * Views are not normally focusable unless specified to be.
1406 * However, TextViews that have input or movement methods *are*
1407 * focusable by default.
1409 a = context.obtainStyledAttributes(
1410 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes);
1412 boolean focusable = mMovement != null || getKeyListener() != null;
1413 boolean clickable = focusable || isClickable();
1414 boolean longClickable = focusable || isLongClickable();
1416 n = a.getIndexCount();
1417 for (int i = 0; i < n; i++) {
1418 int attr = a.getIndex(i);
1421 case com.android.internal.R.styleable.View_focusable:
1422 focusable = a.getBoolean(attr, focusable);
1425 case com.android.internal.R.styleable.View_clickable:
1426 clickable = a.getBoolean(attr, clickable);
1429 case com.android.internal.R.styleable.View_longClickable:
1430 longClickable = a.getBoolean(attr, longClickable);
1436 setFocusable(focusable);
1437 setClickable(clickable);
1438 setLongClickable(longClickable);
1440 if (mEditor != null) mEditor.prepareCursorControllers();
1442 // If not explicitly specified this view is important for accessibility.
1443 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
1444 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
1448 private int[] parseDimensionArray(TypedArray dimens) {
1449 if (dimens == null) {
1452 int[] result = new int[dimens.length()];
1453 for (int i = 0; i < result.length; i++) {
1454 result[i] = dimens.getDimensionPixelSize(i, 0);
1463 public void onActivityResult(int requestCode, int resultCode, Intent data) {
1464 if (requestCode == PROCESS_TEXT_REQUEST_CODE) {
1465 if (resultCode == Activity.RESULT_OK && data != null) {
1466 CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
1467 if (result != null) {
1468 if (isTextEditable()) {
1469 replaceSelectionWithText(result);
1471 if (result.length() > 0) {
1472 Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
1478 if (mEditor.hasSelectionController()) {
1479 mEditor.startSelectionActionMode();
1484 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) {
1486 if (familyName != null) {
1487 tf = Typeface.create(familyName, styleIndex);
1493 switch (typefaceIndex) {
1495 tf = Typeface.SANS_SERIF;
1499 tf = Typeface.SERIF;
1503 tf = Typeface.MONOSPACE;
1507 setTypeface(tf, styleIndex);
1510 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) {
1511 boolean hasRelativeDrawables = (start != null) || (end != null);
1512 if (hasRelativeDrawables) {
1513 Drawables dr = mDrawables;
1515 mDrawables = dr = new Drawables(getContext());
1517 mDrawables.mOverride = true;
1518 final Rect compoundRect = dr.mCompoundRect;
1519 int[] state = getDrawableState();
1520 if (start != null) {
1521 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
1522 start.setState(state);
1523 start.copyBounds(compoundRect);
1524 start.setCallback(this);
1526 dr.mDrawableStart = start;
1527 dr.mDrawableSizeStart = compoundRect.width();
1528 dr.mDrawableHeightStart = compoundRect.height();
1530 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
1533 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
1534 end.setState(state);
1535 end.copyBounds(compoundRect);
1536 end.setCallback(this);
1538 dr.mDrawableEnd = end;
1539 dr.mDrawableSizeEnd = compoundRect.width();
1540 dr.mDrawableHeightEnd = compoundRect.height();
1542 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
1544 resetResolvedDrawables();
1546 applyCompoundDrawableTint();
1551 public void setEnabled(boolean enabled) {
1552 if (enabled == isEnabled()) {
1557 // Hide the soft input if the currently active TextView is disabled
1558 InputMethodManager imm = InputMethodManager.peekInstance();
1559 if (imm != null && imm.isActive(this)) {
1560 imm.hideSoftInputFromWindow(getWindowToken(), 0);
1564 super.setEnabled(enabled);
1567 // Make sure IME is updated with current editor info.
1568 InputMethodManager imm = InputMethodManager.peekInstance();
1569 if (imm != null) imm.restartInput(this);
1572 // Will change text color
1573 if (mEditor != null) {
1574 mEditor.invalidateTextDisplayList();
1575 mEditor.prepareCursorControllers();
1577 // start or stop the cursor blinking as appropriate
1578 mEditor.makeBlink();
1583 * Sets the typeface and style in which the text should be displayed,
1584 * and turns on the fake bold and italic bits in the Paint if the
1585 * Typeface that you provided does not have all the bits in the
1586 * style that you specified.
1588 * @attr ref android.R.styleable#TextView_typeface
1589 * @attr ref android.R.styleable#TextView_textStyle
1591 public void setTypeface(Typeface tf, int style) {
1594 tf = Typeface.defaultFromStyle(style);
1596 tf = Typeface.create(tf, style);
1600 // now compute what (if any) algorithmic styling is needed
1601 int typefaceStyle = tf != null ? tf.getStyle() : 0;
1602 int need = style & ~typefaceStyle;
1603 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
1604 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
1606 mTextPaint.setFakeBoldText(false);
1607 mTextPaint.setTextSkewX(0);
1613 * Subclasses override this to specify that they have a KeyListener
1614 * by default even if not specifically called for in the XML options.
1616 protected boolean getDefaultEditable() {
1621 * Subclasses override this to specify a default movement method.
1623 protected MovementMethod getDefaultMovementMethod() {
1628 * Return the text the TextView is displaying. If setText() was called with
1629 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast
1630 * the return value from this method to Spannable or Editable, respectively.
1632 * Note: The content of the return value should not be modified. If you want
1633 * a modifiable one, you should make your own copy first.
1635 * @attr ref android.R.styleable#TextView_text
1637 @ViewDebug.CapturedViewProperty
1638 public CharSequence getText() {
1643 * Returns the length, in characters, of the text managed by this TextView
1645 public int length() {
1646 return mText.length();
1650 * Return the text the TextView is displaying as an Editable object. If
1651 * the text is not editable, null is returned.
1655 public Editable getEditableText() {
1656 return (mText instanceof Editable) ? (Editable)mText : null;
1660 * @return the height of one standard line in pixels. Note that markup
1661 * within the text can cause individual lines to be taller or shorter
1662 * than this height, and the layout may contain additional first-
1663 * or last-line padding.
1665 public int getLineHeight() {
1666 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd);
1670 * @return the Layout that is currently being used to display the text.
1671 * This can be null if the text or width has recently changes.
1673 public final Layout getLayout() {
1678 * @return the Layout that is currently being used to display the hint text.
1681 final Layout getHintLayout() {
1686 * Retrieve the {@link android.content.UndoManager} that is currently associated
1687 * with this TextView. By default there is no associated UndoManager, so null
1688 * is returned. One can be associated with the TextView through
1689 * {@link #setUndoManager(android.content.UndoManager, String)}
1693 public final UndoManager getUndoManager() {
1694 // TODO: Consider supporting a global undo manager.
1695 throw new UnsupportedOperationException("not implemented");
1699 * Associate an {@link android.content.UndoManager} with this TextView. Once
1700 * done, all edit operations on the TextView will result in appropriate
1701 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's
1704 * @param undoManager The {@link android.content.UndoManager} to associate with
1705 * this TextView, or null to clear any existing association.
1706 * @param tag String tag identifying this particular TextView owner in the
1707 * UndoManager. This is used to keep the correct association with the
1708 * {@link android.content.UndoOwner} of any operations inside of the UndoManager.
1712 public final void setUndoManager(UndoManager undoManager, String tag) {
1713 // TODO: Consider supporting a global undo manager. An implementation will need to:
1714 // * createEditorIfNeeded()
1715 // * Promote to BufferType.EDITABLE if needed.
1716 // * Update the UndoManager and UndoOwner.
1717 // Likewise it will need to be able to restore the default UndoManager.
1718 throw new UnsupportedOperationException("not implemented");
1722 * @return the current key listener for this TextView.
1723 * This will frequently be null for non-EditText TextViews.
1725 * @attr ref android.R.styleable#TextView_numeric
1726 * @attr ref android.R.styleable#TextView_digits
1727 * @attr ref android.R.styleable#TextView_phoneNumber
1728 * @attr ref android.R.styleable#TextView_inputMethod
1729 * @attr ref android.R.styleable#TextView_capitalize
1730 * @attr ref android.R.styleable#TextView_autoText
1732 public final KeyListener getKeyListener() {
1733 return mEditor == null ? null : mEditor.mKeyListener;
1737 * Sets the key listener to be used with this TextView. This can be null
1738 * to disallow user input. Note that this method has significant and
1739 * subtle interactions with soft keyboards and other input method:
1740 * see {@link KeyListener#getInputType() KeyListener.getContentType()}
1741 * for important details. Calling this method will replace the current
1742 * content type of the text view with the content type returned by the
1745 * Be warned that if you want a TextView with a key listener or movement
1746 * method not to be focusable, or if you want a TextView without a
1747 * key listener or movement method to be focusable, you must call
1748 * {@link #setFocusable} again after calling this to get the focusability
1749 * back the way you want it.
1751 * @attr ref android.R.styleable#TextView_numeric
1752 * @attr ref android.R.styleable#TextView_digits
1753 * @attr ref android.R.styleable#TextView_phoneNumber
1754 * @attr ref android.R.styleable#TextView_inputMethod
1755 * @attr ref android.R.styleable#TextView_capitalize
1756 * @attr ref android.R.styleable#TextView_autoText
1758 public void setKeyListener(KeyListener input) {
1759 setKeyListenerOnly(input);
1760 fixFocusableAndClickableSettings();
1762 if (input != null) {
1763 createEditorIfNeeded();
1765 mEditor.mInputType = mEditor.mKeyListener.getInputType();
1766 } catch (IncompatibleClassChangeError e) {
1767 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT;
1769 // Change inputType, without affecting transformation.
1770 // No need to applySingleLine since mSingleLine is unchanged.
1771 setInputTypeSingleLine(mSingleLine);
1773 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL;
1776 InputMethodManager imm = InputMethodManager.peekInstance();
1777 if (imm != null) imm.restartInput(this);
1780 private void setKeyListenerOnly(KeyListener input) {
1781 if (mEditor == null && input == null) return; // null is the default value
1783 createEditorIfNeeded();
1784 if (mEditor.mKeyListener != input) {
1785 mEditor.mKeyListener = input;
1786 if (input != null && !(mText instanceof Editable)) {
1790 setFilters((Editable) mText, mFilters);
1795 * @return the movement method being used for this TextView.
1796 * This will frequently be null for non-EditText TextViews.
1798 public final MovementMethod getMovementMethod() {
1803 * Sets the movement method (arrow key handler) to be used for
1804 * this TextView. This can be null to disallow using the arrow keys
1805 * to move the cursor or scroll the view.
1807 * Be warned that if you want a TextView with a key listener or movement
1808 * method not to be focusable, or if you want a TextView without a
1809 * key listener or movement method to be focusable, you must call
1810 * {@link #setFocusable} again after calling this to get the focusability
1811 * back the way you want it.
1813 public final void setMovementMethod(MovementMethod movement) {
1814 if (mMovement != movement) {
1815 mMovement = movement;
1817 if (movement != null && !(mText instanceof Spannable)) {
1821 fixFocusableAndClickableSettings();
1823 // SelectionModifierCursorController depends on textCanBeSelected, which depends on
1825 if (mEditor != null) mEditor.prepareCursorControllers();
1829 private void fixFocusableAndClickableSettings() {
1830 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) {
1833 setLongClickable(true);
1835 setFocusable(false);
1836 setClickable(false);
1837 setLongClickable(false);
1842 * @return the current transformation method for this TextView.
1843 * This will frequently be null except for single-line and password
1846 * @attr ref android.R.styleable#TextView_password
1847 * @attr ref android.R.styleable#TextView_singleLine
1849 public final TransformationMethod getTransformationMethod() {
1850 return mTransformation;
1854 * Sets the transformation that is applied to the text that this
1855 * TextView is displaying.
1857 * @attr ref android.R.styleable#TextView_password
1858 * @attr ref android.R.styleable#TextView_singleLine
1860 public final void setTransformationMethod(TransformationMethod method) {
1861 if (method == mTransformation) {
1862 // Avoid the setText() below if the transformation is
1866 if (mTransformation != null) {
1867 if (mText instanceof Spannable) {
1868 ((Spannable) mText).removeSpan(mTransformation);
1872 mTransformation = method;
1874 if (method instanceof TransformationMethod2) {
1875 TransformationMethod2 method2 = (TransformationMethod2) method;
1876 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
1877 method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
1879 mAllowTransformationLengthChange = false;
1884 if (hasPasswordTransformationMethod()) {
1885 notifyViewAccessibilityStateChangedIfNeeded(
1886 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
1891 * Returns the top padding of the view, plus space for the top
1894 public int getCompoundPaddingTop() {
1895 final Drawables dr = mDrawables;
1896 if (dr == null || dr.mShowing[Drawables.TOP] == null) {
1899 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop;
1904 * Returns the bottom padding of the view, plus space for the bottom
1907 public int getCompoundPaddingBottom() {
1908 final Drawables dr = mDrawables;
1909 if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) {
1910 return mPaddingBottom;
1912 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom;
1917 * Returns the left padding of the view, plus space for the left
1920 public int getCompoundPaddingLeft() {
1921 final Drawables dr = mDrawables;
1922 if (dr == null || dr.mShowing[Drawables.LEFT] == null) {
1923 return mPaddingLeft;
1925 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft;
1930 * Returns the right padding of the view, plus space for the right
1933 public int getCompoundPaddingRight() {
1934 final Drawables dr = mDrawables;
1935 if (dr == null || dr.mShowing[Drawables.RIGHT] == null) {
1936 return mPaddingRight;
1938 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight;
1943 * Returns the start padding of the view, plus space for the start
1946 public int getCompoundPaddingStart() {
1948 switch(getLayoutDirection()) {
1950 case LAYOUT_DIRECTION_LTR:
1951 return getCompoundPaddingLeft();
1952 case LAYOUT_DIRECTION_RTL:
1953 return getCompoundPaddingRight();
1958 * Returns the end padding of the view, plus space for the end
1961 public int getCompoundPaddingEnd() {
1963 switch(getLayoutDirection()) {
1965 case LAYOUT_DIRECTION_LTR:
1966 return getCompoundPaddingRight();
1967 case LAYOUT_DIRECTION_RTL:
1968 return getCompoundPaddingLeft();
1973 * Returns the extended top padding of the view, including both the
1974 * top Drawable if any and any extra space to keep more than maxLines
1975 * of text from showing. It is only valid to call this after measuring.
1977 public int getExtendedPaddingTop() {
1978 if (mMaxMode != LINES) {
1979 return getCompoundPaddingTop();
1982 if (mLayout == null) {
1986 if (mLayout.getLineCount() <= mMaximum) {
1987 return getCompoundPaddingTop();
1990 int top = getCompoundPaddingTop();
1991 int bottom = getCompoundPaddingBottom();
1992 int viewht = getHeight() - top - bottom;
1993 int layoutht = mLayout.getLineTop(mMaximum);
1995 if (layoutht >= viewht) {
1999 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2000 if (gravity == Gravity.TOP) {
2002 } else if (gravity == Gravity.BOTTOM) {
2003 return top + viewht - layoutht;
2004 } else { // (gravity == Gravity.CENTER_VERTICAL)
2005 return top + (viewht - layoutht) / 2;
2010 * Returns the extended bottom padding of the view, including both the
2011 * bottom Drawable if any and any extra space to keep more than maxLines
2012 * of text from showing. It is only valid to call this after measuring.
2014 public int getExtendedPaddingBottom() {
2015 if (mMaxMode != LINES) {
2016 return getCompoundPaddingBottom();
2019 if (mLayout == null) {
2023 if (mLayout.getLineCount() <= mMaximum) {
2024 return getCompoundPaddingBottom();
2027 int top = getCompoundPaddingTop();
2028 int bottom = getCompoundPaddingBottom();
2029 int viewht = getHeight() - top - bottom;
2030 int layoutht = mLayout.getLineTop(mMaximum);
2032 if (layoutht >= viewht) {
2036 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
2037 if (gravity == Gravity.TOP) {
2038 return bottom + viewht - layoutht;
2039 } else if (gravity == Gravity.BOTTOM) {
2041 } else { // (gravity == Gravity.CENTER_VERTICAL)
2042 return bottom + (viewht - layoutht) / 2;
2047 * Returns the total left padding of the view, including the left
2050 public int getTotalPaddingLeft() {
2051 return getCompoundPaddingLeft();
2055 * Returns the total right padding of the view, including the right
2058 public int getTotalPaddingRight() {
2059 return getCompoundPaddingRight();
2063 * Returns the total start padding of the view, including the start
2066 public int getTotalPaddingStart() {
2067 return getCompoundPaddingStart();
2071 * Returns the total end padding of the view, including the end
2074 public int getTotalPaddingEnd() {
2075 return getCompoundPaddingEnd();
2079 * Returns the total top padding of the view, including the top
2080 * Drawable if any, the extra space to keep more than maxLines
2081 * from showing, and the vertical offset for gravity, if any.
2083 public int getTotalPaddingTop() {
2084 return getExtendedPaddingTop() + getVerticalOffset(true);
2088 * Returns the total bottom padding of the view, including the bottom
2089 * Drawable if any, the extra space to keep more than maxLines
2090 * from showing, and the vertical offset for gravity, if any.
2092 public int getTotalPaddingBottom() {
2093 return getExtendedPaddingBottom() + getBottomVerticalOffset(true);
2097 * Sets the Drawables (if any) to appear to the left of, above, to the
2098 * right of, and below the text. Use {@code null} if you do not want a
2099 * Drawable there. The Drawables must already have had
2100 * {@link Drawable#setBounds} called.
2102 * Calling this method will overwrite any Drawables previously set using
2103 * {@link #setCompoundDrawablesRelative} or related methods.
2105 * @attr ref android.R.styleable#TextView_drawableLeft
2106 * @attr ref android.R.styleable#TextView_drawableTop
2107 * @attr ref android.R.styleable#TextView_drawableRight
2108 * @attr ref android.R.styleable#TextView_drawableBottom
2110 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top,
2111 @Nullable Drawable right, @Nullable Drawable bottom) {
2112 Drawables dr = mDrawables;
2114 // We're switching to absolute, discard relative.
2116 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2117 dr.mDrawableStart = null;
2118 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null);
2119 dr.mDrawableEnd = null;
2120 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2121 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2124 final boolean drawables = left != null || top != null || right != null || bottom != null;
2126 // Clearing drawables... can we free the data structure?
2128 if (dr.mDrawablePadding == 0) {
2131 // We need to retain the last set padding, so just clear
2132 // out all of the fields in the existing structure.
2133 for (int i = dr.mShowing.length - 1; i >= 0; i--) {
2134 if (dr.mShowing[i] != null) {
2135 dr.mShowing[i].setCallback(null);
2137 dr.mShowing[i] = null;
2139 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2140 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2141 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2142 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2147 mDrawables = dr = new Drawables(getContext());
2150 mDrawables.mOverride = false;
2152 if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) {
2153 dr.mShowing[Drawables.LEFT].setCallback(null);
2155 dr.mShowing[Drawables.LEFT] = left;
2157 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2158 dr.mShowing[Drawables.TOP].setCallback(null);
2160 dr.mShowing[Drawables.TOP] = top;
2162 if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) {
2163 dr.mShowing[Drawables.RIGHT].setCallback(null);
2165 dr.mShowing[Drawables.RIGHT] = right;
2167 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2168 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2170 dr.mShowing[Drawables.BOTTOM] = bottom;
2172 final Rect compoundRect = dr.mCompoundRect;
2175 state = getDrawableState();
2178 left.setState(state);
2179 left.copyBounds(compoundRect);
2180 left.setCallback(this);
2181 dr.mDrawableSizeLeft = compoundRect.width();
2182 dr.mDrawableHeightLeft = compoundRect.height();
2184 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2187 if (right != null) {
2188 right.setState(state);
2189 right.copyBounds(compoundRect);
2190 right.setCallback(this);
2191 dr.mDrawableSizeRight = compoundRect.width();
2192 dr.mDrawableHeightRight = compoundRect.height();
2194 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2198 top.setState(state);
2199 top.copyBounds(compoundRect);
2200 top.setCallback(this);
2201 dr.mDrawableSizeTop = compoundRect.height();
2202 dr.mDrawableWidthTop = compoundRect.width();
2204 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2207 if (bottom != null) {
2208 bottom.setState(state);
2209 bottom.copyBounds(compoundRect);
2210 bottom.setCallback(this);
2211 dr.mDrawableSizeBottom = compoundRect.height();
2212 dr.mDrawableWidthBottom = compoundRect.width();
2214 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2218 // Save initial left/right drawables
2220 dr.mDrawableLeftInitial = left;
2221 dr.mDrawableRightInitial = right;
2224 resetResolvedDrawables();
2226 applyCompoundDrawableTint();
2232 * Sets the Drawables (if any) to appear to the left of, above, to the
2233 * right of, and below the text. Use 0 if you do not want a Drawable there.
2234 * The Drawables' bounds will be set to their intrinsic bounds.
2236 * Calling this method will overwrite any Drawables previously set using
2237 * {@link #setCompoundDrawablesRelative} or related methods.
2239 * @param left Resource identifier of the left Drawable.
2240 * @param top Resource identifier of the top Drawable.
2241 * @param right Resource identifier of the right Drawable.
2242 * @param bottom Resource identifier of the bottom Drawable.
2244 * @attr ref android.R.styleable#TextView_drawableLeft
2245 * @attr ref android.R.styleable#TextView_drawableTop
2246 * @attr ref android.R.styleable#TextView_drawableRight
2247 * @attr ref android.R.styleable#TextView_drawableBottom
2249 @android.view.RemotableViewMethod
2250 public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left,
2251 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
2252 final Context context = getContext();
2253 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null,
2254 top != 0 ? context.getDrawable(top) : null,
2255 right != 0 ? context.getDrawable(right) : null,
2256 bottom != 0 ? context.getDrawable(bottom) : null);
2260 * Sets the Drawables (if any) to appear to the left of, above, to the
2261 * right of, and below the text. Use {@code null} if you do not want a
2262 * Drawable there. The Drawables' bounds will be set to their intrinsic
2265 * Calling this method will overwrite any Drawables previously set using
2266 * {@link #setCompoundDrawablesRelative} or related methods.
2268 * @attr ref android.R.styleable#TextView_drawableLeft
2269 * @attr ref android.R.styleable#TextView_drawableTop
2270 * @attr ref android.R.styleable#TextView_drawableRight
2271 * @attr ref android.R.styleable#TextView_drawableBottom
2273 @android.view.RemotableViewMethod
2274 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left,
2275 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) {
2278 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight());
2280 if (right != null) {
2281 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight());
2284 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2286 if (bottom != null) {
2287 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2289 setCompoundDrawables(left, top, right, bottom);
2293 * Sets the Drawables (if any) to appear to the start of, above, to the end
2294 * of, and below the text. Use {@code null} if you do not want a Drawable
2295 * there. The Drawables must already have had {@link Drawable#setBounds}
2298 * Calling this method will overwrite any Drawables previously set using
2299 * {@link #setCompoundDrawables} or related methods.
2301 * @attr ref android.R.styleable#TextView_drawableStart
2302 * @attr ref android.R.styleable#TextView_drawableTop
2303 * @attr ref android.R.styleable#TextView_drawableEnd
2304 * @attr ref android.R.styleable#TextView_drawableBottom
2306 @android.view.RemotableViewMethod
2307 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top,
2308 @Nullable Drawable end, @Nullable Drawable bottom) {
2309 Drawables dr = mDrawables;
2311 // We're switching to relative, discard absolute.
2313 if (dr.mShowing[Drawables.LEFT] != null) {
2314 dr.mShowing[Drawables.LEFT].setCallback(null);
2316 dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null;
2317 if (dr.mShowing[Drawables.RIGHT] != null) {
2318 dr.mShowing[Drawables.RIGHT].setCallback(null);
2320 dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null;
2321 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0;
2322 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0;
2325 final boolean drawables = start != null || top != null
2326 || end != null || bottom != null;
2329 // Clearing drawables... can we free the data structure?
2331 if (dr.mDrawablePadding == 0) {
2334 // We need to retain the last set padding, so just clear
2335 // out all of the fields in the existing structure.
2336 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null);
2337 dr.mDrawableStart = null;
2338 if (dr.mShowing[Drawables.TOP] != null) {
2339 dr.mShowing[Drawables.TOP].setCallback(null);
2341 dr.mShowing[Drawables.TOP] = null;
2342 if (dr.mDrawableEnd != null) {
2343 dr.mDrawableEnd.setCallback(null);
2345 dr.mDrawableEnd = null;
2346 if (dr.mShowing[Drawables.BOTTOM] != null) {
2347 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2349 dr.mShowing[Drawables.BOTTOM] = null;
2350 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2351 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2352 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2353 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2358 mDrawables = dr = new Drawables(getContext());
2361 mDrawables.mOverride = true;
2363 if (dr.mDrawableStart != start && dr.mDrawableStart != null) {
2364 dr.mDrawableStart.setCallback(null);
2366 dr.mDrawableStart = start;
2368 if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) {
2369 dr.mShowing[Drawables.TOP].setCallback(null);
2371 dr.mShowing[Drawables.TOP] = top;
2373 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) {
2374 dr.mDrawableEnd.setCallback(null);
2376 dr.mDrawableEnd = end;
2378 if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) {
2379 dr.mShowing[Drawables.BOTTOM].setCallback(null);
2381 dr.mShowing[Drawables.BOTTOM] = bottom;
2383 final Rect compoundRect = dr.mCompoundRect;
2386 state = getDrawableState();
2388 if (start != null) {
2389 start.setState(state);
2390 start.copyBounds(compoundRect);
2391 start.setCallback(this);
2392 dr.mDrawableSizeStart = compoundRect.width();
2393 dr.mDrawableHeightStart = compoundRect.height();
2395 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0;
2399 end.setState(state);
2400 end.copyBounds(compoundRect);
2401 end.setCallback(this);
2402 dr.mDrawableSizeEnd = compoundRect.width();
2403 dr.mDrawableHeightEnd = compoundRect.height();
2405 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0;
2409 top.setState(state);
2410 top.copyBounds(compoundRect);
2411 top.setCallback(this);
2412 dr.mDrawableSizeTop = compoundRect.height();
2413 dr.mDrawableWidthTop = compoundRect.width();
2415 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0;
2418 if (bottom != null) {
2419 bottom.setState(state);
2420 bottom.copyBounds(compoundRect);
2421 bottom.setCallback(this);
2422 dr.mDrawableSizeBottom = compoundRect.height();
2423 dr.mDrawableWidthBottom = compoundRect.width();
2425 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0;
2429 resetResolvedDrawables();
2436 * Sets the Drawables (if any) to appear to the start of, above, to the end
2437 * of, and below the text. Use 0 if you do not want a Drawable there. The
2438 * Drawables' bounds will be set to their intrinsic bounds.
2440 * Calling this method will overwrite any Drawables previously set using
2441 * {@link #setCompoundDrawables} or related methods.
2443 * @param start Resource identifier of the start Drawable.
2444 * @param top Resource identifier of the top Drawable.
2445 * @param end Resource identifier of the end Drawable.
2446 * @param bottom Resource identifier of the bottom Drawable.
2448 * @attr ref android.R.styleable#TextView_drawableStart
2449 * @attr ref android.R.styleable#TextView_drawableTop
2450 * @attr ref android.R.styleable#TextView_drawableEnd
2451 * @attr ref android.R.styleable#TextView_drawableBottom
2453 @android.view.RemotableViewMethod
2454 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start,
2455 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
2456 final Context context = getContext();
2457 setCompoundDrawablesRelativeWithIntrinsicBounds(
2458 start != 0 ? context.getDrawable(start) : null,
2459 top != 0 ? context.getDrawable(top) : null,
2460 end != 0 ? context.getDrawable(end) : null,
2461 bottom != 0 ? context.getDrawable(bottom) : null);
2465 * Sets the Drawables (if any) to appear to the start of, above, to the end
2466 * of, and below the text. Use {@code null} if you do not want a Drawable
2467 * there. The Drawables' bounds will be set to their intrinsic bounds.
2469 * Calling this method will overwrite any Drawables previously set using
2470 * {@link #setCompoundDrawables} or related methods.
2472 * @attr ref android.R.styleable#TextView_drawableStart
2473 * @attr ref android.R.styleable#TextView_drawableTop
2474 * @attr ref android.R.styleable#TextView_drawableEnd
2475 * @attr ref android.R.styleable#TextView_drawableBottom
2477 @android.view.RemotableViewMethod
2478 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start,
2479 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) {
2481 if (start != null) {
2482 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight());
2485 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight());
2488 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight());
2490 if (bottom != null) {
2491 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight());
2493 setCompoundDrawablesRelative(start, top, end, bottom);
2497 * Returns drawables for the left, top, right, and bottom borders.
2499 * @attr ref android.R.styleable#TextView_drawableLeft
2500 * @attr ref android.R.styleable#TextView_drawableTop
2501 * @attr ref android.R.styleable#TextView_drawableRight
2502 * @attr ref android.R.styleable#TextView_drawableBottom
2505 public Drawable[] getCompoundDrawables() {
2506 final Drawables dr = mDrawables;
2508 return dr.mShowing.clone();
2510 return new Drawable[] { null, null, null, null };
2515 * Returns drawables for the start, top, end, and bottom borders.
2517 * @attr ref android.R.styleable#TextView_drawableStart
2518 * @attr ref android.R.styleable#TextView_drawableTop
2519 * @attr ref android.R.styleable#TextView_drawableEnd
2520 * @attr ref android.R.styleable#TextView_drawableBottom
2523 public Drawable[] getCompoundDrawablesRelative() {
2524 final Drawables dr = mDrawables;
2526 return new Drawable[] {
2527 dr.mDrawableStart, dr.mShowing[Drawables.TOP],
2528 dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM]
2531 return new Drawable[] { null, null, null, null };
2536 * Sets the size of the padding between the compound drawables and
2539 * @attr ref android.R.styleable#TextView_drawablePadding
2541 @android.view.RemotableViewMethod
2542 public void setCompoundDrawablePadding(int pad) {
2543 Drawables dr = mDrawables;
2546 dr.mDrawablePadding = pad;
2550 mDrawables = dr = new Drawables(getContext());
2552 dr.mDrawablePadding = pad;
2560 * Returns the padding between the compound drawables and the text.
2562 * @attr ref android.R.styleable#TextView_drawablePadding
2564 public int getCompoundDrawablePadding() {
2565 final Drawables dr = mDrawables;
2566 return dr != null ? dr.mDrawablePadding : 0;
2570 * Applies a tint to the compound drawables. Does not modify the
2571 * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
2573 * Subsequent calls to
2574 * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)}
2575 * and related methods will automatically mutate the drawables and apply
2576 * the specified tint and tint mode using
2577 * {@link Drawable#setTintList(ColorStateList)}.
2579 * @param tint the tint to apply, may be {@code null} to clear tint
2581 * @attr ref android.R.styleable#TextView_drawableTint
2582 * @see #getCompoundDrawableTintList()
2583 * @see Drawable#setTintList(ColorStateList)
2585 public void setCompoundDrawableTintList(@Nullable ColorStateList tint) {
2586 if (mDrawables == null) {
2587 mDrawables = new Drawables(getContext());
2589 mDrawables.mTintList = tint;
2590 mDrawables.mHasTint = true;
2592 applyCompoundDrawableTint();
2596 * @return the tint applied to the compound drawables
2597 * @attr ref android.R.styleable#TextView_drawableTint
2598 * @see #setCompoundDrawableTintList(ColorStateList)
2600 public ColorStateList getCompoundDrawableTintList() {
2601 return mDrawables != null ? mDrawables.mTintList : null;
2605 * Specifies the blending mode used to apply the tint specified by
2606 * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound
2607 * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}.
2609 * @param tintMode the blending mode used to apply the tint, may be
2610 * {@code null} to clear tint
2611 * @attr ref android.R.styleable#TextView_drawableTintMode
2612 * @see #setCompoundDrawableTintList(ColorStateList)
2613 * @see Drawable#setTintMode(PorterDuff.Mode)
2615 public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) {
2616 if (mDrawables == null) {
2617 mDrawables = new Drawables(getContext());
2619 mDrawables.mTintMode = tintMode;
2620 mDrawables.mHasTintMode = true;
2622 applyCompoundDrawableTint();
2626 * Returns the blending mode used to apply the tint to the compound
2627 * drawables, if specified.
2629 * @return the blending mode used to apply the tint to the compound
2631 * @attr ref android.R.styleable#TextView_drawableTintMode
2632 * @see #setCompoundDrawableTintMode(PorterDuff.Mode)
2634 public PorterDuff.Mode getCompoundDrawableTintMode() {
2635 return mDrawables != null ? mDrawables.mTintMode : null;
2638 private void applyCompoundDrawableTint() {
2639 if (mDrawables == null) {
2643 if (mDrawables.mHasTint || mDrawables.mHasTintMode) {
2644 final ColorStateList tintList = mDrawables.mTintList;
2645 final PorterDuff.Mode tintMode = mDrawables.mTintMode;
2646 final boolean hasTint = mDrawables.mHasTint;
2647 final boolean hasTintMode = mDrawables.mHasTintMode;
2648 final int[] state = getDrawableState();
2650 for (Drawable dr : mDrawables.mShowing) {
2655 if (dr == mDrawables.mDrawableError) {
2656 // From a developer's perspective, the error drawable isn't
2657 // a compound drawable. Don't apply the generic compound
2658 // drawable tint to it.
2665 dr.setTintList(tintList);
2669 dr.setTintMode(tintMode);
2672 // The drawable (or one of its children) may not have been
2673 // stateful before applying the tint, so let's try again.
2674 if (dr.isStateful()) {
2682 public void setPadding(int left, int top, int right, int bottom) {
2683 if (left != mPaddingLeft ||
2684 right != mPaddingRight ||
2685 top != mPaddingTop ||
2686 bottom != mPaddingBottom) {
2690 // the super call will requestLayout()
2691 super.setPadding(left, top, right, bottom);
2696 public void setPaddingRelative(int start, int top, int end, int bottom) {
2697 if (start != getPaddingStart() ||
2698 end != getPaddingEnd() ||
2699 top != mPaddingTop ||
2700 bottom != mPaddingBottom) {
2704 // the super call will requestLayout()
2705 super.setPaddingRelative(start, top, end, bottom);
2710 * Gets the autolink mask of the text. See {@link
2711 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
2714 * @attr ref android.R.styleable#TextView_autoLink
2716 public final int getAutoLinkMask() {
2717 return mAutoLinkMask;
2721 * Sets the text appearance from the specified style resource.
2723 * Use a framework-defined {@code TextAppearance} style like
2724 * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1}
2725 * or see {@link android.R.styleable#TextAppearance TextAppearance} for the
2726 * set of attributes that can be used in a custom style.
2728 * @param resId the resource identifier of the style to apply
2729 * @attr ref android.R.styleable#TextView_textAppearance
2731 @SuppressWarnings("deprecation")
2732 public void setTextAppearance(@StyleRes int resId) {
2733 setTextAppearance(mContext, resId);
2737 * Sets the text color, size, style, hint color, and highlight color
2738 * from the specified TextAppearance resource.
2740 * @deprecated Use {@link #setTextAppearance(int)} instead.
2743 public void setTextAppearance(Context context, @StyleRes int resId) {
2744 final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance);
2746 final int textColorHighlight = ta.getColor(
2747 R.styleable.TextAppearance_textColorHighlight, 0);
2748 if (textColorHighlight != 0) {
2749 setHighlightColor(textColorHighlight);
2752 final ColorStateList textColor = ta.getColorStateList(R.styleable.TextAppearance_textColor);
2753 if (textColor != null) {
2754 setTextColor(textColor);
2757 final int textSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_textSize, 0);
2758 if (textSize != 0) {
2759 setRawTextSize(textSize);
2762 final ColorStateList textColorHint = ta.getColorStateList(
2763 R.styleable.TextAppearance_textColorHint);
2764 if (textColorHint != null) {
2765 setHintTextColor(textColorHint);
2768 final ColorStateList textColorLink = ta.getColorStateList(
2769 R.styleable.TextAppearance_textColorLink);
2770 if (textColorLink != null) {
2771 setLinkTextColor(textColorLink);
2774 final String fontFamily = ta.getString(R.styleable.TextAppearance_fontFamily);
2775 final int typefaceIndex = ta.getInt(R.styleable.TextAppearance_typeface, -1);
2776 final int styleIndex = ta.getInt(R.styleable.TextAppearance_textStyle, -1);
2777 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex);
2779 final int shadowColor = ta.getInt(R.styleable.TextAppearance_shadowColor, 0);
2780 if (shadowColor != 0) {
2781 final float dx = ta.getFloat(R.styleable.TextAppearance_shadowDx, 0);
2782 final float dy = ta.getFloat(R.styleable.TextAppearance_shadowDy, 0);
2783 final float r = ta.getFloat(R.styleable.TextAppearance_shadowRadius, 0);
2784 setShadowLayer(r, dx, dy, shadowColor);
2787 if (ta.getBoolean(R.styleable.TextAppearance_textAllCaps, false)) {
2788 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
2791 if (ta.hasValue(R.styleable.TextAppearance_elegantTextHeight)) {
2792 setElegantTextHeight(ta.getBoolean(
2793 R.styleable.TextAppearance_elegantTextHeight, false));
2796 if (ta.hasValue(R.styleable.TextAppearance_letterSpacing)) {
2797 setLetterSpacing(ta.getFloat(
2798 R.styleable.TextAppearance_letterSpacing, 0));
2801 if (ta.hasValue(R.styleable.TextAppearance_fontFeatureSettings)) {
2802 setFontFeatureSettings(ta.getString(
2803 R.styleable.TextAppearance_fontFeatureSettings));
2810 * Get the default {@link Locale} of the text in this TextView.
2811 * @return the default {@link Locale} of the text in this TextView.
2813 public Locale getTextLocale() {
2814 return mTextPaint.getTextLocale();
2818 * Set the default {@link Locale} of the text in this TextView to the given value. This value
2819 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK
2820 * locales to disambiguate Hanzi/Kanji/Hanja characters.
2822 * @param locale the {@link Locale} for drawing text, must not be null.
2824 * @see Paint#setTextLocale
2826 public void setTextLocale(Locale locale) {
2827 mLocaleChanged = true;
2828 mTextPaint.setTextLocale(locale);
2832 protected void onConfigurationChanged(Configuration newConfig) {
2833 super.onConfigurationChanged(newConfig);
2834 if (!mLocaleChanged) {
2835 mTextPaint.setTextLocale(Locale.getDefault());
2840 * @return the size (in pixels) of the default text size in this TextView.
2842 @ViewDebug.ExportedProperty(category = "text")
2843 public float getTextSize() {
2844 return mTextPaint.getTextSize();
2848 * @return the size (in scaled pixels) of thee default text size in this TextView.
2851 @ViewDebug.ExportedProperty(category = "text")
2852 public float getScaledTextSize() {
2853 return mTextPaint.getTextSize() / mTextPaint.density;
2857 @ViewDebug.ExportedProperty(category = "text", mapping = {
2858 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"),
2859 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"),
2860 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"),
2861 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC")
2863 public int getTypefaceStyle() {
2864 Typeface typeface = mTextPaint.getTypeface();
2865 return typeface != null ? typeface.getStyle() : Typeface.NORMAL;
2869 * Set the default text size to the given value, interpreted as "scaled
2870 * pixel" units. This size is adjusted based on the current density and
2871 * user font size preference.
2873 * @param size The scaled pixel size.
2875 * @attr ref android.R.styleable#TextView_textSize
2877 @android.view.RemotableViewMethod
2878 public void setTextSize(float size) {
2879 setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
2883 * Set the default text size to a given unit and value. See {@link
2884 * TypedValue} for the possible dimension units.
2886 * @param unit The desired dimension unit.
2887 * @param size The desired size in the given units.
2889 * @attr ref android.R.styleable#TextView_textSize
2891 public void setTextSize(int unit, float size) {
2892 Context c = getContext();
2896 r = Resources.getSystem();
2898 r = c.getResources();
2900 setRawTextSize(TypedValue.applyDimension(
2901 unit, size, r.getDisplayMetrics()));
2904 private void setRawTextSize(float size) {
2905 if (size != mTextPaint.getTextSize()) {
2906 mTextPaint.setTextSize(size);
2908 if (mLayout != null) {
2917 * @return the extent by which text is currently being stretched
2918 * horizontally. This will usually be 1.
2920 public float getTextScaleX() {
2921 return mTextPaint.getTextScaleX();
2925 * Sets the extent by which text should be stretched horizontally.
2927 * @attr ref android.R.styleable#TextView_textScaleX
2929 @android.view.RemotableViewMethod
2930 public void setTextScaleX(float size) {
2931 if (size != mTextPaint.getTextScaleX()) {
2932 mUserSetTextScaleX = true;
2933 mTextPaint.setTextScaleX(size);
2935 if (mLayout != null) {
2944 * Sets the typeface and style in which the text should be displayed.
2945 * Note that not all Typeface families actually have bold and italic
2946 * variants, so you may need to use
2947 * {@link #setTypeface(Typeface, int)} to get the appearance
2948 * that you actually want.
2950 * @see #getTypeface()
2952 * @attr ref android.R.styleable#TextView_fontFamily
2953 * @attr ref android.R.styleable#TextView_typeface
2954 * @attr ref android.R.styleable#TextView_textStyle
2956 public void setTypeface(Typeface tf) {
2957 if (mTextPaint.getTypeface() != tf) {
2958 mTextPaint.setTypeface(tf);
2960 if (mLayout != null) {
2969 * @return the current typeface and style in which the text is being
2972 * @see #setTypeface(Typeface)
2974 * @attr ref android.R.styleable#TextView_fontFamily
2975 * @attr ref android.R.styleable#TextView_typeface
2976 * @attr ref android.R.styleable#TextView_textStyle
2978 public Typeface getTypeface() {
2979 return mTextPaint.getTypeface();
2983 * Set the TextView's elegant height metrics flag. This setting selects font
2984 * variants that have not been compacted to fit Latin-based vertical
2985 * metrics, and also increases top and bottom bounds to provide more space.
2987 * @param elegant set the paint's elegant metrics flag.
2989 * @attr ref android.R.styleable#TextView_elegantTextHeight
2991 public void setElegantTextHeight(boolean elegant) {
2992 mTextPaint.setElegantTextHeight(elegant);
2996 * @return the extent by which text is currently being letter-spaced.
2997 * This will normally be 0.
2999 * @see #setLetterSpacing(float)
3000 * @see Paint#setLetterSpacing
3002 public float getLetterSpacing() {
3003 return mTextPaint.getLetterSpacing();
3007 * Sets text letter-spacing. The value is in 'EM' units. Typical values
3008 * for slight expansion will be around 0.05. Negative values tighten text.
3010 * @see #getLetterSpacing()
3011 * @see Paint#getLetterSpacing
3013 * @attr ref android.R.styleable#TextView_letterSpacing
3015 @android.view.RemotableViewMethod
3016 public void setLetterSpacing(float letterSpacing) {
3017 if (letterSpacing != mTextPaint.getLetterSpacing()) {
3018 mTextPaint.setLetterSpacing(letterSpacing);
3020 if (mLayout != null) {
3029 * @return the currently set font feature settings. Default is null.
3031 * @see #setFontFeatureSettings(String)
3032 * @see Paint#setFontFeatureSettings
3035 public String getFontFeatureSettings() {
3036 return mTextPaint.getFontFeatureSettings();
3040 * Sets the break strategy for breaking paragraphs into lines. The default value for
3041 * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for
3042 * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the
3043 * text "dancing" when being edited.
3045 * @attr ref android.R.styleable#TextView_breakStrategy
3046 * @see #getBreakStrategy()
3048 public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) {
3049 mBreakStrategy = breakStrategy;
3050 if (mLayout != null) {
3058 * @return the currently set break strategy.
3060 * @attr ref android.R.styleable#TextView_breakStrategy
3061 * @see #setBreakStrategy(int)
3063 @Layout.BreakStrategy
3064 public int getBreakStrategy() {
3065 return mBreakStrategy;
3069 * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
3070 * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
3072 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3073 * @see #getHyphenationFrequency()
3075 public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
3076 mHyphenationFrequency = hyphenationFrequency;
3077 if (mLayout != null) {
3085 * @return the currently set hyphenation frequency.
3087 * @attr ref android.R.styleable#TextView_hyphenationFrequency
3088 * @see #setHyphenationFrequency(int)
3090 @Layout.HyphenationFrequency
3091 public int getHyphenationFrequency() {
3092 return mHyphenationFrequency;
3096 * Sets font feature settings. The format is the same as the CSS
3097 * font-feature-settings attribute:
3098 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings
3100 * @param fontFeatureSettings font feature settings represented as CSS compatible string
3101 * @see #getFontFeatureSettings()
3102 * @see Paint#getFontFeatureSettings
3104 * @attr ref android.R.styleable#TextView_fontFeatureSettings
3106 @android.view.RemotableViewMethod
3107 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) {
3108 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) {
3109 mTextPaint.setFontFeatureSettings(fontFeatureSettings);
3111 if (mLayout != null) {
3121 * Sets the text color for all the states (normal, selected,
3122 * focused) to be this color.
3124 * @see #setTextColor(ColorStateList)
3125 * @see #getTextColors()
3127 * @attr ref android.R.styleable#TextView_textColor
3129 @android.view.RemotableViewMethod
3130 public void setTextColor(@ColorInt int color) {
3131 mTextColor = ColorStateList.valueOf(color);
3136 * Sets the text color.
3138 * @see #setTextColor(int)
3139 * @see #getTextColors()
3140 * @see #setHintTextColor(ColorStateList)
3141 * @see #setLinkTextColor(ColorStateList)
3143 * @attr ref android.R.styleable#TextView_textColor
3145 public void setTextColor(ColorStateList colors) {
3146 if (colors == null) {
3147 throw new NullPointerException();
3150 mTextColor = colors;
3155 * Gets the text colors for the different states (normal, selected, focused) of the TextView.
3157 * @see #setTextColor(ColorStateList)
3158 * @see #setTextColor(int)
3160 * @attr ref android.R.styleable#TextView_textColor
3162 public final ColorStateList getTextColors() {
3167 * <p>Return the current color selected for normal text.</p>
3169 * @return Returns the current text color.
3172 public final int getCurrentTextColor() {
3173 return mCurTextColor;
3177 * Sets the color used to display the selection highlight.
3179 * @attr ref android.R.styleable#TextView_textColorHighlight
3181 @android.view.RemotableViewMethod
3182 public void setHighlightColor(@ColorInt int color) {
3183 if (mHighlightColor != color) {
3184 mHighlightColor = color;
3190 * @return the color used to display the selection highlight
3192 * @see #setHighlightColor(int)
3194 * @attr ref android.R.styleable#TextView_textColorHighlight
3197 public int getHighlightColor() {
3198 return mHighlightColor;
3202 * Sets whether the soft input method will be made visible when this
3203 * TextView gets focused. The default is true.
3205 @android.view.RemotableViewMethod
3206 public final void setShowSoftInputOnFocus(boolean show) {
3207 createEditorIfNeeded();
3208 mEditor.mShowSoftInputOnFocus = show;
3212 * Returns whether the soft input method will be made visible when this
3213 * TextView gets focused. The default is true.
3215 public final boolean getShowSoftInputOnFocus() {
3216 // When there is no Editor, return default true value
3217 return mEditor == null || mEditor.mShowSoftInputOnFocus;
3221 * Gives the text a shadow of the specified blur radius and color, the specified
3222 * distance from its drawn position.
3224 * The text shadow produced does not interact with the properties on view
3225 * that are responsible for real time shadows,
3226 * {@link View#getElevation() elevation} and
3227 * {@link View#getTranslationZ() translationZ}.
3229 * @see Paint#setShadowLayer(float, float, float, int)
3231 * @attr ref android.R.styleable#TextView_shadowColor
3232 * @attr ref android.R.styleable#TextView_shadowDx
3233 * @attr ref android.R.styleable#TextView_shadowDy
3234 * @attr ref android.R.styleable#TextView_shadowRadius
3236 public void setShadowLayer(float radius, float dx, float dy, int color) {
3237 mTextPaint.setShadowLayer(radius, dx, dy, color);
3239 mShadowRadius = radius;
3242 mShadowColor = color;
3244 // Will change text clip region
3245 if (mEditor != null) mEditor.invalidateTextDisplayList();
3250 * Gets the radius of the shadow layer.
3252 * @return the radius of the shadow layer. If 0, the shadow layer is not visible
3254 * @see #setShadowLayer(float, float, float, int)
3256 * @attr ref android.R.styleable#TextView_shadowRadius
3258 public float getShadowRadius() {
3259 return mShadowRadius;
3263 * @return the horizontal offset of the shadow layer
3265 * @see #setShadowLayer(float, float, float, int)
3267 * @attr ref android.R.styleable#TextView_shadowDx
3269 public float getShadowDx() {
3274 * @return the vertical offset of the shadow layer
3276 * @see #setShadowLayer(float, float, float, int)
3278 * @attr ref android.R.styleable#TextView_shadowDy
3280 public float getShadowDy() {
3285 * @return the color of the shadow layer
3287 * @see #setShadowLayer(float, float, float, int)
3289 * @attr ref android.R.styleable#TextView_shadowColor
3292 public int getShadowColor() {
3293 return mShadowColor;
3297 * @return the base paint used for the text. Please use this only to
3298 * consult the Paint's properties and not to change them.
3300 public TextPaint getPaint() {
3305 * Sets the autolink mask of the text. See {@link
3306 * android.text.util.Linkify#ALL Linkify.ALL} and peers for
3309 * @attr ref android.R.styleable#TextView_autoLink
3311 @android.view.RemotableViewMethod
3312 public final void setAutoLinkMask(int mask) {
3313 mAutoLinkMask = mask;
3317 * Sets whether the movement method will automatically be set to
3318 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3319 * set to nonzero and links are detected in {@link #setText}.
3320 * The default is true.
3322 * @attr ref android.R.styleable#TextView_linksClickable
3324 @android.view.RemotableViewMethod
3325 public final void setLinksClickable(boolean whether) {
3326 mLinksClickable = whether;
3330 * Returns whether the movement method will automatically be set to
3331 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been
3332 * set to nonzero and links are detected in {@link #setText}.
3333 * The default is true.
3335 * @attr ref android.R.styleable#TextView_linksClickable
3337 public final boolean getLinksClickable() {
3338 return mLinksClickable;
3342 * Returns the list of URLSpans attached to the text
3343 * (by {@link Linkify} or otherwise) if any. You can call
3344 * {@link URLSpan#getURL} on them to find where they link to
3345 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd}
3346 * to find the region of the text they are attached to.
3348 public URLSpan[] getUrls() {
3349 if (mText instanceof Spanned) {
3350 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class);
3352 return new URLSpan[0];
3357 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this
3360 * @see #setHintTextColor(ColorStateList)
3361 * @see #getHintTextColors()
3362 * @see #setTextColor(int)
3364 * @attr ref android.R.styleable#TextView_textColorHint
3366 @android.view.RemotableViewMethod
3367 public final void setHintTextColor(@ColorInt int color) {
3368 mHintTextColor = ColorStateList.valueOf(color);
3373 * Sets the color of the hint text.
3375 * @see #getHintTextColors()
3376 * @see #setHintTextColor(int)
3377 * @see #setTextColor(ColorStateList)
3378 * @see #setLinkTextColor(ColorStateList)
3380 * @attr ref android.R.styleable#TextView_textColorHint
3382 public final void setHintTextColor(ColorStateList colors) {
3383 mHintTextColor = colors;
3388 * @return the color of the hint text, for the different states of this TextView.
3390 * @see #setHintTextColor(ColorStateList)
3391 * @see #setHintTextColor(int)
3392 * @see #setTextColor(ColorStateList)
3393 * @see #setLinkTextColor(ColorStateList)
3395 * @attr ref android.R.styleable#TextView_textColorHint
3397 public final ColorStateList getHintTextColors() {
3398 return mHintTextColor;
3402 * <p>Return the current color selected to paint the hint text.</p>
3404 * @return Returns the current hint text color.
3407 public final int getCurrentHintTextColor() {
3408 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor;
3412 * Sets the color of links in the text.
3414 * @see #setLinkTextColor(ColorStateList)
3415 * @see #getLinkTextColors()
3417 * @attr ref android.R.styleable#TextView_textColorLink
3419 @android.view.RemotableViewMethod
3420 public final void setLinkTextColor(@ColorInt int color) {
3421 mLinkTextColor = ColorStateList.valueOf(color);
3426 * Sets the color of links in the text.
3428 * @see #setLinkTextColor(int)
3429 * @see #getLinkTextColors()
3430 * @see #setTextColor(ColorStateList)
3431 * @see #setHintTextColor(ColorStateList)
3433 * @attr ref android.R.styleable#TextView_textColorLink
3435 public final void setLinkTextColor(ColorStateList colors) {
3436 mLinkTextColor = colors;
3441 * @return the list of colors used to paint the links in the text, for the different states of
3444 * @see #setLinkTextColor(ColorStateList)
3445 * @see #setLinkTextColor(int)
3447 * @attr ref android.R.styleable#TextView_textColorLink
3449 public final ColorStateList getLinkTextColors() {
3450 return mLinkTextColor;
3454 * Sets the horizontal alignment of the text and the
3455 * vertical gravity that will be used when there is extra space
3456 * in the TextView beyond what is required for the text itself.
3458 * @see android.view.Gravity
3459 * @attr ref android.R.styleable#TextView_gravity
3461 public void setGravity(int gravity) {
3462 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) {
3463 gravity |= Gravity.START;
3465 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) {
3466 gravity |= Gravity.TOP;
3469 boolean newLayout = false;
3471 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) !=
3472 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) {
3476 if (gravity != mGravity) {
3482 if (mLayout != null && newLayout) {
3483 // XXX this is heavy-handed because no actual content changes.
3484 int want = mLayout.getWidth();
3485 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
3487 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
3488 mRight - mLeft - getCompoundPaddingLeft() -
3489 getCompoundPaddingRight(), true);
3494 * Returns the horizontal and vertical alignment of this TextView.
3496 * @see android.view.Gravity
3497 * @attr ref android.R.styleable#TextView_gravity
3499 public int getGravity() {
3504 * @return the flags on the Paint being used to display the text.
3505 * @see Paint#getFlags
3507 public int getPaintFlags() {
3508 return mTextPaint.getFlags();
3512 * Sets flags on the Paint being used to display the text and
3513 * reflows the text if they are different from the old flags.
3514 * @see Paint#setFlags
3516 @android.view.RemotableViewMethod
3517 public void setPaintFlags(int flags) {
3518 if (mTextPaint.getFlags() != flags) {
3519 mTextPaint.setFlags(flags);
3521 if (mLayout != null) {
3530 * Sets whether the text should be allowed to be wider than the
3531 * View is. If false, it will be wrapped to the width of the View.
3533 * @attr ref android.R.styleable#TextView_scrollHorizontally
3535 public void setHorizontallyScrolling(boolean whether) {
3536 if (mHorizontallyScrolling != whether) {
3537 mHorizontallyScrolling = whether;
3539 if (mLayout != null) {
3548 * Returns whether the text is allowed to be wider than the View is.
3549 * If false, the text will be wrapped to the width of the View.
3551 * @attr ref android.R.styleable#TextView_scrollHorizontally
3554 public boolean getHorizontallyScrolling() {
3555 return mHorizontallyScrolling;
3559 * Makes the TextView at least this many lines tall.
3561 * Setting this value overrides any other (minimum) height setting. A single line TextView will
3562 * set this value to 1.
3564 * @see #getMinLines()
3566 * @attr ref android.R.styleable#TextView_minLines
3568 @android.view.RemotableViewMethod
3569 public void setMinLines(int minlines) {
3570 mMinimum = minlines;
3578 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum
3579 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}.
3581 * @see #setMinLines(int)
3583 * @attr ref android.R.styleable#TextView_minLines
3585 public int getMinLines() {
3586 return mMinMode == LINES ? mMinimum : -1;
3590 * Makes the TextView at least this many pixels tall.
3592 * Setting this value overrides any other (minimum) number of lines setting.
3594 * @attr ref android.R.styleable#TextView_minHeight
3596 @android.view.RemotableViewMethod
3597 public void setMinHeight(int minHeight) {
3598 mMinimum = minHeight;
3606 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum
3607 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}.
3609 * @see #setMinHeight(int)
3611 * @attr ref android.R.styleable#TextView_minHeight
3613 public int getMinHeight() {
3614 return mMinMode == PIXELS ? mMinimum : -1;
3618 * Makes the TextView at most this many lines tall.
3620 * Setting this value overrides any other (maximum) height setting.
3622 * @attr ref android.R.styleable#TextView_maxLines
3624 @android.view.RemotableViewMethod
3625 public void setMaxLines(int maxlines) {
3626 mMaximum = maxlines;
3634 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum
3635 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}.
3637 * @see #setMaxLines(int)
3639 * @attr ref android.R.styleable#TextView_maxLines
3641 public int getMaxLines() {
3642 return mMaxMode == LINES ? mMaximum : -1;
3646 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the
3647 * {@link #setMaxLines(int)} method.
3649 * Setting this value overrides any other (maximum) number of lines setting.
3651 * @attr ref android.R.styleable#TextView_maxHeight
3653 @android.view.RemotableViewMethod
3654 public void setMaxHeight(int maxHeight) {
3655 mMaximum = maxHeight;
3663 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum
3664 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}.
3666 * @see #setMaxHeight(int)
3668 * @attr ref android.R.styleable#TextView_maxHeight
3670 public int getMaxHeight() {
3671 return mMaxMode == PIXELS ? mMaximum : -1;
3675 * Makes the TextView exactly this many lines tall.
3677 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3678 * height setting. A single line TextView will set this value to 1.
3680 * @attr ref android.R.styleable#TextView_lines
3682 @android.view.RemotableViewMethod
3683 public void setLines(int lines) {
3684 mMaximum = mMinimum = lines;
3685 mMaxMode = mMinMode = LINES;
3692 * Makes the TextView exactly this many pixels tall.
3693 * You could do the same thing by specifying this number in the
3696 * Note that setting this value overrides any other (minimum / maximum) number of lines or
3699 * @attr ref android.R.styleable#TextView_height
3701 @android.view.RemotableViewMethod
3702 public void setHeight(int pixels) {
3703 mMaximum = mMinimum = pixels;
3704 mMaxMode = mMinMode = PIXELS;
3711 * Makes the TextView at least this many ems wide
3713 * @attr ref android.R.styleable#TextView_minEms
3715 @android.view.RemotableViewMethod
3716 public void setMinEms(int minems) {
3718 mMinWidthMode = EMS;
3725 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width
3726 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}).
3728 * @see #setMinEms(int)
3731 * @attr ref android.R.styleable#TextView_minEms
3733 public int getMinEms() {
3734 return mMinWidthMode == EMS ? mMinWidth : -1;
3738 * Makes the TextView at least this many pixels wide
3740 * @attr ref android.R.styleable#TextView_minWidth
3742 @android.view.RemotableViewMethod
3743 public void setMinWidth(int minpixels) {
3744 mMinWidth = minpixels;
3745 mMinWidthMode = PIXELS;
3752 * @return the minimum width of the TextView, in pixels or -1 if the minimum width
3753 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}).
3755 * @see #setMinWidth(int)
3756 * @see #setWidth(int)
3758 * @attr ref android.R.styleable#TextView_minWidth
3760 public int getMinWidth() {
3761 return mMinWidthMode == PIXELS ? mMinWidth : -1;
3765 * Makes the TextView at most this many ems wide
3767 * @attr ref android.R.styleable#TextView_maxEms
3769 @android.view.RemotableViewMethod
3770 public void setMaxEms(int maxems) {
3772 mMaxWidthMode = EMS;
3779 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width
3780 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}).
3782 * @see #setMaxEms(int)
3785 * @attr ref android.R.styleable#TextView_maxEms
3787 public int getMaxEms() {
3788 return mMaxWidthMode == EMS ? mMaxWidth : -1;
3792 * Makes the TextView at most this many pixels wide
3794 * @attr ref android.R.styleable#TextView_maxWidth
3796 @android.view.RemotableViewMethod
3797 public void setMaxWidth(int maxpixels) {
3798 mMaxWidth = maxpixels;
3799 mMaxWidthMode = PIXELS;
3806 * @return the maximum width of the TextView, in pixels or -1 if the maximum width
3807 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}).
3809 * @see #setMaxWidth(int)
3810 * @see #setWidth(int)
3812 * @attr ref android.R.styleable#TextView_maxWidth
3814 public int getMaxWidth() {
3815 return mMaxWidthMode == PIXELS ? mMaxWidth : -1;
3819 * Makes the TextView exactly this many ems wide
3821 * @see #setMaxEms(int)
3822 * @see #setMinEms(int)
3826 * @attr ref android.R.styleable#TextView_ems
3828 @android.view.RemotableViewMethod
3829 public void setEms(int ems) {
3830 mMaxWidth = mMinWidth = ems;
3831 mMaxWidthMode = mMinWidthMode = EMS;
3838 * Makes the TextView exactly this many pixels wide.
3839 * You could do the same thing by specifying this number in the
3842 * @see #setMaxWidth(int)
3843 * @see #setMinWidth(int)
3844 * @see #getMinWidth()
3845 * @see #getMaxWidth()
3847 * @attr ref android.R.styleable#TextView_width
3849 @android.view.RemotableViewMethod
3850 public void setWidth(int pixels) {
3851 mMaxWidth = mMinWidth = pixels;
3852 mMaxWidthMode = mMinWidthMode = PIXELS;
3859 * Sets line spacing for this TextView. Each line will have its height
3860 * multiplied by <code>mult</code> and have <code>add</code> added to it.
3862 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3863 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3865 public void setLineSpacing(float add, float mult) {
3866 if (mSpacingAdd != add || mSpacingMult != mult) {
3868 mSpacingMult = mult;
3870 if (mLayout != null) {
3879 * Gets the line spacing multiplier
3881 * @return the value by which each line's height is multiplied to get its actual height.
3883 * @see #setLineSpacing(float, float)
3884 * @see #getLineSpacingExtra()
3886 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
3888 public float getLineSpacingMultiplier() {
3889 return mSpacingMult;
3893 * Gets the line spacing extra space
3895 * @return the extra space that is added to the height of each lines of this TextView.
3897 * @see #setLineSpacing(float, float)
3898 * @see #getLineSpacingMultiplier()
3900 * @attr ref android.R.styleable#TextView_lineSpacingExtra
3902 public float getLineSpacingExtra() {
3907 * Convenience method: Append the specified text to the TextView's
3908 * display buffer, upgrading it to BufferType.EDITABLE if it was
3909 * not already editable.
3911 public final void append(CharSequence text) {
3912 append(text, 0, text.length());
3916 * Convenience method: Append the specified text slice to the TextView's
3917 * display buffer, upgrading it to BufferType.EDITABLE if it was
3918 * not already editable.
3920 public void append(CharSequence text, int start, int end) {
3921 if (!(mText instanceof Editable)) {
3922 setText(mText, BufferType.EDITABLE);
3925 ((Editable) mText).append(text, start, end);
3928 private void updateTextColors() {
3929 boolean inval = false;
3930 int color = mTextColor.getColorForState(getDrawableState(), 0);
3931 if (color != mCurTextColor) {
3932 mCurTextColor = color;
3935 if (mLinkTextColor != null) {
3936 color = mLinkTextColor.getColorForState(getDrawableState(), 0);
3937 if (color != mTextPaint.linkColor) {
3938 mTextPaint.linkColor = color;
3942 if (mHintTextColor != null) {
3943 color = mHintTextColor.getColorForState(getDrawableState(), 0);
3944 if (color != mCurHintTextColor) {
3945 mCurHintTextColor = color;
3946 if (mText.length() == 0) {
3952 // Text needs to be redrawn with the new color
3953 if (mEditor != null) mEditor.invalidateTextDisplayList();
3959 protected void drawableStateChanged() {
3960 super.drawableStateChanged();
3961 if (mTextColor != null && mTextColor.isStateful()
3962 || (mHintTextColor != null && mHintTextColor.isStateful())
3963 || (mLinkTextColor != null && mLinkTextColor.isStateful())) {
3967 if (mDrawables != null) {
3968 final int[] state = getDrawableState();
3969 for (Drawable dr : mDrawables.mShowing) {
3970 if (dr != null && dr.isStateful()) {
3978 public void drawableHotspotChanged(float x, float y) {
3979 super.drawableHotspotChanged(x, y);
3981 if (mDrawables != null) {
3982 final int[] state = getDrawableState();
3983 for (Drawable dr : mDrawables.mShowing) {
3984 if (dr != null && dr.isStateful()) {
3985 dr.setHotspot(x, y);
3992 public Parcelable onSaveInstanceState() {
3993 Parcelable superState = super.onSaveInstanceState();
3995 // Save state if we are forced to
3996 boolean save = mFreezesText;
4000 if (mText != null) {
4001 start = getSelectionStart();
4002 end = getSelectionEnd();
4003 if (start >= 0 || end >= 0) {
4004 // Or save state if there is a selection
4010 SavedState ss = new SavedState(superState);
4011 // XXX Should also save the current scroll position!
4012 ss.selStart = start;
4015 if (mText instanceof Spanned) {
4016 Spannable sp = new SpannableStringBuilder(mText);
4018 if (mEditor != null) {
4019 removeMisspelledSpans(sp);
4020 sp.removeSpan(mEditor.mSuggestionRangeSpan);
4025 ss.text = mText.toString();
4028 if (isFocused() && start >= 0 && end >= 0) {
4029 ss.frozenWithFocus = true;
4032 ss.error = getError();
4034 if (mEditor != null) {
4035 ss.editorState = mEditor.saveInstanceState();
4043 void removeMisspelledSpans(Spannable spannable) {
4044 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
4045 SuggestionSpan.class);
4046 for (int i = 0; i < suggestionSpans.length; i++) {
4047 int flags = suggestionSpans[i].getFlags();
4048 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
4049 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
4050 spannable.removeSpan(suggestionSpans[i]);
4056 public void onRestoreInstanceState(Parcelable state) {
4057 if (!(state instanceof SavedState)) {
4058 super.onRestoreInstanceState(state);
4062 SavedState ss = (SavedState)state;
4063 super.onRestoreInstanceState(ss.getSuperState());
4065 // XXX restore buffer type too, as well as lots of other stuff
4066 if (ss.text != null) {
4070 if (ss.selStart >= 0 && ss.selEnd >= 0) {
4071 if (mText instanceof Spannable) {
4072 int len = mText.length();
4074 if (ss.selStart > len || ss.selEnd > len) {
4075 String restored = "";
4077 if (ss.text != null) {
4078 restored = "(restored) ";
4081 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart +
4082 "/" + ss.selEnd + " out of range for " + restored +
4085 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
4087 if (ss.frozenWithFocus) {
4088 createEditorIfNeeded();
4089 mEditor.mFrozenWithFocus = true;
4095 if (ss.error != null) {
4096 final CharSequence error = ss.error;
4097 // Display the error later, after the first layout pass
4098 post(new Runnable() {
4100 if (mEditor == null || !mEditor.mErrorWasChanged) {
4107 if (ss.editorState != null) {
4108 createEditorIfNeeded();
4109 mEditor.restoreInstanceState(ss.editorState);
4114 * Control whether this text view saves its entire text contents when
4115 * freezing to an icicle, in addition to dynamic state such as cursor
4116 * position. By default this is false, not saving the text. Set to true
4117 * if the text in the text view is not being saved somewhere else in
4118 * persistent storage (such as in a content provider) so that if the
4119 * view is later thawed the user will not lose their data.
4121 * @param freezesText Controls whether a frozen icicle should include the
4122 * entire text data: true to include it, false to not.
4124 * @attr ref android.R.styleable#TextView_freezesText
4126 @android.view.RemotableViewMethod
4127 public void setFreezesText(boolean freezesText) {
4128 mFreezesText = freezesText;
4132 * Return whether this text view is including its entire text contents
4133 * in frozen icicles.
4135 * @return Returns true if text is included, false if it isn't.
4137 * @see #setFreezesText
4139 public boolean getFreezesText() {
4140 return mFreezesText;
4143 ///////////////////////////////////////////////////////////////////////////
4146 * Sets the Factory used to create new Editables.
4148 public final void setEditableFactory(Editable.Factory factory) {
4149 mEditableFactory = factory;
4154 * Sets the Factory used to create new Spannables.
4156 public final void setSpannableFactory(Spannable.Factory factory) {
4157 mSpannableFactory = factory;
4162 * Sets the string value of the TextView. TextView <em>does not</em> accept
4163 * HTML-like formatting, which you can do with text strings in XML resource files.
4164 * To style your strings, attach android.text.style.* objects to a
4165 * {@link android.text.SpannableString SpannableString}, or see the
4166 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources">
4167 * Available Resource Types</a> documentation for an example of setting
4168 * formatted text in the XML resource file.
4170 * @attr ref android.R.styleable#TextView_text
4172 @android.view.RemotableViewMethod
4173 public final void setText(CharSequence text) {
4174 setText(text, mBufferType);
4178 * Like {@link #setText(CharSequence)},
4179 * except that the cursor position (if any) is retained in the new text.
4181 * @param text The new text to place in the text view.
4183 * @see #setText(CharSequence)
4185 @android.view.RemotableViewMethod
4186 public final void setTextKeepState(CharSequence text) {
4187 setTextKeepState(text, mBufferType);
4191 * Sets the text that this TextView is to display (see
4192 * {@link #setText(CharSequence)}) and also sets whether it is stored
4193 * in a styleable/spannable buffer and whether it is editable.
4195 * @attr ref android.R.styleable#TextView_text
4196 * @attr ref android.R.styleable#TextView_bufferType
4198 public void setText(CharSequence text, BufferType type) {
4199 setText(text, type, true, 0);
4201 if (mCharWrapper != null) {
4202 mCharWrapper.mChars = null;
4206 private void setText(CharSequence text, BufferType type,
4207 boolean notifyBefore, int oldlen) {
4212 // If suggestions are not enabled, remove the suggestion spans from the text
4213 if (!isSuggestionsEnabled()) {
4214 text = removeSuggestionSpans(text);
4217 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f);
4219 if (text instanceof Spanned &&
4220 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) {
4221 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) {
4222 setHorizontalFadingEdgeEnabled(true);
4223 mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
4225 setHorizontalFadingEdgeEnabled(false);
4226 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
4228 setEllipsize(TextUtils.TruncateAt.MARQUEE);
4231 int n = mFilters.length;
4232 for (int i = 0; i < n; i++) {
4233 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0);
4240 if (mText != null) {
4241 oldlen = mText.length();
4242 sendBeforeTextChanged(mText, 0, oldlen, text.length());
4244 sendBeforeTextChanged("", 0, 0, text.length());
4248 boolean needEditableForNotification = false;
4250 if (mListeners != null && mListeners.size() != 0) {
4251 needEditableForNotification = true;
4254 if (type == BufferType.EDITABLE || getKeyListener() != null ||
4255 needEditableForNotification) {
4256 createEditorIfNeeded();
4257 mEditor.forgetUndoRedo();
4258 Editable t = mEditableFactory.newEditable(text);
4260 setFilters(t, mFilters);
4261 InputMethodManager imm = InputMethodManager.peekInstance();
4262 if (imm != null) imm.restartInput(this);
4263 } else if (type == BufferType.SPANNABLE || mMovement != null) {
4264 text = mSpannableFactory.newSpannable(text);
4265 } else if (!(text instanceof CharWrapper)) {
4266 text = TextUtils.stringOrSpannedString(text);
4269 if (mAutoLinkMask != 0) {
4272 if (type == BufferType.EDITABLE || text instanceof Spannable) {
4273 s2 = (Spannable) text;
4275 s2 = mSpannableFactory.newSpannable(text);
4278 if (Linkify.addLinks(s2, mAutoLinkMask)) {
4280 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE;
4283 * We must go ahead and set the text before changing the
4284 * movement method, because setMovementMethod() may call
4285 * setText() again to try to upgrade the buffer type.
4289 // Do not change the movement method for text that support text selection as it
4290 // would prevent an arbitrary cursor displacement.
4291 if (mLinksClickable && !textCanBeSelected()) {
4292 setMovementMethod(LinkMovementMethod.getInstance());
4300 if (mTransformation == null) {
4301 mTransformed = text;
4303 mTransformed = mTransformation.getTransformation(text, this);
4306 final int textLength = text.length();
4308 if (text instanceof Spannable && !mAllowTransformationLengthChange) {
4309 Spannable sp = (Spannable) text;
4311 // Remove any ChangeWatchers that might have come from other TextViews.
4312 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class);
4313 final int count = watchers.length;
4314 for (int i = 0; i < count; i++) {
4315 sp.removeSpan(watchers[i]);
4318 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher();
4320 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
4321 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
4323 if (mEditor != null) mEditor.addSpanWatchers(sp);
4325 if (mTransformation != null) {
4326 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
4329 if (mMovement != null) {
4330 mMovement.initialize(this, (Spannable) text);
4333 * Initializing the movement method will have set the
4334 * selection, so reset mSelectionMoved to keep that from
4335 * interfering with the normal on-focus selection-setting.
4337 if (mEditor != null) mEditor.mSelectionMoved = false;
4341 if (mLayout != null) {
4345 sendOnTextChanged(text, 0, oldlen, textLength);
4346 onTextChanged(text, 0, oldlen, textLength);
4348 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
4350 if (needEditableForNotification) {
4351 sendAfterTextChanged((Editable) text);
4354 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text
4355 if (mEditor != null) mEditor.prepareCursorControllers();
4359 * Sets the TextView to display the specified slice of the specified
4360 * char array. You must promise that you will not change the contents
4361 * of the array except for right before another call to setText(),
4362 * since the TextView has no way to know that the text
4363 * has changed and that it needs to invalidate and re-layout.
4365 public final void setText(char[] text, int start, int len) {
4368 if (start < 0 || len < 0 || start + len > text.length) {
4369 throw new IndexOutOfBoundsException(start + ", " + len);
4373 * We must do the before-notification here ourselves because if
4374 * the old text is a CharWrapper we destroy it before calling
4375 * into the normal path.
4377 if (mText != null) {
4378 oldlen = mText.length();
4379 sendBeforeTextChanged(mText, 0, oldlen, len);
4381 sendBeforeTextChanged("", 0, 0, len);
4384 if (mCharWrapper == null) {
4385 mCharWrapper = new CharWrapper(text, start, len);
4387 mCharWrapper.set(text, start, len);
4390 setText(mCharWrapper, mBufferType, false, oldlen);
4394 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
4395 * except that the cursor position (if any) is retained in the new text.
4397 * @see #setText(CharSequence, android.widget.TextView.BufferType)
4399 public final void setTextKeepState(CharSequence text, BufferType type) {
4400 int start = getSelectionStart();
4401 int end = getSelectionEnd();
4402 int len = text.length();
4404 setText(text, type);
4406 if (start >= 0 || end >= 0) {
4407 if (mText instanceof Spannable) {
4408 Selection.setSelection((Spannable) mText,
4409 Math.max(0, Math.min(start, len)),
4410 Math.max(0, Math.min(end, len)));
4415 @android.view.RemotableViewMethod
4416 public final void setText(@StringRes int resid) {
4417 setText(getContext().getResources().getText(resid));
4420 public final void setText(@StringRes int resid, BufferType type) {
4421 setText(getContext().getResources().getText(resid), type);
4425 * Sets the text to be displayed when the text of the TextView is empty.
4426 * Null means to use the normal empty text. The hint does not currently
4427 * participate in determining the size of the view.
4429 * @attr ref android.R.styleable#TextView_hint
4431 @android.view.RemotableViewMethod
4432 public final void setHint(CharSequence hint) {
4433 mHint = TextUtils.stringOrSpannedString(hint);
4435 if (mLayout != null) {
4439 if (mText.length() == 0) {
4443 // Invalidate display list if hint is currently used
4444 if (mEditor != null && mText.length() == 0 && mHint != null) {
4445 mEditor.invalidateTextDisplayList();
4450 * Sets the text to be displayed when the text of the TextView is empty,
4453 * @attr ref android.R.styleable#TextView_hint
4455 @android.view.RemotableViewMethod
4456 public final void setHint(@StringRes int resid) {
4457 setHint(getContext().getResources().getText(resid));
4461 * Returns the hint that is displayed when the text of the TextView
4464 * @attr ref android.R.styleable#TextView_hint
4466 @ViewDebug.CapturedViewProperty
4467 public CharSequence getHint() {
4471 boolean isSingleLine() {
4475 private static boolean isMultilineInputType(int type) {
4476 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) ==
4477 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE);
4481 * Removes the suggestion spans.
4483 CharSequence removeSuggestionSpans(CharSequence text) {
4484 if (text instanceof Spanned) {
4485 Spannable spannable;
4486 if (text instanceof Spannable) {
4487 spannable = (Spannable) text;
4489 spannable = new SpannableString(text);
4493 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
4494 for (int i = 0; i < spans.length; i++) {
4495 spannable.removeSpan(spans[i]);
4502 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This
4503 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)},
4504 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL}
4505 * then a soft keyboard will not be displayed for this text view.
4507 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be
4508 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input
4511 * @see #getInputType()
4512 * @see #setRawInputType(int)
4513 * @see android.text.InputType
4514 * @attr ref android.R.styleable#TextView_inputType
4516 public void setInputType(int type) {
4517 final boolean wasPassword = isPasswordInputType(getInputType());
4518 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
4519 setInputType(type, false);
4520 final boolean isPassword = isPasswordInputType(type);
4521 final boolean isVisiblePassword = isVisiblePasswordInputType(type);
4522 boolean forceUpdate = false;
4524 setTransformationMethod(PasswordTransformationMethod.getInstance());
4525 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4526 } else if (isVisiblePassword) {
4527 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4530 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0);
4531 } else if (wasPassword || wasVisiblePassword) {
4532 // not in password mode, clean up typeface and transformation
4533 setTypefaceFromAttrs(null /* fontFamily */, -1, -1);
4534 if (mTransformation == PasswordTransformationMethod.getInstance()) {
4539 boolean singleLine = !isMultilineInputType(type);
4541 // We need to update the single line mode if it has changed or we
4542 // were previously in password mode.
4543 if (mSingleLine != singleLine || forceUpdate) {
4544 // Change single line mode, but only change the transformation if
4545 // we are not in password mode.
4546 applySingleLine(singleLine, !isPassword, true);
4549 if (!isSuggestionsEnabled()) {
4550 mText = removeSuggestionSpans(mText);
4553 InputMethodManager imm = InputMethodManager.peekInstance();
4554 if (imm != null) imm.restartInput(this);
4558 * It would be better to rely on the input type for everything. A password inputType should have
4559 * a password transformation. We should hence use isPasswordInputType instead of this method.
4562 * - Call setInputType in setKeyListener instead of changing the input type directly (which
4563 * would install the correct transformation).
4564 * - Refuse the installation of a non-password transformation in setTransformation if the input
4567 * However, this is like this for legacy reasons and we cannot break existing apps. This method
4568 * is useful since it matches what the user can see (obfuscated text or not).
4570 * @return true if the current transformation method is of the password type.
4572 boolean hasPasswordTransformationMethod() {
4573 return mTransformation instanceof PasswordTransformationMethod;
4576 private static boolean isPasswordInputType(int inputType) {
4577 final int variation =
4578 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4580 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)
4582 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD)
4584 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD);
4587 private static boolean isVisiblePasswordInputType(int inputType) {
4588 final int variation =
4589 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION);
4591 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
4595 * Directly change the content type integer of the text view, without
4596 * modifying any other state.
4597 * @see #setInputType(int)
4598 * @see android.text.InputType
4599 * @attr ref android.R.styleable#TextView_inputType
4601 public void setRawInputType(int type) {
4602 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
4603 createEditorIfNeeded();
4604 mEditor.mInputType = type;
4607 private void setInputType(int type, boolean direct) {
4608 final int cls = type & EditorInfo.TYPE_MASK_CLASS;
4610 if (cls == EditorInfo.TYPE_CLASS_TEXT) {
4611 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0;
4612 TextKeyListener.Capitalize cap;
4613 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) {
4614 cap = TextKeyListener.Capitalize.CHARACTERS;
4615 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) {
4616 cap = TextKeyListener.Capitalize.WORDS;
4617 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) {
4618 cap = TextKeyListener.Capitalize.SENTENCES;
4620 cap = TextKeyListener.Capitalize.NONE;
4622 input = TextKeyListener.getInstance(autotext, cap);
4623 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) {
4624 input = DigitsKeyListener.getInstance(
4625 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0,
4626 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0);
4627 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) {
4628 switch (type & EditorInfo.TYPE_MASK_VARIATION) {
4629 case EditorInfo.TYPE_DATETIME_VARIATION_DATE:
4630 input = DateKeyListener.getInstance();
4632 case EditorInfo.TYPE_DATETIME_VARIATION_TIME:
4633 input = TimeKeyListener.getInstance();
4636 input = DateTimeKeyListener.getInstance();
4639 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) {
4640 input = DialerKeyListener.getInstance();
4642 input = TextKeyListener.getInstance();
4644 setRawInputType(type);
4646 createEditorIfNeeded();
4647 mEditor.mKeyListener = input;
4649 setKeyListenerOnly(input);
4654 * Get the type of the editable content.
4656 * @see #setInputType(int)
4657 * @see android.text.InputType
4659 public int getInputType() {
4660 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType;
4664 * Change the editor type integer associated with the text view, which
4665 * will be reported to an IME with {@link EditorInfo#imeOptions} when it
4667 * @see #getImeOptions
4668 * @see android.view.inputmethod.EditorInfo
4669 * @attr ref android.R.styleable#TextView_imeOptions
4671 public void setImeOptions(int imeOptions) {
4672 createEditorIfNeeded();
4673 mEditor.createInputContentTypeIfNeeded();
4674 mEditor.mInputContentType.imeOptions = imeOptions;
4678 * Get the type of the IME editor.
4680 * @see #setImeOptions(int)
4681 * @see android.view.inputmethod.EditorInfo
4683 public int getImeOptions() {
4684 return mEditor != null && mEditor.mInputContentType != null
4685 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL;
4689 * Change the custom IME action associated with the text view, which
4690 * will be reported to an IME with {@link EditorInfo#actionLabel}
4691 * and {@link EditorInfo#actionId} when it has focus.
4692 * @see #getImeActionLabel
4693 * @see #getImeActionId
4694 * @see android.view.inputmethod.EditorInfo
4695 * @attr ref android.R.styleable#TextView_imeActionLabel
4696 * @attr ref android.R.styleable#TextView_imeActionId
4698 public void setImeActionLabel(CharSequence label, int actionId) {
4699 createEditorIfNeeded();
4700 mEditor.createInputContentTypeIfNeeded();
4701 mEditor.mInputContentType.imeActionLabel = label;
4702 mEditor.mInputContentType.imeActionId = actionId;
4706 * Get the IME action label previous set with {@link #setImeActionLabel}.
4708 * @see #setImeActionLabel
4709 * @see android.view.inputmethod.EditorInfo
4711 public CharSequence getImeActionLabel() {
4712 return mEditor != null && mEditor.mInputContentType != null
4713 ? mEditor.mInputContentType.imeActionLabel : null;
4717 * Get the IME action ID previous set with {@link #setImeActionLabel}.
4719 * @see #setImeActionLabel
4720 * @see android.view.inputmethod.EditorInfo
4722 public int getImeActionId() {
4723 return mEditor != null && mEditor.mInputContentType != null
4724 ? mEditor.mInputContentType.imeActionId : 0;
4728 * Set a special listener to be called when an action is performed
4729 * on the text view. This will be called when the enter key is pressed,
4730 * or when an action supplied to the IME is selected by the user. Setting
4731 * this means that the normal hard key event will not insert a newline
4732 * into the text view, even if it is multi-line; holding down the ALT
4733 * modifier will, however, allow the user to insert a newline character.
4735 public void setOnEditorActionListener(OnEditorActionListener l) {
4736 createEditorIfNeeded();
4737 mEditor.createInputContentTypeIfNeeded();
4738 mEditor.mInputContentType.onEditorActionListener = l;
4742 * Called when an attached input method calls
4743 * {@link InputConnection#performEditorAction(int)
4744 * InputConnection.performEditorAction()}
4745 * for this text view. The default implementation will call your action
4746 * listener supplied to {@link #setOnEditorActionListener}, or perform
4747 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT
4748 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS
4749 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE
4750 * EditorInfo.IME_ACTION_DONE}.
4752 * <p>For backwards compatibility, if no IME options have been set and the
4753 * text view would not normally advance focus on enter, then
4754 * the NEXT and DONE actions received here will be turned into an enter
4755 * key down/up pair to go through the normal key handling.
4757 * @param actionCode The code of the action being performed.
4759 * @see #setOnEditorActionListener
4761 public void onEditorAction(int actionCode) {
4762 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType;
4764 if (ict.onEditorActionListener != null) {
4765 if (ict.onEditorActionListener.onEditorAction(this,
4766 actionCode, null)) {
4771 // This is the handling for some default action.
4772 // Note that for backwards compatibility we don't do this
4773 // default handling if explicit ime options have not been given,
4774 // instead turning this into the normal enter key codes that an
4775 // app may be expecting.
4776 if (actionCode == EditorInfo.IME_ACTION_NEXT) {
4777 View v = focusSearch(FOCUS_FORWARD);
4779 if (!v.requestFocus(FOCUS_FORWARD)) {
4780 throw new IllegalStateException("focus search returned a view " +
4781 "that wasn't able to take focus!");
4786 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) {
4787 View v = focusSearch(FOCUS_BACKWARD);
4789 if (!v.requestFocus(FOCUS_BACKWARD)) {
4790 throw new IllegalStateException("focus search returned a view " +
4791 "that wasn't able to take focus!");
4796 } else if (actionCode == EditorInfo.IME_ACTION_DONE) {
4797 InputMethodManager imm = InputMethodManager.peekInstance();
4798 if (imm != null && imm.isActive(this)) {
4799 imm.hideSoftInputFromWindow(getWindowToken(), 0);
4805 ViewRootImpl viewRootImpl = getViewRootImpl();
4806 if (viewRootImpl != null) {
4807 long eventTime = SystemClock.uptimeMillis();
4808 viewRootImpl.dispatchKeyFromIme(
4809 new KeyEvent(eventTime, eventTime,
4810 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0,
4811 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4812 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4813 | KeyEvent.FLAG_EDITOR_ACTION));
4814 viewRootImpl.dispatchKeyFromIme(
4815 new KeyEvent(SystemClock.uptimeMillis(), eventTime,
4816 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0,
4817 KeyCharacterMap.VIRTUAL_KEYBOARD, 0,
4818 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE
4819 | KeyEvent.FLAG_EDITOR_ACTION));
4824 * Set the private content type of the text, which is the
4825 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions}
4826 * field that will be filled in when creating an input connection.
4828 * @see #getPrivateImeOptions()
4829 * @see EditorInfo#privateImeOptions
4830 * @attr ref android.R.styleable#TextView_privateImeOptions
4832 public void setPrivateImeOptions(String type) {
4833 createEditorIfNeeded();
4834 mEditor.createInputContentTypeIfNeeded();
4835 mEditor.mInputContentType.privateImeOptions = type;
4839 * Get the private type of the content.
4841 * @see #setPrivateImeOptions(String)
4842 * @see EditorInfo#privateImeOptions
4844 public String getPrivateImeOptions() {
4845 return mEditor != null && mEditor.mInputContentType != null
4846 ? mEditor.mInputContentType.privateImeOptions : null;
4850 * Set the extra input data of the text, which is the
4851 * {@link EditorInfo#extras TextBoxAttribute.extras}
4852 * Bundle that will be filled in when creating an input connection. The
4853 * given integer is the resource ID of an XML resource holding an
4854 * {@link android.R.styleable#InputExtras <input-extras>} XML tree.
4856 * @see #getInputExtras(boolean)
4857 * @see EditorInfo#extras
4858 * @attr ref android.R.styleable#TextView_editorExtras
4860 public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException {
4861 createEditorIfNeeded();
4862 XmlResourceParser parser = getResources().getXml(xmlResId);
4863 mEditor.createInputContentTypeIfNeeded();
4864 mEditor.mInputContentType.extras = new Bundle();
4865 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras);
4869 * Retrieve the input extras currently associated with the text view, which
4870 * can be viewed as well as modified.
4872 * @param create If true, the extras will be created if they don't already
4873 * exist. Otherwise, null will be returned if none have been created.
4874 * @see #setInputExtras(int)
4875 * @see EditorInfo#extras
4876 * @attr ref android.R.styleable#TextView_editorExtras
4878 public Bundle getInputExtras(boolean create) {
4879 if (mEditor == null && !create) return null;
4880 createEditorIfNeeded();
4881 if (mEditor.mInputContentType == null) {
4882 if (!create) return null;
4883 mEditor.createInputContentTypeIfNeeded();
4885 if (mEditor.mInputContentType.extras == null) {
4886 if (!create) return null;
4887 mEditor.mInputContentType.extras = new Bundle();
4889 return mEditor.mInputContentType.extras;
4893 * Returns the error message that was set to be displayed with
4894 * {@link #setError}, or <code>null</code> if no error was set
4895 * or if it the error was cleared by the widget after user input.
4897 public CharSequence getError() {
4898 return mEditor == null ? null : mEditor.mError;
4902 * Sets the right-hand compound drawable of the TextView to the "error"
4903 * icon and sets an error message that will be displayed in a popup when
4904 * the TextView has focus. The icon and error message will be reset to
4905 * null when any key events cause changes to the TextView's text. If the
4906 * <code>error</code> is <code>null</code>, the error message and icon
4909 @android.view.RemotableViewMethod
4910 public void setError(CharSequence error) {
4911 if (error == null) {
4912 setError(null, null);
4914 Drawable dr = getContext().getDrawable(
4915 com.android.internal.R.drawable.indicator_input_error);
4917 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight());
4918 setError(error, dr);
4923 * Sets the right-hand compound drawable of the TextView to the specified
4924 * icon and sets an error message that will be displayed in a popup when
4925 * the TextView has focus. The icon and error message will be reset to
4926 * null when any key events cause changes to the TextView's text. The
4927 * drawable must already have had {@link Drawable#setBounds} set on it.
4928 * If the <code>error</code> is <code>null</code>, the error message will
4929 * be cleared (and you should provide a <code>null</code> icon as well).
4931 public void setError(CharSequence error, Drawable icon) {
4932 createEditorIfNeeded();
4933 mEditor.setError(error, icon);
4934 notifyViewAccessibilityStateChangedIfNeeded(
4935 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
4939 protected boolean setFrame(int l, int t, int r, int b) {
4940 boolean result = super.setFrame(l, t, r, b);
4942 if (mEditor != null) mEditor.setFrame();
4944 restartMarqueeIfNeeded();
4949 private void restartMarqueeIfNeeded() {
4950 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
4951 mRestartMarquee = false;
4957 * Sets the list of input filters that will be used if the buffer is
4958 * Editable. Has no effect otherwise.
4960 * @attr ref android.R.styleable#TextView_maxLength
4962 public void setFilters(InputFilter[] filters) {
4963 if (filters == null) {
4964 throw new IllegalArgumentException();
4969 if (mText instanceof Editable) {
4970 setFilters((Editable) mText, filters);
4975 * Sets the list of input filters on the specified Editable,
4976 * and includes mInput in the list if it is an InputFilter.
4978 private void setFilters(Editable e, InputFilter[] filters) {
4979 if (mEditor != null) {
4980 final boolean undoFilter = mEditor.mUndoInputFilter != null;
4981 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter;
4983 if (undoFilter) num++;
4984 if (keyFilter) num++;
4986 InputFilter[] nf = new InputFilter[filters.length + num];
4988 System.arraycopy(filters, 0, nf, 0, filters.length);
4991 nf[filters.length] = mEditor.mUndoInputFilter;
4995 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener;
5002 e.setFilters(filters);
5006 * Returns the current list of input filters.
5008 * @attr ref android.R.styleable#TextView_maxLength
5010 public InputFilter[] getFilters() {
5014 /////////////////////////////////////////////////////////////////////////
5016 private int getBoxHeight(Layout l) {
5017 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE;
5018 int padding = (l == mHintLayout) ?
5019 getCompoundPaddingTop() + getCompoundPaddingBottom() :
5020 getExtendedPaddingTop() + getExtendedPaddingBottom();
5021 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom;
5024 int getVerticalOffset(boolean forceNormal) {
5026 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5029 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5033 if (gravity != Gravity.TOP) {
5034 int boxht = getBoxHeight(l);
5035 int textht = l.getHeight();
5037 if (textht < boxht) {
5038 if (gravity == Gravity.BOTTOM)
5039 voffset = boxht - textht;
5040 else // (gravity == Gravity.CENTER_VERTICAL)
5041 voffset = (boxht - textht) >> 1;
5047 private int getBottomVerticalOffset(boolean forceNormal) {
5049 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
5052 if (!forceNormal && mText.length() == 0 && mHintLayout != null) {
5056 if (gravity != Gravity.BOTTOM) {
5057 int boxht = getBoxHeight(l);
5058 int textht = l.getHeight();
5060 if (textht < boxht) {
5061 if (gravity == Gravity.TOP)
5062 voffset = boxht - textht;
5063 else // (gravity == Gravity.CENTER_VERTICAL)
5064 voffset = (boxht - textht) >> 1;
5070 void invalidateCursorPath() {
5071 if (mHighlightPathBogus) {
5074 final int horizontalPadding = getCompoundPaddingLeft();
5075 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5077 if (mEditor.mCursorCount == 0) {
5078 synchronized (TEMP_RECTF) {
5080 * The reason for this concern about the thickness of the
5081 * cursor and doing the floor/ceil on the coordinates is that
5082 * some EditTexts (notably textfields in the Browser) have
5083 * anti-aliased text where not all the characters are
5084 * necessarily at integer-multiple locations. This should
5085 * make sure the entire cursor gets invalidated instead of
5086 * sometimes missing half a pixel.
5088 float thick = (float) Math.ceil(mTextPaint.getStrokeWidth());
5095 // mHighlightPath is guaranteed to be non null at that point.
5096 mHighlightPath.computeBounds(TEMP_RECTF, false);
5098 invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick),
5099 (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick),
5100 (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick),
5101 (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
5104 for (int i = 0; i < mEditor.mCursorCount; i++) {
5105 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5106 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
5107 bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
5113 void invalidateCursor() {
5114 int where = getSelectionEnd();
5116 invalidateCursor(where, where, where);
5119 private void invalidateCursor(int a, int b, int c) {
5120 if (a >= 0 || b >= 0 || c >= 0) {
5121 int start = Math.min(Math.min(a, b), c);
5122 int end = Math.max(Math.max(a, b), c);
5123 invalidateRegion(start, end, true /* Also invalidates blinking cursor */);
5128 * Invalidates the region of text enclosed between the start and end text offsets.
5130 void invalidateRegion(int start, int end, boolean invalidateCursor) {
5131 if (mLayout == null) {
5134 int lineStart = mLayout.getLineForOffset(start);
5135 int top = mLayout.getLineTop(lineStart);
5137 // This is ridiculous, but the descent from the line above
5138 // can hang down into the line we really want to redraw,
5139 // so we have to invalidate part of the line above to make
5140 // sure everything that needs to be redrawn really is.
5141 // (But not the whole line above, because that would cause
5142 // the same problem with the descenders on the line above it!)
5143 if (lineStart > 0) {
5144 top -= mLayout.getLineDescent(lineStart - 1);
5150 lineEnd = lineStart;
5152 lineEnd = mLayout.getLineForOffset(end);
5154 int bottom = mLayout.getLineBottom(lineEnd);
5156 // mEditor can be null in case selection is set programmatically.
5157 if (invalidateCursor && mEditor != null) {
5158 for (int i = 0; i < mEditor.mCursorCount; i++) {
5159 Rect bounds = mEditor.mCursorDrawable[i].getBounds();
5160 top = Math.min(top, bounds.top);
5161 bottom = Math.max(bottom, bounds.bottom);
5165 final int compoundPaddingLeft = getCompoundPaddingLeft();
5166 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
5169 if (lineStart == lineEnd && !invalidateCursor) {
5170 left = (int) mLayout.getPrimaryHorizontal(start);
5171 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0);
5172 left += compoundPaddingLeft;
5173 right += compoundPaddingLeft;
5175 // Rectangle bounding box when the region spans several lines
5176 left = compoundPaddingLeft;
5177 right = getWidth() - getCompoundPaddingRight();
5180 invalidate(mScrollX + left, verticalPadding + top,
5181 mScrollX + right, verticalPadding + bottom);
5185 private void registerForPreDraw() {
5186 if (!mPreDrawRegistered) {
5187 getViewTreeObserver().addOnPreDrawListener(this);
5188 mPreDrawRegistered = true;
5192 private void unregisterForPreDraw() {
5193 getViewTreeObserver().removeOnPreDrawListener(this);
5194 mPreDrawRegistered = false;
5195 mPreDrawListenerDetached = false;
5201 public boolean onPreDraw() {
5202 if (mLayout == null) {
5206 if (mMovement != null) {
5207 /* This code also provides auto-scrolling when a cursor is moved using a
5208 * CursorController (insertion point or selection limits).
5209 * For selection, ensure start or end is visible depending on controller's state.
5211 int curs = getSelectionEnd();
5212 // Do not create the controller if it is not already created.
5213 if (mEditor != null && mEditor.mSelectionModifierCursorController != null &&
5214 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) {
5215 curs = getSelectionStart();
5219 * TODO: This should really only keep the end in view if
5220 * it already was before the text changed. I'm not sure
5221 * of a good way to tell from here if it was.
5223 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
5224 curs = mText.length();
5228 bringPointIntoView(curs);
5231 bringTextIntoView();
5234 // This has to be checked here since:
5235 // - onFocusChanged cannot start it when focus is given to a view with selected text (after
5236 // a screen rotation) since layout is not yet initialized at that point.
5237 if (mEditor != null && mEditor.mCreatedWithASelection) {
5238 if (mEditor.extractedTextModeWillBeStarted()) {
5239 mEditor.checkFieldAndSelectCurrentWord();
5241 mEditor.startSelectionActionMode();
5243 mEditor.mCreatedWithASelection = false;
5246 // Phone specific code (there is no ExtractEditText on tablets).
5247 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
5248 // not be set. Do the test here instead.
5249 if (isInExtractedMode() && hasSelection() && mEditor != null
5250 && mEditor.mTextActionMode == null && isShown() && hasWindowFocus()) {
5251 mEditor.startSelectionActionMode();
5254 unregisterForPreDraw();
5260 protected void onAttachedToWindow() {
5261 super.onAttachedToWindow();
5263 mTemporaryDetach = false;
5265 if (mEditor != null) mEditor.onAttachedToWindow();
5267 if (mPreDrawListenerDetached) {
5268 getViewTreeObserver().addOnPreDrawListener(this);
5269 mPreDrawListenerDetached = false;
5275 protected void onDetachedFromWindowInternal() {
5276 if (mPreDrawRegistered) {
5277 getViewTreeObserver().removeOnPreDrawListener(this);
5278 mPreDrawListenerDetached = true;
5281 resetResolvedDrawables();
5283 if (mEditor != null) mEditor.onDetachedFromWindow();
5285 super.onDetachedFromWindowInternal();
5289 public void onScreenStateChanged(int screenState) {
5290 super.onScreenStateChanged(screenState);
5291 if (mEditor != null) mEditor.onScreenStateChanged(screenState);
5295 protected boolean isPaddingOffsetRequired() {
5296 return mShadowRadius != 0 || mDrawables != null;
5300 protected int getLeftPaddingOffset() {
5301 return getCompoundPaddingLeft() - mPaddingLeft +
5302 (int) Math.min(0, mShadowDx - mShadowRadius);
5306 protected int getTopPaddingOffset() {
5307 return (int) Math.min(0, mShadowDy - mShadowRadius);
5311 protected int getBottomPaddingOffset() {
5312 return (int) Math.max(0, mShadowDy + mShadowRadius);
5315 private int getFudgedPaddingRight() {
5316 // Add sufficient space for cursor and tone marks
5317 int cursorWidth = 2 + (int)mTextPaint.density; // adequate for Material cursors
5318 return Math.max(0, getCompoundPaddingRight() - (cursorWidth - 1));
5322 protected int getRightPaddingOffset() {
5323 return -(getFudgedPaddingRight() - mPaddingRight) +
5324 (int) Math.max(0, mShadowDx + mShadowRadius);
5328 protected boolean verifyDrawable(Drawable who) {
5329 final boolean verified = super.verifyDrawable(who);
5330 if (!verified && mDrawables != null) {
5331 for (Drawable dr : mDrawables.mShowing) {
5341 public void jumpDrawablesToCurrentState() {
5342 super.jumpDrawablesToCurrentState();
5343 if (mDrawables != null) {
5344 for (Drawable dr : mDrawables.mShowing) {
5346 dr.jumpToCurrentState();
5353 public void invalidateDrawable(Drawable drawable) {
5354 boolean handled = false;
5356 if (verifyDrawable(drawable)) {
5357 final Rect dirty = drawable.getBounds();
5358 int scrollX = mScrollX;
5359 int scrollY = mScrollY;
5361 // IMPORTANT: The coordinates below are based on the coordinates computed
5362 // for each compound drawable in onDraw(). Make sure to update each section
5364 final TextView.Drawables drawables = mDrawables;
5365 if (drawables != null) {
5366 if (drawable == drawables.mShowing[Drawables.LEFT]) {
5367 final int compoundPaddingTop = getCompoundPaddingTop();
5368 final int compoundPaddingBottom = getCompoundPaddingBottom();
5369 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5371 scrollX += mPaddingLeft;
5372 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2;
5374 } else if (drawable == drawables.mShowing[Drawables.RIGHT]) {
5375 final int compoundPaddingTop = getCompoundPaddingTop();
5376 final int compoundPaddingBottom = getCompoundPaddingBottom();
5377 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5379 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight);
5380 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2;
5382 } else if (drawable == drawables.mShowing[Drawables.TOP]) {
5383 final int compoundPaddingLeft = getCompoundPaddingLeft();
5384 final int compoundPaddingRight = getCompoundPaddingRight();
5385 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5387 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2;
5388 scrollY += mPaddingTop;
5390 } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) {
5391 final int compoundPaddingLeft = getCompoundPaddingLeft();
5392 final int compoundPaddingRight = getCompoundPaddingRight();
5393 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft;
5395 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2;
5396 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom);
5402 invalidate(dirty.left + scrollX, dirty.top + scrollY,
5403 dirty.right + scrollX, dirty.bottom + scrollY);
5408 super.invalidateDrawable(drawable);
5413 public boolean hasOverlappingRendering() {
5414 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
5415 return ((getBackground() != null && getBackground().getCurrent() != null)
5416 || mText instanceof Spannable || hasSelection()
5417 || isHorizontalFadingEdgeEnabled());
5422 * Returns the state of the {@code textIsSelectable} flag (See
5423 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag
5424 * to allow users to select and copy text in a non-editable TextView, the content of an
5425 * {@link EditText} can always be selected, independently of the value of this flag.
5428 * @return True if the text displayed in this TextView can be selected by the user.
5430 * @attr ref android.R.styleable#TextView_textIsSelectable
5432 public boolean isTextSelectable() {
5433 return mEditor == null ? false : mEditor.mTextIsSelectable;
5437 * Sets whether the content of this view is selectable by the user. The default is
5438 * {@code false}, meaning that the content is not selectable.
5440 * When you use a TextView to display a useful piece of information to the user (such as a
5441 * contact's address), make it selectable, so that the user can select and copy its
5442 * content. You can also use set the XML attribute
5443 * {@link android.R.styleable#TextView_textIsSelectable} to "true".
5445 * When you call this method to set the value of {@code textIsSelectable}, it sets
5446 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable},
5447 * and {@code longClickable} to the same value. These flags correspond to the attributes
5448 * {@link android.R.styleable#View_focusable android:focusable},
5449 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode},
5450 * {@link android.R.styleable#View_clickable android:clickable}, and
5451 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these
5452 * flags to a state you had set previously, call one or more of the following methods:
5453 * {@link #setFocusable(boolean) setFocusable()},
5454 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()},
5455 * {@link #setClickable(boolean) setClickable()} or
5456 * {@link #setLongClickable(boolean) setLongClickable()}.
5458 * @param selectable Whether the content of this TextView should be selectable.
5460 public void setTextIsSelectable(boolean selectable) {
5461 if (!selectable && mEditor == null) return; // false is default value with no edit data
5463 createEditorIfNeeded();
5464 if (mEditor.mTextIsSelectable == selectable) return;
5466 mEditor.mTextIsSelectable = selectable;
5467 setFocusableInTouchMode(selectable);
5468 setFocusable(selectable);
5469 setClickable(selectable);
5470 setLongClickable(selectable);
5472 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
5474 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
5475 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
5477 // Called by setText above, but safer in case of future code changes
5478 mEditor.prepareCursorControllers();
5482 protected int[] onCreateDrawableState(int extraSpace) {
5483 final int[] drawableState;
5486 drawableState = super.onCreateDrawableState(extraSpace);
5488 drawableState = super.onCreateDrawableState(extraSpace + 1);
5489 mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
5492 if (isTextSelectable()) {
5493 // Disable pressed state, which was introduced when TextView was made clickable.
5494 // Prevents text color change.
5495 // setClickable(false) would have a similar effect, but it also disables focus changes
5496 // and long press actions, which are both needed by text selection.
5497 final int length = drawableState.length;
5498 for (int i = 0; i < length; i++) {
5499 if (drawableState[i] == R.attr.state_pressed) {
5500 final int[] nonPressedState = new int[length - 1];
5501 System.arraycopy(drawableState, 0, nonPressedState, 0, i);
5502 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1);
5503 return nonPressedState;
5508 return drawableState;
5511 private Path getUpdatedHighlightPath() {
5512 Path highlight = null;
5513 Paint highlightPaint = mHighlightPaint;
5515 final int selStart = getSelectionStart();
5516 final int selEnd = getSelectionEnd();
5517 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) {
5518 if (selStart == selEnd) {
5519 if (mEditor != null && mEditor.isCursorVisible() &&
5520 (SystemClock.uptimeMillis() - mEditor.mShowCursor) %
5521 (2 * Editor.BLINK) < Editor.BLINK) {
5522 if (mHighlightPathBogus) {
5523 if (mHighlightPath == null) mHighlightPath = new Path();
5524 mHighlightPath.reset();
5525 mLayout.getCursorPath(selStart, mHighlightPath, mText);
5526 mEditor.updateCursorsPositions();
5527 mHighlightPathBogus = false;
5530 // XXX should pass to skin instead of drawing directly
5531 highlightPaint.setColor(mCurTextColor);
5532 highlightPaint.setStyle(Paint.Style.STROKE);
5533 highlight = mHighlightPath;
5536 if (mHighlightPathBogus) {
5537 if (mHighlightPath == null) mHighlightPath = new Path();
5538 mHighlightPath.reset();
5539 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5540 mHighlightPathBogus = false;
5543 // XXX should pass to skin instead of drawing directly
5544 highlightPaint.setColor(mHighlightColor);
5545 highlightPaint.setStyle(Paint.Style.FILL);
5547 highlight = mHighlightPath;
5556 public int getHorizontalOffsetForDrawables() {
5561 protected void onDraw(Canvas canvas) {
5562 restartMarqueeIfNeeded();
5564 // Draw the background for this view
5565 super.onDraw(canvas);
5567 final int compoundPaddingLeft = getCompoundPaddingLeft();
5568 final int compoundPaddingTop = getCompoundPaddingTop();
5569 final int compoundPaddingRight = getCompoundPaddingRight();
5570 final int compoundPaddingBottom = getCompoundPaddingBottom();
5571 final int scrollX = mScrollX;
5572 final int scrollY = mScrollY;
5573 final int right = mRight;
5574 final int left = mLeft;
5575 final int bottom = mBottom;
5576 final int top = mTop;
5577 final boolean isLayoutRtl = isLayoutRtl();
5578 final int offset = getHorizontalOffsetForDrawables();
5579 final int leftOffset = isLayoutRtl ? 0 : offset;
5580 final int rightOffset = isLayoutRtl ? offset : 0 ;
5582 final Drawables dr = mDrawables;
5585 * Compound, not extended, because the icon is not clipped
5586 * if the text height is smaller.
5589 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop;
5590 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft;
5592 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5593 // Make sure to update invalidateDrawable() when changing this code.
5594 if (dr.mShowing[Drawables.LEFT] != null) {
5596 canvas.translate(scrollX + mPaddingLeft + leftOffset,
5597 scrollY + compoundPaddingTop +
5598 (vspace - dr.mDrawableHeightLeft) / 2);
5599 dr.mShowing[Drawables.LEFT].draw(canvas);
5603 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5604 // Make sure to update invalidateDrawable() when changing this code.
5605 if (dr.mShowing[Drawables.RIGHT] != null) {
5607 canvas.translate(scrollX + right - left - mPaddingRight
5608 - dr.mDrawableSizeRight - rightOffset,
5609 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2);
5610 dr.mShowing[Drawables.RIGHT].draw(canvas);
5614 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5615 // Make sure to update invalidateDrawable() when changing this code.
5616 if (dr.mShowing[Drawables.TOP] != null) {
5618 canvas.translate(scrollX + compoundPaddingLeft +
5619 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop);
5620 dr.mShowing[Drawables.TOP].draw(canvas);
5624 // IMPORTANT: The coordinates computed are also used in invalidateDrawable()
5625 // Make sure to update invalidateDrawable() when changing this code.
5626 if (dr.mShowing[Drawables.BOTTOM] != null) {
5628 canvas.translate(scrollX + compoundPaddingLeft +
5629 (hspace - dr.mDrawableWidthBottom) / 2,
5630 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom);
5631 dr.mShowing[Drawables.BOTTOM].draw(canvas);
5636 int color = mCurTextColor;
5638 if (mLayout == null) {
5642 Layout layout = mLayout;
5644 if (mHint != null && mText.length() == 0) {
5645 if (mHintTextColor != null) {
5646 color = mCurHintTextColor;
5649 layout = mHintLayout;
5652 mTextPaint.setColor(color);
5653 mTextPaint.drawableState = getDrawableState();
5656 /* Would be faster if we didn't have to do this. Can we chop the
5657 (displayable) text so that we don't need to do this ever?
5660 int extendedPaddingTop = getExtendedPaddingTop();
5661 int extendedPaddingBottom = getExtendedPaddingBottom();
5663 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop;
5664 final int maxScrollY = mLayout.getHeight() - vspace;
5666 float clipLeft = compoundPaddingLeft + scrollX;
5667 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY;
5668 float clipRight = right - left - getFudgedPaddingRight() + scrollX;
5669 float clipBottom = bottom - top + scrollY -
5670 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom);
5672 if (mShadowRadius != 0) {
5673 clipLeft += Math.min(0, mShadowDx - mShadowRadius);
5674 clipRight += Math.max(0, mShadowDx + mShadowRadius);
5676 clipTop += Math.min(0, mShadowDy - mShadowRadius);
5677 clipBottom += Math.max(0, mShadowDy + mShadowRadius);
5680 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom);
5682 int voffsetText = 0;
5683 int voffsetCursor = 0;
5685 // translate in by our padding
5686 /* shortcircuit calling getVerticaOffset() */
5687 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5688 voffsetText = getVerticalOffset(false);
5689 voffsetCursor = getVerticalOffset(true);
5691 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
5693 final int layoutDirection = getLayoutDirection();
5694 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
5695 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
5696 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
5697 if (!mSingleLine && getLineCount() == 1 && canMarquee() &&
5698 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) {
5699 final int width = mRight - mLeft;
5700 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight();
5701 final float dx = mLayout.getLineRight(0) - (width - padding);
5702 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5705 if (mMarquee != null && mMarquee.isRunning()) {
5706 final float dx = -mMarquee.getScroll();
5707 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5711 final int cursorOffsetVertical = voffsetCursor - voffsetText;
5713 Path highlight = getUpdatedHighlightPath();
5714 if (mEditor != null) {
5715 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical);
5717 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5720 if (mMarquee != null && mMarquee.shouldDrawGhost()) {
5721 final float dx = mMarquee.getGhostOffset();
5722 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f);
5723 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
5730 public void getFocusedRect(Rect r) {
5731 if (mLayout == null) {
5732 super.getFocusedRect(r);
5736 int selEnd = getSelectionEnd();
5738 super.getFocusedRect(r);
5742 int selStart = getSelectionStart();
5743 if (selStart < 0 || selStart >= selEnd) {
5744 int line = mLayout.getLineForOffset(selEnd);
5745 r.top = mLayout.getLineTop(line);
5746 r.bottom = mLayout.getLineBottom(line);
5747 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2;
5748 r.right = r.left + 4;
5750 int lineStart = mLayout.getLineForOffset(selStart);
5751 int lineEnd = mLayout.getLineForOffset(selEnd);
5752 r.top = mLayout.getLineTop(lineStart);
5753 r.bottom = mLayout.getLineBottom(lineEnd);
5754 if (lineStart == lineEnd) {
5755 r.left = (int) mLayout.getPrimaryHorizontal(selStart);
5756 r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
5758 // Selection extends across multiple lines -- make the focused
5759 // rect cover the entire width.
5760 if (mHighlightPathBogus) {
5761 if (mHighlightPath == null) mHighlightPath = new Path();
5762 mHighlightPath.reset();
5763 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
5764 mHighlightPathBogus = false;
5766 synchronized (TEMP_RECTF) {
5767 mHighlightPath.computeBounds(TEMP_RECTF, true);
5768 r.left = (int)TEMP_RECTF.left-1;
5769 r.right = (int)TEMP_RECTF.right+1;
5774 // Adjust for padding and gravity.
5775 int paddingLeft = getCompoundPaddingLeft();
5776 int paddingTop = getExtendedPaddingTop();
5777 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5778 paddingTop += getVerticalOffset(false);
5780 r.offset(paddingLeft, paddingTop);
5781 int paddingBottom = getExtendedPaddingBottom();
5782 r.bottom += paddingBottom;
5786 * Return the number of lines of text, or 0 if the internal Layout has not
5789 public int getLineCount() {
5790 return mLayout != null ? mLayout.getLineCount() : 0;
5794 * Return the baseline for the specified line (0...getLineCount() - 1)
5795 * If bounds is not null, return the top, left, right, bottom extents
5796 * of the specified line in it. If the internal Layout has not been built,
5797 * return 0 and set bounds to (0, 0, 0, 0)
5798 * @param line which line to examine (0..getLineCount() - 1)
5799 * @param bounds Optional. If not null, it returns the extent of the line
5800 * @return the Y-coordinate of the baseline
5802 public int getLineBounds(int line, Rect bounds) {
5803 if (mLayout == null) {
5804 if (bounds != null) {
5805 bounds.set(0, 0, 0, 0);
5810 int baseline = mLayout.getLineBounds(line, bounds);
5812 int voffset = getExtendedPaddingTop();
5813 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5814 voffset += getVerticalOffset(true);
5816 if (bounds != null) {
5817 bounds.offset(getCompoundPaddingLeft(), voffset);
5819 return baseline + voffset;
5824 public int getBaseline() {
5825 if (mLayout == null) {
5826 return super.getBaseline();
5829 return getBaselineOffset() + mLayout.getLineBaseline(0);
5832 int getBaselineOffset() {
5834 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5835 voffset = getVerticalOffset(true);
5838 if (isLayoutModeOptical(mParent)) {
5839 voffset -= getOpticalInsets().top;
5842 return getExtendedPaddingTop() + voffset;
5849 protected int getFadeTop(boolean offsetRequired) {
5850 if (mLayout == null) return 0;
5853 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
5854 voffset = getVerticalOffset(true);
5857 if (offsetRequired) voffset += getTopPaddingOffset();
5859 return getExtendedPaddingTop() + voffset;
5866 protected int getFadeHeight(boolean offsetRequired) {
5867 return mLayout != null ? mLayout.getHeight() : 0;
5871 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
5872 // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode,
5873 // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call
5874 // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event).
5875 if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) {
5878 return super.onKeyPreIme(keyCode, event);
5884 public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) {
5885 // Do nothing unless mEditor is in text action mode.
5886 if (mEditor == null || mEditor.mTextActionMode == null) {
5890 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
5891 KeyEvent.DispatcherState state = getKeyDispatcherState();
5892 if (state != null) {
5893 state.startTracking(event, this);
5896 } else if (event.getAction() == KeyEvent.ACTION_UP) {
5897 KeyEvent.DispatcherState state = getKeyDispatcherState();
5898 if (state != null) {
5899 state.handleUpEvent(event);
5901 if (event.isTracking() && !event.isCanceled()) {
5902 stopTextActionMode();
5910 public boolean onKeyDown(int keyCode, KeyEvent event) {
5911 int which = doKeyDown(keyCode, event, null);
5913 return super.onKeyDown(keyCode, event);
5920 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
5921 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN);
5923 int which = doKeyDown(keyCode, down, event);
5925 // Go through default dispatching.
5926 return super.onKeyMultiple(keyCode, repeatCount, event);
5929 // Consumed the whole thing.
5935 // We are going to dispatch the remaining events to either the input
5936 // or movement method. To do this, we will just send a repeated stream
5937 // of down and up events until we have done the complete repeatCount.
5938 // It would be nice if those interfaces had an onKeyMultiple() method,
5939 // but adding that is a more complicated change.
5940 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
5942 // mEditor and mEditor.mInput are not null from doKeyDown
5943 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5944 while (--repeatCount > 0) {
5945 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
5946 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
5948 hideErrorIfUnchanged();
5950 } else if (which == 2) {
5951 // mMovement is not null from doKeyDown
5952 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5953 while (--repeatCount > 0) {
5954 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
5955 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
5963 * Returns true if pressing ENTER in this field advances focus instead
5964 * of inserting the character. This is true mostly in single-line fields,
5965 * but also in mail addresses and subjects which will display on multiple
5966 * lines but where it doesn't make sense to insert newlines.
5968 private boolean shouldAdvanceFocusOnEnter() {
5969 if (getKeyListener() == null) {
5977 if (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_VARIATION_EMAIL_ADDRESS
5981 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
5990 * Returns true if pressing TAB in this field advances focus instead
5991 * of inserting the character. Insert tabs only in multi-line editors.
5993 private boolean shouldAdvanceFocusOnTab() {
5994 if (getKeyListener() != null && !mSingleLine && mEditor != null &&
5995 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
5996 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
5997 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
5998 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
6005 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) {
6010 // If this is the initial keydown, we don't want to prevent a movement away from this view.
6011 // While this shouldn't be necessary because any time we're preventing default movement we
6012 // should be restricting the focus to remain within this view, thus we'll also receive
6013 // the key up event, occasionally key up events will get dropped and we don't want to
6014 // prevent the user from traversing out of this on the next key down.
6015 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6016 mPreventDefaultMovement = false;
6020 case KeyEvent.KEYCODE_ENTER:
6021 if (event.hasNoModifiers()) {
6022 // When mInputContentType is set, we know that we are
6023 // running in a "modern" cupcake environment, so don't need
6024 // to worry about the application trying to capture
6025 // enter key events.
6026 if (mEditor != null && mEditor.mInputContentType != null) {
6027 // If there is an action listener, given them a
6028 // chance to consume the event.
6029 if (mEditor.mInputContentType.onEditorActionListener != null &&
6030 mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6031 this, EditorInfo.IME_NULL, event)) {
6032 mEditor.mInputContentType.enterDown = true;
6033 // We are consuming the enter key for them.
6038 // If our editor should move focus when enter is pressed, or
6039 // this is a generated event from an IME action button, then
6040 // don't let it be inserted into the text.
6041 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6042 || shouldAdvanceFocusOnEnter()) {
6043 if (hasOnClickListeners()) {
6051 case KeyEvent.KEYCODE_DPAD_CENTER:
6052 if (event.hasNoModifiers()) {
6053 if (shouldAdvanceFocusOnEnter()) {
6059 case KeyEvent.KEYCODE_TAB:
6060 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) {
6061 if (shouldAdvanceFocusOnTab()) {
6067 // Has to be done on key down (and not on key up) to correctly be intercepted.
6068 case KeyEvent.KEYCODE_BACK:
6069 if (mEditor != null && mEditor.mTextActionMode != null) {
6070 stopTextActionMode();
6076 if (mEditor != null && mEditor.mKeyListener != null) {
6077 boolean doDown = true;
6078 if (otherEvent != null) {
6081 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText,
6083 hideErrorIfUnchanged();
6088 } catch (AbstractMethodError e) {
6089 // onKeyOther was added after 1.0, so if it isn't
6090 // implemented we need to try to dispatch as a regular down.
6098 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText,
6101 hideErrorIfUnchanged();
6102 if (handled) return 1;
6106 // bug 650865: sometimes we get a key event before a layout.
6107 // don't try to move around if we don't know the layout.
6109 if (mMovement != null && mLayout != null) {
6110 boolean doDown = true;
6111 if (otherEvent != null) {
6113 boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
6119 } catch (AbstractMethodError e) {
6120 // onKeyOther was added after 1.0, so if it isn't
6121 // implemented we need to try to dispatch as a regular down.
6125 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) {
6126 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
6127 mPreventDefaultMovement = true;
6134 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0;
6138 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)}
6142 public void resetErrorChangedFlag() {
6144 * Keep track of what the error was before doing the input
6145 * so that if an input filter changed the error, we leave
6146 * that error showing. Otherwise, we take down whatever
6147 * error was showing when the user types something.
6149 if (mEditor != null) mEditor.mErrorWasChanged = false;
6155 public void hideErrorIfUnchanged() {
6156 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) {
6157 setError(null, null);
6162 public boolean onKeyUp(int keyCode, KeyEvent event) {
6164 return super.onKeyUp(keyCode, event);
6167 if (!KeyEvent.isModifierKey(keyCode)) {
6168 mPreventDefaultMovement = false;
6172 case KeyEvent.KEYCODE_DPAD_CENTER:
6173 if (event.hasNoModifiers()) {
6175 * If there is a click listener, just call through to
6176 * super, which will invoke it.
6178 * If there isn't a click listener, try to show the soft
6179 * input method. (It will also
6180 * call performClick(), but that won't do anything in
6183 if (!hasOnClickListeners()) {
6184 if (mMovement != null && mText instanceof Editable
6185 && mLayout != null && onCheckIsTextEditor()) {
6186 InputMethodManager imm = InputMethodManager.peekInstance();
6188 if (imm != null && getShowSoftInputOnFocus()) {
6189 imm.showSoftInput(this, 0);
6194 return super.onKeyUp(keyCode, event);
6196 case KeyEvent.KEYCODE_ENTER:
6197 if (event.hasNoModifiers()) {
6198 if (mEditor != null && mEditor.mInputContentType != null
6199 && mEditor.mInputContentType.onEditorActionListener != null
6200 && mEditor.mInputContentType.enterDown) {
6201 mEditor.mInputContentType.enterDown = false;
6202 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction(
6203 this, EditorInfo.IME_NULL, event)) {
6208 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
6209 || shouldAdvanceFocusOnEnter()) {
6211 * If there is a click listener, just call through to
6212 * super, which will invoke it.
6214 * If there isn't a click listener, try to advance focus,
6215 * but still call through to super, which will reset the
6216 * pressed state and longpress state. (It will also
6217 * call performClick(), but that won't do anything in
6220 if (!hasOnClickListeners()) {
6221 View v = focusSearch(FOCUS_DOWN);
6224 if (!v.requestFocus(FOCUS_DOWN)) {
6225 throw new IllegalStateException(
6226 "focus search returned a view " +
6227 "that wasn't able to take focus!");
6231 * Return true because we handled the key; super
6232 * will return false because there was no click
6235 super.onKeyUp(keyCode, event);
6237 } else if ((event.getFlags()
6238 & KeyEvent.FLAG_EDITOR_ACTION) != 0) {
6239 // No target for next focus, but make sure the IME
6240 // if this came from it.
6241 InputMethodManager imm = InputMethodManager.peekInstance();
6242 if (imm != null && imm.isActive(this)) {
6243 imm.hideSoftInputFromWindow(getWindowToken(), 0);
6248 return super.onKeyUp(keyCode, event);
6253 if (mEditor != null && mEditor.mKeyListener != null)
6254 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
6257 if (mMovement != null && mLayout != null)
6258 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event))
6261 return super.onKeyUp(keyCode, event);
6265 public boolean onCheckIsTextEditor() {
6266 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL;
6270 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
6271 if (onCheckIsTextEditor() && isEnabled()) {
6272 mEditor.createInputMethodStateIfNeeded();
6273 outAttrs.inputType = getInputType();
6274 if (mEditor.mInputContentType != null) {
6275 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions;
6276 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions;
6277 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel;
6278 outAttrs.actionId = mEditor.mInputContentType.imeActionId;
6279 outAttrs.extras = mEditor.mInputContentType.extras;
6281 outAttrs.imeOptions = EditorInfo.IME_NULL;
6283 if (focusSearch(FOCUS_DOWN) != null) {
6284 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
6286 if (focusSearch(FOCUS_UP) != null) {
6287 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS;
6289 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION)
6290 == EditorInfo.IME_ACTION_UNSPECIFIED) {
6291 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) {
6292 // An action has not been set, but the enter key will move to
6293 // the next focus, so set the action to that.
6294 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT;
6296 // An action has not been set, and there is no focus to move
6297 // to, so let's just supply a "done" action.
6298 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE;
6300 if (!shouldAdvanceFocusOnEnter()) {
6301 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6304 if (isMultilineInputType(outAttrs.inputType)) {
6305 // Multi-line text editors should always show an enter key.
6306 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION;
6308 outAttrs.hintText = mHint;
6309 if (mText instanceof Editable) {
6310 InputConnection ic = new EditableInputConnection(this);
6311 outAttrs.initialSelStart = getSelectionStart();
6312 outAttrs.initialSelEnd = getSelectionEnd();
6313 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
6321 * If this TextView contains editable content, extract a portion of it
6322 * based on the information in <var>request</var> in to <var>outText</var>.
6323 * @return Returns true if the text was successfully extracted, else false.
6325 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) {
6326 createEditorIfNeeded();
6327 return mEditor.extractText(request, outText);
6331 * This is used to remove all style-impacting spans from text before new
6332 * extracted text is being replaced into it, so that we don't have any
6333 * lingering spans applied during the replace.
6335 static void removeParcelableSpans(Spannable spannable, int start, int end) {
6336 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class);
6337 int i = spans.length;
6340 spannable.removeSpan(spans[i]);
6345 * Apply to this text view the given extracted text, as previously
6346 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}.
6348 public void setExtractedText(ExtractedText text) {
6349 Editable content = getEditableText();
6350 if (text.text != null) {
6351 if (content == null) {
6352 setText(text.text, TextView.BufferType.EDITABLE);
6355 int end = content.length();
6357 if (text.partialStartOffset >= 0) {
6358 final int N = content.length();
6359 start = text.partialStartOffset;
6360 if (start > N) start = N;
6361 end = text.partialEndOffset;
6362 if (end > N) end = N;
6365 removeParcelableSpans(content, start, end);
6366 if (TextUtils.equals(content.subSequence(start, end), text.text)) {
6367 if (text.text instanceof Spanned) {
6368 // OK to copy spans only.
6369 TextUtils.copySpansFrom((Spanned) text.text, start, end,
6370 Object.class, content, start);
6373 content.replace(start, end, text.text);
6378 // Now set the selection position... make sure it is in range, to
6379 // avoid crashes. If this is a partial update, it is possible that
6380 // the underlying text may have changed, causing us problems here.
6381 // Also we just don't want to trust clients to do the right thing.
6382 Spannable sp = (Spannable)getText();
6383 final int N = sp.length();
6384 int start = text.selectionStart;
6385 if (start < 0) start = 0;
6386 else if (start > N) start = N;
6387 int end = text.selectionEnd;
6388 if (end < 0) end = 0;
6389 else if (end > N) end = N;
6390 Selection.setSelection(sp, start, end);
6392 // Finally, update the selection mode.
6393 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) {
6394 MetaKeyKeyListener.startSelecting(this, sp);
6396 MetaKeyKeyListener.stopSelecting(this, sp);
6403 public void setExtracting(ExtractedTextRequest req) {
6404 if (mEditor.mInputMethodState != null) {
6405 mEditor.mInputMethodState.mExtractedTextRequest = req;
6407 // This would stop a possible selection mode, but no such mode is started in case
6408 // extracted mode will start. Some text is selected though, and will trigger an action mode
6409 // in the extracted view.
6410 mEditor.hideCursorAndSpanControllers();
6411 stopTextActionMode();
6415 * Called by the framework in response to a text completion from
6416 * the current input method, provided by it calling
6417 * {@link InputConnection#commitCompletion
6418 * InputConnection.commitCompletion()}. The default implementation does
6419 * nothing; text views that are supporting auto-completion should override
6420 * this to do their desired behavior.
6422 * @param text The auto complete text the user has selected.
6424 public void onCommitCompletion(CompletionInfo text) {
6425 // intentionally empty
6429 * Called by the framework in response to a text auto-correction (such as fixing a typo using a
6430 * a dictionnary) from the current input method, provided by it calling
6431 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default
6432 * implementation flashes the background of the corrected word to provide feedback to the user.
6434 * @param info The auto correct info about the text that was corrected.
6436 public void onCommitCorrection(CorrectionInfo info) {
6437 if (mEditor != null) mEditor.onCommitCorrection(info);
6440 public void beginBatchEdit() {
6441 if (mEditor != null) mEditor.beginBatchEdit();
6444 public void endBatchEdit() {
6445 if (mEditor != null) mEditor.endBatchEdit();
6449 * Called by the framework in response to a request to begin a batch
6450 * of edit operations through a call to link {@link #beginBatchEdit()}.
6452 public void onBeginBatchEdit() {
6453 // intentionally empty
6457 * Called by the framework in response to a request to end a batch
6458 * of edit operations through a call to link {@link #endBatchEdit}.
6460 public void onEndBatchEdit() {
6461 // intentionally empty
6465 * Called by the framework in response to a private command from the
6466 * current method, provided by it calling
6467 * {@link InputConnection#performPrivateCommand
6468 * InputConnection.performPrivateCommand()}.
6470 * @param action The action name of the command.
6471 * @param data Any additional data for the command. This may be null.
6472 * @return Return true if you handled the command, else false.
6474 public boolean onPrivateIMECommand(String action, Bundle data) {
6478 private void nullLayouts() {
6479 if (mLayout instanceof BoringLayout && mSavedLayout == null) {
6480 mSavedLayout = (BoringLayout) mLayout;
6482 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) {
6483 mSavedHintLayout = (BoringLayout) mHintLayout;
6486 mSavedMarqueeModeLayout = mLayout = mHintLayout = null;
6488 mBoring = mHintBoring = null;
6490 // Since it depends on the value of mLayout
6491 if (mEditor != null) mEditor.prepareCursorControllers();
6495 * Make a new Layout based on the already-measured size of the view,
6496 * on the assumption that it was measured correctly at some point.
6498 private void assumeLayout() {
6499 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
6505 int physicalWidth = width;
6507 if (mHorizontallyScrolling) {
6511 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING,
6512 physicalWidth, false);
6515 private Layout.Alignment getLayoutAlignment() {
6516 Layout.Alignment alignment;
6517 switch (getTextAlignment()) {
6518 case TEXT_ALIGNMENT_GRAVITY:
6519 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
6521 alignment = Layout.Alignment.ALIGN_NORMAL;
6524 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6527 alignment = Layout.Alignment.ALIGN_LEFT;
6530 alignment = Layout.Alignment.ALIGN_RIGHT;
6532 case Gravity.CENTER_HORIZONTAL:
6533 alignment = Layout.Alignment.ALIGN_CENTER;
6536 alignment = Layout.Alignment.ALIGN_NORMAL;
6540 case TEXT_ALIGNMENT_TEXT_START:
6541 alignment = Layout.Alignment.ALIGN_NORMAL;
6543 case TEXT_ALIGNMENT_TEXT_END:
6544 alignment = Layout.Alignment.ALIGN_OPPOSITE;
6546 case TEXT_ALIGNMENT_CENTER:
6547 alignment = Layout.Alignment.ALIGN_CENTER;
6549 case TEXT_ALIGNMENT_VIEW_START:
6550 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6551 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT;
6553 case TEXT_ALIGNMENT_VIEW_END:
6554 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ?
6555 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT;
6557 case TEXT_ALIGNMENT_INHERIT:
6558 // This should never happen as we have already resolved the text alignment
6559 // but better safe than sorry so we just fall through
6561 alignment = Layout.Alignment.ALIGN_NORMAL;
6568 * The width passed in is now the desired layout width,
6569 * not the full view width with padding.
6572 protected void makeNewLayout(int wantWidth, int hintWidth,
6573 BoringLayout.Metrics boring,
6574 BoringLayout.Metrics hintBoring,
6575 int ellipsisWidth, boolean bringIntoView) {
6578 // Update "old" cached values
6579 mOldMaximum = mMaximum;
6580 mOldMaxMode = mMaxMode;
6582 mHighlightPathBogus = true;
6584 if (wantWidth < 0) {
6587 if (hintWidth < 0) {
6591 Layout.Alignment alignment = getLayoutAlignment();
6592 final boolean testDirChange = mSingleLine && mLayout != null &&
6593 (alignment == Layout.Alignment.ALIGN_NORMAL ||
6594 alignment == Layout.Alignment.ALIGN_OPPOSITE);
6596 if (testDirChange) oldDir = mLayout.getParagraphDirection(0);
6597 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
6598 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
6599 mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
6600 TruncateAt effectiveEllipsize = mEllipsize;
6601 if (mEllipsize == TruncateAt.MARQUEE &&
6602 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
6603 effectiveEllipsize = TruncateAt.END_SMALL;
6606 if (mTextDir == null) {
6607 mTextDir = getTextDirectionHeuristic();
6610 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize,
6611 effectiveEllipsize, effectiveEllipsize == mEllipsize);
6612 if (switchEllipsize) {
6613 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ?
6614 TruncateAt.END : TruncateAt.MARQUEE;
6615 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment,
6616 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize);
6619 shouldEllipsize = mEllipsize != null;
6622 if (mHint != null) {
6623 if (shouldEllipsize) hintWidth = wantWidth;
6625 if (hintBoring == UNKNOWN_BORING) {
6626 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir,
6628 if (hintBoring != null) {
6629 mHintBoring = hintBoring;
6633 if (hintBoring != null) {
6634 if (hintBoring.width <= hintWidth &&
6635 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) {
6636 if (mSavedHintLayout != null) {
6637 mHintLayout = mSavedHintLayout.
6638 replaceOrMake(mHint, mTextPaint,
6639 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6640 hintBoring, mIncludePad);
6642 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6643 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6644 hintBoring, mIncludePad);
6647 mSavedHintLayout = (BoringLayout) mHintLayout;
6648 } else if (shouldEllipsize && hintBoring.width <= hintWidth) {
6649 if (mSavedHintLayout != null) {
6650 mHintLayout = mSavedHintLayout.
6651 replaceOrMake(mHint, mTextPaint,
6652 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6653 hintBoring, mIncludePad, mEllipsize,
6656 mHintLayout = BoringLayout.make(mHint, mTextPaint,
6657 hintWidth, alignment, mSpacingMult, mSpacingAdd,
6658 hintBoring, mIncludePad, mEllipsize,
6663 // TODO: code duplication with makeSingleLayout()
6664 if (mHintLayout == null) {
6665 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0,
6666 mHint.length(), mTextPaint, hintWidth)
6667 .setAlignment(alignment)
6668 .setTextDirection(mTextDir)
6669 .setLineSpacing(mSpacingAdd, mSpacingMult)
6670 .setIncludePad(mIncludePad)
6671 .setBreakStrategy(mBreakStrategy)
6672 .setHyphenationFrequency(mHyphenationFrequency);
6673 if (shouldEllipsize) {
6674 builder.setEllipsize(mEllipsize)
6675 .setEllipsizedWidth(ellipsisWidth)
6676 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6678 mHintLayout = builder.build();
6682 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) {
6683 registerForPreDraw();
6686 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
6687 if (!compressText(ellipsisWidth)) {
6688 final int height = mLayoutParams.height;
6689 // If the size of the view does not depend on the size of the text, try to
6690 // start the marquee immediately
6691 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) {
6694 // Defer the start of the marquee until we know our width (see setFrame())
6695 mRestartMarquee = true;
6700 // CursorControllers need a non-null mLayout
6701 if (mEditor != null) mEditor.prepareCursorControllers();
6704 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
6705 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
6707 Layout result = null;
6708 if (mText instanceof Spannable) {
6709 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
6710 alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
6711 mBreakStrategy, mHyphenationFrequency,
6712 getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
6714 if (boring == UNKNOWN_BORING) {
6715 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6716 if (boring != null) {
6721 if (boring != null) {
6722 if (boring.width <= wantWidth &&
6723 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) {
6724 if (useSaved && mSavedLayout != null) {
6725 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6726 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6727 boring, mIncludePad);
6729 result = BoringLayout.make(mTransformed, mTextPaint,
6730 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6731 boring, mIncludePad);
6735 mSavedLayout = (BoringLayout) result;
6737 } else if (shouldEllipsize && boring.width <= wantWidth) {
6738 if (useSaved && mSavedLayout != null) {
6739 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint,
6740 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6741 boring, mIncludePad, effectiveEllipsize,
6744 result = BoringLayout.make(mTransformed, mTextPaint,
6745 wantWidth, alignment, mSpacingMult, mSpacingAdd,
6746 boring, mIncludePad, effectiveEllipsize,
6752 if (result == null) {
6753 StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed,
6754 0, mTransformed.length(), mTextPaint, wantWidth)
6755 .setAlignment(alignment)
6756 .setTextDirection(mTextDir)
6757 .setLineSpacing(mSpacingAdd, mSpacingMult)
6758 .setIncludePad(mIncludePad)
6759 .setBreakStrategy(mBreakStrategy)
6760 .setHyphenationFrequency(mHyphenationFrequency);
6761 if (shouldEllipsize) {
6762 builder.setEllipsize(effectiveEllipsize)
6763 .setEllipsizedWidth(ellipsisWidth)
6764 .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE);
6766 // TODO: explore always setting maxLines
6767 result = builder.build();
6772 private boolean compressText(float width) {
6773 if (isHardwareAccelerated()) return false;
6775 // Only compress the text if it hasn't been compressed by the previous pass
6776 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX &&
6777 mTextPaint.getTextScaleX() == 1.0f) {
6778 final float textWidth = mLayout.getLineWidth(0);
6779 final float overflow = (textWidth + 1.0f - width) / width;
6780 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) {
6781 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f);
6782 post(new Runnable() {
6794 private static int desired(Layout layout) {
6795 int n = layout.getLineCount();
6796 CharSequence text = layout.getText();
6799 // if any line was wrapped, we can't use it.
6800 // but it's ok for the last line not to have a newline
6802 for (int i = 0; i < n - 1; i++) {
6803 if (text.charAt(layout.getLineEnd(i) - 1) != '\n')
6807 for (int i = 0; i < n; i++) {
6808 max = Math.max(max, layout.getLineWidth(i));
6811 return (int) Math.ceil(max);
6815 * Set whether the TextView includes extra top and bottom padding to make
6816 * room for accents that go above the normal ascent and descent.
6817 * The default is true.
6819 * @see #getIncludeFontPadding()
6821 * @attr ref android.R.styleable#TextView_includeFontPadding
6823 public void setIncludeFontPadding(boolean includepad) {
6824 if (mIncludePad != includepad) {
6825 mIncludePad = includepad;
6827 if (mLayout != null) {
6836 * Gets whether the TextView includes extra top and bottom padding to make
6837 * room for accents that go above the normal ascent and descent.
6839 * @see #setIncludeFontPadding(boolean)
6841 * @attr ref android.R.styleable#TextView_includeFontPadding
6843 public boolean getIncludeFontPadding() {
6847 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics();
6850 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
6851 int widthMode = MeasureSpec.getMode(widthMeasureSpec);
6852 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
6853 int widthSize = MeasureSpec.getSize(widthMeasureSpec);
6854 int heightSize = MeasureSpec.getSize(heightMeasureSpec);
6859 BoringLayout.Metrics boring = UNKNOWN_BORING;
6860 BoringLayout.Metrics hintBoring = UNKNOWN_BORING;
6862 if (mTextDir == null) {
6863 mTextDir = getTextDirectionHeuristic();
6867 boolean fromexisting = false;
6869 if (widthMode == MeasureSpec.EXACTLY) {
6870 // Parent has told us how big to be. So be it.
6873 if (mLayout != null && mEllipsize == null) {
6874 des = desired(mLayout);
6878 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
6879 if (boring != null) {
6883 fromexisting = true;
6886 if (boring == null || boring == UNKNOWN_BORING) {
6888 des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
6892 width = boring.width;
6895 final Drawables dr = mDrawables;
6897 width = Math.max(width, dr.mDrawableWidthTop);
6898 width = Math.max(width, dr.mDrawableWidthBottom);
6901 if (mHint != null) {
6905 if (mHintLayout != null && mEllipsize == null) {
6906 hintDes = desired(mHintLayout);
6910 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
6911 if (hintBoring != null) {
6912 mHintBoring = hintBoring;
6916 if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
6918 hintDes = (int) Math.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
6920 hintWidth = hintDes;
6922 hintWidth = hintBoring.width;
6925 if (hintWidth > width) {
6930 width += getCompoundPaddingLeft() + getCompoundPaddingRight();
6932 if (mMaxWidthMode == EMS) {
6933 width = Math.min(width, mMaxWidth * getLineHeight());
6935 width = Math.min(width, mMaxWidth);
6938 if (mMinWidthMode == EMS) {
6939 width = Math.max(width, mMinWidth * getLineHeight());
6941 width = Math.max(width, mMinWidth);
6944 // Check against our minimum width
6945 width = Math.max(width, getSuggestedMinimumWidth());
6947 if (widthMode == MeasureSpec.AT_MOST) {
6948 width = Math.min(widthSize, width);
6952 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
6953 int unpaddedWidth = want;
6955 if (mHorizontallyScrolling) want = VERY_WIDE;
6957 int hintWant = want;
6958 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();
6960 if (mLayout == null) {
6961 makeNewLayout(want, hintWant, boring, hintBoring,
6962 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6964 final boolean layoutChanged = (mLayout.getWidth() != want) ||
6965 (hintWidth != hintWant) ||
6966 (mLayout.getEllipsizedWidth() !=
6967 width - getCompoundPaddingLeft() - getCompoundPaddingRight());
6969 final boolean widthChanged = (mHint == null) &&
6970 (mEllipsize == null) &&
6971 (want > mLayout.getWidth()) &&
6972 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));
6974 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);
6976 if (layoutChanged || maximumChanged) {
6977 if (!maximumChanged && widthChanged) {
6978 mLayout.increaseWidthTo(want);
6980 makeNewLayout(want, hintWant, boring, hintBoring,
6981 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
6984 // Nothing has changed
6988 if (heightMode == MeasureSpec.EXACTLY) {
6989 // Parent has told us how big to be. So be it.
6990 height = heightSize;
6991 mDesiredHeightAtMeasure = -1;
6993 int desired = getDesiredHeight();
6996 mDesiredHeightAtMeasure = desired;
6998 if (heightMode == MeasureSpec.AT_MOST) {
6999 height = Math.min(desired, heightSize);
7003 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
7004 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
7005 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
7009 * We didn't let makeNewLayout() register to bring the cursor into view,
7010 * so do it here if there is any possibility that it is needed.
7012 if (mMovement != null ||
7013 mLayout.getWidth() > unpaddedWidth ||
7014 mLayout.getHeight() > unpaddedHeight) {
7015 registerForPreDraw();
7020 setMeasuredDimension(width, height);
7023 private int getDesiredHeight() {
7025 getDesiredHeight(mLayout, true),
7026 getDesiredHeight(mHintLayout, mEllipsize != null));
7029 private int getDesiredHeight(Layout layout, boolean cap) {
7030 if (layout == null) {
7034 int linecount = layout.getLineCount();
7035 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom();
7036 int desired = layout.getLineTop(linecount);
7038 final Drawables dr = mDrawables;
7040 desired = Math.max(desired, dr.mDrawableHeightLeft);
7041 desired = Math.max(desired, dr.mDrawableHeightRight);
7046 if (mMaxMode == LINES) {
7048 * Don't cap the hint to a certain number of lines.
7049 * (Do cap it, though, if we have a maximum pixel height.)
7052 if (linecount > mMaximum) {
7053 desired = layout.getLineTop(mMaximum);
7056 desired = Math.max(desired, dr.mDrawableHeightLeft);
7057 desired = Math.max(desired, dr.mDrawableHeightRight);
7061 linecount = mMaximum;
7065 desired = Math.min(desired, mMaximum);
7068 if (mMinMode == LINES) {
7069 if (linecount < mMinimum) {
7070 desired += getLineHeight() * (mMinimum - linecount);
7073 desired = Math.max(desired, mMinimum);
7076 // Check against our minimum height
7077 desired = Math.max(desired, getSuggestedMinimumHeight());
7083 * Check whether a change to the existing text layout requires a
7086 private void checkForResize() {
7087 boolean sizeChanged = false;
7089 if (mLayout != null) {
7090 // Check if our width changed
7091 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) {
7096 // Check if our height changed
7097 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) {
7098 int desiredHeight = getDesiredHeight();
7100 if (desiredHeight != this.getHeight()) {
7103 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) {
7104 if (mDesiredHeightAtMeasure >= 0) {
7105 int desiredHeight = getDesiredHeight();
7107 if (desiredHeight != mDesiredHeightAtMeasure) {
7116 // caller will have already invalidated
7121 * Check whether entirely new text requires a new view layout
7122 * or merely a new text layout.
7124 private void checkForRelayout() {
7125 // If we have a fixed width, we can just swap in a new text layout
7126 // if the text height stays the same or if the view height is fixed.
7128 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT ||
7129 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) &&
7130 (mHint == null || mHintLayout != null) &&
7131 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
7132 // Static width, so try making a new text layout.
7134 int oldht = mLayout.getHeight();
7135 int want = mLayout.getWidth();
7136 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
7139 * No need to bring the text into view, since the size is not
7140 * changing (unless we do the requestLayout(), in which case it
7141 * will happen at measure).
7143 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
7144 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
7147 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
7148 // In a fixed-height view, so use our new text layout.
7149 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT &&
7150 mLayoutParams.height != LayoutParams.MATCH_PARENT) {
7155 // Dynamic height, but height has stayed the same,
7156 // so use our new text layout.
7157 if (mLayout.getHeight() == oldht &&
7158 (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
7164 // We lose: the height has changed and we have a dynamic height.
7165 // Request a new view layout using our new text layout.
7169 // Dynamic width, so we have no choice but to request a new
7170 // view layout with a new text layout.
7178 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
7179 super.onLayout(changed, left, top, right, bottom);
7180 if (mDeferScroll >= 0) {
7181 int curs = mDeferScroll;
7183 bringPointIntoView(Math.min(curs, mText.length()));
7187 private boolean isShowingHint() {
7188 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint);
7192 * Returns true if anything changed.
7194 private boolean bringTextIntoView() {
7195 Layout layout = isShowingHint() ? mHintLayout : mLayout;
7197 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7198 line = layout.getLineCount() - 1;
7201 Layout.Alignment a = layout.getParagraphAlignment(line);
7202 int dir = layout.getParagraphDirection(line);
7203 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7204 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7205 int ht = layout.getHeight();
7207 int scrollx, scrolly;
7209 // Convert to left, center, or right alignment.
7210 if (a == Layout.Alignment.ALIGN_NORMAL) {
7211 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT :
7212 Layout.Alignment.ALIGN_RIGHT;
7213 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){
7214 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT :
7215 Layout.Alignment.ALIGN_LEFT;
7218 if (a == Layout.Alignment.ALIGN_CENTER) {
7220 * Keep centered if possible, or, if it is too wide to fit,
7221 * keep leading edge in view.
7224 int left = (int) Math.floor(layout.getLineLeft(line));
7225 int right = (int) Math.ceil(layout.getLineRight(line));
7227 if (right - left < hspace) {
7228 scrollx = (right + left) / 2 - hspace / 2;
7231 scrollx = right - hspace;
7236 } else if (a == Layout.Alignment.ALIGN_RIGHT) {
7237 int right = (int) Math.ceil(layout.getLineRight(line));
7238 scrollx = right - hspace;
7239 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default)
7240 scrollx = (int) Math.floor(layout.getLineLeft(line));
7246 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
7247 scrolly = ht - vspace;
7253 if (scrollx != mScrollX || scrolly != mScrollY) {
7254 scrollTo(scrollx, scrolly);
7262 * Move the point, specified by the offset, into the view if it is needed.
7263 * This has to be called after layout. Returns true if anything changed.
7265 public boolean bringPointIntoView(int offset) {
7266 if (isLayoutRequested()) {
7267 mDeferScroll = offset;
7270 boolean changed = false;
7272 Layout layout = isShowingHint() ? mHintLayout: mLayout;
7274 if (layout == null) return changed;
7276 int line = layout.getLineForOffset(offset);
7280 switch (layout.getParagraphAlignment(line)) {
7288 grav = layout.getParagraphDirection(line);
7290 case ALIGN_OPPOSITE:
7291 grav = -layout.getParagraphDirection(line);
7299 // We only want to clamp the cursor to fit within the layout width
7300 // in left-to-right modes, because in a right to left alignment,
7301 // we want to scroll to keep the line-right on the screen, as other
7302 // lines are likely to have text flush with the right margin, which
7303 // we want to keep visible.
7304 // A better long-term solution would probably be to measure both
7305 // the full line and a blank-trimmed version, and, for example, use
7306 // the latter measurement for centering and right alignment, but for
7307 // the time being we only implement the cursor clamping in left to
7308 // right where it is most likely to be annoying.
7309 final boolean clamped = grav > 0;
7310 // FIXME: Is it okay to truncate this, or should we round?
7311 final int x = (int)layout.getPrimaryHorizontal(offset, clamped);
7312 final int top = layout.getLineTop(line);
7313 final int bottom = layout.getLineTop(line + 1);
7315 int left = (int) Math.floor(layout.getLineLeft(line));
7316 int right = (int) Math.ceil(layout.getLineRight(line));
7317 int ht = layout.getHeight();
7319 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7320 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7321 if (!mHorizontallyScrolling && right - left > hspace && right > x) {
7322 // If cursor has been clamped, make sure we don't scroll.
7323 right = Math.max(x, left + hspace);
7326 int hslack = (bottom - top) / 2;
7327 int vslack = hslack;
7329 if (vslack > vspace / 4)
7330 vslack = vspace / 4;
7331 if (hslack > hspace / 4)
7332 hslack = hspace / 4;
7337 if (top - vs < vslack)
7339 if (bottom - vs > vspace - vslack)
7340 vs = bottom - (vspace - vslack);
7341 if (ht - vs < vspace)
7347 if (x - hs < hslack) {
7350 if (x - hs > hspace - hslack) {
7351 hs = x - (hspace - hslack);
7358 if (right - hs < hspace)
7359 hs = right - hspace;
7360 } else if (grav > 0) {
7361 if (right - hs < hspace)
7362 hs = right - hspace;
7365 } else /* grav == 0 */ {
7366 if (right - left <= hspace) {
7368 * If the entire text fits, center it exactly.
7370 hs = left - (hspace - (right - left)) / 2;
7371 } else if (x > right - hslack) {
7373 * If we are near the right edge, keep the right edge
7374 * at the edge of the view.
7376 hs = right - hspace;
7377 } else if (x < left + hslack) {
7379 * If we are near the left edge, keep the left edge
7380 * at the edge of the view.
7383 } else if (left > hs) {
7385 * Is there whitespace visible at the left? Fix it if so.
7388 } else if (right < hs + hspace) {
7390 * Is there whitespace visible at the right? Fix it if so.
7392 hs = right - hspace;
7395 * Otherwise, float as needed.
7397 if (x - hs < hslack) {
7400 if (x - hs > hspace - hslack) {
7401 hs = x - (hspace - hslack);
7406 if (hs != mScrollX || vs != mScrollY) {
7407 if (mScroller == null) {
7410 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
7411 int dx = hs - mScrollX;
7412 int dy = vs - mScrollY;
7414 if (duration > ANIMATED_SCROLL_GAP) {
7415 mScroller.startScroll(mScrollX, mScrollY, dx, dy);
7416 awakenScrollBars(mScroller.getDuration());
7419 if (!mScroller.isFinished()) {
7420 mScroller.abortAnimation();
7426 mLastScroll = AnimationUtils.currentAnimationTimeMillis();
7433 // This offsets because getInterestingRect() is in terms of viewport coordinates, but
7434 // requestRectangleOnScreen() is in terms of content coordinates.
7436 // The offsets here are to ensure the rectangle we are using is
7437 // within our view bounds, in case the cursor is on the far left
7438 // or right. If it isn't withing the bounds, then this request
7440 if (mTempRect == null) mTempRect = new Rect();
7441 mTempRect.set(x - 2, top, x + 2, bottom);
7442 getInterestingRect(mTempRect, line);
7443 mTempRect.offset(mScrollX, mScrollY);
7445 if (requestRectangleOnScreen(mTempRect)) {
7454 * Move the cursor, if needed, so that it is at an offset that is visible
7455 * to the user. This will not move the cursor if it represents more than
7456 * one character (a selection range). This will only work if the
7457 * TextView contains spannable text; otherwise it will do nothing.
7459 * @return True if the cursor was actually moved, false otherwise.
7461 public boolean moveCursorToVisibleOffset() {
7462 if (!(mText instanceof Spannable)) {
7465 int start = getSelectionStart();
7466 int end = getSelectionEnd();
7471 // First: make sure the line is visible on screen:
7473 int line = mLayout.getLineForOffset(start);
7475 final int top = mLayout.getLineTop(line);
7476 final int bottom = mLayout.getLineTop(line + 1);
7477 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom();
7478 int vslack = (bottom - top) / 2;
7479 if (vslack > vspace / 4)
7480 vslack = vspace / 4;
7481 final int vs = mScrollY;
7483 if (top < (vs+vslack)) {
7484 line = mLayout.getLineForVertical(vs+vslack+(bottom-top));
7485 } else if (bottom > (vspace+vs-vslack)) {
7486 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top));
7489 // Next: make sure the character is visible on screen:
7491 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight();
7492 final int hs = mScrollX;
7493 final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
7494 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
7496 // line might contain bidirectional text
7497 final int lowChar = leftChar < rightChar ? leftChar : rightChar;
7498 final int highChar = leftChar > rightChar ? leftChar : rightChar;
7500 int newStart = start;
7501 if (newStart < lowChar) {
7503 } else if (newStart > highChar) {
7504 newStart = highChar;
7507 if (newStart != start) {
7508 Selection.setSelection((Spannable)mText, newStart);
7516 public void computeScroll() {
7517 if (mScroller != null) {
7518 if (mScroller.computeScrollOffset()) {
7519 mScrollX = mScroller.getCurrX();
7520 mScrollY = mScroller.getCurrY();
7521 invalidateParentCaches();
7522 postInvalidate(); // So we draw again
7527 private void getInterestingRect(Rect r, int line) {
7528 convertFromViewportToContentCoordinates(r);
7530 // Rectangle can can be expanded on first and last line to take
7531 // padding into account.
7532 // TODO Take left/right padding into account too?
7533 if (line == 0) r.top -= getExtendedPaddingTop();
7534 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom();
7537 private void convertFromViewportToContentCoordinates(Rect r) {
7538 final int horizontalOffset = viewportToContentHorizontalOffset();
7539 r.left += horizontalOffset;
7540 r.right += horizontalOffset;
7542 final int verticalOffset = viewportToContentVerticalOffset();
7543 r.top += verticalOffset;
7544 r.bottom += verticalOffset;
7547 int viewportToContentHorizontalOffset() {
7548 return getCompoundPaddingLeft() - mScrollX;
7551 int viewportToContentVerticalOffset() {
7552 int offset = getExtendedPaddingTop() - mScrollY;
7553 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
7554 offset += getVerticalOffset(false);
7560 public void debug(int depth) {
7563 String output = debugIndent(depth);
7564 output += "frame={" + mLeft + ", " + mTop + ", " + mRight
7565 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY
7568 if (mText != null) {
7570 output += "mText=\"" + mText + "\" ";
7571 if (mLayout != null) {
7572 output += "mLayout width=" + mLayout.getWidth()
7573 + " height=" + mLayout.getHeight();
7576 output += "mText=NULL";
7578 Log.d(VIEW_LOG_TAG, output);
7582 * Convenience for {@link Selection#getSelectionStart}.
7584 @ViewDebug.ExportedProperty(category = "text")
7585 public int getSelectionStart() {
7586 return Selection.getSelectionStart(getText());
7590 * Convenience for {@link Selection#getSelectionEnd}.
7592 @ViewDebug.ExportedProperty(category = "text")
7593 public int getSelectionEnd() {
7594 return Selection.getSelectionEnd(getText());
7598 * Return true iff there is a selection inside this text view.
7600 public boolean hasSelection() {
7601 final int selectionStart = getSelectionStart();
7602 final int selectionEnd = getSelectionEnd();
7604 return selectionStart >= 0 && selectionStart != selectionEnd;
7607 String getSelectedText() {
7608 if (!hasSelection()) {
7612 final int start = getSelectionStart();
7613 final int end = getSelectionEnd();
7614 return String.valueOf(
7615 start > end ? mText.subSequence(end, start) : mText.subSequence(start, end));
7619 * Sets the properties of this field (lines, horizontally scrolling,
7620 * transformation method) to be for a single-line input.
7622 * @attr ref android.R.styleable#TextView_singleLine
7624 public void setSingleLine() {
7625 setSingleLine(true);
7629 * Sets the properties of this field to transform input to ALL CAPS
7630 * display. This may use a "small caps" formatting if available.
7631 * This setting will be ignored if this field is editable or selectable.
7633 * This call replaces the current transformation method. Disabling this
7634 * will not necessarily restore the previous behavior from before this
7637 * @see #setTransformationMethod(TransformationMethod)
7638 * @attr ref android.R.styleable#TextView_textAllCaps
7640 public void setAllCaps(boolean allCaps) {
7642 setTransformationMethod(new AllCapsTransformationMethod(getContext()));
7644 setTransformationMethod(null);
7649 * If true, sets the properties of this field (number of lines, horizontally scrolling,
7650 * transformation method) to be for a single-line input; if false, restores these to the default
7653 * Note that the default conditions are not necessarily those that were in effect prior this
7654 * method, and you may want to reset these properties to your custom values.
7656 * @attr ref android.R.styleable#TextView_singleLine
7658 @android.view.RemotableViewMethod
7659 public void setSingleLine(boolean singleLine) {
7660 // Could be used, but may break backward compatibility.
7661 // if (mSingleLine == singleLine) return;
7662 setInputTypeSingleLine(singleLine);
7663 applySingleLine(singleLine, true, true);
7667 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
7670 private void setInputTypeSingleLine(boolean singleLine) {
7671 if (mEditor != null &&
7672 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
7674 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7676 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
7681 private void applySingleLine(boolean singleLine, boolean applyTransformation,
7682 boolean changeMaxLines) {
7683 mSingleLine = singleLine;
7686 setHorizontallyScrolling(true);
7687 if (applyTransformation) {
7688 setTransformationMethod(SingleLineTransformationMethod.getInstance());
7691 if (changeMaxLines) {
7692 setMaxLines(Integer.MAX_VALUE);
7694 setHorizontallyScrolling(false);
7695 if (applyTransformation) {
7696 setTransformationMethod(null);
7702 * Causes words in the text that are longer than the view is wide
7703 * to be ellipsized instead of broken in the middle. You may also
7704 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling}
7705 * to constrain the text to a single line. Use <code>null</code>
7706 * to turn off ellipsizing.
7708 * If {@link #setMaxLines} has been used to set two or more lines,
7709 * only {@link android.text.TextUtils.TruncateAt#END} and
7710 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported
7711 * (other ellipsizing types will not do anything).
7713 * @attr ref android.R.styleable#TextView_ellipsize
7715 public void setEllipsize(TextUtils.TruncateAt where) {
7716 // TruncateAt is an enum. != comparison is ok between these singleton objects.
7717 if (mEllipsize != where) {
7720 if (mLayout != null) {
7729 * Sets how many times to repeat the marquee animation. Only applied if the
7730 * TextView has marquee enabled. Set to -1 to repeat indefinitely.
7732 * @see #getMarqueeRepeatLimit()
7734 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7736 public void setMarqueeRepeatLimit(int marqueeLimit) {
7737 mMarqueeRepeatLimit = marqueeLimit;
7741 * Gets the number of times the marquee animation is repeated. Only meaningful if the
7742 * TextView has marquee enabled.
7744 * @return the number of times the marquee animation is repeated. -1 if the animation
7745 * repeats indefinitely
7747 * @see #setMarqueeRepeatLimit(int)
7749 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit
7751 public int getMarqueeRepeatLimit() {
7752 return mMarqueeRepeatLimit;
7756 * Returns where, if anywhere, words that are longer than the view
7757 * is wide should be ellipsized.
7759 @ViewDebug.ExportedProperty
7760 public TextUtils.TruncateAt getEllipsize() {
7765 * Set the TextView so that when it takes focus, all the text is
7768 * @attr ref android.R.styleable#TextView_selectAllOnFocus
7770 @android.view.RemotableViewMethod
7771 public void setSelectAllOnFocus(boolean selectAllOnFocus) {
7772 createEditorIfNeeded();
7773 mEditor.mSelectAllOnFocus = selectAllOnFocus;
7775 if (selectAllOnFocus && !(mText instanceof Spannable)) {
7776 setText(mText, BufferType.SPANNABLE);
7781 * Set whether the cursor is visible. The default is true. Note that this property only
7782 * makes sense for editable TextView.
7784 * @see #isCursorVisible()
7786 * @attr ref android.R.styleable#TextView_cursorVisible
7788 @android.view.RemotableViewMethod
7789 public void setCursorVisible(boolean visible) {
7790 if (visible && mEditor == null) return; // visible is the default value with no edit data
7791 createEditorIfNeeded();
7792 if (mEditor.mCursorVisible != visible) {
7793 mEditor.mCursorVisible = visible;
7796 mEditor.makeBlink();
7798 // InsertionPointCursorController depends on mCursorVisible
7799 mEditor.prepareCursorControllers();
7804 * @return whether or not the cursor is visible (assuming this TextView is editable)
7806 * @see #setCursorVisible(boolean)
7808 * @attr ref android.R.styleable#TextView_cursorVisible
7810 public boolean isCursorVisible() {
7811 // true is the default value
7812 return mEditor == null ? true : mEditor.mCursorVisible;
7815 private boolean canMarquee() {
7816 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight());
7817 return width > 0 && (mLayout.getLineWidth(0) > width ||
7818 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null &&
7819 mSavedMarqueeModeLayout.getLineWidth(0) > width));
7822 private void startMarquee() {
7823 // Do not ellipsize EditText
7824 if (getKeyListener() != null) return;
7826 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
7830 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) &&
7831 getLineCount() == 1 && canMarquee()) {
7833 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
7834 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE;
7835 final Layout tmp = mLayout;
7836 mLayout = mSavedMarqueeModeLayout;
7837 mSavedMarqueeModeLayout = tmp;
7838 setHorizontalFadingEdgeEnabled(true);
7843 if (mMarquee == null) mMarquee = new Marquee(this);
7844 mMarquee.start(mMarqueeRepeatLimit);
7848 private void stopMarquee() {
7849 if (mMarquee != null && !mMarquee.isStopped()) {
7853 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) {
7854 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS;
7855 final Layout tmp = mSavedMarqueeModeLayout;
7856 mSavedMarqueeModeLayout = mLayout;
7858 setHorizontalFadingEdgeEnabled(false);
7864 private void startStopMarquee(boolean start) {
7865 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) {
7875 * This method is called when the text is changed, in case any subclasses
7876 * would like to know.
7878 * Within <code>text</code>, the <code>lengthAfter</code> characters
7879 * beginning at <code>start</code> have just replaced old text that had
7880 * length <code>lengthBefore</code>. It is an error to attempt to make
7881 * changes to <code>text</code> from this callback.
7883 * @param text The text the TextView is displaying
7884 * @param start The offset of the start of the range of the text that was
7886 * @param lengthBefore The length of the former text that has been replaced
7887 * @param lengthAfter The length of the replacement modified text
7889 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
7890 // intentionally empty, template pattern method can be overridden by subclasses
7894 * This method is called when the selection has changed, in case any
7895 * subclasses would like to know.
7897 * @param selStart The new selection start location.
7898 * @param selEnd The new selection end location.
7900 protected void onSelectionChanged(int selStart, int selEnd) {
7901 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
7905 * Adds a TextWatcher to the list of those whose methods are called
7906 * whenever this TextView's text changes.
7908 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously
7909 * not called after {@link #setText} calls. Now, doing {@link #setText}
7910 * if there are any text changed listeners forces the buffer type to
7911 * Editable if it would not otherwise be and does call this method.
7913 public void addTextChangedListener(TextWatcher watcher) {
7914 if (mListeners == null) {
7915 mListeners = new ArrayList<TextWatcher>();
7918 mListeners.add(watcher);
7922 * Removes the specified TextWatcher from the list of those whose
7923 * methods are called
7924 * whenever this TextView's text changes.
7926 public void removeTextChangedListener(TextWatcher watcher) {
7927 if (mListeners != null) {
7928 int i = mListeners.indexOf(watcher);
7931 mListeners.remove(i);
7936 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) {
7937 if (mListeners != null) {
7938 final ArrayList<TextWatcher> list = mListeners;
7939 final int count = list.size();
7940 for (int i = 0; i < count; i++) {
7941 list.get(i).beforeTextChanged(text, start, before, after);
7945 // The spans that are inside or intersect the modified region no longer make sense
7946 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class);
7947 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class);
7950 // Removes all spans that are inside or actually overlap the start..end range
7951 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) {
7952 if (!(mText instanceof Editable)) return;
7953 Editable text = (Editable) mText;
7955 T[] spans = text.getSpans(start, end, type);
7956 final int length = spans.length;
7957 for (int i = 0; i < length; i++) {
7958 final int spanStart = text.getSpanStart(spans[i]);
7959 final int spanEnd = text.getSpanEnd(spans[i]);
7960 if (spanEnd == start || spanStart == end) break;
7961 text.removeSpan(spans[i]);
7965 void removeAdjacentSuggestionSpans(final int pos) {
7966 if (!(mText instanceof Editable)) return;
7967 final Editable text = (Editable) mText;
7969 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class);
7970 final int length = spans.length;
7971 for (int i = 0; i < length; i++) {
7972 final int spanStart = text.getSpanStart(spans[i]);
7973 final int spanEnd = text.getSpanEnd(spans[i]);
7974 if (spanEnd == pos || spanStart == pos) {
7975 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) {
7976 text.removeSpan(spans[i]);
7983 * Not private so it can be called from an inner class without going
7986 void sendOnTextChanged(CharSequence text, int start, int before, int after) {
7987 if (mListeners != null) {
7988 final ArrayList<TextWatcher> list = mListeners;
7989 final int count = list.size();
7990 for (int i = 0; i < count; i++) {
7991 list.get(i).onTextChanged(text, start, before, after);
7995 if (mEditor != null) mEditor.sendOnTextChanged(start, after);
7999 * Not private so it can be called from an inner class without going
8002 void sendAfterTextChanged(Editable text) {
8003 if (mListeners != null) {
8004 final ArrayList<TextWatcher> list = mListeners;
8005 final int count = list.size();
8006 for (int i = 0; i < count; i++) {
8007 list.get(i).afterTextChanged(text);
8010 hideErrorIfUnchanged();
8013 void updateAfterEdit() {
8015 int curs = getSelectionStart();
8017 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
8018 registerForPreDraw();
8024 mHighlightPathBogus = true;
8025 if (mEditor != null) mEditor.makeBlink();
8026 bringPointIntoView(curs);
8031 * Not private so it can be called from an inner class without going
8034 void handleTextChanged(CharSequence buffer, int start, int before, int after) {
8035 sLastCutCopyOrTextChangedTime = 0;
8037 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8038 if (ims == null || ims.mBatchEditNesting == 0) {
8042 ims.mContentChanged = true;
8043 if (ims.mChangedStart < 0) {
8044 ims.mChangedStart = start;
8045 ims.mChangedEnd = start+before;
8047 ims.mChangedStart = Math.min(ims.mChangedStart, start);
8048 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta);
8050 ims.mChangedDelta += after-before;
8052 resetErrorChangedFlag();
8053 sendOnTextChanged(buffer, start, before, after);
8054 onTextChanged(buffer, start, before, after);
8058 * Not private so it can be called from an inner class without going
8061 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) {
8062 // XXX Make the start and end move together if this ends up
8063 // spending too much time invalidating.
8065 boolean selChanged = false;
8066 int newSelStart=-1, newSelEnd=-1;
8068 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState;
8070 if (what == Selection.SELECTION_END) {
8072 newSelEnd = newStart;
8074 if (oldStart >= 0 || newStart >= 0) {
8075 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
8077 registerForPreDraw();
8078 if (mEditor != null) mEditor.makeBlink();
8082 if (what == Selection.SELECTION_START) {
8084 newSelStart = newStart;
8086 if (oldStart >= 0 || newStart >= 0) {
8087 int end = Selection.getSelectionEnd(buf);
8088 invalidateCursor(end, oldStart, newStart);
8093 mHighlightPathBogus = true;
8094 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true;
8096 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
8097 if (newSelStart < 0) {
8098 newSelStart = Selection.getSelectionStart(buf);
8100 if (newSelEnd < 0) {
8101 newSelEnd = Selection.getSelectionEnd(buf);
8103 onSelectionChanged(newSelStart, newSelEnd);
8107 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
8108 what instanceof CharacterStyle) {
8109 if (ims == null || ims.mBatchEditNesting == 0) {
8111 mHighlightPathBogus = true;
8114 ims.mContentChanged = true;
8116 if (mEditor != null) {
8117 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd);
8118 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd);
8122 if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
8123 mHighlightPathBogus = true;
8124 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
8125 ims.mSelectionModeChanged = true;
8128 if (Selection.getSelectionStart(buf) >= 0) {
8129 if (ims == null || ims.mBatchEditNesting == 0) {
8132 ims.mCursorChanged = true;
8137 if (what instanceof ParcelableSpan) {
8138 // If this is a span that can be sent to a remote process,
8139 // the current extract editor would be interested in it.
8140 if (ims != null && ims.mExtractedTextRequest != null) {
8141 if (ims.mBatchEditNesting != 0) {
8142 if (oldStart >= 0) {
8143 if (ims.mChangedStart > oldStart) {
8144 ims.mChangedStart = oldStart;
8146 if (ims.mChangedStart > oldEnd) {
8147 ims.mChangedStart = oldEnd;
8150 if (newStart >= 0) {
8151 if (ims.mChangedStart > newStart) {
8152 ims.mChangedStart = newStart;
8154 if (ims.mChangedStart > newEnd) {
8155 ims.mChangedStart = newEnd;
8159 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: "
8160 + oldStart + "-" + oldEnd + ","
8161 + newStart + "-" + newEnd + " " + what);
8162 ims.mContentChanged = true;
8167 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 &&
8168 what instanceof SpellCheckSpan) {
8169 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what);
8177 public void dispatchFinishTemporaryDetach() {
8178 mDispatchTemporaryDetach = true;
8179 super.dispatchFinishTemporaryDetach();
8180 mDispatchTemporaryDetach = false;
8184 public void onStartTemporaryDetach() {
8185 super.onStartTemporaryDetach();
8186 // Only track when onStartTemporaryDetach() is called directly,
8187 // usually because this instance is an editable field in a list
8188 if (!mDispatchTemporaryDetach) mTemporaryDetach = true;
8190 // Tell the editor that we are temporarily detached. It can use this to preserve
8191 // selection state as needed.
8192 if (mEditor != null) mEditor.mTemporaryDetach = true;
8196 public void onFinishTemporaryDetach() {
8197 super.onFinishTemporaryDetach();
8198 // Only track when onStartTemporaryDetach() is called directly,
8199 // usually because this instance is an editable field in a list
8200 if (!mDispatchTemporaryDetach) mTemporaryDetach = false;
8201 if (mEditor != null) mEditor.mTemporaryDetach = false;
8205 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
8206 if (mTemporaryDetach) {
8207 // If we are temporarily in the detach state, then do nothing.
8208 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8212 if (mEditor != null) mEditor.onFocusChanged(focused, direction);
8215 if (mText instanceof Spannable) {
8216 Spannable sp = (Spannable) mText;
8217 MetaKeyKeyListener.resetMetaState(sp);
8221 startStopMarquee(focused);
8223 if (mTransformation != null) {
8224 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect);
8227 super.onFocusChanged(focused, direction, previouslyFocusedRect);
8231 public void onWindowFocusChanged(boolean hasWindowFocus) {
8232 super.onWindowFocusChanged(hasWindowFocus);
8234 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus);
8236 startStopMarquee(hasWindowFocus);
8240 protected void onVisibilityChanged(View changedView, int visibility) {
8241 super.onVisibilityChanged(changedView, visibility);
8242 if (mEditor != null && visibility != VISIBLE) {
8243 mEditor.hideCursorAndSpanControllers();
8244 stopTextActionMode();
8249 * Use {@link BaseInputConnection#removeComposingSpans
8250 * BaseInputConnection.removeComposingSpans()} to remove any IME composing
8251 * state from this text view.
8253 public void clearComposingText() {
8254 if (mText instanceof Spannable) {
8255 BaseInputConnection.removeComposingSpans((Spannable)mText);
8260 public void setSelected(boolean selected) {
8261 boolean wasSelected = isSelected();
8263 super.setSelected(selected);
8265 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) {
8275 public boolean onTouchEvent(MotionEvent event) {
8276 final int action = event.getActionMasked();
8278 if (mEditor != null && action == MotionEvent.ACTION_DOWN) {
8279 // Detect double tap and inform the Editor.
8280 if (mFirstTouch && (SystemClock.uptimeMillis() - mLastTouchUpTime) <=
8281 ViewConfiguration.getDoubleTapTimeout()) {
8282 mEditor.mDoubleTap = true;
8283 mFirstTouch = false;
8285 mEditor.mDoubleTap = false;
8290 if (action == MotionEvent.ACTION_UP) {
8291 mLastTouchUpTime = SystemClock.uptimeMillis();
8294 if (mEditor != null) {
8295 mEditor.onTouchEvent(event);
8297 if (mEditor.mSelectionModifierCursorController != null &&
8298 mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) {
8303 final boolean superResult = super.onTouchEvent(event);
8306 * Don't handle the release after a long press, because it will move the selection away from
8307 * whatever the menu action was trying to affect. If the long press should have triggered an
8308 * insertion action mode, we can now actually show it.
8310 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
8311 mEditor.mDiscardNextActionUp = false;
8313 if (mEditor.mIsInsertionActionModeStartPending) {
8314 mEditor.startInsertionActionMode();
8315 mEditor.mIsInsertionActionModeStartPending = false;
8320 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
8321 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused();
8323 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
8324 && mText instanceof Spannable && mLayout != null) {
8325 boolean handled = false;
8327 if (mMovement != null) {
8328 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
8331 final boolean textIsSelectable = isTextSelectable();
8332 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
8333 // The LinkMovementMethod which should handle taps on links has not been installed
8334 // on non editable text that support text selection.
8335 // We reproduce its behavior here to open links for these.
8336 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
8337 getSelectionEnd(), ClickableSpan.class);
8339 if (links.length > 0) {
8340 links[0].onClick(this);
8345 if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
8346 // Show the IME, except when selecting in read-only text.
8347 final InputMethodManager imm = InputMethodManager.peekInstance();
8349 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) {
8350 handled |= imm != null && imm.showSoftInput(this, 0);
8353 // The above condition ensures that the mEditor is not null
8354 mEditor.onTouchUpEvent(event);
8368 public boolean onGenericMotionEvent(MotionEvent event) {
8369 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8371 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
8374 } catch (AbstractMethodError ex) {
8375 // onGenericMotionEvent was added to the MovementMethod interface in API 12.
8376 // Ignore its absence in case third party applications implemented the
8377 // interface directly.
8380 return super.onGenericMotionEvent(event);
8384 * @return True iff this TextView contains a text that can be edited, or if this is
8385 * a selectable TextView.
8387 boolean isTextEditable() {
8388 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled();
8392 * Returns true, only while processing a touch gesture, if the initial
8393 * touch down event caused focus to move to the text view and as a result
8394 * its selection changed. Only valid while processing the touch gesture
8395 * of interest, in an editable text view.
8397 public boolean didTouchFocusSelect() {
8398 return mEditor != null && mEditor.mTouchFocusSelected;
8402 public void cancelLongPress() {
8403 super.cancelLongPress();
8404 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true;
8408 public boolean onTrackballEvent(MotionEvent event) {
8409 if (mMovement != null && mText instanceof Spannable && mLayout != null) {
8410 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
8415 return super.onTrackballEvent(event);
8418 public void setScroller(Scroller s) {
8423 protected float getLeftFadingEdgeStrength() {
8424 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8425 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8426 if (mMarquee != null && !mMarquee.isStopped()) {
8427 final Marquee marquee = mMarquee;
8428 if (marquee.shouldDrawLeftFade()) {
8429 final float scroll = marquee.getScroll();
8430 return scroll / getHorizontalFadingEdgeLength();
8434 } else if (getLineCount() == 1) {
8435 final int layoutDirection = getLayoutDirection();
8436 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8437 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8441 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8442 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8443 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8444 case Gravity.CENTER_HORIZONTAL:
8445 case Gravity.FILL_HORIZONTAL:
8446 final int textDirection = mLayout.getParagraphDirection(0);
8447 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) {
8450 return (mLayout.getLineRight(0) - (mRight - mLeft) -
8451 getCompoundPaddingLeft() - getCompoundPaddingRight() -
8452 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength();
8457 return super.getLeftFadingEdgeStrength();
8461 protected float getRightFadingEdgeStrength() {
8462 if (mEllipsize == TextUtils.TruncateAt.MARQUEE &&
8463 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) {
8464 if (mMarquee != null && !mMarquee.isStopped()) {
8465 final Marquee marquee = mMarquee;
8466 final float maxFadeScroll = marquee.getMaxFadeScroll();
8467 final float scroll = marquee.getScroll();
8468 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength();
8469 } else if (getLineCount() == 1) {
8470 final int layoutDirection = getLayoutDirection();
8471 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
8472 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
8474 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() -
8475 getCompoundPaddingRight();
8476 final float lineWidth = mLayout.getLineWidth(0);
8477 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength();
8480 case Gravity.CENTER_HORIZONTAL:
8481 case Gravity.FILL_HORIZONTAL:
8482 final int textDirection = mLayout.getParagraphDirection(0);
8483 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) {
8486 return (mLayout.getLineWidth(0) - ((mRight - mLeft) -
8487 getCompoundPaddingLeft() - getCompoundPaddingRight())) /
8488 getHorizontalFadingEdgeLength();
8493 return super.getRightFadingEdgeStrength();
8497 protected int computeHorizontalScrollRange() {
8498 if (mLayout != null) {
8499 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ?
8500 (int) mLayout.getLineWidth(0) : mLayout.getWidth();
8503 return super.computeHorizontalScrollRange();
8507 protected int computeVerticalScrollRange() {
8508 if (mLayout != null)
8509 return mLayout.getHeight();
8511 return super.computeVerticalScrollRange();
8515 protected int computeVerticalScrollExtent() {
8516 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom();
8520 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) {
8521 super.findViewsWithText(outViews, searched, flags);
8522 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0
8523 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) {
8524 String searchedLowerCase = searched.toString().toLowerCase();
8525 String textLowerCase = mText.toString().toLowerCase();
8526 if (textLowerCase.contains(searchedLowerCase)) {
8532 public enum BufferType {
8533 NORMAL, SPANNABLE, EDITABLE,
8537 * Returns the TextView_textColor attribute from the TypedArray, if set, or
8538 * the TextAppearance_textColor from the TextView_textAppearance attribute,
8539 * if TextView_textColor was not set directly.
8543 public static ColorStateList getTextColors(Context context, TypedArray attrs) {
8544 if (attrs == null) {
8545 // Preserve behavior prior to removal of this API.
8546 throw new NullPointerException();
8549 // It's not safe to use this method from apps. The parameter 'attrs'
8550 // must have been obtained using the TextView filter array which is not
8551 // available to the SDK. As such, we grab a default TypedArray with the
8552 // right filter instead here.
8553 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView);
8554 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor);
8555 if (colors == null) {
8556 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0);
8558 final TypedArray appearance = context.obtainStyledAttributes(
8559 ap, R.styleable.TextAppearance);
8560 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor);
8561 appearance.recycle();
8570 * Returns the default color from the TextView_textColor attribute from the
8571 * AttributeSet, if set, or the default color from the
8572 * TextAppearance_textColor from the TextView_textAppearance attribute, if
8573 * TextView_textColor was not set directly.
8577 public static int getTextColor(Context context, TypedArray attrs, int def) {
8578 final ColorStateList colors = getTextColors(context, attrs);
8579 if (colors == null) {
8582 return colors.getDefaultColor();
8587 public boolean onKeyShortcut(int keyCode, KeyEvent event) {
8588 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) {
8589 // Handle Ctrl-only shortcuts.
8591 case KeyEvent.KEYCODE_A:
8592 if (canSelectText()) {
8593 return onTextContextMenuItem(ID_SELECT_ALL);
8596 case KeyEvent.KEYCODE_Z:
8598 return onTextContextMenuItem(ID_UNDO);
8601 case KeyEvent.KEYCODE_X:
8603 return onTextContextMenuItem(ID_CUT);
8606 case KeyEvent.KEYCODE_C:
8608 return onTextContextMenuItem(ID_COPY);
8611 case KeyEvent.KEYCODE_V:
8613 return onTextContextMenuItem(ID_PASTE);
8617 } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) {
8618 // Handle Ctrl-Shift shortcuts.
8620 case KeyEvent.KEYCODE_Z:
8622 return onTextContextMenuItem(ID_REDO);
8625 case KeyEvent.KEYCODE_V:
8627 return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT);
8631 return super.onKeyShortcut(keyCode, event);
8635 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the
8636 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have
8637 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not
8640 boolean canSelectText() {
8641 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController();
8645 * Test based on the <i>intrinsic</i> charateristics of the TextView.
8646 * The text must be spannable and the movement method must allow for arbitary selection.
8648 * See also {@link #canSelectText()}.
8650 boolean textCanBeSelected() {
8651 // prepareCursorController() relies on this method.
8652 // If you change this condition, make sure prepareCursorController is called anywhere
8653 // the value of this condition might be changed.
8654 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
8655 return isTextEditable() ||
8656 (isTextSelectable() && mText instanceof Spannable && isEnabled());
8659 private Locale getTextServicesLocale(boolean allowNullLocale) {
8660 // Start fetching the text services locale asynchronously.
8661 updateTextServicesLocaleAsync();
8662 // If !allowNullLocale and there is no cached text services locale, just return the default
8664 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault()
8665 : mCurrentSpellCheckerLocaleCache;
8669 * This is a temporary method. Future versions may support multi-locale text.
8670 * Caveat: This method may not return the latest text services locale, but this should be
8671 * acceptable and it's more important to make this method asynchronous.
8673 * @return The locale that should be used for a word iterator
8674 * in this TextView, based on the current spell checker settings,
8675 * the current IME's locale, or the system default locale.
8676 * Please note that a word iterator in this TextView is different from another word iterator
8677 * used by SpellChecker.java of TextView. This method should be used for the former.
8680 // TODO: Support multi-locale
8681 // TODO: Update the text services locale immediately after the keyboard locale is switched
8682 // by catching intent of keyboard switch event
8683 public Locale getTextServicesLocale() {
8684 return getTextServicesLocale(false /* allowNullLocale */);
8688 * @return true if this TextView is specialized for showing and interacting with the extracted
8689 * text in a full-screen input method.
8692 public boolean isInExtractedMode() {
8697 * This is a temporary method. Future versions may support multi-locale text.
8698 * Caveat: This method may not return the latest spell checker locale, but this should be
8699 * acceptable and it's more important to make this method asynchronous.
8701 * @return The locale that should be used for a spell checker in this TextView,
8702 * based on the current spell checker settings, the current IME's locale, or the system default
8706 public Locale getSpellCheckerLocale() {
8707 return getTextServicesLocale(true /* allowNullLocale */);
8710 private void updateTextServicesLocaleAsync() {
8711 // AsyncTask.execute() uses a serial executor which means we don't have
8712 // to lock around updateTextServicesLocaleLocked() to prevent it from
8713 // being executed n times in parallel.
8714 AsyncTask.execute(new Runnable() {
8717 updateTextServicesLocaleLocked();
8722 private void updateTextServicesLocaleLocked() {
8723 final TextServicesManager textServicesManager = (TextServicesManager)
8724 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
8725 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
8726 final Locale locale;
8727 if (subtype != null) {
8728 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale());
8732 mCurrentSpellCheckerLocaleCache = locale;
8735 void onLocaleChanged() {
8736 // Will be re-created on demand in getWordIterator with the proper new locale
8737 mEditor.mWordIterator = null;
8741 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other.
8742 * Made available to achieve a consistent behavior.
8745 public WordIterator getWordIterator() {
8746 if (mEditor != null) {
8747 return mEditor.getWordIterator();
8755 public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
8756 super.onPopulateAccessibilityEventInternal(event);
8758 final boolean isPassword = hasPasswordTransformationMethod();
8759 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8760 final CharSequence text = getTextForAccessibility();
8761 if (!TextUtils.isEmpty(text)) {
8762 event.getText().add(text);
8768 * @return true if the user has explicitly allowed accessibility services
8769 * to speak passwords.
8771 private boolean shouldSpeakPasswordsForAccessibility() {
8772 return (Settings.Secure.getIntForUser(mContext.getContentResolver(),
8773 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0,
8774 UserHandle.USER_CURRENT_OR_SELF) == 1);
8778 public CharSequence getAccessibilityClassName() {
8779 return TextView.class.getName();
8783 public void onProvideStructure(ViewStructure structure) {
8784 super.onProvideStructure(structure);
8785 final boolean isPassword = hasPasswordTransformationMethod()
8786 || isPasswordInputType(getInputType());
8788 if (mLayout == null) {
8791 Layout layout = mLayout;
8792 final int lineCount = layout.getLineCount();
8793 if (lineCount <= 1) {
8794 // Simple case: this is a single line.
8795 structure.setText(getText(), getSelectionStart(), getSelectionEnd());
8797 // Complex case: multi-line, could be scrolled or within a scroll container
8798 // so some lines are not visible.
8799 final int[] tmpCords = new int[2];
8800 getLocationInWindow(tmpCords);
8801 final int topWindowLocation = tmpCords[1];
8803 ViewParent viewParent = getParent();
8804 while (viewParent instanceof View) {
8805 root = (View) viewParent;
8806 viewParent = root.getParent();
8808 final int windowHeight = root.getHeight();
8810 final int bottomLine;
8811 if (topWindowLocation >= 0) {
8812 // The top of the view is fully within its window; start text at line 0.
8813 topLine = getLineAtCoordinateUnclamped(0);
8814 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1);
8816 // The top of hte window has scrolled off the top of the window; figure out
8817 // the starting line for this.
8818 topLine = getLineAtCoordinateUnclamped(-topWindowLocation);
8819 bottomLine = getLineAtCoordinateUnclamped(windowHeight-1-topWindowLocation);
8821 // We want to return some contextual lines above/below the lines that are
8822 // actually visible.
8823 int expandedTopLine = topLine - (bottomLine-topLine)/2;
8824 if (expandedTopLine < 0) {
8825 expandedTopLine = 0;
8827 int expandedBottomLine = bottomLine + (bottomLine-topLine)/2;
8828 if (expandedBottomLine >= lineCount) {
8829 expandedBottomLine = lineCount-1;
8831 // Convert lines into character offsets.
8832 int expandedTopChar = layout.getLineStart(expandedTopLine);
8833 int expandedBottomChar = layout.getLineEnd(expandedBottomLine);
8834 // Take into account selection -- if there is a selection, we need to expand
8835 // the text we are returning to include that selection.
8836 final int selStart = getSelectionStart();
8837 final int selEnd = getSelectionEnd();
8838 if (selStart < selEnd) {
8839 if (selStart < expandedTopChar) {
8840 expandedTopChar = selStart;
8842 if (selEnd > expandedBottomChar) {
8843 expandedBottomChar = selEnd;
8846 // Get the text and trim it to the range we are reporting.
8847 CharSequence text = getText();
8848 if (expandedTopChar > 0 || expandedBottomChar < text.length()) {
8849 text = text.subSequence(expandedTopChar, expandedBottomChar);
8851 structure.setText(text, selStart-expandedTopChar, selEnd-expandedTopChar);
8852 final int[] lineOffsets = new int[bottomLine-topLine+1];
8853 final int[] lineBaselines = new int[bottomLine-topLine+1];
8854 final int baselineOffset = getBaselineOffset();
8855 for (int i=topLine; i<=bottomLine; i++) {
8856 lineOffsets[i-topLine] = layout.getLineStart(i);
8857 lineBaselines[i-topLine] = layout.getLineBaseline(i) + baselineOffset;
8859 structure.setTextLines(lineOffsets, lineBaselines);
8862 // Extract style information that applies to the TextView as a whole.
8864 int typefaceStyle = getTypefaceStyle();
8865 if ((typefaceStyle & Typeface.BOLD) != 0) {
8866 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8868 if ((typefaceStyle & Typeface.ITALIC) != 0) {
8869 style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC;
8872 // Global styles can also be set via TextView.setPaintFlags().
8873 int paintFlags = mTextPaint.getFlags();
8874 if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) {
8875 style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD;
8877 if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) {
8878 style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE;
8880 if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) {
8881 style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU;
8884 // TextView does not have its own text background color. A background is either part
8885 // of the View (and can be any drawable) or a BackgroundColorSpan inside the text.
8886 structure.setTextStyle(getTextSize(), getCurrentTextColor(),
8887 AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style);
8889 structure.setHint(getHint());
8894 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
8895 super.onInitializeAccessibilityEventInternal(event);
8897 final boolean isPassword = hasPasswordTransformationMethod();
8898 event.setPassword(isPassword);
8900 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) {
8901 event.setFromIndex(Selection.getSelectionStart(mText));
8902 event.setToIndex(Selection.getSelectionEnd(mText));
8903 event.setItemCount(mText.length());
8909 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
8910 super.onInitializeAccessibilityNodeInfoInternal(info);
8912 final boolean isPassword = hasPasswordTransformationMethod();
8913 info.setPassword(isPassword);
8915 if (!isPassword || shouldSpeakPasswordsForAccessibility()) {
8916 info.setText(getTextForAccessibility());
8919 if (mBufferType == BufferType.EDITABLE) {
8920 info.setEditable(true);
8923 if (mEditor != null) {
8924 info.setInputType(mEditor.mInputType);
8926 if (mEditor.mError != null) {
8927 info.setContentInvalid(true);
8928 info.setError(mEditor.mError);
8932 if (!TextUtils.isEmpty(mText)) {
8933 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY);
8934 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY);
8935 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER
8936 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD
8937 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE
8938 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH
8939 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE);
8940 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION);
8945 info.addAction(AccessibilityNodeInfo.ACTION_COPY);
8948 info.addAction(AccessibilityNodeInfo.ACTION_PASTE);
8951 info.addAction(AccessibilityNodeInfo.ACTION_CUT);
8954 info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
8955 ACCESSIBILITY_ACTION_SHARE,
8956 getResources().getString(com.android.internal.R.string.share)));
8958 if (canProcessText()) { // also implies mEditor is not null.
8959 mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info);
8963 // Check for known input filter types.
8964 final int numFilters = mFilters.length;
8965 for (int i = 0; i < numFilters; i++) {
8966 final InputFilter filter = mFilters[i];
8967 if (filter instanceof InputFilter.LengthFilter) {
8968 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax());
8972 if (!isSingleLine()) {
8973 info.setMultiLine(true);
8978 * Performs an accessibility action after it has been offered to the
8984 public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
8986 && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) {
8990 case AccessibilityNodeInfo.ACTION_CLICK: {
8991 boolean handled = false;
8993 // Simulate View.onTouchEvent for an ACTION_UP event.
8994 if (isClickable() || isLongClickable()) {
8995 if (isFocusable() && !isFocused()) {
9003 // Simulate TextView.onTouchEvent for an ACTION_UP event.
9004 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
9005 && mText instanceof Spannable && mLayout != null
9006 && (isTextEditable() || isTextSelectable()) && isFocused()) {
9007 // Show the IME, except when selecting in read-only text.
9008 final InputMethodManager imm = InputMethodManager.peekInstance();
9010 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) {
9011 handled |= imm.showSoftInput(this, 0);
9017 case AccessibilityNodeInfo.ACTION_COPY: {
9018 if (isFocused() && canCopy()) {
9019 if (onTextContextMenuItem(ID_COPY)) {
9024 case AccessibilityNodeInfo.ACTION_PASTE: {
9025 if (isFocused() && canPaste()) {
9026 if (onTextContextMenuItem(ID_PASTE)) {
9031 case AccessibilityNodeInfo.ACTION_CUT: {
9032 if (isFocused() && canCut()) {
9033 if (onTextContextMenuItem(ID_CUT)) {
9038 case AccessibilityNodeInfo.ACTION_SET_SELECTION: {
9039 ensureIterableTextForAccessibilitySelectable();
9040 CharSequence text = getIterableTextForAccessibility();
9044 final int start = (arguments != null) ? arguments.getInt(
9045 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1;
9046 final int end = (arguments != null) ? arguments.getInt(
9047 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1;
9048 if ((getSelectionStart() != start || getSelectionEnd() != end)) {
9049 // No arguments clears the selection.
9050 if (start == end && end == -1) {
9051 Selection.removeSelection((Spannable) text);
9054 if (start >= 0 && start <= end && end <= text.length()) {
9055 Selection.setSelection((Spannable) text, start, end);
9056 // Make sure selection mode is engaged.
9057 if (mEditor != null) {
9058 mEditor.startSelectionActionMode();
9064 case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:
9065 case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: {
9066 ensureIterableTextForAccessibilitySelectable();
9067 return super.performAccessibilityActionInternal(action, arguments);
9069 case ACCESSIBILITY_ACTION_SHARE: {
9070 if (isFocused() && canShare()) {
9071 if (onTextContextMenuItem(ID_SHARE)) {
9077 return super.performAccessibilityActionInternal(action, arguments);
9084 public void sendAccessibilityEventInternal(int eventType) {
9085 if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) {
9086 mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions();
9089 // Do not send scroll events since first they are not interesting for
9090 // accessibility and second such events a generated too frequently.
9091 // For details see the implementation of bringTextIntoView().
9092 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
9095 super.sendAccessibilityEventInternal(eventType);
9099 * Gets the text reported for accessibility purposes.
9101 * @return The accessibility text.
9105 public CharSequence getTextForAccessibility() {
9106 CharSequence text = getText();
9107 if (TextUtils.isEmpty(text)) {
9113 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText,
9114 int fromIndex, int removedCount, int addedCount) {
9115 AccessibilityEvent event =
9116 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
9117 event.setFromIndex(fromIndex);
9118 event.setRemovedCount(removedCount);
9119 event.setAddedCount(addedCount);
9120 event.setBeforeText(beforeText);
9121 sendAccessibilityEventUnchecked(event);
9125 * Returns whether this text view is a current input method target. The
9126 * default implementation just checks with {@link InputMethodManager}.
9128 public boolean isInputMethodTarget() {
9129 InputMethodManager imm = InputMethodManager.peekInstance();
9130 return imm != null && imm.isActive(this);
9133 static final int ID_SELECT_ALL = android.R.id.selectAll;
9134 static final int ID_UNDO = android.R.id.undo;
9135 static final int ID_REDO = android.R.id.redo;
9136 static final int ID_CUT = android.R.id.cut;
9137 static final int ID_COPY = android.R.id.copy;
9138 static final int ID_PASTE = android.R.id.paste;
9139 static final int ID_SHARE = android.R.id.shareText;
9140 static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText;
9141 static final int ID_REPLACE = android.R.id.replaceText;
9144 * Called when a context menu option for the text view is selected. Currently
9145 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
9146 * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}.
9148 * @return true if the context menu item action was performed.
9150 public boolean onTextContextMenuItem(int id) {
9152 int max = mText.length();
9155 final int selStart = getSelectionStart();
9156 final int selEnd = getSelectionEnd();
9158 min = Math.max(0, Math.min(selStart, selEnd));
9159 max = Math.max(0, Math.max(selStart, selEnd));
9164 // This starts an action mode if triggered from another action mode. Text is
9165 // highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
9166 // true even if text is empty.
9167 boolean shouldRestartActionMode =
9168 mEditor != null && mEditor.mTextActionMode != null;
9169 stopTextActionMode();
9171 if (shouldRestartActionMode) {
9172 mEditor.startSelectionActionMode();
9177 if (mEditor != null) {
9180 return true; // Returns true even if nothing was undone.
9183 if (mEditor != null) {
9186 return true; // Returns true even if nothing was undone.
9189 paste(min, max, true /* withFormatting */);
9192 case ID_PASTE_AS_PLAIN_TEXT:
9193 paste(min, max, false /* withFormatting */);
9197 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9198 deleteText_internal(min, max);
9199 stopTextActionMode();
9203 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
9204 stopTextActionMode();
9208 if (mEditor != null) {
9214 shareSelectedText();
9220 CharSequence getTransformedText(int start, int end) {
9221 return removeSuggestionSpans(mTransformed.subSequence(start, end));
9225 public boolean performLongClick() {
9226 boolean handled = false;
9228 if (super.performLongClick()) {
9232 if (mEditor != null) {
9233 handled |= mEditor.performLongClick(handled);
9237 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
9238 if (mEditor != null) mEditor.mDiscardNextActionUp = true;
9245 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
9246 super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
9247 if (mEditor != null) {
9248 mEditor.onScrollChanged();
9253 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
9254 * by the IME or by the spell checker as the user types. This is done by adding
9255 * {@link SuggestionSpan}s to the text.
9257 * When suggestions are enabled (default), this list of suggestions will be displayed when the
9258 * user asks for them on these parts of the text. This value depends on the inputType of this
9261 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
9263 * In addition, the type variation must be one of
9264 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
9265 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
9266 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
9267 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
9268 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
9270 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
9272 * @return true if the suggestions popup window is enabled, based on the inputType.
9274 public boolean isSuggestionsEnabled() {
9275 if (mEditor == null) return false;
9276 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) {
9279 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
9281 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION;
9282 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
9283 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
9284 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
9285 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
9286 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
9290 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9291 * selection is initiated in this View.
9293 * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy,
9294 * Paste, Replace and Share actions, depending on what this View supports.
9296 * <p>A custom implementation can add new entries in the default menu in its
9297 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)}
9298 * method. The default actions can also be removed from the menu using
9299 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9300 * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
9301 * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
9303 * <p>Returning false from
9304 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)}
9305 * will prevent the action mode from being started.
9307 * <p>Action click events should be handled by the custom implementation of
9308 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode,
9309 * android.view.MenuItem)}.
9311 * <p>Note that text selection mode is not started when a TextView receives focus and the
9312 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
9313 * that case, to allow for quick replacement.
9315 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
9316 createEditorIfNeeded();
9317 mEditor.mCustomSelectionActionModeCallback = actionModeCallback;
9321 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
9323 * @return The current custom selection callback.
9325 public ActionMode.Callback getCustomSelectionActionModeCallback() {
9326 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback;
9330 * If provided, this ActionMode.Callback will be used to create the ActionMode when text
9331 * insertion is initiated in this View.
9332 * The standard implementation populates the menu with a subset of Select All,
9333 * Paste and Replace actions, depending on what this View supports.
9335 * <p>A custom implementation can add new entries in the default menu in its
9336 * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode,
9337 * android.view.Menu)} method. The default actions can also be removed from the menu using
9338 * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
9339 * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p>
9341 * <p>Returning false from
9342 * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode,
9343 * android.view.Menu)} will prevent the action mode from being started.</p>
9345 * <p>Action click events should be handled by the custom implementation of
9346 * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode,
9347 * android.view.MenuItem)}.</p>
9349 * <p>Note that text insertion mode is not started when a TextView receives focus and the
9350 * {@link android.R.attr#selectAllOnFocus} flag has been set.</p>
9352 public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
9353 createEditorIfNeeded();
9354 mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
9358 * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
9360 * @return The current custom insertion callback.
9362 public ActionMode.Callback getCustomInsertionActionModeCallback() {
9363 return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
9369 protected void stopTextActionMode() {
9370 if (mEditor != null) {
9371 mEditor.stopTextActionMode();
9376 return mEditor != null && mEditor.canUndo();
9380 return mEditor != null && mEditor.canRedo();
9384 if (hasPasswordTransformationMethod()) {
9388 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null &&
9389 mEditor.mKeyListener != null) {
9397 if (hasPasswordTransformationMethod()) {
9401 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9408 boolean canShare() {
9412 boolean canPaste() {
9413 return (mText instanceof Editable &&
9414 mEditor != null && mEditor.mKeyListener != null &&
9415 getSelectionStart() >= 0 &&
9416 getSelectionEnd() >= 0 &&
9417 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
9421 boolean canProcessText() {
9422 if (!getContext().canStartActivityForResult() || getId() == View.NO_ID
9423 || hasPasswordTransformationMethod()) {
9427 if (mText.length() > 0 && hasSelection() && mEditor != null) {
9434 boolean canSelectAllText() {
9435 return canSelectText() && !hasPasswordTransformationMethod()
9436 && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length());
9439 boolean selectAllText() {
9440 // Need to hide insert point cursor controller before settings selection, otherwise insert
9441 // point cursor controller obtains cursor update event and update cursor with cancelling
9443 if (mEditor != null) {
9444 mEditor.hideInsertionPointCursorController();
9446 final int length = mText.length();
9447 Selection.setSelection((Spannable) mText, 0, length);
9451 void replaceSelectionWithText(CharSequence text) {
9452 ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text);
9456 * Paste clipboard content between min and max positions.
9458 private void paste(int min, int max, boolean withFormatting) {
9459 ClipboardManager clipboard =
9460 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
9461 ClipData clip = clipboard.getPrimaryClip();
9463 boolean didFirst = false;
9464 for (int i=0; i<clip.getItemCount(); i++) {
9465 final CharSequence paste;
9466 if (withFormatting) {
9467 paste = clip.getItemAt(i).coerceToStyledText(getContext());
9469 // Get an item as text and remove all spans by toString().
9470 final CharSequence text = clip.getItemAt(i).coerceToText(getContext());
9471 paste = (text instanceof Spanned) ? text.toString() : text;
9473 if (paste != null) {
9475 Selection.setSelection((Spannable) mText, max);
9476 ((Editable) mText).replace(min, max, paste);
9479 ((Editable) mText).insert(getSelectionEnd(), "\n");
9480 ((Editable) mText).insert(getSelectionEnd(), paste);
9484 stopTextActionMode();
9485 sLastCutCopyOrTextChangedTime = 0;
9489 private void shareSelectedText() {
9490 String selectedText = getSelectedText();
9491 if (selectedText != null && !selectedText.isEmpty()) {
9492 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
9493 sharingIntent.setType("text/plain");
9494 sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
9495 sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
9496 getContext().startActivity(Intent.createChooser(sharingIntent, null));
9497 stopTextActionMode();
9501 private void setPrimaryClip(ClipData clip) {
9502 ClipboardManager clipboard = (ClipboardManager) getContext().
9503 getSystemService(Context.CLIPBOARD_SERVICE);
9504 clipboard.setPrimaryClip(clip);
9505 sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis();
9509 * Get the character offset closest to the specified absolute position. A typical use case is to
9510 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
9512 * @param x The horizontal absolute position of a point on screen
9513 * @param y The vertical absolute position of a point on screen
9514 * @return the character offset for the character whose position is closest to the specified
9515 * position. Returns -1 if there is no layout.
9517 public int getOffsetForPosition(float x, float y) {
9518 if (getLayout() == null) return -1;
9519 final int line = getLineAtCoordinate(y);
9520 final int offset = getOffsetAtCoordinate(line, x);
9524 float convertToLocalHorizontalCoordinate(float x) {
9525 x -= getTotalPaddingLeft();
9526 // Clamp the position to inside of the view.
9527 x = Math.max(0.0f, x);
9528 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
9533 int getLineAtCoordinate(float y) {
9534 y -= getTotalPaddingTop();
9535 // Clamp the position to inside of the view.
9536 y = Math.max(0.0f, y);
9537 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
9539 return getLayout().getLineForVertical((int) y);
9542 int getLineAtCoordinateUnclamped(float y) {
9543 y -= getTotalPaddingTop();
9545 return getLayout().getLineForVertical((int) y);
9548 int getOffsetAtCoordinate(int line, float x) {
9549 x = convertToLocalHorizontalCoordinate(x);
9550 return getLayout().getOffsetForHorizontal(line, x);
9554 public boolean onDragEvent(DragEvent event) {
9555 switch (event.getAction()) {
9556 case DragEvent.ACTION_DRAG_STARTED:
9557 return mEditor != null && mEditor.hasInsertionController();
9559 case DragEvent.ACTION_DRAG_ENTERED:
9560 TextView.this.requestFocus();
9563 case DragEvent.ACTION_DRAG_LOCATION:
9564 final int offset = getOffsetForPosition(event.getX(), event.getY());
9565 Selection.setSelection((Spannable)mText, offset);
9568 case DragEvent.ACTION_DROP:
9569 if (mEditor != null) mEditor.onDrop(event);
9572 case DragEvent.ACTION_DRAG_ENDED:
9573 case DragEvent.ACTION_DRAG_EXITED:
9579 boolean isInBatchEditMode() {
9580 if (mEditor == null) return false;
9581 final Editor.InputMethodState ims = mEditor.mInputMethodState;
9583 return ims.mBatchEditNesting > 0;
9585 return mEditor.mInBatchEditControllers;
9589 public void onRtlPropertiesChanged(int layoutDirection) {
9590 super.onRtlPropertiesChanged(layoutDirection);
9592 final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic();
9593 if (mTextDir != newTextDir) {
9594 mTextDir = newTextDir;
9595 if (mLayout != null) {
9601 TextDirectionHeuristic getTextDirectionHeuristic() {
9602 if (hasPasswordTransformationMethod()) {
9603 // passwords fields should be LTR
9604 return TextDirectionHeuristics.LTR;
9607 // Always need to resolve layout direction first
9608 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
9610 // Now, we can select the heuristic
9611 switch (getTextDirection()) {
9613 case TEXT_DIRECTION_FIRST_STRONG:
9614 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
9615 TextDirectionHeuristics.FIRSTSTRONG_LTR);
9616 case TEXT_DIRECTION_ANY_RTL:
9617 return TextDirectionHeuristics.ANYRTL_LTR;
9618 case TEXT_DIRECTION_LTR:
9619 return TextDirectionHeuristics.LTR;
9620 case TEXT_DIRECTION_RTL:
9621 return TextDirectionHeuristics.RTL;
9622 case TEXT_DIRECTION_LOCALE:
9623 return TextDirectionHeuristics.LOCALE;
9624 case TEXT_DIRECTION_FIRST_STRONG_LTR:
9625 return TextDirectionHeuristics.FIRSTSTRONG_LTR;
9626 case TEXT_DIRECTION_FIRST_STRONG_RTL:
9627 return TextDirectionHeuristics.FIRSTSTRONG_RTL;
9635 public void onResolveDrawables(int layoutDirection) {
9636 // No need to resolve twice
9637 if (mLastLayoutDirection == layoutDirection) {
9640 mLastLayoutDirection = layoutDirection;
9642 // Resolve drawables
9643 if (mDrawables != null) {
9644 mDrawables.resolveWithLayoutDirection(layoutDirection);
9651 protected void resetResolvedDrawables() {
9652 super.resetResolvedDrawables();
9653 mLastLayoutDirection = -1;
9659 protected void viewClicked(InputMethodManager imm) {
9661 imm.viewClicked(this);
9666 * Deletes the range of text [start, end[.
9669 protected void deleteText_internal(int start, int end) {
9670 ((Editable) mText).delete(start, end);
9674 * Replaces the range of text [start, end[ by replacement text
9677 protected void replaceText_internal(int start, int end, CharSequence text) {
9678 ((Editable) mText).replace(start, end, text);
9682 * Sets a span on the specified range of text
9685 protected void setSpan_internal(Object span, int start, int end, int flags) {
9686 ((Editable) mText).setSpan(span, start, end, flags);
9690 * Moves the cursor to the specified offset position in text
9693 protected void setCursorPosition_internal(int start, int end) {
9694 Selection.setSelection(((Editable) mText), start, end);
9698 * An Editor should be created as soon as any of the editable-specific fields (grouped
9699 * inside the Editor object) is assigned to a non-default value.
9700 * This method will create the Editor if needed.
9702 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
9703 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
9704 * Editor for backward compatibility, as soon as one of these fields is assigned.
9706 * Also note that for performance reasons, the mEditor is created when needed, but not
9707 * reset when no more edit-specific fields are needed.
9709 private void createEditorIfNeeded() {
9710 if (mEditor == null) {
9711 mEditor = new Editor(this);
9719 public CharSequence getIterableTextForAccessibility() {
9723 private void ensureIterableTextForAccessibilitySelectable() {
9724 if (!(mText instanceof Spannable)) {
9725 setText(mText, BufferType.SPANNABLE);
9733 public TextSegmentIterator getIteratorForGranularity(int granularity) {
9734 switch (granularity) {
9735 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: {
9736 Spannable text = (Spannable) getIterableTextForAccessibility();
9737 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9738 AccessibilityIterators.LineTextSegmentIterator iterator =
9739 AccessibilityIterators.LineTextSegmentIterator.getInstance();
9740 iterator.initialize(text, getLayout());
9744 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: {
9745 Spannable text = (Spannable) getIterableTextForAccessibility();
9746 if (!TextUtils.isEmpty(text) && getLayout() != null) {
9747 AccessibilityIterators.PageTextSegmentIterator iterator =
9748 AccessibilityIterators.PageTextSegmentIterator.getInstance();
9749 iterator.initialize(this);
9754 return super.getIteratorForGranularity(granularity);
9761 public int getAccessibilitySelectionStart() {
9762 return getSelectionStart();
9768 public boolean isAccessibilitySelectionExtendable() {
9776 public int getAccessibilitySelectionEnd() {
9777 return getSelectionEnd();
9784 public void setAccessibilitySelection(int start, int end) {
9785 if (getAccessibilitySelectionStart() == start
9786 && getAccessibilitySelectionEnd() == end) {
9789 // Hide all selection controllers used for adjusting selection
9790 // since we are doing so explicitlty by other means and these
9791 // controllers interact with how selection behaves.
9792 if (mEditor != null) {
9793 mEditor.hideCursorAndSpanControllers();
9794 mEditor.stopTextActionMode();
9796 CharSequence text = getIterableTextForAccessibility();
9797 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
9798 Selection.setSelection((Spannable) text, start, end);
9800 Selection.removeSelection((Spannable) text);
9806 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
9807 super.encodeProperties(stream);
9809 TruncateAt ellipsize = getEllipsize();
9810 stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name());
9811 stream.addProperty("text:textSize", getTextSize());
9812 stream.addProperty("text:scaledTextSize", getScaledTextSize());
9813 stream.addProperty("text:typefaceStyle", getTypefaceStyle());
9814 stream.addProperty("text:selectionStart", getSelectionStart());
9815 stream.addProperty("text:selectionEnd", getSelectionEnd());
9816 stream.addProperty("text:curTextColor", mCurTextColor);
9817 stream.addProperty("text:text", mText == null ? null : mText.toString());
9818 stream.addProperty("text:gravity", mGravity);
9822 * User interface state that is stored by TextView for implementing
9823 * {@link View#onSaveInstanceState}.
9825 public static class SavedState extends BaseSavedState {
9829 boolean frozenWithFocus;
9831 ParcelableParcel editorState; // Optional state from Editor.
9833 SavedState(Parcelable superState) {
9838 public void writeToParcel(Parcel out, int flags) {
9839 super.writeToParcel(out, flags);
9840 out.writeInt(selStart);
9841 out.writeInt(selEnd);
9842 out.writeInt(frozenWithFocus ? 1 : 0);
9843 TextUtils.writeToParcel(text, out, flags);
9845 if (error == null) {
9849 TextUtils.writeToParcel(error, out, flags);
9852 if (editorState == null) {
9856 editorState.writeToParcel(out, flags);
9861 public String toString() {
9862 String str = "TextView.SavedState{"
9863 + Integer.toHexString(System.identityHashCode(this))
9864 + " start=" + selStart + " end=" + selEnd;
9866 str += " text=" + text;
9871 @SuppressWarnings("hiding")
9872 public static final Parcelable.Creator<SavedState> CREATOR
9873 = new Parcelable.Creator<SavedState>() {
9874 public SavedState createFromParcel(Parcel in) {
9875 return new SavedState(in);
9878 public SavedState[] newArray(int size) {
9879 return new SavedState[size];
9883 private SavedState(Parcel in) {
9885 selStart = in.readInt();
9886 selEnd = in.readInt();
9887 frozenWithFocus = (in.readInt() != 0);
9888 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9890 if (in.readInt() != 0) {
9891 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
9894 if (in.readInt() != 0) {
9895 editorState = ParcelableParcel.CREATOR.createFromParcel(in);
9900 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
9901 private char[] mChars;
9902 private int mStart, mLength;
9904 public CharWrapper(char[] chars, int start, int len) {
9910 /* package */ void set(char[] chars, int start, int len) {
9916 public int length() {
9920 public char charAt(int off) {
9921 return mChars[off + mStart];
9925 public String toString() {
9926 return new String(mChars, mStart, mLength);
9929 public CharSequence subSequence(int start, int end) {
9930 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9931 throw new IndexOutOfBoundsException(start + ", " + end);
9934 return new String(mChars, start + mStart, end - start);
9937 public void getChars(int start, int end, char[] buf, int off) {
9938 if (start < 0 || end < 0 || start > mLength || end > mLength) {
9939 throw new IndexOutOfBoundsException(start + ", " + end);
9942 System.arraycopy(mChars, start + mStart, buf, off, end - start);
9945 public void drawText(Canvas c, int start, int end,
9946 float x, float y, Paint p) {
9947 c.drawText(mChars, start + mStart, end - start, x, y, p);
9950 public void drawTextRun(Canvas c, int start, int end,
9951 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) {
9952 int count = end - start;
9953 int contextCount = contextEnd - contextStart;
9954 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
9955 contextCount, x, y, isRtl, p);
9958 public float measureText(int start, int end, Paint p) {
9959 return p.measureText(mChars, start + mStart, end - start);
9962 public int getTextWidths(int start, int end, float[] widths, Paint p) {
9963 return p.getTextWidths(mChars, start + mStart, end - start, widths);
9966 public float getTextRunAdvances(int start, int end, int contextStart,
9967 int contextEnd, boolean isRtl, float[] advances, int advancesIndex,
9969 int count = end - start;
9970 int contextCount = contextEnd - contextStart;
9971 return p.getTextRunAdvances(mChars, start + mStart, count,
9972 contextStart + mStart, contextCount, isRtl, advances,
9976 public int getTextRunCursor(int contextStart, int contextEnd, int dir,
9977 int offset, int cursorOpt, Paint p) {
9978 int contextCount = contextEnd - contextStart;
9979 return p.getTextRunCursor(mChars, contextStart + mStart,
9980 contextCount, dir, offset + mStart, cursorOpt);
9984 private static final class Marquee {
9985 // TODO: Add an option to configure this
9986 private static final float MARQUEE_DELTA_MAX = 0.07f;
9987 private static final int MARQUEE_DELAY = 1200;
9988 private static final int MARQUEE_DP_PER_SECOND = 30;
9990 private static final byte MARQUEE_STOPPED = 0x0;
9991 private static final byte MARQUEE_STARTING = 0x1;
9992 private static final byte MARQUEE_RUNNING = 0x2;
9994 private final WeakReference<TextView> mView;
9995 private final Choreographer mChoreographer;
9997 private byte mStatus = MARQUEE_STOPPED;
9998 private final float mPixelsPerSecond;
9999 private float mMaxScroll;
10000 private float mMaxFadeScroll;
10001 private float mGhostStart;
10002 private float mGhostOffset;
10003 private float mFadeStop;
10004 private int mRepeatLimit;
10006 private float mScroll;
10007 private long mLastAnimationMs;
10009 Marquee(TextView v) {
10010 final float density = v.getContext().getResources().getDisplayMetrics().density;
10011 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density;
10012 mView = new WeakReference<TextView>(v);
10013 mChoreographer = Choreographer.getInstance();
10016 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() {
10018 public void doFrame(long frameTimeNanos) {
10023 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() {
10025 public void doFrame(long frameTimeNanos) {
10026 mStatus = MARQUEE_RUNNING;
10027 mLastAnimationMs = mChoreographer.getFrameTime();
10032 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() {
10034 public void doFrame(long frameTimeNanos) {
10035 if (mStatus == MARQUEE_RUNNING) {
10036 if (mRepeatLimit >= 0) {
10039 start(mRepeatLimit);
10045 if (mStatus != MARQUEE_RUNNING) {
10049 mChoreographer.removeFrameCallback(mTickCallback);
10051 final TextView textView = mView.get();
10052 if (textView != null && (textView.isFocused() || textView.isSelected())) {
10053 long currentMs = mChoreographer.getFrameTime();
10054 long deltaMs = currentMs - mLastAnimationMs;
10055 mLastAnimationMs = currentMs;
10056 float deltaPx = deltaMs / 1000f * mPixelsPerSecond;
10057 mScroll += deltaPx;
10058 if (mScroll > mMaxScroll) {
10059 mScroll = mMaxScroll;
10060 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY);
10062 mChoreographer.postFrameCallback(mTickCallback);
10064 textView.invalidate();
10069 mStatus = MARQUEE_STOPPED;
10070 mChoreographer.removeFrameCallback(mStartCallback);
10071 mChoreographer.removeFrameCallback(mRestartCallback);
10072 mChoreographer.removeFrameCallback(mTickCallback);
10076 private void resetScroll() {
10078 final TextView textView = mView.get();
10079 if (textView != null) textView.invalidate();
10082 void start(int repeatLimit) {
10083 if (repeatLimit == 0) {
10087 mRepeatLimit = repeatLimit;
10088 final TextView textView = mView.get();
10089 if (textView != null && textView.mLayout != null) {
10090 mStatus = MARQUEE_STARTING;
10092 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
10093 textView.getCompoundPaddingRight();
10094 final float lineWidth = textView.mLayout.getLineWidth(0);
10095 final float gap = textWidth / 3.0f;
10096 mGhostStart = lineWidth - textWidth + gap;
10097 mMaxScroll = mGhostStart + textWidth;
10098 mGhostOffset = lineWidth + gap;
10099 mFadeStop = lineWidth + textWidth / 6.0f;
10100 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
10102 textView.invalidate();
10103 mChoreographer.postFrameCallback(mStartCallback);
10107 float getGhostOffset() {
10108 return mGhostOffset;
10111 float getScroll() {
10115 float getMaxFadeScroll() {
10116 return mMaxFadeScroll;
10119 boolean shouldDrawLeftFade() {
10120 return mScroll <= mFadeStop;
10123 boolean shouldDrawGhost() {
10124 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
10127 boolean isRunning() {
10128 return mStatus == MARQUEE_RUNNING;
10131 boolean isStopped() {
10132 return mStatus == MARQUEE_STOPPED;
10136 private class ChangeWatcher implements TextWatcher, SpanWatcher {
10138 private CharSequence mBeforeText;
10140 public void beforeTextChanged(CharSequence buffer, int start,
10141 int before, int after) {
10142 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
10143 + " before=" + before + " after=" + after + ": " + buffer);
10145 if (AccessibilityManager.getInstance(mContext).isEnabled()
10146 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod())
10147 || shouldSpeakPasswordsForAccessibility())) {
10148 mBeforeText = buffer.toString();
10151 TextView.this.sendBeforeTextChanged(buffer, start, before, after);
10154 public void onTextChanged(CharSequence buffer, int start, int before, int after) {
10155 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
10156 + " before=" + before + " after=" + after + ": " + buffer);
10157 TextView.this.handleTextChanged(buffer, start, before, after);
10159 if (AccessibilityManager.getInstance(mContext).isEnabled() &&
10160 (isFocused() || isSelected() && isShown())) {
10161 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
10162 mBeforeText = null;
10166 public void afterTextChanged(Editable buffer) {
10167 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
10168 TextView.this.sendAfterTextChanged(buffer);
10170 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
10171 MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
10175 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) {
10176 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
10177 + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
10178 TextView.this.spanChange(buf, what, s, st, e, en);
10181 public void onSpanAdded(Spannable buf, Object what, int s, int e) {
10182 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
10183 + " what=" + what + ": " + buf);
10184 TextView.this.spanChange(buf, what, -1, s, -1, e);
10187 public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
10188 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
10189 + " what=" + what + ": " + buf);
10190 TextView.this.spanChange(buf, what, s, -1, e, -1);