/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.widget;

import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;

import android.R;
import android.annotation.CheckResult;
import android.annotation.ColorInt;
import android.annotation.DrawableRes;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.Size;
import android.annotation.StringRes;
import android.annotation.StyleRes;
import android.annotation.XmlRes;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.assist.AssistStructure;
import android.content.ClipData;
import android.content.ClipDescription;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.UndoManager;
import android.content.res.ColorStateList;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.ResourceId;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.graphics.BaseCanvas;
import android.graphics.Canvas;
import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.fonts.FontVariationAxis;
import android.icu.text.DecimalFormatSymbols;
import android.os.AsyncTask;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
import android.os.LocaleList;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.ParcelableParcel;
import android.os.SystemClock;
import android.provider.Settings;
import android.text.BoringLayout;
import android.text.DynamicLayout;
import android.text.Editable;
import android.text.GetChars;
import android.text.GraphicsOperations;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Layout;
import android.text.ParcelableSpan;
import android.text.PrecomputedText;
import android.text.Selection;
import android.text.SpanWatcher;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.StaticLayout;
import android.text.TextDirectionHeuristic;
import android.text.TextDirectionHeuristics;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextUtils.TruncateAt;
import android.text.TextWatcher;
import android.text.method.AllCapsTransformationMethod;
import android.text.method.ArrowKeyMovementMethod;
import android.text.method.DateKeyListener;
import android.text.method.DateTimeKeyListener;
import android.text.method.DialerKeyListener;
import android.text.method.DigitsKeyListener;
import android.text.method.KeyListener;
import android.text.method.LinkMovementMethod;
import android.text.method.MetaKeyKeyListener;
import android.text.method.MovementMethod;
import android.text.method.PasswordTransformationMethod;
import android.text.method.SingleLineTransformationMethod;
import android.text.method.TextKeyListener;
import android.text.method.TimeKeyListener;
import android.text.method.TransformationMethod;
import android.text.method.TransformationMethod2;
import android.text.method.WordIterator;
import android.text.style.CharacterStyle;
import android.text.style.ClickableSpan;
import android.text.style.ParagraphStyle;
import android.text.style.SpellCheckSpan;
import android.text.style.SuggestionSpan;
import android.text.style.URLSpan;
import android.text.style.UpdateAppearance;
import android.text.util.Linkify;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Log;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.ActionMode;
import android.view.Choreographer;
import android.view.ContextMenu;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.PointerIcon;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewHierarchyEncoder;
import android.view.ViewParent;
import android.view.ViewRootImpl;
import android.view.ViewStructure;
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.autofill.AutofillManager;
import android.view.autofill.AutofillValue;
import android.view.inputmethod.BaseInputConnection;
import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.CorrectionInfo;
import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.textclassifier.TextClassification;
import android.view.textclassifier.TextClassificationContext;
import android.view.textclassifier.TextClassificationManager;
import android.view.textclassifier.TextClassifier;
import android.view.textclassifier.TextLinks;
import android.view.textservice.SpellCheckerSubtype;
import android.view.textservice.TextServicesManager;
import android.widget.RemoteViews.RemoteView;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FastMath;
import com.android.internal.util.Preconditions;
import com.android.internal.widget.EditableInputConnection;

import libcore.util.EmptyArray;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;

A user interface element that displays text to the user. To provide user-editable text, see EditText.

The following code sample shows a typical use, with an XML layout and code to modify the contents of the text view:

<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
   <TextView
       android:id="@+id/text_view_id"
       android:layout_height="wrap_content"
       android:layout_width="wrap_content"
       android:text="@string/hello" />
</LinearLayout>

This code sample demonstrates how to modify the contents of the text view defined in the previous XML layout:

public class MainActivity extends Activity {
   protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView helloTextView = (TextView) findViewById(R.id.text_view_id);
        helloTextView.setText(R.string.user_greeting);
    }
}

To customize the appearance of TextView, see Styles and Themes.

XML attributes

See TextView Attributes, View Attributes

@attrref android.R.styleable#TextView_text
@attrref android.R.styleable#TextView_bufferType
@attrref android.R.styleable#TextView_hint
@attrref android.R.styleable#TextView_textColor
@attrref android.R.styleable#TextView_textColorHighlight
@attrref android.R.styleable#TextView_textColorHint
@attrref android.R.styleable#TextView_textAppearance
@attrref android.R.styleable#TextView_textColorLink
@attrref android.R.styleable#TextView_textSize
@attrref android.R.styleable#TextView_textScaleX
@attrref android.R.styleable#TextView_fontFamily
@attrref android.R.styleable#TextView_typeface
@attrref android.R.styleable#TextView_textStyle
@attrref android.R.styleable#TextView_cursorVisible
@attrref android.R.styleable#TextView_maxLines
@attrref android.R.styleable#TextView_maxHeight
@attrref android.R.styleable#TextView_lines
@attrref android.R.styleable#TextView_height
@attrref android.R.styleable#TextView_minLines
@attrref android.R.styleable#TextView_minHeight
@attrref android.R.styleable#TextView_maxEms
@attrref android.R.styleable#TextView_maxWidth
@attrref android.R.styleable#TextView_ems
@attrref android.R.styleable#TextView_width
@attrref android.R.styleable#TextView_minEms
@attrref android.R.styleable#TextView_minWidth
@attrref android.R.styleable#TextView_gravity
@attrref android.R.styleable#TextView_scrollHorizontally
@attrref android.R.styleable#TextView_password
@attrref android.R.styleable#TextView_singleLine
@attrref android.R.styleable#TextView_selectAllOnFocus
@attrref android.R.styleable#TextView_includeFontPadding
@attrref android.R.styleable#TextView_maxLength
@attrref android.R.styleable#TextView_shadowColor
@attrref android.R.styleable#TextView_shadowDx
@attrref android.R.styleable#TextView_shadowDy
@attrref android.R.styleable#TextView_shadowRadius
@attrref android.R.styleable#TextView_autoLink
@attrref android.R.styleable#TextView_linksClickable
@attrref android.R.styleable#TextView_numeric
@attrref android.R.styleable#TextView_digits
@attrref android.R.styleable#TextView_phoneNumber
@attrref android.R.styleable#TextView_inputMethod
@attrref android.R.styleable#TextView_capitalize
@attrref android.R.styleable#TextView_autoText
@attrref android.R.styleable#TextView_editable
@attrref android.R.styleable#TextView_freezesText
@attrref android.R.styleable#TextView_ellipsize
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableBottom
@attrref android.R.styleable#TextView_drawableRight
@attrref android.R.styleable#TextView_drawableLeft
@attrref android.R.styleable#TextView_drawableStart
@attrref android.R.styleable#TextView_drawableEnd
@attrref android.R.styleable#TextView_drawablePadding
@attrref android.R.styleable#TextView_drawableTint
@attrref android.R.styleable#TextView_drawableTintMode
@attrref android.R.styleable#TextView_lineSpacingExtra
@attrref android.R.styleable#TextView_lineSpacingMultiplier
@attrref android.R.styleable#TextView_justificationMode
@attrref android.R.styleable#TextView_marqueeRepeatLimit
@attrref android.R.styleable#TextView_inputType
@attrref android.R.styleable#TextView_imeOptions
@attrref android.R.styleable#TextView_privateImeOptions
@attrref android.R.styleable#TextView_imeActionLabel
@attrref android.R.styleable#TextView_imeActionId
@attrref android.R.styleable#TextView_editorExtras
@attrref android.R.styleable#TextView_elegantTextHeight
@attrref android.R.styleable#TextView_fallbackLineSpacing
@attrref android.R.styleable#TextView_letterSpacing
@attrref android.R.styleable#TextView_fontFeatureSettings
@attrref android.R.styleable#TextView_breakStrategy
@attrref android.R.styleable#TextView_hyphenationFrequency
@attrref android.R.styleable#TextView_autoSizeTextType
@attrref android.R.styleable#TextView_autoSizeMinTextSize
@attrref android.R.styleable#TextView_autoSizeMaxTextSize
@attrref android.R.styleable#TextView_autoSizeStepGranularity
@attrref android.R.styleable#TextView_autoSizePresetSizes
/** * A user interface element that displays text to the user. * To provide user-editable text, see {@link EditText}. * <p> * The following code sample shows a typical use, with an XML layout * and code to modify the contents of the text view: * </p> * <pre> * &lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"&gt; * &lt;TextView * android:id="@+id/text_view_id" * android:layout_height="wrap_content" * android:layout_width="wrap_content" * android:text="@string/hello" /&gt; * &lt;/LinearLayout&gt; * </pre> * <p> * This code sample demonstrates how to modify the contents of the text view * defined in the previous XML layout: * </p> * <pre> * public class MainActivity extends Activity { * * protected void onCreate(Bundle savedInstanceState) { * super.onCreate(savedInstanceState); * setContentView(R.layout.activity_main); * final TextView helloTextView = (TextView) findViewById(R.id.text_view_id); * helloTextView.setText(R.string.user_greeting); * } * } * </pre> * <p> * To customize the appearance of TextView, see <a href="https://developer.android.com/guide/topics/ui/themes.html">Styles and Themes</a>. * </p> * <p> * <b>XML attributes</b> * <p> * See {@link android.R.styleable#TextView TextView Attributes}, * {@link android.R.styleable#View View Attributes} * * @attr ref android.R.styleable#TextView_text * @attr ref android.R.styleable#TextView_bufferType * @attr ref android.R.styleable#TextView_hint * @attr ref android.R.styleable#TextView_textColor * @attr ref android.R.styleable#TextView_textColorHighlight * @attr ref android.R.styleable#TextView_textColorHint * @attr ref android.R.styleable#TextView_textAppearance * @attr ref android.R.styleable#TextView_textColorLink * @attr ref android.R.styleable#TextView_textSize * @attr ref android.R.styleable#TextView_textScaleX * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle * @attr ref android.R.styleable#TextView_cursorVisible * @attr ref android.R.styleable#TextView_maxLines * @attr ref android.R.styleable#TextView_maxHeight * @attr ref android.R.styleable#TextView_lines * @attr ref android.R.styleable#TextView_height * @attr ref android.R.styleable#TextView_minLines * @attr ref android.R.styleable#TextView_minHeight * @attr ref android.R.styleable#TextView_maxEms * @attr ref android.R.styleable#TextView_maxWidth * @attr ref android.R.styleable#TextView_ems * @attr ref android.R.styleable#TextView_width * @attr ref android.R.styleable#TextView_minEms * @attr ref android.R.styleable#TextView_minWidth * @attr ref android.R.styleable#TextView_gravity * @attr ref android.R.styleable#TextView_scrollHorizontally * @attr ref android.R.styleable#TextView_password * @attr ref android.R.styleable#TextView_singleLine * @attr ref android.R.styleable#TextView_selectAllOnFocus * @attr ref android.R.styleable#TextView_includeFontPadding * @attr ref android.R.styleable#TextView_maxLength * @attr ref android.R.styleable#TextView_shadowColor * @attr ref android.R.styleable#TextView_shadowDx * @attr ref android.R.styleable#TextView_shadowDy * @attr ref android.R.styleable#TextView_shadowRadius * @attr ref android.R.styleable#TextView_autoLink * @attr ref android.R.styleable#TextView_linksClickable * @attr ref android.R.styleable#TextView_numeric * @attr ref android.R.styleable#TextView_digits * @attr ref android.R.styleable#TextView_phoneNumber * @attr ref android.R.styleable#TextView_inputMethod * @attr ref android.R.styleable#TextView_capitalize * @attr ref android.R.styleable#TextView_autoText * @attr ref android.R.styleable#TextView_editable * @attr ref android.R.styleable#TextView_freezesText * @attr ref android.R.styleable#TextView_ellipsize * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableBottom * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawablePadding * @attr ref android.R.styleable#TextView_drawableTint * @attr ref android.R.styleable#TextView_drawableTintMode * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier * @attr ref android.R.styleable#TextView_justificationMode * @attr ref android.R.styleable#TextView_marqueeRepeatLimit * @attr ref android.R.styleable#TextView_inputType * @attr ref android.R.styleable#TextView_imeOptions * @attr ref android.R.styleable#TextView_privateImeOptions * @attr ref android.R.styleable#TextView_imeActionLabel * @attr ref android.R.styleable#TextView_imeActionId * @attr ref android.R.styleable#TextView_editorExtras * @attr ref android.R.styleable#TextView_elegantTextHeight * @attr ref android.R.styleable#TextView_fallbackLineSpacing * @attr ref android.R.styleable#TextView_letterSpacing * @attr ref android.R.styleable#TextView_fontFeatureSettings * @attr ref android.R.styleable#TextView_breakStrategy * @attr ref android.R.styleable#TextView_hyphenationFrequency * @attr ref android.R.styleable#TextView_autoSizeTextType * @attr ref android.R.styleable#TextView_autoSizeMinTextSize * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize * @attr ref android.R.styleable#TextView_autoSizeStepGranularity * @attr ref android.R.styleable#TextView_autoSizePresetSizes */
@RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { static final String LOG_TAG = "TextView"; static final boolean DEBUG_EXTRACT = false; private static final float[] TEMP_POSITION = new float[2]; // Enum for the "typeface" XML parameter. // TODO: How can we get this from the XML instead of hardcoding it here?
@hide
/** @hide */
@IntDef(value = {DEFAULT_TYPEFACE, SANS, SERIF, MONOSPACE}) @Retention(RetentionPolicy.SOURCE) public @interface XMLTypefaceAttr{} private static final int DEFAULT_TYPEFACE = -1; private static final int SANS = 1; private static final int SERIF = 2; private static final int MONOSPACE = 3; // Enum for the "ellipsize" XML parameter. private static final int ELLIPSIZE_NOT_SET = -1; private static final int ELLIPSIZE_NONE = 0; private static final int ELLIPSIZE_START = 1; private static final int ELLIPSIZE_MIDDLE = 2; private static final int ELLIPSIZE_END = 3; private static final int ELLIPSIZE_MARQUEE = 4; // Bitfield for the "numeric" XML parameter. // TODO: How can we get this from the XML instead of hardcoding it here? private static final int SIGNED = 2; private static final int DECIMAL = 4;
Draw marquee text with fading edges as usual
/** * Draw marquee text with fading edges as usual */
private static final int MARQUEE_FADE_NORMAL = 0;
Draw marquee text as ellipsize end while inactive instead of with the fade. (Useful for devices where the fade can be expensive if overdone)
/** * Draw marquee text as ellipsize end while inactive instead of with the fade. * (Useful for devices where the fade can be expensive if overdone) */
private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
Draw marquee text with fading edges because it is currently active/animating.
/** * Draw marquee text with fading edges because it is currently active/animating. */
private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; private static final int LINES = 1; private static final int EMS = LINES; private static final int PIXELS = 2; private static final RectF TEMP_RECTF = new RectF();
@hide
/** @hide */
static final int VERY_WIDE = 1024 * 1024; // XXX should be much larger private static final int ANIMATED_SCROLL_GAP = 250; private static final InputFilter[] NO_FILTERS = new InputFilter[0]; private static final Spanned EMPTY_SPANNED = new SpannedString(""); private static final int CHANGE_WATCHER_PRIORITY = 100; // New state used to change background based on whether this TextView is multiline. private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; // Accessibility action to share selected text. private static final int ACCESSIBILITY_ACTION_SHARE = 0x10000000;
@hide
/** * @hide */
// Accessibility action start id for "process text" actions. static final int ACCESSIBILITY_ACTION_PROCESS_TEXT_START_ID = 0x10000100;
@hide
/** * @hide */
static final int PROCESS_TEXT_REQUEST_CODE = 100;
Return code of doKeyDown.
/** * Return code of {@link #doKeyDown}. */
private static final int KEY_EVENT_NOT_HANDLED = 0; private static final int KEY_EVENT_HANDLED = -1; private static final int KEY_DOWN_HANDLED_BY_KEY_LISTENER = 1; private static final int KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD = 2; private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500; // System wide time for last cut, copy or text changed action. static long sLastCutCopyOrTextChangedTime; private ColorStateList mTextColor; private ColorStateList mHintTextColor; private ColorStateList mLinkTextColor; @ViewDebug.ExportedProperty(category = "text") private int mCurTextColor; private int mCurHintTextColor; private boolean mFreezesText; private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); private float mShadowRadius, mShadowDx, mShadowDy; private int mShadowColor; private boolean mPreDrawRegistered; private boolean mPreDrawListenerDetached; private TextClassifier mTextClassifier; private TextClassifier mTextClassificationSession; // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is // that if a user is holding down a movement key to traverse text, we shouldn't also traverse // the view hierarchy. On the other hand, if the user is using the movement key to traverse // views (i.e. the first movement was to traverse out of this view, or this view was traversed // into by the user holding the movement key down) then we shouldn't prevent the focus from // changing. private boolean mPreventDefaultMovement; private TextUtils.TruncateAt mEllipsize; static class Drawables { static final int LEFT = 0; static final int TOP = 1; static final int RIGHT = 2; static final int BOTTOM = 3; static final int DRAWABLE_NONE = -1; static final int DRAWABLE_RIGHT = 0; static final int DRAWABLE_LEFT = 1; final Rect mCompoundRect = new Rect(); final Drawable[] mShowing = new Drawable[4]; ColorStateList mTintList; PorterDuff.Mode mTintMode; boolean mHasTint; boolean mHasTintMode; Drawable mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; Drawable mDrawableLeftInitial, mDrawableRightInitial; boolean mIsRtlCompatibilityMode; boolean mOverride; int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; int mDrawablePadding; int mDrawableSaved = DRAWABLE_NONE; public Drawables(Context context) { final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; mIsRtlCompatibilityMode = targetSdkVersion < VERSION_CODES.JELLY_BEAN_MR1 || !context.getApplicationInfo().hasRtlSupport(); mOverride = false; }
Returns:true if this object contains metadata that needs to be retained, false otherwise
/** * @return {@code true} if this object contains metadata that needs to * be retained, {@code false} otherwise */
public boolean hasMetadata() { return mDrawablePadding != 0 || mHasTintMode || mHasTint; }
Updates the list of displayed drawables to account for the current layout direction.
Params:
  • layoutDirection – the current layout direction
Returns:true if the displayed drawables changed
/** * Updates the list of displayed drawables to account for the current * layout direction. * * @param layoutDirection the current layout direction * @return {@code true} if the displayed drawables changed */
public boolean resolveWithLayoutDirection(int layoutDirection) { final Drawable previousLeft = mShowing[Drawables.LEFT]; final Drawable previousRight = mShowing[Drawables.RIGHT]; // First reset "left" and "right" drawables to their initial values mShowing[Drawables.LEFT] = mDrawableLeftInitial; mShowing[Drawables.RIGHT] = mDrawableRightInitial; if (mIsRtlCompatibilityMode) { // Use "start" drawable as "left" drawable if the "left" drawable was not defined if (mDrawableStart != null && mShowing[Drawables.LEFT] == null) { mShowing[Drawables.LEFT] = mDrawableStart; mDrawableSizeLeft = mDrawableSizeStart; mDrawableHeightLeft = mDrawableHeightStart; } // Use "end" drawable as "right" drawable if the "right" drawable was not defined if (mDrawableEnd != null && mShowing[Drawables.RIGHT] == null) { mShowing[Drawables.RIGHT] = mDrawableEnd; mDrawableSizeRight = mDrawableSizeEnd; mDrawableHeightRight = mDrawableHeightEnd; } } else { // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" // drawable if and only if they have been defined switch(layoutDirection) { case LAYOUT_DIRECTION_RTL: if (mOverride) { mShowing[Drawables.RIGHT] = mDrawableStart; mDrawableSizeRight = mDrawableSizeStart; mDrawableHeightRight = mDrawableHeightStart; mShowing[Drawables.LEFT] = mDrawableEnd; mDrawableSizeLeft = mDrawableSizeEnd; mDrawableHeightLeft = mDrawableHeightEnd; } break; case LAYOUT_DIRECTION_LTR: default: if (mOverride) { mShowing[Drawables.LEFT] = mDrawableStart; mDrawableSizeLeft = mDrawableSizeStart; mDrawableHeightLeft = mDrawableHeightStart; mShowing[Drawables.RIGHT] = mDrawableEnd; mDrawableSizeRight = mDrawableSizeEnd; mDrawableHeightRight = mDrawableHeightEnd; } break; } } applyErrorDrawableIfNeeded(layoutDirection); return mShowing[Drawables.LEFT] != previousLeft || mShowing[Drawables.RIGHT] != previousRight; } public void setErrorDrawable(Drawable dr, TextView tv) { if (mDrawableError != dr && mDrawableError != null) { mDrawableError.setCallback(null); } mDrawableError = dr; if (mDrawableError != null) { final Rect compoundRect = mCompoundRect; final int[] state = tv.getDrawableState(); mDrawableError.setState(state); mDrawableError.copyBounds(compoundRect); mDrawableError.setCallback(tv); mDrawableSizeError = compoundRect.width(); mDrawableHeightError = compoundRect.height(); } else { mDrawableSizeError = mDrawableHeightError = 0; } } private void applyErrorDrawableIfNeeded(int layoutDirection) { // first restore the initial state if needed switch (mDrawableSaved) { case DRAWABLE_LEFT: mShowing[Drawables.LEFT] = mDrawableTemp; mDrawableSizeLeft = mDrawableSizeTemp; mDrawableHeightLeft = mDrawableHeightTemp; break; case DRAWABLE_RIGHT: mShowing[Drawables.RIGHT] = mDrawableTemp; mDrawableSizeRight = mDrawableSizeTemp; mDrawableHeightRight = mDrawableHeightTemp; break; case DRAWABLE_NONE: default: } // then, if needed, assign the Error drawable to the correct location if (mDrawableError != null) { switch(layoutDirection) { case LAYOUT_DIRECTION_RTL: mDrawableSaved = DRAWABLE_LEFT; mDrawableTemp = mShowing[Drawables.LEFT]; mDrawableSizeTemp = mDrawableSizeLeft; mDrawableHeightTemp = mDrawableHeightLeft; mShowing[Drawables.LEFT] = mDrawableError; mDrawableSizeLeft = mDrawableSizeError; mDrawableHeightLeft = mDrawableHeightError; break; case LAYOUT_DIRECTION_LTR: default: mDrawableSaved = DRAWABLE_RIGHT; mDrawableTemp = mShowing[Drawables.RIGHT]; mDrawableSizeTemp = mDrawableSizeRight; mDrawableHeightTemp = mDrawableHeightRight; mShowing[Drawables.RIGHT] = mDrawableError; mDrawableSizeRight = mDrawableSizeError; mDrawableHeightRight = mDrawableHeightError; break; } } } } Drawables mDrawables; private CharWrapper mCharWrapper; private Marquee mMarquee; private boolean mRestartMarquee; private int mMarqueeRepeatLimit = 3; private int mLastLayoutDirection = -1;
On some devices the fading edges add a performance penalty if used extensively in the same layout. This mode indicates how the marquee is currently being shown, if applicable. (mEllipsize will == MARQUEE)
/** * On some devices the fading edges add a performance penalty if used * extensively in the same layout. This mode indicates how the marquee * is currently being shown, if applicable. (mEllipsize will == MARQUEE) */
private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL;
When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores the layout that should be used when the mode switches.
/** * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores * the layout that should be used when the mode switches. */
private Layout mSavedMarqueeModeLayout; // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal() @ViewDebug.ExportedProperty(category = "text") private @Nullable CharSequence mText; private @Nullable Spannable mSpannable; private @Nullable PrecomputedText mPrecomputed; private CharSequence mTransformed; private BufferType mBufferType = BufferType.NORMAL; private CharSequence mHint; private Layout mHintLayout; private MovementMethod mMovement; private TransformationMethod mTransformation; private boolean mAllowTransformationLengthChange; private ChangeWatcher mChangeWatcher; private ArrayList<TextWatcher> mListeners; // display attributes private final TextPaint mTextPaint; private boolean mUserSetTextScaleX; private Layout mLayout; private boolean mLocalesChanged = false; // True if setKeyListener() has been explicitly called private boolean mListenerChanged = false; // True if internationalized input should be used for numbers and date and time. private final boolean mUseInternationalizedInput; // True if fallback fonts that end up getting used should be allowed to affect line spacing. /* package */ boolean mUseFallbackLineSpacing; @ViewDebug.ExportedProperty(category = "text") private int mGravity = Gravity.TOP | Gravity.START; private boolean mHorizontallyScrolling; private int mAutoLinkMask; private boolean mLinksClickable = true; private float mSpacingMult = 1.0f; private float mSpacingAdd = 0.0f; private int mBreakStrategy; private int mHyphenationFrequency; private int mJustificationMode; private int mMaximum = Integer.MAX_VALUE; private int mMaxMode = LINES; private int mMinimum = 0; private int mMinMode = LINES; private int mOldMaximum = mMaximum; private int mOldMaxMode = mMaxMode; private int mMaxWidth = Integer.MAX_VALUE; private int mMaxWidthMode = PIXELS; private int mMinWidth = 0; private int mMinWidthMode = PIXELS; private boolean mSingleLine; private int mDesiredHeightAtMeasure = -1; private boolean mIncludePad = true; private int mDeferScroll = -1; // tmp primitives, so we don't alloc them on each draw private Rect mTempRect; private long mLastScroll; private Scroller mScroller; private TextPaint mTempTextPaint; private BoringLayout.Metrics mBoring, mHintBoring; private BoringLayout mSavedLayout, mSavedHintLayout; private TextDirectionHeuristic mTextDir; private InputFilter[] mFilters = NO_FILTERS; private volatile Locale mCurrentSpellCheckerLocaleCache; // It is possible to have a selection even when mEditor is null (programmatically set, like when // a link is pressed). These highlight-related fields do not go in mEditor. int mHighlightColor = 0x6633B5E5; private Path mHighlightPath; private final Paint mHighlightPaint; private boolean mHighlightPathBogus = true; // Although these fields are specific to editable text, they are not added to Editor because // they are defined by the TextView's style and are theme-dependent. int mCursorDrawableRes; // These six fields, could be moved to Editor, since we know their default values and we // could condition the creation of the Editor to a non standard value. This is however // brittle since the hardcoded values here (such as // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the // default style is modified. int mTextSelectHandleLeftRes; int mTextSelectHandleRightRes; int mTextSelectHandleRes; int mTextEditSuggestionItemLayout; int mTextEditSuggestionContainerLayout; int mTextEditSuggestionHighlightStyle;
EditText specific data, created on demand when one of the Editor fields is used. See createEditorIfNeeded().
/** * {@link EditText} specific data, created on demand when one of the Editor fields is used. * See {@link #createEditorIfNeeded()}. */
private Editor mEditor; private static final int DEVICE_PROVISIONED_UNKNOWN = 0; private static final int DEVICE_PROVISIONED_NO = 1; private static final int DEVICE_PROVISIONED_YES = 2;
Some special options such as sharing selected text should only be shown if the device is provisioned. Only check the provisioned state once for a given view instance.
/** * Some special options such as sharing selected text should only be shown if the device * is provisioned. Only check the provisioned state once for a given view instance. */
private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN;
The TextView does not auto-size text (default).
/** * The TextView does not auto-size text (default). */
public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0;
The TextView scales text size both horizontally and vertically to fit within the container.
/** * The TextView scales text size both horizontally and vertically to fit within the * container. */
public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1;
@hide
/** @hide */
@IntDef(prefix = { "AUTO_SIZE_TEXT_TYPE_" }, value = { AUTO_SIZE_TEXT_TYPE_NONE, AUTO_SIZE_TEXT_TYPE_UNIFORM }) @Retention(RetentionPolicy.SOURCE) public @interface AutoSizeTextType {} // Default minimum size for auto-sizing text in scaled pixels. private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; // Default maximum size for auto-sizing text in scaled pixels. private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; // Default value for the step size in pixels. private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; // Use this to specify that any of the auto-size configuration int values have not been set. private static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; // Auto-size text type. private int mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; // Specify if auto-size text is needed. private boolean mNeedsAutoSizeText = false; // Step size for auto-sizing in pixels. private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; // Minimum text size for auto-sizing in pixels. private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; // Maximum text size for auto-sizing in pixels. private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from // when auto-sizing text. private int[] mAutoSizeTextSizesInPx = EmptyArray.INT; // Specifies whether auto-size should use the provided auto size steps set or if it should // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and // mAutoSizeStepGranularityInPx. private boolean mHasPresetAutoSizeValues = false; // Autofill-related attributes // // Indicates whether the text was set statically or dynamically, so it can be used to // sanitize autofill requests. private boolean mTextSetFromXmlOrResourceId = false; // Resource id used to set the text. private @StringRes int mTextId = ResourceId.ID_NULL; // Last value used on AFM.notifyValueChanged(), used to optimize autofill workflow by avoiding // calls when the value did not change private CharSequence mLastValueSentToAutofillManager; // // End of autofill-related attributes
Kick-start the font cache for the zygote process (to pay the cost of initializing freetype for our default font only once).
@hide
/** * Kick-start the font cache for the zygote process (to pay the cost of * initializing freetype for our default font only once). * @hide */
public static void preloadFontCache() { Paint p = new Paint(); p.setAntiAlias(true); // Ensure that the Typeface is loaded here. // Typically, Typeface is preloaded by zygote but not on all devices, e.g. Android Auto. // So, sets Typeface.DEFAULT explicitly here for ensuring that the Typeface is loaded here // since Paint.measureText can not be called without Typeface static initializer. p.setTypeface(Typeface.DEFAULT); // We don't care about the result, just the side-effect of measuring. p.measureText("H"); }
Interface definition for a callback to be invoked when an action is performed on the editor.
/** * Interface definition for a callback to be invoked when an action is * performed on the editor. */
public interface OnEditorActionListener {
Called when an action is being performed.
Params:
  • v – The view that was clicked.
  • actionId – Identifier of the action. This will be either the identifier you supplied, or EditorInfo.IME_NULL if being called due to the enter key being pressed.
  • event – If triggered by an enter key, this is the event; otherwise, this is null.
Returns:Return true if you have consumed the action, else false.
/** * Called when an action is being performed. * * @param v The view that was clicked. * @param actionId Identifier of the action. This will be either the * identifier you supplied, or {@link EditorInfo#IME_NULL * EditorInfo.IME_NULL} if being called due to the enter key * being pressed. * @param event If triggered by an enter key, this is the event; * otherwise, this is null. * @return Return true if you have consumed the action, else false. */
boolean onEditorAction(TextView v, int actionId, KeyEvent event); } public TextView(Context context) { this(context, null); } public TextView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.textViewStyle); } public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { this(context, attrs, defStyleAttr, 0); } @SuppressWarnings("deprecation") public TextView( Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); // TextView is important by default, unless app developer overrode attribute. if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) { setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES); } setTextInternal(""); final Resources res = getResources(); final CompatibilityInfo compat = res.getCompatibilityInfo(); mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); mTextPaint.density = res.getDisplayMetrics().density; mTextPaint.setCompatibilityScaling(compat.applicationScale); mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mHighlightPaint.setCompatibilityScaling(compat.applicationScale); mMovement = getDefaultMovementMethod(); mTransformation = null; final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); attributes.mTextColor = ColorStateList.valueOf(0xFF000000); attributes.mTextSize = 15; mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE; mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE; mJustificationMode = Layout.JUSTIFICATION_MODE_NONE; final Resources.Theme theme = context.getTheme(); /* * Look the appearance up without checking first if it exists because * almost every TextView has one and it greatly simplifies the logic * to be able to parse the appearance first and then let specific tags * for this View override it. */ TypedArray a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); TypedArray appearance = null; int ap = a.getResourceId( com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); a.recycle(); if (ap != -1) { appearance = theme.obtainStyledAttributes( ap, com.android.internal.R.styleable.TextAppearance); } if (appearance != null) { readTextAppearance(context, appearance, attributes, false /* styleArray */); attributes.mFontFamilyExplicit = false; appearance.recycle(); } boolean editable = getDefaultEditable(); CharSequence inputMethod = null; int numeric = 0; CharSequence digits = null; boolean phone = false; boolean autotext = false; int autocap = -1; int buffertype = 0; boolean selectallonfocus = false; Drawable drawableLeft = null, drawableTop = null, drawableRight = null, drawableBottom = null, drawableStart = null, drawableEnd = null; ColorStateList drawableTint = null; PorterDuff.Mode drawableTintMode = null; int drawablePadding = 0; int ellipsize = ELLIPSIZE_NOT_SET; boolean singleLine = false; int maxlength = -1; CharSequence text = ""; CharSequence hint = null; boolean password = false; float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; int inputType = EditorInfo.TYPE_NULL; a = theme.obtainStyledAttributes( attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); int firstBaselineToTopHeight = -1; int lastBaselineToBottomHeight = -1; int lineHeight = -1; readTextAppearance(context, a, attributes, true /* styleArray */); int n = a.getIndexCount(); // Must set id in a temporary variable because it will be reset by setText() boolean textIsSetFromXml = false; for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case com.android.internal.R.styleable.TextView_editable: editable = a.getBoolean(attr, editable); break; case com.android.internal.R.styleable.TextView_inputMethod: inputMethod = a.getText(attr); break; case com.android.internal.R.styleable.TextView_numeric: numeric = a.getInt(attr, numeric); break; case com.android.internal.R.styleable.TextView_digits: digits = a.getText(attr); break; case com.android.internal.R.styleable.TextView_phoneNumber: phone = a.getBoolean(attr, phone); break; case com.android.internal.R.styleable.TextView_autoText: autotext = a.getBoolean(attr, autotext); break; case com.android.internal.R.styleable.TextView_capitalize: autocap = a.getInt(attr, autocap); break; case com.android.internal.R.styleable.TextView_bufferType: buffertype = a.getInt(attr, buffertype); break; case com.android.internal.R.styleable.TextView_selectAllOnFocus: selectallonfocus = a.getBoolean(attr, selectallonfocus); break; case com.android.internal.R.styleable.TextView_autoLink: mAutoLinkMask = a.getInt(attr, 0); break; case com.android.internal.R.styleable.TextView_linksClickable: mLinksClickable = a.getBoolean(attr, true); break; case com.android.internal.R.styleable.TextView_drawableLeft: drawableLeft = a.getDrawable(attr); break; case com.android.internal.R.styleable.TextView_drawableTop: drawableTop = a.getDrawable(attr); break; case com.android.internal.R.styleable.TextView_drawableRight: drawableRight = a.getDrawable(attr); break; case com.android.internal.R.styleable.TextView_drawableBottom: drawableBottom = a.getDrawable(attr); break; case com.android.internal.R.styleable.TextView_drawableStart: drawableStart = a.getDrawable(attr); break; case com.android.internal.R.styleable.TextView_drawableEnd: drawableEnd = a.getDrawable(attr); break; case com.android.internal.R.styleable.TextView_drawableTint: drawableTint = a.getColorStateList(attr); break; case com.android.internal.R.styleable.TextView_drawableTintMode: drawableTintMode = Drawable.parseTintMode(a.getInt(attr, -1), drawableTintMode); break; case com.android.internal.R.styleable.TextView_drawablePadding: drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); break; case com.android.internal.R.styleable.TextView_maxLines: setMaxLines(a.getInt(attr, -1)); break; case com.android.internal.R.styleable.TextView_maxHeight: setMaxHeight(a.getDimensionPixelSize(attr, -1)); break; case com.android.internal.R.styleable.TextView_lines: setLines(a.getInt(attr, -1)); break; case com.android.internal.R.styleable.TextView_height: setHeight(a.getDimensionPixelSize(attr, -1)); break; case com.android.internal.R.styleable.TextView_minLines: setMinLines(a.getInt(attr, -1)); break; case com.android.internal.R.styleable.TextView_minHeight: setMinHeight(a.getDimensionPixelSize(attr, -1)); break; case com.android.internal.R.styleable.TextView_maxEms: setMaxEms(a.getInt(attr, -1)); break; case com.android.internal.R.styleable.TextView_maxWidth: setMaxWidth(a.getDimensionPixelSize(attr, -1)); break; case com.android.internal.R.styleable.TextView_ems: setEms(a.getInt(attr, -1)); break; case com.android.internal.R.styleable.TextView_width: setWidth(a.getDimensionPixelSize(attr, -1)); break; case com.android.internal.R.styleable.TextView_minEms: setMinEms(a.getInt(attr, -1)); break; case com.android.internal.R.styleable.TextView_minWidth: setMinWidth(a.getDimensionPixelSize(attr, -1)); break; case com.android.internal.R.styleable.TextView_gravity: setGravity(a.getInt(attr, -1)); break; case com.android.internal.R.styleable.TextView_hint: hint = a.getText(attr); break; case com.android.internal.R.styleable.TextView_text: textIsSetFromXml = true; mTextId = a.getResourceId(attr, ResourceId.ID_NULL); text = a.getText(attr); break; case com.android.internal.R.styleable.TextView_scrollHorizontally: if (a.getBoolean(attr, false)) { setHorizontallyScrolling(true); } break; case com.android.internal.R.styleable.TextView_singleLine: singleLine = a.getBoolean(attr, singleLine); break; case com.android.internal.R.styleable.TextView_ellipsize: ellipsize = a.getInt(attr, ellipsize); break; case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); break; case com.android.internal.R.styleable.TextView_includeFontPadding: if (!a.getBoolean(attr, true)) { setIncludeFontPadding(false); } break; case com.android.internal.R.styleable.TextView_cursorVisible: if (!a.getBoolean(attr, true)) { setCursorVisible(false); } break; case com.android.internal.R.styleable.TextView_maxLength: maxlength = a.getInt(attr, -1); break; case com.android.internal.R.styleable.TextView_textScaleX: setTextScaleX(a.getFloat(attr, 1.0f)); break; case com.android.internal.R.styleable.TextView_freezesText: mFreezesText = a.getBoolean(attr, false); break; case com.android.internal.R.styleable.TextView_enabled: setEnabled(a.getBoolean(attr, isEnabled())); break; case com.android.internal.R.styleable.TextView_password: password = a.getBoolean(attr, password); break; case com.android.internal.R.styleable.TextView_lineSpacingExtra: mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); break; case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: mSpacingMult = a.getFloat(attr, mSpacingMult); break; case com.android.internal.R.styleable.TextView_inputType: inputType = a.getInt(attr, EditorInfo.TYPE_NULL); break; case com.android.internal.R.styleable.TextView_allowUndo: createEditorIfNeeded(); mEditor.mAllowUndo = a.getBoolean(attr, true); break; case com.android.internal.R.styleable.TextView_imeOptions: createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeOptions = a.getInt(attr, mEditor.mInputContentType.imeOptions); break; case com.android.internal.R.styleable.TextView_imeActionLabel: createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeActionLabel = a.getText(attr); break; case com.android.internal.R.styleable.TextView_imeActionId: createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeActionId = a.getInt(attr, mEditor.mInputContentType.imeActionId); break; case com.android.internal.R.styleable.TextView_privateImeOptions: setPrivateImeOptions(a.getString(attr)); break; case com.android.internal.R.styleable.TextView_editorExtras: try { setInputExtras(a.getResourceId(attr, 0)); } catch (XmlPullParserException e) { Log.w(LOG_TAG, "Failure reading input extras", e); } catch (IOException e) { Log.w(LOG_TAG, "Failure reading input extras", e); } break; case com.android.internal.R.styleable.TextView_textCursorDrawable: mCursorDrawableRes = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textSelectHandleLeft: mTextSelectHandleLeftRes = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textSelectHandleRight: mTextSelectHandleRightRes = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textSelectHandle: mTextSelectHandleRes = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textEditSuggestionContainerLayout: mTextEditSuggestionContainerLayout = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textEditSuggestionHighlightStyle: mTextEditSuggestionHighlightStyle = a.getResourceId(attr, 0); break; case com.android.internal.R.styleable.TextView_textIsSelectable: setTextIsSelectable(a.getBoolean(attr, false)); break; case com.android.internal.R.styleable.TextView_breakStrategy: mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE); break; case com.android.internal.R.styleable.TextView_hyphenationFrequency: mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); break; case com.android.internal.R.styleable.TextView_autoSizeTextType: mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE); break; case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: autoSizeStepGranularityInPx = a.getDimension(attr, UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); break; case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: autoSizeMinTextSizeInPx = a.getDimension(attr, UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); break; case com.android.internal.R.styleable.TextView_autoSizeMaxTextSize: autoSizeMaxTextSizeInPx = a.getDimension(attr, UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); break; case com.android.internal.R.styleable.TextView_autoSizePresetSizes: final int autoSizeStepSizeArrayResId = a.getResourceId(attr, 0); if (autoSizeStepSizeArrayResId > 0) { final TypedArray autoSizePresetTextSizes = a.getResources() .obtainTypedArray(autoSizeStepSizeArrayResId); setupAutoSizeUniformPresetSizes(autoSizePresetTextSizes); autoSizePresetTextSizes.recycle(); } break; case com.android.internal.R.styleable.TextView_justificationMode: mJustificationMode = a.getInt(attr, Layout.JUSTIFICATION_MODE_NONE); break; case com.android.internal.R.styleable.TextView_firstBaselineToTopHeight: firstBaselineToTopHeight = a.getDimensionPixelSize(attr, -1); break; case com.android.internal.R.styleable.TextView_lastBaselineToBottomHeight: lastBaselineToBottomHeight = a.getDimensionPixelSize(attr, -1); break; case com.android.internal.R.styleable.TextView_lineHeight: lineHeight = a.getDimensionPixelSize(attr, -1); break; } } a.recycle(); BufferType bufferType = BufferType.EDITABLE; final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); final boolean passwordInputType = variation == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); final boolean webPasswordInputType = variation == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); final boolean numberPasswordInputType = variation == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; mUseInternationalizedInput = targetSdkVersion >= VERSION_CODES.O; mUseFallbackLineSpacing = targetSdkVersion >= VERSION_CODES.P; if (inputMethod != null) { Class<?> c; try { c = Class.forName(inputMethod.toString()); } catch (ClassNotFoundException ex) { throw new RuntimeException(ex); } try { createEditorIfNeeded(); mEditor.mKeyListener = (KeyListener) c.newInstance(); } catch (InstantiationException ex) { throw new RuntimeException(ex); } catch (IllegalAccessException ex) { throw new RuntimeException(ex); } try { mEditor.mInputType = inputType != EditorInfo.TYPE_NULL ? inputType : mEditor.mKeyListener.getInputType(); } catch (IncompatibleClassChangeError e) { mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; } } else if (digits != null) { createEditorIfNeeded(); mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); // If no input type was specified, we will default to generic // text, since we can't tell the IME about the set of digits // that was selected. mEditor.mInputType = inputType != EditorInfo.TYPE_NULL ? inputType : EditorInfo.TYPE_CLASS_TEXT; } else if (inputType != EditorInfo.TYPE_NULL) { setInputType(inputType, true); // If set, the input type overrides what was set using the deprecated singleLine flag. singleLine = !isMultilineInputType(inputType); } else if (phone) { createEditorIfNeeded(); mEditor.mKeyListener = DialerKeyListener.getInstance(); mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; } else if (numeric != 0) { createEditorIfNeeded(); mEditor.mKeyListener = DigitsKeyListener.getInstance( null, // locale (numeric & SIGNED) != 0, (numeric & DECIMAL) != 0); inputType = mEditor.mKeyListener.getInputType(); mEditor.mInputType = inputType; } else if (autotext || autocap != -1) { TextKeyListener.Capitalize cap; inputType = EditorInfo.TYPE_CLASS_TEXT; switch (autocap) { case 1: cap = TextKeyListener.Capitalize.SENTENCES; inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; break; case 2: cap = TextKeyListener.Capitalize.WORDS; inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; break; case 3: cap = TextKeyListener.Capitalize.CHARACTERS; inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; break; default: cap = TextKeyListener.Capitalize.NONE; break; } createEditorIfNeeded(); mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); mEditor.mInputType = inputType; } else if (editable) { createEditorIfNeeded(); mEditor.mKeyListener = TextKeyListener.getInstance(); mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; } else if (isTextSelectable()) { // Prevent text changes from keyboard. if (mEditor != null) { mEditor.mKeyListener = null; mEditor.mInputType = EditorInfo.TYPE_NULL; } bufferType = BufferType.SPANNABLE; // So that selection can be changed using arrow keys and touch is handled. setMovementMethod(ArrowKeyMovementMethod.getInstance()); } else { if (mEditor != null) mEditor.mKeyListener = null; switch (buffertype) { case 0: bufferType = BufferType.NORMAL; break; case 1: bufferType = BufferType.SPANNABLE; break; case 2: bufferType = BufferType.EDITABLE; break; } } if (mEditor != null) { mEditor.adjustInputType(password, passwordInputType, webPasswordInputType, numberPasswordInputType); } if (selectallonfocus) { createEditorIfNeeded(); mEditor.mSelectAllOnFocus = true; if (bufferType == BufferType.NORMAL) { bufferType = BufferType.SPANNABLE; } } // Set up the tint (if needed) before setting the drawables so that it // gets applied correctly. if (drawableTint != null || drawableTintMode != null) { if (mDrawables == null) { mDrawables = new Drawables(context); } if (drawableTint != null) { mDrawables.mTintList = drawableTint; mDrawables.mHasTint = true; } if (drawableTintMode != null) { mDrawables.mTintMode = drawableTintMode; mDrawables.mHasTintMode = true; } } // This call will save the initial left/right drawables setCompoundDrawablesWithIntrinsicBounds( drawableLeft, drawableTop, drawableRight, drawableBottom); setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); setCompoundDrawablePadding(drawablePadding); // Same as setSingleLine(), but make sure the transformation method and the maximum number // of lines of height are unchanged for multi-line TextViews. setInputTypeSingleLine(singleLine); applySingleLine(singleLine, singleLine, singleLine); if (singleLine && getKeyListener() == null && ellipsize == ELLIPSIZE_NOT_SET) { ellipsize = ELLIPSIZE_END; } switch (ellipsize) { case ELLIPSIZE_START: setEllipsize(TextUtils.TruncateAt.START); break; case ELLIPSIZE_MIDDLE: setEllipsize(TextUtils.TruncateAt.MIDDLE); break; case ELLIPSIZE_END: setEllipsize(TextUtils.TruncateAt.END); break; case ELLIPSIZE_MARQUEE: if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { setHorizontalFadingEdgeEnabled(true); mMarqueeFadeMode = MARQUEE_FADE_NORMAL; } else { setHorizontalFadingEdgeEnabled(false); mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; } setEllipsize(TextUtils.TruncateAt.MARQUEE); break; } final boolean isPassword = password || passwordInputType || webPasswordInputType || numberPasswordInputType; final boolean isMonospaceEnforced = isPassword || (mEditor != null && (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)); if (isMonospaceEnforced) { attributes.mTypefaceIndex = MONOSPACE; } applyTextAppearance(attributes); if (isPassword) { setTransformationMethod(PasswordTransformationMethod.getInstance()); } if (maxlength >= 0) { setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); } else { setFilters(NO_FILTERS); } setText(text, bufferType); if (textIsSetFromXml) { mTextSetFromXmlOrResourceId = true; } if (hint != null) setHint(hint); /* * Views are not normally clickable unless specified to be. * However, TextViews that have input or movement methods *are* * clickable by default. By setting clickable here, we implicitly set focusable as well * if not overridden by the developer. */ a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); boolean canInputOrMove = (mMovement != null || getKeyListener() != null); boolean clickable = canInputOrMove || isClickable(); boolean longClickable = canInputOrMove || isLongClickable(); int focusable = getFocusable(); n = a.getIndexCount(); for (int i = 0; i < n; i++) { int attr = a.getIndex(i); switch (attr) { case com.android.internal.R.styleable.View_focusable: TypedValue val = new TypedValue(); if (a.getValue(attr, val)) { focusable = (val.type == TypedValue.TYPE_INT_BOOLEAN) ? (val.data == 0 ? NOT_FOCUSABLE : FOCUSABLE) : val.data; } break; case com.android.internal.R.styleable.View_clickable: clickable = a.getBoolean(attr, clickable); break; case com.android.internal.R.styleable.View_longClickable: longClickable = a.getBoolean(attr, longClickable); break; } } a.recycle(); // Some apps were relying on the undefined behavior of focusable winning over // focusableInTouchMode != focusable in TextViews if both were specified in XML (usually // when starting with EditText and setting only focusable=false). To keep those apps from // breaking, re-apply the focusable attribute here. if (focusable != getFocusable()) { setFocusable(focusable); } setClickable(clickable); setLongClickable(longClickable); if (mEditor != null) mEditor.prepareCursorControllers(); // If not explicitly specified this view is important for accessibility. if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } if (supportsAutoSizeText()) { if (mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { // If uniform auto-size has been specified but preset values have not been set then // replace the auto-size configuration values that have not been specified with the // defaults. if (!mHasPresetAutoSizeValues) { final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { autoSizeMinTextSizeInPx = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, displayMetrics); } if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { autoSizeMaxTextSizeInPx = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, displayMetrics); } if (autoSizeStepGranularityInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; } validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, autoSizeMaxTextSizeInPx, autoSizeStepGranularityInPx); } setupAutoSizeText(); } } else { mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; } if (firstBaselineToTopHeight >= 0) { setFirstBaselineToTopHeight(firstBaselineToTopHeight); } if (lastBaselineToBottomHeight >= 0) { setLastBaselineToBottomHeight(lastBaselineToBottomHeight); } if (lineHeight >= 0) { setLineHeight(lineHeight); } } // Update mText and mPrecomputed private void setTextInternal(@Nullable CharSequence text) { mText = text; mSpannable = (text instanceof Spannable) ? (Spannable) text : null; mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; }
Specify whether this widget should automatically scale the text to try to perfectly fit within the layout bounds by using the default auto-size configuration.
Params:
Throws:
See Also:
@attrref android.R.styleable#TextView_autoSizeTextType
/** * Specify whether this widget should automatically scale the text to try to perfectly fit * within the layout bounds by using the default auto-size configuration. * * @param autoSizeTextType the type of auto-size. Must be one of * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} * * @throws IllegalArgumentException if <code>autoSizeTextType</code> is none of the types above. * * @attr ref android.R.styleable#TextView_autoSizeTextType * * @see #getAutoSizeTextType() */
public void setAutoSizeTextTypeWithDefaults(@AutoSizeTextType int autoSizeTextType) { if (supportsAutoSizeText()) { switch (autoSizeTextType) { case AUTO_SIZE_TEXT_TYPE_NONE: clearAutoSizeConfiguration(); break; case AUTO_SIZE_TEXT_TYPE_UNIFORM: final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, displayMetrics); final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, displayMetrics); validateAndSetAutoSizeTextTypeUniformConfiguration( autoSizeMinTextSizeInPx, autoSizeMaxTextSizeInPx, DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); if (setupAutoSizeText()) { autoSizeText(); invalidate(); } break; default: throw new IllegalArgumentException( "Unknown auto-size text type: " + autoSizeTextType); } } }
Specify whether this widget should automatically scale the text to try to perfectly fit within the layout bounds. If all the configuration params are valid the type of auto-size is set to AUTO_SIZE_TEXT_TYPE_UNIFORM.
Params:
  • autoSizeMinTextSize – the minimum text size available for auto-size
  • autoSizeMaxTextSize – the maximum text size available for auto-size
  • autoSizeStepGranularity – the auto-size step granularity. It is used in conjunction with the minimum and maximum text size in order to build the set of text sizes the system uses to choose from when auto-sizing
  • unit – the desired dimension unit for all sizes above. See TypedValue for the possible dimension units
Throws:
See Also:
@attrref android.R.styleable#TextView_autoSizeTextType
@attrref android.R.styleable#TextView_autoSizeMinTextSize
@attrref android.R.styleable#TextView_autoSizeMaxTextSize
@attrref android.R.styleable#TextView_autoSizeStepGranularity
/** * Specify whether this widget should automatically scale the text to try to perfectly fit * within the layout bounds. If all the configuration params are valid the type of auto-size is * set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. * * @param autoSizeMinTextSize the minimum text size available for auto-size * @param autoSizeMaxTextSize the maximum text size available for auto-size * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with * the minimum and maximum text size in order to build the set of * text sizes the system uses to choose from when auto-sizing * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the * possible dimension units * * @throws IllegalArgumentException if any of the configuration params are invalid. * * @attr ref android.R.styleable#TextView_autoSizeTextType * @attr ref android.R.styleable#TextView_autoSizeMinTextSize * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize * @attr ref android.R.styleable#TextView_autoSizeStepGranularity * * @see #setAutoSizeTextTypeWithDefaults(int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) * @see #getAutoSizeMinTextSize() * @see #getAutoSizeMaxTextSize() * @see #getAutoSizeStepGranularity() * @see #getAutoSizeTextAvailableSizes() */
public void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) { if (supportsAutoSizeText()) { final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); final float autoSizeMinTextSizeInPx = TypedValue.applyDimension( unit, autoSizeMinTextSize, displayMetrics); final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension( unit, autoSizeMaxTextSize, displayMetrics); final float autoSizeStepGranularityInPx = TypedValue.applyDimension( unit, autoSizeStepGranularity, displayMetrics); validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, autoSizeMaxTextSizeInPx, autoSizeStepGranularityInPx); if (setupAutoSizeText()) { autoSizeText(); invalidate(); } } }
Specify whether this widget should automatically scale the text to try to perfectly fit within the layout bounds. If at least one value from the presetSizes is valid then the type of auto-size is set to AUTO_SIZE_TEXT_TYPE_UNIFORM.
Params:
  • presetSizes – an int array of sizes in pixels
  • unit – the desired dimension unit for the preset sizes above. See TypedValue for the possible dimension units
Throws:
See Also:
@attrref android.R.styleable#TextView_autoSizeTextType
@attrref android.R.styleable#TextView_autoSizePresetSizes
/** * Specify whether this widget should automatically scale the text to try to perfectly fit * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid * then the type of auto-size is set to {@link #AUTO_SIZE_TEXT_TYPE_UNIFORM}. * * @param presetSizes an {@code int} array of sizes in pixels * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for * the possible dimension units * * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. * * @attr ref android.R.styleable#TextView_autoSizeTextType * @attr ref android.R.styleable#TextView_autoSizePresetSizes * * @see #setAutoSizeTextTypeWithDefaults(int) * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #getAutoSizeMinTextSize() * @see #getAutoSizeMaxTextSize() * @see #getAutoSizeTextAvailableSizes() */
public void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) { if (supportsAutoSizeText()) { final int presetSizesLength = presetSizes.length; if (presetSizesLength > 0) { int[] presetSizesInPx = new int[presetSizesLength]; if (unit == TypedValue.COMPLEX_UNIT_PX) { presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); } else { final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); // Convert all to sizes to pixels. for (int i = 0; i < presetSizesLength; i++) { presetSizesInPx[i] = Math.round(TypedValue.applyDimension(unit, presetSizes[i], displayMetrics)); } } mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); if (!setupAutoSizeUniformPresetSizesConfiguration()) { throw new IllegalArgumentException("None of the preset sizes is valid: " + Arrays.toString(presetSizes)); } } else { mHasPresetAutoSizeValues = false; } if (setupAutoSizeText()) { autoSizeText(); invalidate(); } } }
Returns the type of auto-size set for this widget.
See Also:
Returns:an int corresponding to one of the auto-size types: AUTO_SIZE_TEXT_TYPE_NONE or AUTO_SIZE_TEXT_TYPE_UNIFORM
@attrref android.R.styleable#TextView_autoSizeTextType
/** * Returns the type of auto-size set for this widget. * * @return an {@code int} corresponding to one of the auto-size types: * {@link TextView#AUTO_SIZE_TEXT_TYPE_NONE} or * {@link TextView#AUTO_SIZE_TEXT_TYPE_UNIFORM} * * @attr ref android.R.styleable#TextView_autoSizeTextType * * @see #setAutoSizeTextTypeWithDefaults(int) * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) */
@AutoSizeTextType public int getAutoSizeTextType() { return mAutoSizeTextType; }
See Also:
Returns:the current auto-size step granularity in pixels.
@attrref android.R.styleable#TextView_autoSizeStepGranularity
/** * @return the current auto-size step granularity in pixels. * * @attr ref android.R.styleable#TextView_autoSizeStepGranularity * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) */
public int getAutoSizeStepGranularity() { return Math.round(mAutoSizeStepGranularityInPx); }
See Also:
Returns:the current auto-size minimum text size in pixels (the default is 12sp). Note that if auto-size has not been configured this function returns -1.
@attrref android.R.styleable#TextView_autoSizeMinTextSize
/** * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that * if auto-size has not been configured this function returns {@code -1}. * * @attr ref android.R.styleable#TextView_autoSizeMinTextSize * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) */
public int getAutoSizeMinTextSize() { return Math.round(mAutoSizeMinTextSizeInPx); }
See Also:
Returns:the current auto-size maximum text size in pixels (the default is 112sp). Note that if auto-size has not been configured this function returns -1.
@attrref android.R.styleable#TextView_autoSizeMaxTextSize
/** * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that * if auto-size has not been configured this function returns {@code -1}. * * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) */
public int getAutoSizeMaxTextSize() { return Math.round(mAutoSizeMaxTextSizeInPx); }
See Also:
Returns:the current auto-size int sizes array (in pixels).
/** * @return the current auto-size {@code int} sizes array (in pixels). * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) */
public int[] getAutoSizeTextAvailableSizes() { return mAutoSizeTextSizesInPx; } private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { final int textSizesLength = textSizes.length(); final int[] parsedSizes = new int[textSizesLength]; if (textSizesLength > 0) { for (int i = 0; i < textSizesLength; i++) { parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); } mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); setupAutoSizeUniformPresetSizesConfiguration(); } } private boolean setupAutoSizeUniformPresetSizesConfiguration() { final int sizesLength = mAutoSizeTextSizesInPx.length; mHasPresetAutoSizeValues = sizesLength > 0; if (mHasPresetAutoSizeValues) { mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; } return mHasPresetAutoSizeValues; }
If all params are valid then save the auto-size configuration.
Throws:
  • IllegalArgumentException – if any of the params are invalid
/** * If all params are valid then save the auto-size configuration. * * @throws IllegalArgumentException if any of the params are invalid */
private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) { // First validate. if (autoSizeMinTextSizeInPx <= 0) { throw new IllegalArgumentException("Minimum auto-size text size (" + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); } if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { throw new IllegalArgumentException("Maximum auto-size text size (" + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " + "text size (" + autoSizeMinTextSizeInPx + "px)"); } if (autoSizeStepGranularityInPx <= 0) { throw new IllegalArgumentException("The auto-size step granularity (" + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); } // All good, persist the configuration. mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_UNIFORM; mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; mHasPresetAutoSizeValues = false; } private void clearAutoSizeConfiguration() { mAutoSizeTextType = AUTO_SIZE_TEXT_TYPE_NONE; mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; mAutoSizeTextSizesInPx = EmptyArray.INT; mNeedsAutoSizeText = false; } // Returns distinct sorted positive values. private int[] cleanupAutoSizePresetSizes(int[] presetValues) { final int presetValuesLength = presetValues.length; if (presetValuesLength == 0) { return presetValues; } Arrays.sort(presetValues); final IntArray uniqueValidSizes = new IntArray(); for (int i = 0; i < presetValuesLength; i++) { final int currentPresetValue = presetValues[i]; if (currentPresetValue > 0 && uniqueValidSizes.binarySearch(currentPresetValue) < 0) { uniqueValidSizes.add(currentPresetValue); } } return presetValuesLength == uniqueValidSizes.size() ? presetValues : uniqueValidSizes.toArray(); } private boolean setupAutoSizeText() { if (supportsAutoSizeText() && mAutoSizeTextType == AUTO_SIZE_TEXT_TYPE_UNIFORM) { // Calculate the sizes set based on minimum size, maximum size and step size if we do // not have a predefined set of sizes or if the current sizes array is empty. if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { final int autoSizeValuesLength = ((int) Math.floor((mAutoSizeMaxTextSizeInPx - mAutoSizeMinTextSizeInPx) / mAutoSizeStepGranularityInPx)) + 1; final int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; for (int i = 0; i < autoSizeValuesLength; i++) { autoSizeTextSizesInPx[i] = Math.round( mAutoSizeMinTextSizeInPx + (i * mAutoSizeStepGranularityInPx)); } mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); } mNeedsAutoSizeText = true; } else { mNeedsAutoSizeText = false; } return mNeedsAutoSizeText; } private int[] parseDimensionArray(TypedArray dimens) { if (dimens == null) { return null; } int[] result = new int[dimens.length()]; for (int i = 0; i < result.length; i++) { result[i] = dimens.getDimensionPixelSize(i, 0); } return result; }
@hide
/** * @hide */
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == PROCESS_TEXT_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT); if (result != null) { if (isTextEditable()) { replaceSelectionWithText(result); if (mEditor != null) { mEditor.refreshTextActionMode(); } } else { if (result.length() > 0) { Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG) .show(); } } } } else if (mSpannable != null) { // Reset the selection. Selection.setSelection(mSpannable, getSelectionEnd()); } } }
Sets the Typeface taking into account the given attributes.
Params:
  • typeface – a typeface
  • familyName – family name string, e.g. "serif"
  • typefaceIndex – an index of the typeface enum, e.g. SANS, SERIF.
  • style – a typeface style
  • weight – a weight value for the Typeface or -1 if not specified.
/** * Sets the Typeface taking into account the given attributes. * * @param typeface a typeface * @param familyName family name string, e.g. "serif" * @param typefaceIndex an index of the typeface enum, e.g. SANS, SERIF. * @param style a typeface style * @param weight a weight value for the Typeface or -1 if not specified. */
private void setTypefaceFromAttrs(@Nullable Typeface typeface, @Nullable String familyName, @XMLTypefaceAttr int typefaceIndex, @Typeface.Style int style, @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight) { if (typeface == null && familyName != null) { // Lookup normal Typeface from system font map. final Typeface normalTypeface = Typeface.create(familyName, Typeface.NORMAL); resolveStyleAndSetTypeface(normalTypeface, style, weight); } else if (typeface != null) { resolveStyleAndSetTypeface(typeface, style, weight); } else { // both typeface and familyName is null. switch (typefaceIndex) { case SANS: resolveStyleAndSetTypeface(Typeface.SANS_SERIF, style, weight); break; case SERIF: resolveStyleAndSetTypeface(Typeface.SERIF, style, weight); break; case MONOSPACE: resolveStyleAndSetTypeface(Typeface.MONOSPACE, style, weight); break; case DEFAULT_TYPEFACE: default: resolveStyleAndSetTypeface(null, style, weight); break; } } } private void resolveStyleAndSetTypeface(@NonNull Typeface typeface, @Typeface.Style int style, @IntRange(from = -1, to = Typeface.MAX_WEIGHT) int weight) { if (weight >= 0) { weight = Math.min(Typeface.MAX_WEIGHT, weight); final boolean italic = (style & Typeface.ITALIC) != 0; setTypeface(Typeface.create(typeface, weight, italic)); } else { setTypeface(typeface, style); } } private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { boolean hasRelativeDrawables = (start != null) || (end != null); if (hasRelativeDrawables) { Drawables dr = mDrawables; if (dr == null) { mDrawables = dr = new Drawables(getContext()); } mDrawables.mOverride = true; final Rect compoundRect = dr.mCompoundRect; int[] state = getDrawableState(); if (start != null) { start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); start.setState(state); start.copyBounds(compoundRect); start.setCallback(this); dr.mDrawableStart = start; dr.mDrawableSizeStart = compoundRect.width(); dr.mDrawableHeightStart = compoundRect.height(); } else { dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; } if (end != null) { end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); end.setState(state); end.copyBounds(compoundRect); end.setCallback(this); dr.mDrawableEnd = end; dr.mDrawableSizeEnd = compoundRect.width(); dr.mDrawableHeightEnd = compoundRect.height(); } else { dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; } resetResolvedDrawables(); resolveDrawables(); applyCompoundDrawableTint(); } } @android.view.RemotableViewMethod @Override public void setEnabled(boolean enabled) { if (enabled == isEnabled()) { return; } if (!enabled) { // Hide the soft input if the currently active TextView is disabled InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null && imm.isActive(this)) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } } super.setEnabled(enabled); if (enabled) { // Make sure IME is updated with current editor info. InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) imm.restartInput(this); } // Will change text color if (mEditor != null) { mEditor.invalidateTextDisplayList(); mEditor.prepareCursorControllers(); // start or stop the cursor blinking as appropriate mEditor.makeBlink(); } }
Sets the typeface and style in which the text should be displayed, and turns on the fake bold and italic bits in the Paint if the Typeface that you provided does not have all the bits in the style that you specified.
@attrref android.R.styleable#TextView_typeface
@attrref android.R.styleable#TextView_textStyle
/** * Sets the typeface and style in which the text should be displayed, * and turns on the fake bold and italic bits in the Paint if the * Typeface that you provided does not have all the bits in the * style that you specified. * * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */
public void setTypeface(@Nullable Typeface tf, @Typeface.Style int style) { if (style > 0) { if (tf == null) { tf = Typeface.defaultFromStyle(style); } else { tf = Typeface.create(tf, style); } setTypeface(tf); // now compute what (if any) algorithmic styling is needed int typefaceStyle = tf != null ? tf.getStyle() : 0; int need = style & ~typefaceStyle; mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); } else { mTextPaint.setFakeBoldText(false); mTextPaint.setTextSkewX(0); setTypeface(tf); } }
Subclasses override this to specify that they have a KeyListener by default even if not specifically called for in the XML options.
/** * Subclasses override this to specify that they have a KeyListener * by default even if not specifically called for in the XML options. */
protected boolean getDefaultEditable() { return false; }
Subclasses override this to specify a default movement method.
/** * Subclasses override this to specify a default movement method. */
protected MovementMethod getDefaultMovementMethod() { return null; }
Return the text that TextView is displaying. If setText(CharSequence) was called with an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast the return value from this method to Spannable or Editable, respectively.

The content of the return value should not be modified. If you want a modifiable one, you should make your own copy first.

Returns:The text displayed by the text view.
@attrref android.R.styleable#TextView_text
/** * Return the text that TextView is displaying. If {@link #setText(CharSequence)} was called * with an argument of {@link android.widget.TextView.BufferType#SPANNABLE BufferType.SPANNABLE} * or {@link android.widget.TextView.BufferType#EDITABLE BufferType.EDITABLE}, you can cast * the return value from this method to Spannable or Editable, respectively. * * <p>The content of the return value should not be modified. If you want a modifiable one, you * should make your own copy first.</p> * * @return The text displayed by the text view. * @attr ref android.R.styleable#TextView_text */
@ViewDebug.CapturedViewProperty public CharSequence getText() { return mText; }
Returns the length, in characters, of the text managed by this TextView
Returns:The length of the text managed by the TextView in characters.
/** * Returns the length, in characters, of the text managed by this TextView * @return The length of the text managed by the TextView in characters. */
public int length() { return mText.length(); }
Return the text that TextView is displaying as an Editable object. If the text is not editable, null is returned.
See Also:
  • getText
/** * Return the text that TextView is displaying as an Editable object. If the text is not * editable, null is returned. * * @see #getText */
public Editable getEditableText() { return (mText instanceof Editable) ? (Editable) mText : null; }
Gets the vertical distance between lines of text, in pixels. Note that markup within the text can cause individual lines to be taller or shorter than this height, and the layout may contain additional first-or last-line padding.
Returns:The height of one standard line in pixels.
/** * Gets the vertical distance between lines of text, in pixels. * Note that markup within the text can cause individual lines * to be taller or shorter than this height, and the layout may * contain additional first-or last-line padding. * @return The height of one standard line in pixels. */
public int getLineHeight() { return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); }
Gets the Layout that is currently being used to display the text. This value can be null if the text or width has recently changed.
Returns:The Layout that is currently being used to display the text.
/** * Gets the {@link android.text.Layout} that is currently being used to display the text. * This value can be null if the text or width has recently changed. * @return The Layout that is currently being used to display the text. */
public final Layout getLayout() { return mLayout; }
Returns:the Layout that is currently being used to display the hint text. This can be null.
/** * @return the {@link android.text.Layout} that is currently being used to * display the hint text. This can be null. */
final Layout getHintLayout() { return mHintLayout; }
Retrieve the UndoManager that is currently associated with this TextView. By default there is no associated UndoManager, so null is returned. One can be associated with the TextView through setUndoManager(UndoManager, String)
@hide
/** * Retrieve the {@link android.content.UndoManager} that is currently associated * with this TextView. By default there is no associated UndoManager, so null * is returned. One can be associated with the TextView through * {@link #setUndoManager(android.content.UndoManager, String)} * * @hide */
public final UndoManager getUndoManager() { // TODO: Consider supporting a global undo manager. throw new UnsupportedOperationException("not implemented"); }
@hide
/** * @hide */
@VisibleForTesting public final Editor getEditorForTesting() { return mEditor; }
Associate an UndoManager with this TextView. Once done, all edit operations on the TextView will result in appropriate UndoOperation objects pushed on the given UndoManager's stack.
Params:
  • undoManager – The UndoManager to associate with this TextView, or null to clear any existing association.
  • tag – String tag identifying this particular TextView owner in the UndoManager. This is used to keep the correct association with the UndoOwner of any operations inside of the UndoManager.
@hide
/** * Associate an {@link android.content.UndoManager} with this TextView. Once * done, all edit operations on the TextView will result in appropriate * {@link android.content.UndoOperation} objects pushed on the given UndoManager's * stack. * * @param undoManager The {@link android.content.UndoManager} to associate with * this TextView, or null to clear any existing association. * @param tag String tag identifying this particular TextView owner in the * UndoManager. This is used to keep the correct association with the * {@link android.content.UndoOwner} of any operations inside of the UndoManager. * * @hide */
public final void setUndoManager(UndoManager undoManager, String tag) { // TODO: Consider supporting a global undo manager. An implementation will need to: // * createEditorIfNeeded() // * Promote to BufferType.EDITABLE if needed. // * Update the UndoManager and UndoOwner. // Likewise it will need to be able to restore the default UndoManager. throw new UnsupportedOperationException("not implemented"); }
Gets the current KeyListener for the TextView. This will frequently be null for non-EditText TextViews.
Returns:the current key listener for this TextView.
@attrref android.R.styleable#TextView_numeric
@attrref android.R.styleable#TextView_digits
@attrref android.R.styleable#TextView_phoneNumber
@attrref android.R.styleable#TextView_inputMethod
@attrref android.R.styleable#TextView_capitalize
@attrref android.R.styleable#TextView_autoText
/** * Gets the current {@link KeyListener} for the TextView. * This will frequently be null for non-EditText TextViews. * @return the current key listener for this TextView. * * @attr ref android.R.styleable#TextView_numeric * @attr ref android.R.styleable#TextView_digits * @attr ref android.R.styleable#TextView_phoneNumber * @attr ref android.R.styleable#TextView_inputMethod * @attr ref android.R.styleable#TextView_capitalize * @attr ref android.R.styleable#TextView_autoText */
public final KeyListener getKeyListener() { return mEditor == null ? null : mEditor.mKeyListener; }
Sets the key listener to be used with this TextView. This can be null to disallow user input. Note that this method has significant and subtle interactions with soft keyboards and other input method: see KeyListener.getInputType() for important details. Calling this method will replace the current content type of the text view with the content type returned by the key listener.

Be warned that if you want a TextView with a key listener or movement method not to be focusable, or if you want a TextView without a key listener or movement method to be focusable, you must call View.setFocusable again after calling this to get the focusability back the way you want it.

@attrref android.R.styleable#TextView_numeric
@attrref android.R.styleable#TextView_digits
@attrref android.R.styleable#TextView_phoneNumber
@attrref android.R.styleable#TextView_inputMethod
@attrref android.R.styleable#TextView_capitalize
@attrref android.R.styleable#TextView_autoText
/** * Sets the key listener to be used with this TextView. This can be null * to disallow user input. Note that this method has significant and * subtle interactions with soft keyboards and other input method: * see {@link KeyListener#getInputType() KeyListener.getInputType()} * for important details. Calling this method will replace the current * content type of the text view with the content type returned by the * key listener. * <p> * Be warned that if you want a TextView with a key listener or movement * method not to be focusable, or if you want a TextView without a * key listener or movement method to be focusable, you must call * {@link #setFocusable} again after calling this to get the focusability * back the way you want it. * * @attr ref android.R.styleable#TextView_numeric * @attr ref android.R.styleable#TextView_digits * @attr ref android.R.styleable#TextView_phoneNumber * @attr ref android.R.styleable#TextView_inputMethod * @attr ref android.R.styleable#TextView_capitalize * @attr ref android.R.styleable#TextView_autoText */
public void setKeyListener(KeyListener input) { mListenerChanged = true; setKeyListenerOnly(input); fixFocusableAndClickableSettings(); if (input != null) { createEditorIfNeeded(); setInputTypeFromEditor(); } else { if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; } InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) imm.restartInput(this); } private void setInputTypeFromEditor() { try { mEditor.mInputType = mEditor.mKeyListener.getInputType(); } catch (IncompatibleClassChangeError e) { mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; } // Change inputType, without affecting transformation. // No need to applySingleLine since mSingleLine is unchanged. setInputTypeSingleLine(mSingleLine); } private void setKeyListenerOnly(KeyListener input) { if (mEditor == null && input == null) return; // null is the default value createEditorIfNeeded(); if (mEditor.mKeyListener != input) { mEditor.mKeyListener = input; if (input != null && !(mText instanceof Editable)) { setText(mText); } setFilters((Editable) mText, mFilters); } }
Gets the MovementMethod being used for this TextView, which provides positioning, scrolling, and text selection functionality. This will frequently be null for non-EditText TextViews.
See Also:
Returns:the movement method being used for this TextView.
/** * Gets the {@link android.text.method.MovementMethod} being used for this TextView, * which provides positioning, scrolling, and text selection functionality. * This will frequently be null for non-EditText TextViews. * @return the movement method being used for this TextView. * @see android.text.method.MovementMethod */
public final MovementMethod getMovementMethod() { return mMovement; }
Sets the MovementMethod for handling arrow key movement for this TextView. This can be null to disallow using the arrow keys to move the cursor or scroll the view.

Be warned that if you want a TextView with a key listener or movement method not to be focusable, or if you want a TextView without a key listener or movement method to be focusable, you must call View.setFocusable again after calling this to get the focusability back the way you want it.

/** * Sets the {@link android.text.method.MovementMethod} for handling arrow key movement * for this TextView. This can be null to disallow using the arrow keys to move the * cursor or scroll the view. * <p> * Be warned that if you want a TextView with a key listener or movement * method not to be focusable, or if you want a TextView without a * key listener or movement method to be focusable, you must call * {@link #setFocusable} again after calling this to get the focusability * back the way you want it. */
public final void setMovementMethod(MovementMethod movement) { if (mMovement != movement) { mMovement = movement; if (movement != null && mSpannable == null) { setText(mText); } fixFocusableAndClickableSettings(); // SelectionModifierCursorController depends on textCanBeSelected, which depends on // mMovement if (mEditor != null) mEditor.prepareCursorControllers(); } } private void fixFocusableAndClickableSettings() { if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { setFocusable(FOCUSABLE); setClickable(true); setLongClickable(true); } else { setFocusable(FOCUSABLE_AUTO); setClickable(false); setLongClickable(false); } }
Gets the current TransformationMethod for the TextView. This is frequently null, except for single-line and password fields.
Returns:the current transformation method for this TextView.
@attrref android.R.styleable#TextView_password
@attrref android.R.styleable#TextView_singleLine
/** * Gets the current {@link android.text.method.TransformationMethod} for the TextView. * This is frequently null, except for single-line and password fields. * @return the current transformation method for this TextView. * * @attr ref android.R.styleable#TextView_password * @attr ref android.R.styleable#TextView_singleLine */
public final TransformationMethod getTransformationMethod() { return mTransformation; }
Sets the transformation that is applied to the text that this TextView is displaying.
@attrref android.R.styleable#TextView_password
@attrref android.R.styleable#TextView_singleLine
/** * Sets the transformation that is applied to the text that this * TextView is displaying. * * @attr ref android.R.styleable#TextView_password * @attr ref android.R.styleable#TextView_singleLine */
public final void setTransformationMethod(TransformationMethod method) { if (method == mTransformation) { // Avoid the setText() below if the transformation is // the same. return; } if (mTransformation != null) { if (mSpannable != null) { mSpannable.removeSpan(mTransformation); } } mTransformation = method; if (method instanceof TransformationMethod2) { TransformationMethod2 method2 = (TransformationMethod2) method; mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); method2.setLengthChangesAllowed(mAllowTransformationLengthChange); } else { mAllowTransformationLengthChange = false; } setText(mText); if (hasPasswordTransformationMethod()) { notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } // PasswordTransformationMethod always have LTR text direction heuristics returned by // getTextDirectionHeuristic, needs reset mTextDir = getTextDirectionHeuristic(); }
Returns the top padding of the view, plus space for the top Drawable if any.
/** * Returns the top padding of the view, plus space for the top * Drawable if any. */
public int getCompoundPaddingTop() { final Drawables dr = mDrawables; if (dr == null || dr.mShowing[Drawables.TOP] == null) { return mPaddingTop; } else { return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; } }
Returns the bottom padding of the view, plus space for the bottom Drawable if any.
/** * Returns the bottom padding of the view, plus space for the bottom * Drawable if any. */
public int getCompoundPaddingBottom() { final Drawables dr = mDrawables; if (dr == null || dr.mShowing[Drawables.BOTTOM] == null) { return mPaddingBottom; } else { return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; } }
Returns the left padding of the view, plus space for the left Drawable if any.
/** * Returns the left padding of the view, plus space for the left * Drawable if any. */
public int getCompoundPaddingLeft() { final Drawables dr = mDrawables; if (dr == null || dr.mShowing[Drawables.LEFT] == null) { return mPaddingLeft; } else { return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; } }
Returns the right padding of the view, plus space for the right Drawable if any.
/** * Returns the right padding of the view, plus space for the right * Drawable if any. */
public int getCompoundPaddingRight() { final Drawables dr = mDrawables; if (dr == null || dr.mShowing[Drawables.RIGHT] == null) { return mPaddingRight; } else { return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; } }
Returns the start padding of the view, plus space for the start Drawable if any.
/** * Returns the start padding of the view, plus space for the start * Drawable if any. */
public int getCompoundPaddingStart() { resolveDrawables(); switch(getLayoutDirection()) { default: case LAYOUT_DIRECTION_LTR: return getCompoundPaddingLeft(); case LAYOUT_DIRECTION_RTL: return getCompoundPaddingRight(); } }
Returns the end padding of the view, plus space for the end Drawable if any.
/** * Returns the end padding of the view, plus space for the end * Drawable if any. */
public int getCompoundPaddingEnd() { resolveDrawables(); switch(getLayoutDirection()) { default: case LAYOUT_DIRECTION_LTR: return getCompoundPaddingRight(); case LAYOUT_DIRECTION_RTL: return getCompoundPaddingLeft(); } }
Returns the extended top padding of the view, including both the top Drawable if any and any extra space to keep more than maxLines of text from showing. It is only valid to call this after measuring.
/** * Returns the extended top padding of the view, including both the * top Drawable if any and any extra space to keep more than maxLines * of text from showing. It is only valid to call this after measuring. */
public int getExtendedPaddingTop() { if (mMaxMode != LINES) { return getCompoundPaddingTop(); } if (mLayout == null) { assumeLayout(); } if (mLayout.getLineCount() <= mMaximum) { return getCompoundPaddingTop(); } int top = getCompoundPaddingTop(); int bottom = getCompoundPaddingBottom(); int viewht = getHeight() - top - bottom; int layoutht = mLayout.getLineTop(mMaximum); if (layoutht >= viewht) { return top; } final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; if (gravity == Gravity.TOP) { return top; } else if (gravity == Gravity.BOTTOM) { return top + viewht - layoutht; } else { // (gravity == Gravity.CENTER_VERTICAL) return top + (viewht - layoutht) / 2; } }
Returns the extended bottom padding of the view, including both the bottom Drawable if any and any extra space to keep more than maxLines of text from showing. It is only valid to call this after measuring.
/** * Returns the extended bottom padding of the view, including both the * bottom Drawable if any and any extra space to keep more than maxLines * of text from showing. It is only valid to call this after measuring. */
public int getExtendedPaddingBottom() { if (mMaxMode != LINES) { return getCompoundPaddingBottom(); } if (mLayout == null) { assumeLayout(); } if (mLayout.getLineCount() <= mMaximum) { return getCompoundPaddingBottom(); } int top = getCompoundPaddingTop(); int bottom = getCompoundPaddingBottom(); int viewht = getHeight() - top - bottom; int layoutht = mLayout.getLineTop(mMaximum); if (layoutht >= viewht) { return bottom; } final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; if (gravity == Gravity.TOP) { return bottom + viewht - layoutht; } else if (gravity == Gravity.BOTTOM) { return bottom; } else { // (gravity == Gravity.CENTER_VERTICAL) return bottom + (viewht - layoutht) / 2; } }
Returns the total left padding of the view, including the left Drawable if any.
/** * Returns the total left padding of the view, including the left * Drawable if any. */
public int getTotalPaddingLeft() { return getCompoundPaddingLeft(); }
Returns the total right padding of the view, including the right Drawable if any.
/** * Returns the total right padding of the view, including the right * Drawable if any. */
public int getTotalPaddingRight() { return getCompoundPaddingRight(); }
Returns the total start padding of the view, including the start Drawable if any.
/** * Returns the total start padding of the view, including the start * Drawable if any. */
public int getTotalPaddingStart() { return getCompoundPaddingStart(); }
Returns the total end padding of the view, including the end Drawable if any.
/** * Returns the total end padding of the view, including the end * Drawable if any. */
public int getTotalPaddingEnd() { return getCompoundPaddingEnd(); }
Returns the total top padding of the view, including the top Drawable if any, the extra space to keep more than maxLines from showing, and the vertical offset for gravity, if any.
/** * Returns the total top padding of the view, including the top * Drawable if any, the extra space to keep more than maxLines * from showing, and the vertical offset for gravity, if any. */
public int getTotalPaddingTop() { return getExtendedPaddingTop() + getVerticalOffset(true); }
Returns the total bottom padding of the view, including the bottom Drawable if any, the extra space to keep more than maxLines from showing, and the vertical offset for gravity, if any.
/** * Returns the total bottom padding of the view, including the bottom * Drawable if any, the extra space to keep more than maxLines * from showing, and the vertical offset for gravity, if any. */
public int getTotalPaddingBottom() { return getExtendedPaddingBottom() + getBottomVerticalOffset(true); }
Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the text. Use null if you do not want a Drawable there. The Drawables must already have had Drawable.setBounds called.

Calling this method will overwrite any Drawables previously set using setCompoundDrawablesRelative or related methods.

@attrref android.R.styleable#TextView_drawableLeft
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableRight
@attrref android.R.styleable#TextView_drawableBottom
/** * Sets the Drawables (if any) to appear to the left of, above, to the * right of, and below the text. Use {@code null} if you do not want a * Drawable there. The Drawables must already have had * {@link Drawable#setBounds} called. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawablesRelative} or related methods. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */
public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { Drawables dr = mDrawables; // We're switching to absolute, discard relative. if (dr != null) { if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); dr.mDrawableStart = null; if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); dr.mDrawableEnd = null; dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; } final boolean drawables = left != null || top != null || right != null || bottom != null; if (!drawables) { // Clearing drawables... can we free the data structure? if (dr != null) { if (!dr.hasMetadata()) { mDrawables = null; } else { // We need to retain the last set padding, so just clear // out all of the fields in the existing structure. for (int i = dr.mShowing.length - 1; i >= 0; i--) { if (dr.mShowing[i] != null) { dr.mShowing[i].setCallback(null); } dr.mShowing[i] = null; } dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; } } } else { if (dr == null) { mDrawables = dr = new Drawables(getContext()); } mDrawables.mOverride = false; if (dr.mShowing[Drawables.LEFT] != left && dr.mShowing[Drawables.LEFT] != null) { dr.mShowing[Drawables.LEFT].setCallback(null); } dr.mShowing[Drawables.LEFT] = left; if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { dr.mShowing[Drawables.TOP].setCallback(null); } dr.mShowing[Drawables.TOP] = top; if (dr.mShowing[Drawables.RIGHT] != right && dr.mShowing[Drawables.RIGHT] != null) { dr.mShowing[Drawables.RIGHT].setCallback(null); } dr.mShowing[Drawables.RIGHT] = right; if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { dr.mShowing[Drawables.BOTTOM].setCallback(null); } dr.mShowing[Drawables.BOTTOM] = bottom; final Rect compoundRect = dr.mCompoundRect; int[] state; state = getDrawableState(); if (left != null) { left.setState(state); left.copyBounds(compoundRect); left.setCallback(this); dr.mDrawableSizeLeft = compoundRect.width(); dr.mDrawableHeightLeft = compoundRect.height(); } else { dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; } if (right != null) { right.setState(state); right.copyBounds(compoundRect); right.setCallback(this); dr.mDrawableSizeRight = compoundRect.width(); dr.mDrawableHeightRight = compoundRect.height(); } else { dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; } if (top != null) { top.setState(state); top.copyBounds(compoundRect); top.setCallback(this); dr.mDrawableSizeTop = compoundRect.height(); dr.mDrawableWidthTop = compoundRect.width(); } else { dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; } if (bottom != null) { bottom.setState(state); bottom.copyBounds(compoundRect); bottom.setCallback(this); dr.mDrawableSizeBottom = compoundRect.height(); dr.mDrawableWidthBottom = compoundRect.width(); } else { dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; } } // Save initial left/right drawables if (dr != null) { dr.mDrawableLeftInitial = left; dr.mDrawableRightInitial = right; } resetResolvedDrawables(); resolveDrawables(); applyCompoundDrawableTint(); invalidate(); requestLayout(); }
Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the text. Use 0 if you do not want a Drawable there. The Drawables' bounds will be set to their intrinsic bounds.

Calling this method will overwrite any Drawables previously set using setCompoundDrawablesRelative or related methods.

Params:
  • left – Resource identifier of the left Drawable.
  • top – Resource identifier of the top Drawable.
  • right – Resource identifier of the right Drawable.
  • bottom – Resource identifier of the bottom Drawable.
@attrref android.R.styleable#TextView_drawableLeft
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableRight
@attrref android.R.styleable#TextView_drawableBottom
/** * Sets the Drawables (if any) to appear to the left of, above, to the * right of, and below the text. Use 0 if you do not want a Drawable there. * The Drawables' bounds will be set to their intrinsic bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawablesRelative} or related methods. * * @param left Resource identifier of the left Drawable. * @param top Resource identifier of the top Drawable. * @param right Resource identifier of the right Drawable. * @param bottom Resource identifier of the bottom Drawable. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */
@android.view.RemotableViewMethod public void setCompoundDrawablesWithIntrinsicBounds(@DrawableRes int left, @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { final Context context = getContext(); setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, top != 0 ? context.getDrawable(top) : null, right != 0 ? context.getDrawable(right) : null, bottom != 0 ? context.getDrawable(bottom) : null); }
Sets the Drawables (if any) to appear to the left of, above, to the right of, and below the text. Use null if you do not want a Drawable there. The Drawables' bounds will be set to their intrinsic bounds.

Calling this method will overwrite any Drawables previously set using setCompoundDrawablesRelative or related methods.

@attrref android.R.styleable#TextView_drawableLeft
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableRight
@attrref android.R.styleable#TextView_drawableBottom
/** * Sets the Drawables (if any) to appear to the left of, above, to the * right of, and below the text. Use {@code null} if you do not want a * Drawable there. The Drawables' bounds will be set to their intrinsic * bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawablesRelative} or related methods. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */
@android.view.RemotableViewMethod public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { if (left != null) { left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); } if (right != null) { right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); } if (top != null) { top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); } if (bottom != null) { bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); } setCompoundDrawables(left, top, right, bottom); }
Sets the Drawables (if any) to appear to the start of, above, to the end of, and below the text. Use null if you do not want a Drawable there. The Drawables must already have had Drawable.setBounds called.

Calling this method will overwrite any Drawables previously set using setCompoundDrawables or related methods.

@attrref android.R.styleable#TextView_drawableStart
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableEnd
@attrref android.R.styleable#TextView_drawableBottom
/** * Sets the Drawables (if any) to appear to the start of, above, to the end * of, and below the text. Use {@code null} if you do not want a Drawable * there. The Drawables must already have had {@link Drawable#setBounds} * called. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawables} or related methods. * * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */
@android.view.RemotableViewMethod public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { Drawables dr = mDrawables; // We're switching to relative, discard absolute. if (dr != null) { if (dr.mShowing[Drawables.LEFT] != null) { dr.mShowing[Drawables.LEFT].setCallback(null); } dr.mShowing[Drawables.LEFT] = dr.mDrawableLeftInitial = null; if (dr.mShowing[Drawables.RIGHT] != null) { dr.mShowing[Drawables.RIGHT].setCallback(null); } dr.mShowing[Drawables.RIGHT] = dr.mDrawableRightInitial = null; dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; } final boolean drawables = start != null || top != null || end != null || bottom != null; if (!drawables) { // Clearing drawables... can we free the data structure? if (dr != null) { if (!dr.hasMetadata()) { mDrawables = null; } else { // We need to retain the last set padding, so just clear // out all of the fields in the existing structure. if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); dr.mDrawableStart = null; if (dr.mShowing[Drawables.TOP] != null) { dr.mShowing[Drawables.TOP].setCallback(null); } dr.mShowing[Drawables.TOP] = null; if (dr.mDrawableEnd != null) { dr.mDrawableEnd.setCallback(null); } dr.mDrawableEnd = null; if (dr.mShowing[Drawables.BOTTOM] != null) { dr.mShowing[Drawables.BOTTOM].setCallback(null); } dr.mShowing[Drawables.BOTTOM] = null; dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; } } } else { if (dr == null) { mDrawables = dr = new Drawables(getContext()); } mDrawables.mOverride = true; if (dr.mDrawableStart != start && dr.mDrawableStart != null) { dr.mDrawableStart.setCallback(null); } dr.mDrawableStart = start; if (dr.mShowing[Drawables.TOP] != top && dr.mShowing[Drawables.TOP] != null) { dr.mShowing[Drawables.TOP].setCallback(null); } dr.mShowing[Drawables.TOP] = top; if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { dr.mDrawableEnd.setCallback(null); } dr.mDrawableEnd = end; if (dr.mShowing[Drawables.BOTTOM] != bottom && dr.mShowing[Drawables.BOTTOM] != null) { dr.mShowing[Drawables.BOTTOM].setCallback(null); } dr.mShowing[Drawables.BOTTOM] = bottom; final Rect compoundRect = dr.mCompoundRect; int[] state; state = getDrawableState(); if (start != null) { start.setState(state); start.copyBounds(compoundRect); start.setCallback(this); dr.mDrawableSizeStart = compoundRect.width(); dr.mDrawableHeightStart = compoundRect.height(); } else { dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; } if (end != null) { end.setState(state); end.copyBounds(compoundRect); end.setCallback(this); dr.mDrawableSizeEnd = compoundRect.width(); dr.mDrawableHeightEnd = compoundRect.height(); } else { dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; } if (top != null) { top.setState(state); top.copyBounds(compoundRect); top.setCallback(this); dr.mDrawableSizeTop = compoundRect.height(); dr.mDrawableWidthTop = compoundRect.width(); } else { dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; } if (bottom != null) { bottom.setState(state); bottom.copyBounds(compoundRect); bottom.setCallback(this); dr.mDrawableSizeBottom = compoundRect.height(); dr.mDrawableWidthBottom = compoundRect.width(); } else { dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; } } resetResolvedDrawables(); resolveDrawables(); invalidate(); requestLayout(); }
Sets the Drawables (if any) to appear to the start of, above, to the end of, and below the text. Use 0 if you do not want a Drawable there. The Drawables' bounds will be set to their intrinsic bounds.

Calling this method will overwrite any Drawables previously set using setCompoundDrawables or related methods.

Params:
  • start – Resource identifier of the start Drawable.
  • top – Resource identifier of the top Drawable.
  • end – Resource identifier of the end Drawable.
  • bottom – Resource identifier of the bottom Drawable.
@attrref android.R.styleable#TextView_drawableStart
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableEnd
@attrref android.R.styleable#TextView_drawableBottom
/** * Sets the Drawables (if any) to appear to the start of, above, to the end * of, and below the text. Use 0 if you do not want a Drawable there. The * Drawables' bounds will be set to their intrinsic bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawables} or related methods. * * @param start Resource identifier of the start Drawable. * @param top Resource identifier of the top Drawable. * @param end Resource identifier of the end Drawable. * @param bottom Resource identifier of the bottom Drawable. * * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */
@android.view.RemotableViewMethod public void setCompoundDrawablesRelativeWithIntrinsicBounds(@DrawableRes int start, @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { final Context context = getContext(); setCompoundDrawablesRelativeWithIntrinsicBounds( start != 0 ? context.getDrawable(start) : null, top != 0 ? context.getDrawable(top) : null, end != 0 ? context.getDrawable(end) : null, bottom != 0 ? context.getDrawable(bottom) : null); }
Sets the Drawables (if any) to appear to the start of, above, to the end of, and below the text. Use null if you do not want a Drawable there. The Drawables' bounds will be set to their intrinsic bounds.

Calling this method will overwrite any Drawables previously set using setCompoundDrawables or related methods.

@attrref android.R.styleable#TextView_drawableStart
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableEnd
@attrref android.R.styleable#TextView_drawableBottom
/** * Sets the Drawables (if any) to appear to the start of, above, to the end * of, and below the text. Use {@code null} if you do not want a Drawable * there. The Drawables' bounds will be set to their intrinsic bounds. * <p> * Calling this method will overwrite any Drawables previously set using * {@link #setCompoundDrawables} or related methods. * * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */
@android.view.RemotableViewMethod public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { if (start != null) { start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); } if (end != null) { end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); } if (top != null) { top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); } if (bottom != null) { bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); } setCompoundDrawablesRelative(start, top, end, bottom); }
Returns drawables for the left, top, right, and bottom borders.
@attrref android.R.styleable#TextView_drawableLeft
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableRight
@attrref android.R.styleable#TextView_drawableBottom
/** * Returns drawables for the left, top, right, and bottom borders. * * @attr ref android.R.styleable#TextView_drawableLeft * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableRight * @attr ref android.R.styleable#TextView_drawableBottom */
@NonNull public Drawable[] getCompoundDrawables() { final Drawables dr = mDrawables; if (dr != null) { return dr.mShowing.clone(); } else { return new Drawable[] { null, null, null, null }; } }
Returns drawables for the start, top, end, and bottom borders.
@attrref android.R.styleable#TextView_drawableStart
@attrref android.R.styleable#TextView_drawableTop
@attrref android.R.styleable#TextView_drawableEnd
@attrref android.R.styleable#TextView_drawableBottom
/** * Returns drawables for the start, top, end, and bottom borders. * * @attr ref android.R.styleable#TextView_drawableStart * @attr ref android.R.styleable#TextView_drawableTop * @attr ref android.R.styleable#TextView_drawableEnd * @attr ref android.R.styleable#TextView_drawableBottom */
@NonNull public Drawable[] getCompoundDrawablesRelative() { final Drawables dr = mDrawables; if (dr != null) { return new Drawable[] { dr.mDrawableStart, dr.mShowing[Drawables.TOP], dr.mDrawableEnd, dr.mShowing[Drawables.BOTTOM] }; } else { return new Drawable[] { null, null, null, null }; } }
Sets the size of the padding between the compound drawables and the text.
@attrref android.R.styleable#TextView_drawablePadding
/** * Sets the size of the padding between the compound drawables and * the text. * * @attr ref android.R.styleable#TextView_drawablePadding */
@android.view.RemotableViewMethod public void setCompoundDrawablePadding(int pad) { Drawables dr = mDrawables; if (pad == 0) { if (dr != null) { dr.mDrawablePadding = pad; } } else { if (dr == null) { mDrawables = dr = new Drawables(getContext()); } dr.mDrawablePadding = pad; } invalidate(); requestLayout(); }
Returns the padding between the compound drawables and the text.
@attrref android.R.styleable#TextView_drawablePadding
/** * Returns the padding between the compound drawables and the text. * * @attr ref android.R.styleable#TextView_drawablePadding */
public int getCompoundDrawablePadding() { final Drawables dr = mDrawables; return dr != null ? dr.mDrawablePadding : 0; }
Applies a tint to the compound drawables. Does not modify the current tint mode, which is Mode.SRC_IN by default.

Subsequent calls to setCompoundDrawables(Drawable, Drawable, Drawable, Drawable) and related methods will automatically mutate the drawables and apply the specified tint and tint mode using Drawable.setTintList(ColorStateList).

Params:
  • tint – the tint to apply, may be null to clear tint
See Also:
@attrref android.R.styleable#TextView_drawableTint
/** * Applies a tint to the compound drawables. Does not modify the * current tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default. * <p> * Subsequent calls to * {@link #setCompoundDrawables(Drawable, Drawable, Drawable, Drawable)} * and related methods will automatically mutate the drawables and apply * the specified tint and tint mode using * {@link Drawable#setTintList(ColorStateList)}. * * @param tint the tint to apply, may be {@code null} to clear tint * * @attr ref android.R.styleable#TextView_drawableTint * @see #getCompoundDrawableTintList() * @see Drawable#setTintList(ColorStateList) */
public void setCompoundDrawableTintList(@Nullable ColorStateList tint) { if (mDrawables == null) { mDrawables = new Drawables(getContext()); } mDrawables.mTintList = tint; mDrawables.mHasTint = true; applyCompoundDrawableTint(); }
See Also:
Returns:the tint applied to the compound drawables
@attrref android.R.styleable#TextView_drawableTint
/** * @return the tint applied to the compound drawables * @attr ref android.R.styleable#TextView_drawableTint * @see #setCompoundDrawableTintList(ColorStateList) */
public ColorStateList getCompoundDrawableTintList() { return mDrawables != null ? mDrawables.mTintList : null; }
Specifies the blending mode used to apply the tint specified by setCompoundDrawableTintList(ColorStateList) to the compound drawables. The default mode is Mode.SRC_IN.
Params:
  • tintMode – the blending mode used to apply the tint, may be null to clear tint
See Also:
@attrref android.R.styleable#TextView_drawableTintMode
/** * Specifies the blending mode used to apply the tint specified by * {@link #setCompoundDrawableTintList(ColorStateList)} to the compound * drawables. The default mode is {@link PorterDuff.Mode#SRC_IN}. * * @param tintMode the blending mode used to apply the tint, may be * {@code null} to clear tint * @attr ref android.R.styleable#TextView_drawableTintMode * @see #setCompoundDrawableTintList(ColorStateList) * @see Drawable#setTintMode(PorterDuff.Mode) */
public void setCompoundDrawableTintMode(@Nullable PorterDuff.Mode tintMode) { if (mDrawables == null) { mDrawables = new Drawables(getContext()); } mDrawables.mTintMode = tintMode; mDrawables.mHasTintMode = true; applyCompoundDrawableTint(); }
Returns the blending mode used to apply the tint to the compound drawables, if specified.
See Also:
Returns:the blending mode used to apply the tint to the compound drawables
@attrref android.R.styleable#TextView_drawableTintMode
/** * Returns the blending mode used to apply the tint to the compound * drawables, if specified. * * @return the blending mode used to apply the tint to the compound * drawables * @attr ref android.R.styleable#TextView_drawableTintMode * @see #setCompoundDrawableTintMode(PorterDuff.Mode) */
public PorterDuff.Mode getCompoundDrawableTintMode() { return mDrawables != null ? mDrawables.mTintMode : null; } private void applyCompoundDrawableTint() { if (mDrawables == null) { return; } if (mDrawables.mHasTint || mDrawables.mHasTintMode) { final ColorStateList tintList = mDrawables.mTintList; final PorterDuff.Mode tintMode = mDrawables.mTintMode; final boolean hasTint = mDrawables.mHasTint; final boolean hasTintMode = mDrawables.mHasTintMode; final int[] state = getDrawableState(); for (Drawable dr : mDrawables.mShowing) { if (dr == null) { continue; } if (dr == mDrawables.mDrawableError) { // From a developer's perspective, the error drawable isn't // a compound drawable. Don't apply the generic compound // drawable tint to it. continue; } dr.mutate(); if (hasTint) { dr.setTintList(tintList); } if (hasTintMode) { dr.setTintMode(tintMode); } // The drawable (or one of its children) may not have been // stateful before applying the tint, so let's try again. if (dr.isStateful()) { dr.setState(state); } } } }
See Also:
@inheritDoc
/** * @inheritDoc * * @see #setFirstBaselineToTopHeight(int) * @see #setLastBaselineToBottomHeight(int) */
@Override public void setPadding(int left, int top, int right, int bottom) { if (left != mPaddingLeft || right != mPaddingRight || top != mPaddingTop || bottom != mPaddingBottom) { nullLayouts(); } // the super call will requestLayout() super.setPadding(left, top, right, bottom); invalidate(); }
See Also:
@inheritDoc
/** * @inheritDoc * * @see #setFirstBaselineToTopHeight(int) * @see #setLastBaselineToBottomHeight(int) */
@Override public void setPaddingRelative(int start, int top, int end, int bottom) { if (start != getPaddingStart() || end != getPaddingEnd() || top != mPaddingTop || bottom != mPaddingBottom) { nullLayouts(); } // the super call will requestLayout() super.setPaddingRelative(start, top, end, bottom); invalidate(); }
Updates the top padding of the TextView so that firstBaselineToTopHeight is equal to the distance between the firt text baseline and the top of this TextView. Note that if FontMetrics.top or FontMetrics.ascent was already greater than firstBaselineToTopHeight, the top padding is not updated.
Params:
  • firstBaselineToTopHeight – distance between first baseline to top of the container in pixels
See Also:
@attrref android.R.styleable#TextView_firstBaselineToTopHeight
/** * Updates the top padding of the TextView so that {@code firstBaselineToTopHeight} is * equal to the distance between the firt text baseline and the top of this TextView. * <strong>Note</strong> that if {@code FontMetrics.top} or {@code FontMetrics.ascent} was * already greater than {@code firstBaselineToTopHeight}, the top padding is not updated. * * @param firstBaselineToTopHeight distance between first baseline to top of the container * in pixels * * @see #getFirstBaselineToTopHeight() * @see #setPadding(int, int, int, int) * @see #setPaddingRelative(int, int, int, int) * * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight */
public void setFirstBaselineToTopHeight(@Px @IntRange(from = 0) int firstBaselineToTopHeight) { Preconditions.checkArgumentNonnegative(firstBaselineToTopHeight); final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); final int fontMetricsTop; if (getIncludeFontPadding()) { fontMetricsTop = fontMetrics.top; } else { fontMetricsTop = fontMetrics.ascent; } // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size // in settings). At the moment, we don't. if (firstBaselineToTopHeight > Math.abs(fontMetricsTop)) { final int paddingTop = firstBaselineToTopHeight - (-fontMetricsTop); setPadding(getPaddingLeft(), paddingTop, getPaddingRight(), getPaddingBottom()); } }
Updates the bottom padding of the TextView so that lastBaselineToBottomHeight is equal to the distance between the last text baseline and the bottom of this TextView. Note that if FontMetrics.bottom or FontMetrics.descent was already greater than lastBaselineToBottomHeight, the bottom padding is not updated.
Params:
  • lastBaselineToBottomHeight – distance between last baseline to bottom of the container in pixels
See Also:
@attrref android.R.styleable#TextView_lastBaselineToBottomHeight
/** * Updates the bottom padding of the TextView so that {@code lastBaselineToBottomHeight} is * equal to the distance between the last text baseline and the bottom of this TextView. * <strong>Note</strong> that if {@code FontMetrics.bottom} or {@code FontMetrics.descent} was * already greater than {@code lastBaselineToBottomHeight}, the bottom padding is not updated. * * @param lastBaselineToBottomHeight distance between last baseline to bottom of the container * in pixels * * @see #getLastBaselineToBottomHeight() * @see #setPadding(int, int, int, int) * @see #setPaddingRelative(int, int, int, int) * * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight */
public void setLastBaselineToBottomHeight( @Px @IntRange(from = 0) int lastBaselineToBottomHeight) { Preconditions.checkArgumentNonnegative(lastBaselineToBottomHeight); final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt(); final int fontMetricsBottom; if (getIncludeFontPadding()) { fontMetricsBottom = fontMetrics.bottom; } else { fontMetricsBottom = fontMetrics.descent; } // TODO: Decide if we want to ignore density ratio (i.e. when the user changes font size // in settings). At the moment, we don't. if (lastBaselineToBottomHeight > Math.abs(fontMetricsBottom)) { final int paddingBottom = lastBaselineToBottomHeight - fontMetricsBottom; setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), paddingBottom); } }
Returns the distance between the first text baseline and the top of this TextView.
See Also:
  • setFirstBaselineToTopHeight(int)
@attrref android.R.styleable#TextView_firstBaselineToTopHeight
/** * Returns the distance between the first text baseline and the top of this TextView. * * @see #setFirstBaselineToTopHeight(int) * @attr ref android.R.styleable#TextView_firstBaselineToTopHeight */
public int getFirstBaselineToTopHeight() { return getPaddingTop() - getPaint().getFontMetricsInt().top; }
Returns the distance between the last text baseline and the bottom of this TextView.
See Also:
  • setLastBaselineToBottomHeight(int)
@attrref android.R.styleable#TextView_lastBaselineToBottomHeight
/** * Returns the distance between the last text baseline and the bottom of this TextView. * * @see #setLastBaselineToBottomHeight(int) * @attr ref android.R.styleable#TextView_lastBaselineToBottomHeight */
public int getLastBaselineToBottomHeight() { return getPaddingBottom() + getPaint().getFontMetricsInt().bottom; }
Gets the autolink mask of the text. See Linkify.ALL and peers for possible values.
@attrref android.R.styleable#TextView_autoLink
/** * Gets the autolink mask of the text. See {@link * android.text.util.Linkify#ALL Linkify.ALL} and peers for * possible values. * * @attr ref android.R.styleable#TextView_autoLink */
public final int getAutoLinkMask() { return mAutoLinkMask; }
Sets the text appearance from the specified style resource.

Use a framework-defined TextAppearance style like @android:style/TextAppearance.Material.Body1 or see TextAppearance for the set of attributes that can be used in a custom style.

Params:
  • resId – the resource identifier of the style to apply
@attrref android.R.styleable#TextView_textAppearance
/** * Sets the text appearance from the specified style resource. * <p> * Use a framework-defined {@code TextAppearance} style like * {@link android.R.style#TextAppearance_Material_Body1 @android:style/TextAppearance.Material.Body1} * or see {@link android.R.styleable#TextAppearance TextAppearance} for the * set of attributes that can be used in a custom style. * * @param resId the resource identifier of the style to apply * @attr ref android.R.styleable#TextView_textAppearance */
@SuppressWarnings("deprecation") public void setTextAppearance(@StyleRes int resId) { setTextAppearance(mContext, resId); }
Sets the text color, size, style, hint color, and highlight color from the specified TextAppearance resource.
Deprecated:Use setTextAppearance(int) instead.
/** * Sets the text color, size, style, hint color, and highlight color * from the specified TextAppearance resource. * * @deprecated Use {@link #setTextAppearance(int)} instead. */
@Deprecated public void setTextAppearance(Context context, @StyleRes int resId) { final TypedArray ta = context.obtainStyledAttributes(resId, R.styleable.TextAppearance); final TextAppearanceAttributes attributes = new TextAppearanceAttributes(); readTextAppearance(context, ta, attributes, false /* styleArray */); ta.recycle(); applyTextAppearance(attributes); }
Set of attributes that can be defined in a Text Appearance. This is used to simplify the code that reads these attributes in the constructor and in setTextAppearance.
/** * Set of attributes that can be defined in a Text Appearance. This is used to simplify the code * that reads these attributes in the constructor and in {@link #setTextAppearance}. */
private static class TextAppearanceAttributes { int mTextColorHighlight = 0; ColorStateList mTextColor = null; ColorStateList mTextColorHint = null; ColorStateList mTextColorLink = null; int mTextSize = 0; String mFontFamily = null; Typeface mFontTypeface = null; boolean mFontFamilyExplicit = false; int mTypefaceIndex = -1; int mStyleIndex = -1; int mFontWeight = -1; boolean mAllCaps = false; int mShadowColor = 0; float mShadowDx = 0, mShadowDy = 0, mShadowRadius = 0; boolean mHasElegant = false; boolean mElegant = false; boolean mHasFallbackLineSpacing = false; boolean mFallbackLineSpacing = false; boolean mHasLetterSpacing = false; float mLetterSpacing = 0; String mFontFeatureSettings = null; @Override public String toString() { return "TextAppearanceAttributes {\n" + " mTextColorHighlight:" + mTextColorHighlight + "\n" + " mTextColor:" + mTextColor + "\n" + " mTextColorHint:" + mTextColorHint + "\n" + " mTextColorLink:" + mTextColorLink + "\n" + " mTextSize:" + mTextSize + "\n" + " mFontFamily:" + mFontFamily + "\n" + " mFontTypeface:" + mFontTypeface + "\n" + " mFontFamilyExplicit:" + mFontFamilyExplicit + "\n" + " mTypefaceIndex:" + mTypefaceIndex + "\n" + " mStyleIndex:" + mStyleIndex + "\n" + " mFontWeight:" + mFontWeight + "\n" + " mAllCaps:" + mAllCaps + "\n" + " mShadowColor:" + mShadowColor + "\n" + " mShadowDx:" + mShadowDx + "\n" + " mShadowDy:" + mShadowDy + "\n" + " mShadowRadius:" + mShadowRadius + "\n" + " mHasElegant:" + mHasElegant + "\n" + " mElegant:" + mElegant + "\n" + " mHasFallbackLineSpacing:" + mHasFallbackLineSpacing + "\n" + " mFallbackLineSpacing:" + mFallbackLineSpacing + "\n" + " mHasLetterSpacing:" + mHasLetterSpacing + "\n" + " mLetterSpacing:" + mLetterSpacing + "\n" + " mFontFeatureSettings:" + mFontFeatureSettings + "\n" + "}"; } } // Maps styleable attributes that exist both in TextView style and TextAppearance. private static final SparseIntArray sAppearanceValues = new SparseIntArray(); static { sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHighlight, com.android.internal.R.styleable.TextAppearance_textColorHighlight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColor, com.android.internal.R.styleable.TextAppearance_textColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorHint, com.android.internal.R.styleable.TextAppearance_textColorHint); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textColorLink, com.android.internal.R.styleable.TextAppearance_textColorLink); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textSize, com.android.internal.R.styleable.TextAppearance_textSize); sAppearanceValues.put(com.android.internal.R.styleable.TextView_typeface, com.android.internal.R.styleable.TextAppearance_typeface); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFamily, com.android.internal.R.styleable.TextAppearance_fontFamily); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textStyle, com.android.internal.R.styleable.TextAppearance_textStyle); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textFontWeight, com.android.internal.R.styleable.TextAppearance_textFontWeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_textAllCaps, com.android.internal.R.styleable.TextAppearance_textAllCaps); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowColor, com.android.internal.R.styleable.TextAppearance_shadowColor); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDx, com.android.internal.R.styleable.TextAppearance_shadowDx); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowDy, com.android.internal.R.styleable.TextAppearance_shadowDy); sAppearanceValues.put(com.android.internal.R.styleable.TextView_shadowRadius, com.android.internal.R.styleable.TextAppearance_shadowRadius); sAppearanceValues.put(com.android.internal.R.styleable.TextView_elegantTextHeight, com.android.internal.R.styleable.TextAppearance_elegantTextHeight); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fallbackLineSpacing, com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_letterSpacing, com.android.internal.R.styleable.TextAppearance_letterSpacing); sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontFeatureSettings, com.android.internal.R.styleable.TextAppearance_fontFeatureSettings); }
Read the Text Appearance attributes from a given TypedArray and set its values to the given set. If the TypedArray contains a value that was already set in the given attributes, that will be overridden.
Params:
  • context – The Context to be used
  • appearance – The TypedArray to read properties from
  • attributes – the TextAppearanceAttributes to fill in
  • styleArray – Whether the given TypedArray is a style or a TextAppearance. This defines what attribute indexes will be used to read the properties.
/** * Read the Text Appearance attributes from a given TypedArray and set its values to the given * set. If the TypedArray contains a value that was already set in the given attributes, that * will be overridden. * * @param context The Context to be used * @param appearance The TypedArray to read properties from * @param attributes the TextAppearanceAttributes to fill in * @param styleArray Whether the given TypedArray is a style or a TextAppearance. This defines * what attribute indexes will be used to read the properties. */
private void readTextAppearance(Context context, TypedArray appearance, TextAppearanceAttributes attributes, boolean styleArray) { final int n = appearance.getIndexCount(); for (int i = 0; i < n; i++) { final int attr = appearance.getIndex(i); int index = attr; // Translate style array index ids to TextAppearance ids. if (styleArray) { index = sAppearanceValues.get(attr, -1); if (index == -1) { // This value is not part of a Text Appearance and should be ignored. continue; } } switch (index) { case com.android.internal.R.styleable.TextAppearance_textColorHighlight: attributes.mTextColorHighlight = appearance.getColor(attr, attributes.mTextColorHighlight); break; case com.android.internal.R.styleable.TextAppearance_textColor: attributes.mTextColor = appearance.getColorStateList(attr); break; case com.android.internal.R.styleable.TextAppearance_textColorHint: attributes.mTextColorHint = appearance.getColorStateList(attr); break; case com.android.internal.R.styleable.TextAppearance_textColorLink: attributes.mTextColorLink = appearance.getColorStateList(attr); break; case com.android.internal.R.styleable.TextAppearance_textSize: attributes.mTextSize = appearance.getDimensionPixelSize(attr, attributes.mTextSize); break; case com.android.internal.R.styleable.TextAppearance_typeface: attributes.mTypefaceIndex = appearance.getInt(attr, attributes.mTypefaceIndex); if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { attributes.mFontFamily = null; } break; case com.android.internal.R.styleable.TextAppearance_fontFamily: if (!context.isRestricted() && context.canLoadUnsafeResources()) { try { attributes.mFontTypeface = appearance.getFont(attr); } catch (UnsupportedOperationException | Resources.NotFoundException e) { // Expected if it is not a font resource. } } if (attributes.mFontTypeface == null) { attributes.mFontFamily = appearance.getString(attr); } attributes.mFontFamilyExplicit = true; break; case com.android.internal.R.styleable.TextAppearance_textStyle: attributes.mStyleIndex = appearance.getInt(attr, attributes.mStyleIndex); break; case com.android.internal.R.styleable.TextAppearance_textFontWeight: attributes.mFontWeight = appearance.getInt(attr, attributes.mFontWeight); break; case com.android.internal.R.styleable.TextAppearance_textAllCaps: attributes.mAllCaps = appearance.getBoolean(attr, attributes.mAllCaps); break; case com.android.internal.R.styleable.TextAppearance_shadowColor: attributes.mShadowColor = appearance.getInt(attr, attributes.mShadowColor); break; case com.android.internal.R.styleable.TextAppearance_shadowDx: attributes.mShadowDx = appearance.getFloat(attr, attributes.mShadowDx); break; case com.android.internal.R.styleable.TextAppearance_shadowDy: attributes.mShadowDy = appearance.getFloat(attr, attributes.mShadowDy); break; case com.android.internal.R.styleable.TextAppearance_shadowRadius: attributes.mShadowRadius = appearance.getFloat(attr, attributes.mShadowRadius); break; case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: attributes.mHasElegant = true; attributes.mElegant = appearance.getBoolean(attr, attributes.mElegant); break; case com.android.internal.R.styleable.TextAppearance_fallbackLineSpacing: attributes.mHasFallbackLineSpacing = true; attributes.mFallbackLineSpacing = appearance.getBoolean(attr, attributes.mFallbackLineSpacing); break; case com.android.internal.R.styleable.TextAppearance_letterSpacing: attributes.mHasLetterSpacing = true; attributes.mLetterSpacing = appearance.getFloat(attr, attributes.mLetterSpacing); break; case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: attributes.mFontFeatureSettings = appearance.getString(attr); break; default: } } } private void applyTextAppearance(TextAppearanceAttributes attributes) { if (attributes.mTextColor != null) { setTextColor(attributes.mTextColor); } if (attributes.mTextColorHint != null) { setHintTextColor(attributes.mTextColorHint); } if (attributes.mTextColorLink != null) { setLinkTextColor(attributes.mTextColorLink); } if (attributes.mTextColorHighlight != 0) { setHighlightColor(attributes.mTextColorHighlight); } if (attributes.mTextSize != 0) { setRawTextSize(attributes.mTextSize, true /* shouldRequestLayout */); } if (attributes.mTypefaceIndex != -1 && !attributes.mFontFamilyExplicit) { attributes.mFontFamily = null; } setTypefaceFromAttrs(attributes.mFontTypeface, attributes.mFontFamily, attributes.mTypefaceIndex, attributes.mStyleIndex, attributes.mFontWeight); if (attributes.mShadowColor != 0) { setShadowLayer(attributes.mShadowRadius, attributes.mShadowDx, attributes.mShadowDy, attributes.mShadowColor); } if (attributes.mAllCaps) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); } if (attributes.mHasElegant) { setElegantTextHeight(attributes.mElegant); } if (attributes.mHasFallbackLineSpacing) { setFallbackLineSpacing(attributes.mFallbackLineSpacing); } if (attributes.mHasLetterSpacing) { setLetterSpacing(attributes.mLetterSpacing); } if (attributes.mFontFeatureSettings != null) { setFontFeatureSettings(attributes.mFontFeatureSettings); } }
Get the default primary Locale of the text in this TextView. This will always be the first member of getTextLocales().
Returns:the default primary Locale of the text in this TextView.
/** * Get the default primary {@link Locale} of the text in this TextView. This will always be * the first member of {@link #getTextLocales()}. * @return the default primary {@link Locale} of the text in this TextView. */
@NonNull public Locale getTextLocale() { return mTextPaint.getTextLocale(); }
Get the default LocaleList of the text in this TextView.
Returns:the default LocaleList of the text in this TextView.
/** * Get the default {@link LocaleList} of the text in this TextView. * @return the default {@link LocaleList} of the text in this TextView. */
@NonNull @Size(min = 1) public LocaleList getTextLocales() { return mTextPaint.getTextLocales(); } private void changeListenerLocaleTo(@Nullable Locale locale) { if (mListenerChanged) { // If a listener has been explicitly set, don't change it. We may break something. return; } // The following null check is not absolutely necessary since all calling points of // changeListenerLocaleTo() guarantee a non-null mEditor at the moment. But this is left // here in case others would want to call this method in the future. if (mEditor != null) { KeyListener listener = mEditor.mKeyListener; if (listener instanceof DigitsKeyListener) { listener = DigitsKeyListener.getInstance(locale, (DigitsKeyListener) listener); } else if (listener instanceof DateKeyListener) { listener = DateKeyListener.getInstance(locale); } else if (listener instanceof TimeKeyListener) { listener = TimeKeyListener.getInstance(locale); } else if (listener instanceof DateTimeKeyListener) { listener = DateTimeKeyListener.getInstance(locale); } else { return; } final boolean wasPasswordType = isPasswordInputType(mEditor.mInputType); setKeyListenerOnly(listener); setInputTypeFromEditor(); if (wasPasswordType) { final int newInputClass = mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS; if (newInputClass == EditorInfo.TYPE_CLASS_TEXT) { mEditor.mInputType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; } else if (newInputClass == EditorInfo.TYPE_CLASS_NUMBER) { mEditor.mInputType |= EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD; } } } }
Set the default Locale of the text in this TextView to a one-member LocaleList containing just the given Locale.
Params:
  • locale – the Locale for drawing text, must not be null.
See Also:
/** * Set the default {@link Locale} of the text in this TextView to a one-member * {@link LocaleList} containing just the given Locale. * * @param locale the {@link Locale} for drawing text, must not be null. * * @see #setTextLocales */
public void setTextLocale(@NonNull Locale locale) { mLocalesChanged = true; mTextPaint.setTextLocale(locale); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } }
Set the default LocaleList of the text in this TextView to the given value. This value is used to choose appropriate typefaces for ambiguous characters (typically used for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects other aspects of text display, including line breaking.
Params:
  • locales – the LocaleList for drawing text, must not be null or empty.
See Also:
/** * Set the default {@link LocaleList} of the text in this TextView to the given value. * * This value is used to choose appropriate typefaces for ambiguous characters (typically used * for CJK locales to disambiguate Hanzi/Kanji/Hanja characters). It also affects * other aspects of text display, including line breaking. * * @param locales the {@link LocaleList} for drawing text, must not be null or empty. * * @see Paint#setTextLocales */
public void setTextLocales(@NonNull @Size(min = 1) LocaleList locales) { mLocalesChanged = true; mTextPaint.setTextLocales(locales); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (!mLocalesChanged) { mTextPaint.setTextLocales(LocaleList.getDefault()); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Returns:the size (in pixels) of the default text size in this TextView.
/** * @return the size (in pixels) of the default text size in this TextView. */
@ViewDebug.ExportedProperty(category = "text") public float getTextSize() { return mTextPaint.getTextSize(); }
Returns:the size (in scaled pixels) of the default text size in this TextView.
@hide
/** * @return the size (in scaled pixels) of the default text size in this TextView. * @hide */
@ViewDebug.ExportedProperty(category = "text") public float getScaledTextSize() { return mTextPaint.getTextSize() / mTextPaint.density; }
@hide
/** @hide */
@ViewDebug.ExportedProperty(category = "text", mapping = { @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") }) public int getTypefaceStyle() { Typeface typeface = mTextPaint.getTypeface(); return typeface != null ? typeface.getStyle() : Typeface.NORMAL; }
Set the default text size to the given value, interpreted as "scaled pixel" units. This size is adjusted based on the current density and user font size preference.

Note: if this TextView has the auto-size feature enabled than this function is no-op.

Params:
  • size – The scaled pixel size.
@attrref android.R.styleable#TextView_textSize
/** * Set the default text size to the given value, interpreted as "scaled * pixel" units. This size is adjusted based on the current density and * user font size preference. * * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. * * @param size The scaled pixel size. * * @attr ref android.R.styleable#TextView_textSize */
@android.view.RemotableViewMethod public void setTextSize(float size) { setTextSize(TypedValue.COMPLEX_UNIT_SP, size); }
Set the default text size to a given unit and value. See TypedValue for the possible dimension units.

Note: if this TextView has the auto-size feature enabled than this function is no-op.

Params:
  • unit – The desired dimension unit.
  • size – The desired size in the given units.
@attrref android.R.styleable#TextView_textSize
/** * Set the default text size to a given unit and value. See {@link * TypedValue} for the possible dimension units. * * <p>Note: if this TextView has the auto-size feature enabled than this function is no-op. * * @param unit The desired dimension unit. * @param size The desired size in the given units. * * @attr ref android.R.styleable#TextView_textSize */
public void setTextSize(int unit, float size) { if (!isAutoSizeEnabled()) { setTextSizeInternal(unit, size, true /* shouldRequestLayout */); } } private void setTextSizeInternal(int unit, float size, boolean shouldRequestLayout) { Context c = getContext(); Resources r; if (c == null) { r = Resources.getSystem(); } else { r = c.getResources(); } setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()), shouldRequestLayout); } private void setRawTextSize(float size, boolean shouldRequestLayout) { if (size != mTextPaint.getTextSize()) { mTextPaint.setTextSize(size); if (shouldRequestLayout && mLayout != null) { // Do not auto-size right after setting the text size. mNeedsAutoSizeText = false; nullLayouts(); requestLayout(); invalidate(); } } }
Gets the extent by which text should be stretched horizontally. This will usually be 1.0.
Returns:The horizontal scale factor.
/** * Gets the extent by which text should be stretched horizontally. * This will usually be 1.0. * @return The horizontal scale factor. */
public float getTextScaleX() { return mTextPaint.getTextScaleX(); }
Sets the horizontal scale factor for text. The default value is 1.0. Values greater than 1.0 stretch the text wider. Values less than 1.0 make the text narrower. By default, this value is 1.0.
Params:
  • size – The horizontal scale factor.
@attrref android.R.styleable#TextView_textScaleX
/** * Sets the horizontal scale factor for text. The default value * is 1.0. Values greater than 1.0 stretch the text wider. * Values less than 1.0 make the text narrower. By default, this value is 1.0. * @param size The horizontal scale factor. * @attr ref android.R.styleable#TextView_textScaleX */
@android.view.RemotableViewMethod public void setTextScaleX(float size) { if (size != mTextPaint.getTextScaleX()) { mUserSetTextScaleX = true; mTextPaint.setTextScaleX(size); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Sets the typeface and style in which the text should be displayed. Note that not all Typeface families actually have bold and italic variants, so you may need to use setTypeface(Typeface, int) to get the appearance that you actually want.
See Also:
@attrref android.R.styleable#TextView_fontFamily
@attrref android.R.styleable#TextView_typeface
@attrref android.R.styleable#TextView_textStyle
/** * Sets the typeface and style in which the text should be displayed. * Note that not all Typeface families actually have bold and italic * variants, so you may need to use * {@link #setTypeface(Typeface, int)} to get the appearance * that you actually want. * * @see #getTypeface() * * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */
public void setTypeface(@Nullable Typeface tf) { if (mTextPaint.getTypeface() != tf) { mTextPaint.setTypeface(tf); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Gets the current Typeface that is used to style the text.
See Also:
Returns:The current Typeface.
@attrref android.R.styleable#TextView_fontFamily
@attrref android.R.styleable#TextView_typeface
@attrref android.R.styleable#TextView_textStyle
/** * Gets the current {@link Typeface} that is used to style the text. * @return The current Typeface. * * @see #setTypeface(Typeface) * * @attr ref android.R.styleable#TextView_fontFamily * @attr ref android.R.styleable#TextView_typeface * @attr ref android.R.styleable#TextView_textStyle */
public Typeface getTypeface() { return mTextPaint.getTypeface(); }
Set the TextView's elegant height metrics flag. This setting selects font variants that have not been compacted to fit Latin-based vertical metrics, and also increases top and bottom bounds to provide more space.
Params:
  • elegant – set the paint's elegant metrics flag.
See Also:
@attrref android.R.styleable#TextView_elegantTextHeight
/** * Set the TextView's elegant height metrics flag. This setting selects font * variants that have not been compacted to fit Latin-based vertical * metrics, and also increases top and bottom bounds to provide more space. * * @param elegant set the paint's elegant metrics flag. * * @see #isElegantTextHeight() * @see Paint#isElegantTextHeight() * * @attr ref android.R.styleable#TextView_elegantTextHeight */
public void setElegantTextHeight(boolean elegant) { if (elegant != mTextPaint.isElegantTextHeight()) { mTextPaint.setElegantTextHeight(elegant); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Set whether to respect the ascent and descent of the fallback fonts that are used in displaying the text (which is needed to avoid text from consecutive lines running into each other). If set, fallback fonts that end up getting used can increase the ascent and descent of the lines that they are used on.

It is required to be true if text could be in languages like Burmese or Tibetan where text is typically much taller or deeper than Latin text.
Params:
  • enabled – whether to expand linespacing based on fallback fonts, true by default
See Also:
@attrref android.R.styleable#TextView_fallbackLineSpacing
/** * Set whether to respect the ascent and descent of the fallback fonts that are used in * displaying the text (which is needed to avoid text from consecutive lines running into * each other). If set, fallback fonts that end up getting used can increase the ascent * and descent of the lines that they are used on. * <p/> * It is required to be true if text could be in languages like Burmese or Tibetan where text * is typically much taller or deeper than Latin text. * * @param enabled whether to expand linespacing based on fallback fonts, {@code true} by default * * @see StaticLayout.Builder#setUseLineSpacingFromFallbacks(boolean) * * @attr ref android.R.styleable#TextView_fallbackLineSpacing */
public void setFallbackLineSpacing(boolean enabled) { if (mUseFallbackLineSpacing != enabled) { mUseFallbackLineSpacing = enabled; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
See Also:
Returns:whether fallback line spacing is enabled, true by default
@attrref android.R.styleable#TextView_fallbackLineSpacing
/** * @return whether fallback line spacing is enabled, {@code true} by default * * @see #setFallbackLineSpacing(boolean) * * @attr ref android.R.styleable#TextView_fallbackLineSpacing */
public boolean isFallbackLineSpacing() { return mUseFallbackLineSpacing; }
Get the value of the TextView's elegant height metrics flag. This setting selects font variants that have not been compacted to fit Latin-based vertical metrics, and also increases top and bottom bounds to provide more space.
See Also:
Returns:true if the elegant height metrics flag is set.
/** * Get the value of the TextView's elegant height metrics flag. This setting selects font * variants that have not been compacted to fit Latin-based vertical * metrics, and also increases top and bottom bounds to provide more space. * @return {@code true} if the elegant height metrics flag is set. * * @see #setElegantTextHeight(boolean) * @see Paint#setElegantTextHeight(boolean) */
public boolean isElegantTextHeight() { return mTextPaint.isElegantTextHeight(); }
Gets the text letter-space value, which determines the spacing between characters. The value returned is in ems. Normally, this value is 0.0.
See Also:
Returns:The text letter-space value in ems.
/** * Gets the text letter-space value, which determines the spacing between characters. * The value returned is in ems. Normally, this value is 0.0. * @return The text letter-space value in ems. * * @see #setLetterSpacing(float) * @see Paint#setLetterSpacing */
public float getLetterSpacing() { return mTextPaint.getLetterSpacing(); }
Sets text letter-spacing in em units. Typical values for slight expansion will be around 0.05. Negative values tighten text.
Params:
  • letterSpacing – A text letter-space value in ems.
See Also:
@attrref android.R.styleable#TextView_letterSpacing
/** * Sets text letter-spacing in em units. Typical values * for slight expansion will be around 0.05. Negative values tighten text. * * @see #getLetterSpacing() * @see Paint#getLetterSpacing * * @param letterSpacing A text letter-space value in ems. * @attr ref android.R.styleable#TextView_letterSpacing */
@android.view.RemotableViewMethod public void setLetterSpacing(float letterSpacing) { if (letterSpacing != mTextPaint.getLetterSpacing()) { mTextPaint.setLetterSpacing(letterSpacing); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Returns the font feature settings. The format is the same as the CSS font-feature-settings attribute: https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
See Also:
Returns:the currently set font feature settings. Default is null.
/** * Returns the font feature settings. The format is the same as the CSS * font-feature-settings attribute: * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> * * @return the currently set font feature settings. Default is null. * * @see #setFontFeatureSettings(String) * @see Paint#setFontFeatureSettings(String) Paint.setFontFeatureSettings(String) */
@Nullable public String getFontFeatureSettings() { return mTextPaint.getFontFeatureSettings(); }
Returns the font variation settings.
See Also:
Returns:the currently set font variation settings. Returns null if no variation is specified.
/** * Returns the font variation settings. * * @return the currently set font variation settings. Returns null if no variation is * specified. * * @see #setFontVariationSettings(String) * @see Paint#setFontVariationSettings(String) Paint.setFontVariationSettings(String) */
@Nullable public String getFontVariationSettings() { return mTextPaint.getFontVariationSettings(); }
Sets the break strategy for breaking paragraphs into lines. The default value for TextView is Layout.BREAK_STRATEGY_HIGH_QUALITY, and the default value for EditText is Layout.BREAK_STRATEGY_SIMPLE, the latter to avoid the text "dancing" when being edited.
See Also:
@attrref android.R.styleable#TextView_breakStrategy
/** * Sets the break strategy for breaking paragraphs into lines. The default value for * TextView is {@link Layout#BREAK_STRATEGY_HIGH_QUALITY}, and the default value for * EditText is {@link Layout#BREAK_STRATEGY_SIMPLE}, the latter to avoid the * text "dancing" when being edited. * * @attr ref android.R.styleable#TextView_breakStrategy * @see #getBreakStrategy() */
public void setBreakStrategy(@Layout.BreakStrategy int breakStrategy) { mBreakStrategy = breakStrategy; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } }
Gets the current strategy for breaking paragraphs into lines.
See Also:
Returns:the current strategy for breaking paragraphs into lines.
@attrref android.R.styleable#TextView_breakStrategy
/** * Gets the current strategy for breaking paragraphs into lines. * @return the current strategy for breaking paragraphs into lines. * * @attr ref android.R.styleable#TextView_breakStrategy * @see #setBreakStrategy(int) */
@Layout.BreakStrategy public int getBreakStrategy() { return mBreakStrategy; }
Sets the frequency of automatic hyphenation to use when determining word breaks. The default value for both TextView and EditText is Layout.HYPHENATION_FREQUENCY_NORMAL. Note that the default hyphenation frequency value is set from the theme.
Params:
  • hyphenationFrequency – The hyphenation frequency to use.
See Also:
@attrref android.R.styleable#TextView_hyphenationFrequency
/** * Sets the frequency of automatic hyphenation to use when determining word breaks. * The default value for both TextView and {@link EditText} is * {@link Layout#HYPHENATION_FREQUENCY_NORMAL}. * Note that the default hyphenation frequency value is set from the theme. * * @param hyphenationFrequency The hyphenation frequency to use. * @attr ref android.R.styleable#TextView_hyphenationFrequency * @see #getHyphenationFrequency() */
public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) { mHyphenationFrequency = hyphenationFrequency; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } }
Gets the current frequency of automatic hyphenation to be used when determining word breaks.
See Also:
Returns:the current frequency of automatic hyphenation to be used when determining word breaks.
@attrref android.R.styleable#TextView_hyphenationFrequency
/** * Gets the current frequency of automatic hyphenation to be used when determining word breaks. * @return the current frequency of automatic hyphenation to be used when determining word * breaks. * * @attr ref android.R.styleable#TextView_hyphenationFrequency * @see #setHyphenationFrequency(int) */
@Layout.HyphenationFrequency public int getHyphenationFrequency() { return mHyphenationFrequency; }
Gets the parameters for text layout precomputation, for use with PrecomputedText.
See Also:
Returns:a current Params
/** * Gets the parameters for text layout precomputation, for use with {@link PrecomputedText}. * * @return a current {@link PrecomputedText.Params} * @see PrecomputedText */
public @NonNull PrecomputedText.Params getTextMetricsParams() { return new PrecomputedText.Params(new TextPaint(mTextPaint), getTextDirectionHeuristic(), mBreakStrategy, mHyphenationFrequency); }
Apply the text layout parameter. Update the TextView parameters to be compatible with Params.
See Also:
/** * Apply the text layout parameter. * * Update the TextView parameters to be compatible with {@link PrecomputedText.Params}. * @see PrecomputedText */
public void setTextMetricsParams(@NonNull PrecomputedText.Params params) { mTextPaint.set(params.getTextPaint()); mUserSetTextScaleX = true; mTextDir = params.getTextDirection(); mBreakStrategy = params.getBreakStrategy(); mHyphenationFrequency = params.getHyphenationFrequency(); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } }
Set justification mode. The default value is Layout.JUSTIFICATION_MODE_NONE. If the last line is too short for justification, the last line will be displayed with the alignment set by View.setTextAlignment.
See Also:
/** * Set justification mode. The default value is {@link Layout#JUSTIFICATION_MODE_NONE}. If the * last line is too short for justification, the last line will be displayed with the * alignment set by {@link android.view.View#setTextAlignment}. * * @see #getJustificationMode() */
@Layout.JustificationMode public void setJustificationMode(@Layout.JustificationMode int justificationMode) { mJustificationMode = justificationMode; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } }
See Also:
Returns:true if currently paragraph justification mode.
/** * @return true if currently paragraph justification mode. * * @see #setJustificationMode(int) */
public @Layout.JustificationMode int getJustificationMode() { return mJustificationMode; }
Sets font feature settings. The format is the same as the CSS font-feature-settings attribute: https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop
Params:
  • fontFeatureSettings – font feature settings represented as CSS compatible string
See Also:
@attrref android.R.styleable#TextView_fontFeatureSettings
/** * Sets font feature settings. The format is the same as the CSS * font-feature-settings attribute: * <a href="https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop"> * https://www.w3.org/TR/css-fonts-3/#font-feature-settings-prop</a> * * @param fontFeatureSettings font feature settings represented as CSS compatible string * * @see #getFontFeatureSettings() * @see Paint#getFontFeatureSettings() Paint.getFontFeatureSettings() * * @attr ref android.R.styleable#TextView_fontFeatureSettings */
@android.view.RemotableViewMethod public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { mTextPaint.setFontFeatureSettings(fontFeatureSettings); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Sets TrueType or OpenType font variation settings. The settings string is constructed from multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that are longer or shorter than four characters, or contain characters outside of U+0020..U+007E are invalid. If a specified axis name is not defined in the font, the settings will be ignored.

Examples,

  • Set font width to 150.
    
      TextView textView = (TextView) findViewById(R.id.textView);
      textView.setFontVariationSettings("'wdth' 150");
    
    
  • Set the font slant to 20 degrees and ask for italic style.
    
      TextView textView = (TextView) findViewById(R.id.textView);
      textView.setFontVariationSettings("'slnt' 20, 'ital' 1");
    
    

Params:
  • fontVariationSettings – font variation settings. You can pass null or empty string as no variation settings.
Throws:
See Also:
Returns:true if the given settings is effective to at least one font file underlying this TextView. This function also returns true for empty settings string. Otherwise returns false.
/** * Sets TrueType or OpenType font variation settings. The settings string is constructed from * multiple pairs of axis tag and style values. The axis tag must contain four ASCII characters * and must be wrapped with single quotes (U+0027) or double quotes (U+0022). Axis strings that * are longer or shorter than four characters, or contain characters outside of U+0020..U+007E * are invalid. If a specified axis name is not defined in the font, the settings will be * ignored. * * <p> * Examples, * <ul> * <li>Set font width to 150. * <pre> * <code> * TextView textView = (TextView) findViewById(R.id.textView); * textView.setFontVariationSettings("'wdth' 150"); * </code> * </pre> * </li> * * <li>Set the font slant to 20 degrees and ask for italic style. * <pre> * <code> * TextView textView = (TextView) findViewById(R.id.textView); * textView.setFontVariationSettings("'slnt' 20, 'ital' 1"); * </code> * </pre> * </p> * </li> * </ul> * * @param fontVariationSettings font variation settings. You can pass null or empty string as * no variation settings. * @return true if the given settings is effective to at least one font file underlying this * TextView. This function also returns true for empty settings string. Otherwise * returns false. * * @throws IllegalArgumentException If given string is not a valid font variation settings * format. * * @see #getFontVariationSettings() * @see FontVariationAxis */
public boolean setFontVariationSettings(@Nullable String fontVariationSettings) { final String existingSettings = mTextPaint.getFontVariationSettings(); if (fontVariationSettings == existingSettings || (fontVariationSettings != null && fontVariationSettings.equals(existingSettings))) { return true; } boolean effective = mTextPaint.setFontVariationSettings(fontVariationSettings); if (effective && mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } return effective; }
Sets the text color for all the states (normal, selected, focused) to be this color.
Params:
  • color – A color value in the form 0xAARRGGBB. Do not pass a resource ID. To get a color value from a resource ID, call getColor.
See Also:
@attrref android.R.styleable#TextView_textColor
/** * Sets the text color for all the states (normal, selected, * focused) to be this color. * * @param color A color value in the form 0xAARRGGBB. * Do not pass a resource ID. To get a color value from a resource ID, call * {@link android.support.v4.content.ContextCompat#getColor(Context, int) getColor}. * * @see #setTextColor(ColorStateList) * @see #getTextColors() * * @attr ref android.R.styleable#TextView_textColor */
@android.view.RemotableViewMethod public void setTextColor(@ColorInt int color) { mTextColor = ColorStateList.valueOf(color); updateTextColors(); }
Sets the text color.
See Also:
@attrref android.R.styleable#TextView_textColor
/** * Sets the text color. * * @see #setTextColor(int) * @see #getTextColors() * @see #setHintTextColor(ColorStateList) * @see #setLinkTextColor(ColorStateList) * * @attr ref android.R.styleable#TextView_textColor */
@android.view.RemotableViewMethod public void setTextColor(ColorStateList colors) { if (colors == null) { throw new NullPointerException(); } mTextColor = colors; updateTextColors(); }
Gets the text colors for the different states (normal, selected, focused) of the TextView.
See Also:
@attrref android.R.styleable#TextView_textColor
/** * Gets the text colors for the different states (normal, selected, focused) of the TextView. * * @see #setTextColor(ColorStateList) * @see #setTextColor(int) * * @attr ref android.R.styleable#TextView_textColor */
public final ColorStateList getTextColors() { return mTextColor; }
Return the current color selected for normal text.
Returns:Returns the current text color.
/** * Return the current color selected for normal text. * * @return Returns the current text color. */
@ColorInt public final int getCurrentTextColor() { return mCurTextColor; }
Sets the color used to display the selection highlight.
@attrref android.R.styleable#TextView_textColorHighlight
/** * Sets the color used to display the selection highlight. * * @attr ref android.R.styleable#TextView_textColorHighlight */
@android.view.RemotableViewMethod public void setHighlightColor(@ColorInt int color) { if (mHighlightColor != color) { mHighlightColor = color; invalidate(); } }
See Also:
Returns:the color used to display the selection highlight
@attrref android.R.styleable#TextView_textColorHighlight
/** * @return the color used to display the selection highlight * * @see #setHighlightColor(int) * * @attr ref android.R.styleable#TextView_textColorHighlight */
@ColorInt public int getHighlightColor() { return mHighlightColor; }
Sets whether the soft input method will be made visible when this TextView gets focused. The default is true.
/** * Sets whether the soft input method will be made visible when this * TextView gets focused. The default is true. */
@android.view.RemotableViewMethod public final void setShowSoftInputOnFocus(boolean show) { createEditorIfNeeded(); mEditor.mShowSoftInputOnFocus = show; }
Returns whether the soft input method will be made visible when this TextView gets focused. The default is true.
/** * Returns whether the soft input method will be made visible when this * TextView gets focused. The default is true. */
public final boolean getShowSoftInputOnFocus() { // When there is no Editor, return default true value return mEditor == null || mEditor.mShowSoftInputOnFocus; }
Gives the text a shadow of the specified blur radius and color, the specified distance from its drawn position.

The text shadow produced does not interact with the properties on view that are responsible for real time shadows, elevation and translationZ.

See Also:
@attrref android.R.styleable#TextView_shadowColor
@attrref android.R.styleable#TextView_shadowDx
@attrref android.R.styleable#TextView_shadowDy
@attrref android.R.styleable#TextView_shadowRadius
/** * Gives the text a shadow of the specified blur radius and color, the specified * distance from its drawn position. * <p> * The text shadow produced does not interact with the properties on view * that are responsible for real time shadows, * {@link View#getElevation() elevation} and * {@link View#getTranslationZ() translationZ}. * * @see Paint#setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowColor * @attr ref android.R.styleable#TextView_shadowDx * @attr ref android.R.styleable#TextView_shadowDy * @attr ref android.R.styleable#TextView_shadowRadius */
public void setShadowLayer(float radius, float dx, float dy, int color) { mTextPaint.setShadowLayer(radius, dx, dy, color); mShadowRadius = radius; mShadowDx = dx; mShadowDy = dy; mShadowColor = color; // Will change text clip region if (mEditor != null) { mEditor.invalidateTextDisplayList(); mEditor.invalidateHandlesAndActionMode(); } invalidate(); }
Gets the radius of the shadow layer.
See Also:
Returns:the radius of the shadow layer. If 0, the shadow layer is not visible
@attrref android.R.styleable#TextView_shadowRadius
/** * Gets the radius of the shadow layer. * * @return the radius of the shadow layer. If 0, the shadow layer is not visible * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowRadius */
public float getShadowRadius() { return mShadowRadius; }
See Also:
Returns:the horizontal offset of the shadow layer
@attrref android.R.styleable#TextView_shadowDx
/** * @return the horizontal offset of the shadow layer * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowDx */
public float getShadowDx() { return mShadowDx; }
Gets the vertical offset of the shadow layer.
See Also:
Returns:The vertical offset of the shadow layer.
@attrref android.R.styleable#TextView_shadowDy
/** * Gets the vertical offset of the shadow layer. * @return The vertical offset of the shadow layer. * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowDy */
public float getShadowDy() { return mShadowDy; }
Gets the color of the shadow layer.
See Also:
Returns:the color of the shadow layer
@attrref android.R.styleable#TextView_shadowColor
/** * Gets the color of the shadow layer. * @return the color of the shadow layer * * @see #setShadowLayer(float, float, float, int) * * @attr ref android.R.styleable#TextView_shadowColor */
@ColorInt public int getShadowColor() { return mShadowColor; }
Gets the TextPaint used for the text. Use this only to consult the Paint's properties and not to change them.
Returns:The base paint used for the text.
/** * Gets the {@link TextPaint} used for the text. * Use this only to consult the Paint's properties and not to change them. * @return The base paint used for the text. */
public TextPaint getPaint() { return mTextPaint; }
Sets the autolink mask of the text. See Linkify.ALL and peers for possible values.
@attrref android.R.styleable#TextView_autoLink
/** * Sets the autolink mask of the text. See {@link * android.text.util.Linkify#ALL Linkify.ALL} and peers for * possible values. * * @attr ref android.R.styleable#TextView_autoLink */
@android.view.RemotableViewMethod public final void setAutoLinkMask(int mask) { mAutoLinkMask = mask; }
Sets whether the movement method will automatically be set to LinkMovementMethod if setAutoLinkMask has been set to nonzero and links are detected in setText. The default is true.
@attrref android.R.styleable#TextView_linksClickable
/** * Sets whether the movement method will automatically be set to * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been * set to nonzero and links are detected in {@link #setText}. * The default is true. * * @attr ref android.R.styleable#TextView_linksClickable */
@android.view.RemotableViewMethod public final void setLinksClickable(boolean whether) { mLinksClickable = whether; }
Returns whether the movement method will automatically be set to LinkMovementMethod if setAutoLinkMask has been set to nonzero and links are detected in setText. The default is true.
@attrref android.R.styleable#TextView_linksClickable
/** * Returns whether the movement method will automatically be set to * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been * set to nonzero and links are detected in {@link #setText}. * The default is true. * * @attr ref android.R.styleable#TextView_linksClickable */
public final boolean getLinksClickable() { return mLinksClickable; }
Returns the list of URLSpans attached to the text (by Linkify or otherwise) if any. You can call URLSpan.getURL on them to find where they link to or use Spanned.getSpanStart and Spanned.getSpanEnd to find the region of the text they are attached to.
/** * Returns the list of {@link android.text.style.URLSpan URLSpans} attached to the text * (by {@link Linkify} or otherwise) if any. You can call * {@link URLSpan#getURL} on them to find where they link to * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} * to find the region of the text they are attached to. */
public URLSpan[] getUrls() { if (mText instanceof Spanned) { return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); } else { return new URLSpan[0]; } }
Sets the color of the hint text for all the states (disabled, focussed, selected...) of this TextView.
See Also:
@attrref android.R.styleable#TextView_textColorHint
/** * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this * TextView. * * @see #setHintTextColor(ColorStateList) * @see #getHintTextColors() * @see #setTextColor(int) * * @attr ref android.R.styleable#TextView_textColorHint */
@android.view.RemotableViewMethod public final void setHintTextColor(@ColorInt int color) { mHintTextColor = ColorStateList.valueOf(color); updateTextColors(); }
Sets the color of the hint text.
See Also:
@attrref android.R.styleable#TextView_textColorHint
/** * Sets the color of the hint text. * * @see #getHintTextColors() * @see #setHintTextColor(int) * @see #setTextColor(ColorStateList) * @see #setLinkTextColor(ColorStateList) * * @attr ref android.R.styleable#TextView_textColorHint */
public final void setHintTextColor(ColorStateList colors) { mHintTextColor = colors; updateTextColors(); }
See Also:
Returns:the color of the hint text, for the different states of this TextView.
@attrref android.R.styleable#TextView_textColorHint
/** * @return the color of the hint text, for the different states of this TextView. * * @see #setHintTextColor(ColorStateList) * @see #setHintTextColor(int) * @see #setTextColor(ColorStateList) * @see #setLinkTextColor(ColorStateList) * * @attr ref android.R.styleable#TextView_textColorHint */
public final ColorStateList getHintTextColors() { return mHintTextColor; }

Return the current color selected to paint the hint text.

Returns:Returns the current hint text color.
/** * <p>Return the current color selected to paint the hint text.</p> * * @return Returns the current hint text color. */
@ColorInt public final int getCurrentHintTextColor() { return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; }
Sets the color of links in the text.
See Also:
@attrref android.R.styleable#TextView_textColorLink
/** * Sets the color of links in the text. * * @see #setLinkTextColor(ColorStateList) * @see #getLinkTextColors() * * @attr ref android.R.styleable#TextView_textColorLink */
@android.view.RemotableViewMethod public final void setLinkTextColor(@ColorInt int color) { mLinkTextColor = ColorStateList.valueOf(color); updateTextColors(); }
Sets the color of links in the text.
See Also:
@attrref android.R.styleable#TextView_textColorLink
/** * Sets the color of links in the text. * * @see #setLinkTextColor(int) * @see #getLinkTextColors() * @see #setTextColor(ColorStateList) * @see #setHintTextColor(ColorStateList) * * @attr ref android.R.styleable#TextView_textColorLink */
public final void setLinkTextColor(ColorStateList colors) { mLinkTextColor = colors; updateTextColors(); }
See Also:
Returns:the list of colors used to paint the links in the text, for the different states of this TextView
@attrref android.R.styleable#TextView_textColorLink
/** * @return the list of colors used to paint the links in the text, for the different states of * this TextView * * @see #setLinkTextColor(ColorStateList) * @see #setLinkTextColor(int) * * @attr ref android.R.styleable#TextView_textColorLink */
public final ColorStateList getLinkTextColors() { return mLinkTextColor; }
Sets the horizontal alignment of the text and the vertical gravity that will be used when there is extra space in the TextView beyond what is required for the text itself.
See Also:
  • Gravity
@attrref android.R.styleable#TextView_gravity
/** * Sets the horizontal alignment of the text and the * vertical gravity that will be used when there is extra space * in the TextView beyond what is required for the text itself. * * @see android.view.Gravity * @attr ref android.R.styleable#TextView_gravity */
public void setGravity(int gravity) { if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { gravity |= Gravity.START; } if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { gravity |= Gravity.TOP; } boolean newLayout = false; if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { newLayout = true; } if (gravity != mGravity) { invalidate(); } mGravity = gravity; if (mLayout != null && newLayout) { // XXX this is heavy-handed because no actual content changes. int want = mLayout.getWidth(); int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), true); } }
Returns the horizontal and vertical alignment of this TextView.
See Also:
  • Gravity
@attrref android.R.styleable#TextView_gravity
/** * Returns the horizontal and vertical alignment of this TextView. * * @see android.view.Gravity * @attr ref android.R.styleable#TextView_gravity */
public int getGravity() { return mGravity; }
Gets the flags on the Paint being used to display the text.
See Also:
Returns:The flags on the Paint being used to display the text.
/** * Gets the flags on the Paint being used to display the text. * @return The flags on the Paint being used to display the text. * @see Paint#getFlags */
public int getPaintFlags() { return mTextPaint.getFlags(); }
Sets flags on the Paint being used to display the text and reflows the text if they are different from the old flags.
See Also:
  • setFlags.setFlags
/** * Sets flags on the Paint being used to display the text and * reflows the text if they are different from the old flags. * @see Paint#setFlags */
@android.view.RemotableViewMethod public void setPaintFlags(int flags) { if (mTextPaint.getFlags() != flags) { mTextPaint.setFlags(flags); if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Sets whether the text should be allowed to be wider than the View is. If false, it will be wrapped to the width of the View.
@attrref android.R.styleable#TextView_scrollHorizontally
/** * Sets whether the text should be allowed to be wider than the * View is. If false, it will be wrapped to the width of the View. * * @attr ref android.R.styleable#TextView_scrollHorizontally */
public void setHorizontallyScrolling(boolean whether) { if (mHorizontallyScrolling != whether) { mHorizontallyScrolling = whether; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Returns whether the text is allowed to be wider than the View is. If false, the text will be wrapped to the width of the View.
@attrref android.R.styleable#TextView_scrollHorizontally
@hide
/** * Returns whether the text is allowed to be wider than the View is. * If false, the text will be wrapped to the width of the View. * * @attr ref android.R.styleable#TextView_scrollHorizontally * @hide */
public boolean getHorizontallyScrolling() { return mHorizontallyScrolling; }
Sets the height of the TextView to be at least minLines tall.

This value is used for height calculation if LayoutParams does not force TextView to have an exact height. Setting this value overrides other previous minimum height configurations such as setMinHeight(int) or setHeight(int). setSingleLine() will set this value to 1.

Params:
  • minLines – the minimum height of TextView in terms of number of lines
See Also:
@attrref android.R.styleable#TextView_minLines
/** * Sets the height of the TextView to be at least {@code minLines} tall. * <p> * This value is used for height calculation if LayoutParams does not force TextView to have an * exact height. Setting this value overrides other previous minimum height configurations such * as {@link #setMinHeight(int)} or {@link #setHeight(int)}. {@link #setSingleLine()} will set * this value to 1. * * @param minLines the minimum height of TextView in terms of number of lines * * @see #getMinLines() * @see #setLines(int) * * @attr ref android.R.styleable#TextView_minLines */
@android.view.RemotableViewMethod public void setMinLines(int minLines) { mMinimum = minLines; mMinMode = LINES; requestLayout(); invalidate(); }
Returns the minimum height of TextView in terms of number of lines or -1 if the minimum height was set using setMinHeight(int) or setHeight(int).
See Also:
Returns:the minimum height of TextView in terms of number of lines or -1 if the minimum height is not defined in lines
@attrref android.R.styleable#TextView_minLines
/** * Returns the minimum height of TextView in terms of number of lines or -1 if the minimum * height was set using {@link #setMinHeight(int)} or {@link #setHeight(int)}. * * @return the minimum height of TextView in terms of number of lines or -1 if the minimum * height is not defined in lines * * @see #setMinLines(int) * @see #setLines(int) * * @attr ref android.R.styleable#TextView_minLines */
public int getMinLines() { return mMinMode == LINES ? mMinimum : -1; }
Sets the height of the TextView to be at least minPixels tall.

This value is used for height calculation if LayoutParams does not force TextView to have an exact height. Setting this value overrides previous minimum height configurations such as setMinLines(int) or setLines(int).

The value given here is different than View.setMinimumHeight(int). Between minHeight and the value set in View.setMinimumHeight(int), the greater one is used to decide the final height.

Params:
  • minPixels – the minimum height of TextView in terms of pixels
See Also:
@attrref android.R.styleable#TextView_minHeight
/** * Sets the height of the TextView to be at least {@code minPixels} tall. * <p> * This value is used for height calculation if LayoutParams does not force TextView to have an * exact height. Setting this value overrides previous minimum height configurations such as * {@link #setMinLines(int)} or {@link #setLines(int)}. * <p> * The value given here is different than {@link #setMinimumHeight(int)}. Between * {@code minHeight} and the value set in {@link #setMinimumHeight(int)}, the greater one is * used to decide the final height. * * @param minPixels the minimum height of TextView in terms of pixels * * @see #getMinHeight() * @see #setHeight(int) * * @attr ref android.R.styleable#TextView_minHeight */
@android.view.RemotableViewMethod public void setMinHeight(int minPixels) { mMinimum = minPixels; mMinMode = PIXELS; requestLayout(); invalidate(); }
Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was set using setMinLines(int) or setLines(int).
See Also:
Returns:the minimum height of TextView in terms of pixels or -1 if the minimum height is not defined in pixels
@attrref android.R.styleable#TextView_minHeight
/** * Returns the minimum height of TextView in terms of pixels or -1 if the minimum height was * set using {@link #setMinLines(int)} or {@link #setLines(int)}. * * @return the minimum height of TextView in terms of pixels or -1 if the minimum height is not * defined in pixels * * @see #setMinHeight(int) * @see #setHeight(int) * * @attr ref android.R.styleable#TextView_minHeight */
public int getMinHeight() { return mMinMode == PIXELS ? mMinimum : -1; }
Sets the height of the TextView to be at most maxLines tall.

This value is used for height calculation if LayoutParams does not force TextView to have an exact height. Setting this value overrides previous maximum height configurations such as setMaxHeight(int) or setLines(int).

Params:
  • maxLines – the maximum height of TextView in terms of number of lines
See Also:
@attrref android.R.styleable#TextView_maxLines
/** * Sets the height of the TextView to be at most {@code maxLines} tall. * <p> * This value is used for height calculation if LayoutParams does not force TextView to have an * exact height. Setting this value overrides previous maximum height configurations such as * {@link #setMaxHeight(int)} or {@link #setLines(int)}. * * @param maxLines the maximum height of TextView in terms of number of lines * * @see #getMaxLines() * @see #setLines(int) * * @attr ref android.R.styleable#TextView_maxLines */
@android.view.RemotableViewMethod public void setMaxLines(int maxLines) { mMaximum = maxLines; mMaxMode = LINES; requestLayout(); invalidate(); }
Returns the maximum height of TextView in terms of number of lines or -1 if the maximum height was set using setMaxHeight(int) or setHeight(int).
See Also:
Returns:the maximum height of TextView in terms of number of lines. -1 if the maximum height is not defined in lines.
@attrref android.R.styleable#TextView_maxLines
/** * Returns the maximum height of TextView in terms of number of lines or -1 if the * maximum height was set using {@link #setMaxHeight(int)} or {@link #setHeight(int)}. * * @return the maximum height of TextView in terms of number of lines. -1 if the maximum height * is not defined in lines. * * @see #setMaxLines(int) * @see #setLines(int) * * @attr ref android.R.styleable#TextView_maxLines */
public int getMaxLines() { return mMaxMode == LINES ? mMaximum : -1; }
Sets the height of the TextView to be at most maxPixels tall.

This value is used for height calculation if LayoutParams does not force TextView to have an exact height. Setting this value overrides previous maximum height configurations such as setMaxLines(int) or setLines(int).

Params:
  • maxPixels – the maximum height of TextView in terms of pixels
See Also:
@attrref android.R.styleable#TextView_maxHeight
/** * Sets the height of the TextView to be at most {@code maxPixels} tall. * <p> * This value is used for height calculation if LayoutParams does not force TextView to have an * exact height. Setting this value overrides previous maximum height configurations such as * {@link #setMaxLines(int)} or {@link #setLines(int)}. * * @param maxPixels the maximum height of TextView in terms of pixels * * @see #getMaxHeight() * @see #setHeight(int) * * @attr ref android.R.styleable#TextView_maxHeight */
@android.view.RemotableViewMethod public void setMaxHeight(int maxPixels) { mMaximum = maxPixels; mMaxMode = PIXELS; requestLayout(); invalidate(); }
Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was set using setMaxLines(int) or setLines(int).
See Also:
Returns:the maximum height of TextView in terms of pixels or -1 if the maximum height is not defined in pixels
@attrref android.R.styleable#TextView_maxHeight
/** * Returns the maximum height of TextView in terms of pixels or -1 if the maximum height was * set using {@link #setMaxLines(int)} or {@link #setLines(int)}. * * @return the maximum height of TextView in terms of pixels or -1 if the maximum height * is not defined in pixels * * @see #setMaxHeight(int) * @see #setHeight(int) * * @attr ref android.R.styleable#TextView_maxHeight */
public int getMaxHeight() { return mMaxMode == PIXELS ? mMaximum : -1; }
Sets the height of the TextView to be exactly lines tall.

This value is used for height calculation if LayoutParams does not force TextView to have an exact height. Setting this value overrides previous minimum/maximum height configurations such as setMinLines(int) or setMaxLines(int). setSingleLine() will set this value to 1.

Params:
  • lines – the exact height of the TextView in terms of lines
See Also:
@attrref android.R.styleable#TextView_lines
/** * Sets the height of the TextView to be exactly {@code lines} tall. * <p> * This value is used for height calculation if LayoutParams does not force TextView to have an * exact height. Setting this value overrides previous minimum/maximum height configurations * such as {@link #setMinLines(int)} or {@link #setMaxLines(int)}. {@link #setSingleLine()} will * set this value to 1. * * @param lines the exact height of the TextView in terms of lines * * @see #setHeight(int) * * @attr ref android.R.styleable#TextView_lines */
@android.view.RemotableViewMethod public void setLines(int lines) { mMaximum = mMinimum = lines; mMaxMode = mMinMode = LINES; requestLayout(); invalidate(); }
Sets the height of the TextView to be exactly pixels tall.

This value is used for height calculation if LayoutParams does not force TextView to have an exact height. Setting this value overrides previous minimum/maximum height configurations such as setMinHeight(int) or setMaxHeight(int).

Params:
  • pixels – the exact height of the TextView in terms of pixels
See Also:
@attrref android.R.styleable#TextView_height
/** * Sets the height of the TextView to be exactly <code>pixels</code> tall. * <p> * This value is used for height calculation if LayoutParams does not force TextView to have an * exact height. Setting this value overrides previous minimum/maximum height configurations * such as {@link #setMinHeight(int)} or {@link #setMaxHeight(int)}. * * @param pixels the exact height of the TextView in terms of pixels * * @see #setLines(int) * * @attr ref android.R.styleable#TextView_height */
@android.view.RemotableViewMethod public void setHeight(int pixels) { mMaximum = mMinimum = pixels; mMaxMode = mMinMode = PIXELS; requestLayout(); invalidate(); }
Sets the width of the TextView to be at least minEms wide.

This value is used for width calculation if LayoutParams does not force TextView to have an exact width. Setting this value overrides previous minimum width configurations such as setMinWidth(int) or setWidth(int).

Params:
  • minEms – the minimum width of TextView in terms of ems
See Also:
@attrref android.R.styleable#TextView_minEms
/** * Sets the width of the TextView to be at least {@code minEms} wide. * <p> * This value is used for width calculation if LayoutParams does not force TextView to have an * exact width. Setting this value overrides previous minimum width configurations such as * {@link #setMinWidth(int)} or {@link #setWidth(int)}. * * @param minEms the minimum width of TextView in terms of ems * * @see #getMinEms() * @see #setEms(int) * * @attr ref android.R.styleable#TextView_minEms */
@android.view.RemotableViewMethod public void setMinEms(int minEms) { mMinWidth = minEms; mMinWidthMode = EMS; requestLayout(); invalidate(); }
Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set using setMinWidth(int) or setWidth(int).
See Also:
Returns:the minimum width of TextView in terms of ems. -1 if the minimum width is not defined in ems
@attrref android.R.styleable#TextView_minEms
/** * Returns the minimum width of TextView in terms of ems or -1 if the minimum width was set * using {@link #setMinWidth(int)} or {@link #setWidth(int)}. * * @return the minimum width of TextView in terms of ems. -1 if the minimum width is not * defined in ems * * @see #setMinEms(int) * @see #setEms(int) * * @attr ref android.R.styleable#TextView_minEms */
public int getMinEms() { return mMinWidthMode == EMS ? mMinWidth : -1; }
Sets the width of the TextView to be at least minPixels wide.

This value is used for width calculation if LayoutParams does not force TextView to have an exact width. Setting this value overrides previous minimum width configurations such as setMinEms(int) or setEms(int).

The value given here is different than View.setMinimumWidth(int). Between minWidth and the value set in View.setMinimumWidth(int), the greater one is used to decide the final width.

Params:
  • minPixels – the minimum width of TextView in terms of pixels
See Also:
@attrref android.R.styleable#TextView_minWidth
/** * Sets the width of the TextView to be at least {@code minPixels} wide. * <p> * This value is used for width calculation if LayoutParams does not force TextView to have an * exact width. Setting this value overrides previous minimum width configurations such as * {@link #setMinEms(int)} or {@link #setEms(int)}. * <p> * The value given here is different than {@link #setMinimumWidth(int)}. Between * {@code minWidth} and the value set in {@link #setMinimumWidth(int)}, the greater one is used * to decide the final width. * * @param minPixels the minimum width of TextView in terms of pixels * * @see #getMinWidth() * @see #setWidth(int) * * @attr ref android.R.styleable#TextView_minWidth */
@android.view.RemotableViewMethod public void setMinWidth(int minPixels) { mMinWidth = minPixels; mMinWidthMode = PIXELS; requestLayout(); invalidate(); }
Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set using setMinEms(int) or setEms(int).
See Also:
Returns:the minimum width of TextView in terms of pixels or -1 if the minimum width is not defined in pixels
@attrref android.R.styleable#TextView_minWidth
/** * Returns the minimum width of TextView in terms of pixels or -1 if the minimum width was set * using {@link #setMinEms(int)} or {@link #setEms(int)}. * * @return the minimum width of TextView in terms of pixels or -1 if the minimum width is not * defined in pixels * * @see #setMinWidth(int) * @see #setWidth(int) * * @attr ref android.R.styleable#TextView_minWidth */
public int getMinWidth() { return mMinWidthMode == PIXELS ? mMinWidth : -1; }
Sets the width of the TextView to be at most maxEms wide.

This value is used for width calculation if LayoutParams does not force TextView to have an exact width. Setting this value overrides previous maximum width configurations such as setMaxWidth(int) or setWidth(int).

Params:
  • maxEms – the maximum width of TextView in terms of ems
See Also:
@attrref android.R.styleable#TextView_maxEms
/** * Sets the width of the TextView to be at most {@code maxEms} wide. * <p> * This value is used for width calculation if LayoutParams does not force TextView to have an * exact width. Setting this value overrides previous maximum width configurations such as * {@link #setMaxWidth(int)} or {@link #setWidth(int)}. * * @param maxEms the maximum width of TextView in terms of ems * * @see #getMaxEms() * @see #setEms(int) * * @attr ref android.R.styleable#TextView_maxEms */
@android.view.RemotableViewMethod public void setMaxEms(int maxEms) { mMaxWidth = maxEms; mMaxWidthMode = EMS; requestLayout(); invalidate(); }
Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set using setMaxWidth(int) or setWidth(int).
See Also:
Returns:the maximum width of TextView in terms of ems or -1 if the maximum width is not defined in ems
@attrref android.R.styleable#TextView_maxEms
/** * Returns the maximum width of TextView in terms of ems or -1 if the maximum width was set * using {@link #setMaxWidth(int)} or {@link #setWidth(int)}. * * @return the maximum width of TextView in terms of ems or -1 if the maximum width is not * defined in ems * * @see #setMaxEms(int) * @see #setEms(int) * * @attr ref android.R.styleable#TextView_maxEms */
public int getMaxEms() { return mMaxWidthMode == EMS ? mMaxWidth : -1; }
Sets the width of the TextView to be at most maxPixels wide.

This value is used for width calculation if LayoutParams does not force TextView to have an exact width. Setting this value overrides previous maximum width configurations such as setMaxEms(int) or setEms(int).

Params:
  • maxPixels – the maximum width of TextView in terms of pixels
See Also:
@attrref android.R.styleable#TextView_maxWidth
/** * Sets the width of the TextView to be at most {@code maxPixels} wide. * <p> * This value is used for width calculation if LayoutParams does not force TextView to have an * exact width. Setting this value overrides previous maximum width configurations such as * {@link #setMaxEms(int)} or {@link #setEms(int)}. * * @param maxPixels the maximum width of TextView in terms of pixels * * @see #getMaxWidth() * @see #setWidth(int) * * @attr ref android.R.styleable#TextView_maxWidth */
@android.view.RemotableViewMethod public void setMaxWidth(int maxPixels) { mMaxWidth = maxPixels; mMaxWidthMode = PIXELS; requestLayout(); invalidate(); }
Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set using setMaxEms(int) or setEms(int).
See Also:
Returns:the maximum width of TextView in terms of pixels. -1 if the maximum width is not defined in pixels
@attrref android.R.styleable#TextView_maxWidth
/** * Returns the maximum width of TextView in terms of pixels or -1 if the maximum width was set * using {@link #setMaxEms(int)} or {@link #setEms(int)}. * * @return the maximum width of TextView in terms of pixels. -1 if the maximum width is not * defined in pixels * * @see #setMaxWidth(int) * @see #setWidth(int) * * @attr ref android.R.styleable#TextView_maxWidth */
public int getMaxWidth() { return mMaxWidthMode == PIXELS ? mMaxWidth : -1; }
Sets the width of the TextView to be exactly ems wide. This value is used for width calculation if LayoutParams does not force TextView to have an exact width. Setting this value overrides previous minimum/maximum configurations such as setMinEms(int) or setMaxEms(int).
Params:
  • ems – the exact width of the TextView in terms of ems
See Also:
@attrref android.R.styleable#TextView_ems
/** * Sets the width of the TextView to be exactly {@code ems} wide. * * This value is used for width calculation if LayoutParams does not force TextView to have an * exact width. Setting this value overrides previous minimum/maximum configurations such as * {@link #setMinEms(int)} or {@link #setMaxEms(int)}. * * @param ems the exact width of the TextView in terms of ems * * @see #setWidth(int) * * @attr ref android.R.styleable#TextView_ems */
@android.view.RemotableViewMethod public void setEms(int ems) { mMaxWidth = mMinWidth = ems; mMaxWidthMode = mMinWidthMode = EMS; requestLayout(); invalidate(); }
Sets the width of the TextView to be exactly pixels wide.

This value is used for width calculation if LayoutParams does not force TextView to have an exact width. Setting this value overrides previous minimum/maximum width configurations such as setMinWidth(int) or setMaxWidth(int).

Params:
  • pixels – the exact width of the TextView in terms of pixels
See Also:
@attrref android.R.styleable#TextView_width
/** * Sets the width of the TextView to be exactly {@code pixels} wide. * <p> * This value is used for width calculation if LayoutParams does not force TextView to have an * exact width. Setting this value overrides previous minimum/maximum width configurations * such as {@link #setMinWidth(int)} or {@link #setMaxWidth(int)}. * * @param pixels the exact width of the TextView in terms of pixels * * @see #setEms(int) * * @attr ref android.R.styleable#TextView_width */
@android.view.RemotableViewMethod public void setWidth(int pixels) { mMaxWidth = mMinWidth = pixels; mMaxWidthMode = mMinWidthMode = PIXELS; requestLayout(); invalidate(); }
Sets line spacing for this TextView. Each line other than the last line will have its height multiplied by mult and have add added to it.
Params:
  • add – The value in pixels that should be added to each line other than the last line. This will be applied after the multiplier
  • mult – The value by which each line height other than the last line will be multiplied by
@attrref android.R.styleable#TextView_lineSpacingExtra
@attrref android.R.styleable#TextView_lineSpacingMultiplier
/** * Sets line spacing for this TextView. Each line other than the last line will have its height * multiplied by {@code mult} and have {@code add} added to it. * * @param add The value in pixels that should be added to each line other than the last line. * This will be applied after the multiplier * @param mult The value by which each line height other than the last line will be multiplied * by * * @attr ref android.R.styleable#TextView_lineSpacingExtra * @attr ref android.R.styleable#TextView_lineSpacingMultiplier */
public void setLineSpacing(float add, float mult) { if (mSpacingAdd != add || mSpacingMult != mult) { mSpacingAdd = add; mSpacingMult = mult; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Gets the line spacing multiplier
See Also:
Returns:the value by which each line's height is multiplied to get its actual height.
@attrref android.R.styleable#TextView_lineSpacingMultiplier
/** * Gets the line spacing multiplier * * @return the value by which each line's height is multiplied to get its actual height. * * @see #setLineSpacing(float, float) * @see #getLineSpacingExtra() * * @attr ref android.R.styleable#TextView_lineSpacingMultiplier */
public float getLineSpacingMultiplier() { return mSpacingMult; }
Gets the line spacing extra space
See Also:
Returns:the extra space that is added to the height of each lines of this TextView.
@attrref android.R.styleable#TextView_lineSpacingExtra
/** * Gets the line spacing extra space * * @return the extra space that is added to the height of each lines of this TextView. * * @see #setLineSpacing(float, float) * @see #getLineSpacingMultiplier() * * @attr ref android.R.styleable#TextView_lineSpacingExtra */
public float getLineSpacingExtra() { return mSpacingAdd; }
Sets an explicit line height for this TextView. This is equivalent to the vertical distance between subsequent baselines in the TextView.
Params:
  • lineHeight – the line height in pixels
See Also:
@attrref android.R.styleable#TextView_lineHeight
/** * Sets an explicit line height for this TextView. This is equivalent to the vertical distance * between subsequent baselines in the TextView. * * @param lineHeight the line height in pixels * * @see #setLineSpacing(float, float) * @see #getLineSpacing() * * @attr ref android.R.styleable#TextView_lineHeight */
public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { Preconditions.checkArgumentNonnegative(lineHeight); final int fontHeight = getPaint().getFontMetricsInt(null); // Make sure we don't setLineSpacing if it's not needed to avoid unnecessary redraw. if (lineHeight != fontHeight) { // Set lineSpacingExtra by the difference of lineSpacing with lineHeight setLineSpacing(lineHeight - fontHeight, 1f); } }
Convenience method to append the specified text to the TextView's display buffer, upgrading it to BufferType.EDITABLE if it was not already editable.
Params:
  • text – text to be appended to the already displayed text
/** * Convenience method to append the specified text to the TextView's * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} * if it was not already editable. * * @param text text to be appended to the already displayed text */
public final void append(CharSequence text) { append(text, 0, text.length()); }
Convenience method to append the specified text slice to the TextView's display buffer, upgrading it to BufferType.EDITABLE if it was not already editable.
Params:
  • text – text to be appended to the already displayed text
  • start – the index of the first character in the text
  • end – the index of the character following the last character in the text
See Also:
/** * Convenience method to append the specified text slice to the TextView's * display buffer, upgrading it to {@link android.widget.TextView.BufferType#EDITABLE} * if it was not already editable. * * @param text text to be appended to the already displayed text * @param start the index of the first character in the {@code text} * @param end the index of the character following the last character in the {@code text} * * @see Appendable#append(CharSequence, int, int) */
public void append(CharSequence text, int start, int end) { if (!(mText instanceof Editable)) { setText(mText, BufferType.EDITABLE); } ((Editable) mText).append(text, start, end); if (mAutoLinkMask != 0) { boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask); // Do not change the movement method for text that support text selection as it // would prevent an arbitrary cursor displacement. if (linksWereAdded && mLinksClickable && !textCanBeSelected()) { setMovementMethod(LinkMovementMethod.getInstance()); } } } private void updateTextColors() { boolean inval = false; final int[] drawableState = getDrawableState(); int color = mTextColor.getColorForState(drawableState, 0); if (color != mCurTextColor) { mCurTextColor = color; inval = true; } if (mLinkTextColor != null) { color = mLinkTextColor.getColorForState(drawableState, 0); if (color != mTextPaint.linkColor) { mTextPaint.linkColor = color; inval = true; } } if (mHintTextColor != null) { color = mHintTextColor.getColorForState(drawableState, 0); if (color != mCurHintTextColor) { mCurHintTextColor = color; if (mText.length() == 0) { inval = true; } } } if (inval) { // Text needs to be redrawn with the new color if (mEditor != null) mEditor.invalidateTextDisplayList(); invalidate(); } } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (mTextColor != null && mTextColor.isStateful() || (mHintTextColor != null && mHintTextColor.isStateful()) || (mLinkTextColor != null && mLinkTextColor.isStateful())) { updateTextColors(); } if (mDrawables != null) { final int[] state = getDrawableState(); for (Drawable dr : mDrawables.mShowing) { if (dr != null && dr.isStateful() && dr.setState(state)) { invalidateDrawable(dr); } } } } @Override public void drawableHotspotChanged(float x, float y) { super.drawableHotspotChanged(x, y); if (mDrawables != null) { for (Drawable dr : mDrawables.mShowing) { if (dr != null) { dr.setHotspot(x, y); } } } } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); // Save state if we are forced to final boolean freezesText = getFreezesText(); boolean hasSelection = false; int start = -1; int end = -1; if (mText != null) { start = getSelectionStart(); end = getSelectionEnd(); if (start >= 0 || end >= 0) { // Or save state if there is a selection hasSelection = true; } } if (freezesText || hasSelection) { SavedState ss = new SavedState(superState); if (freezesText) { if (mText instanceof Spanned) { final Spannable sp = new SpannableStringBuilder(mText); if (mEditor != null) { removeMisspelledSpans(sp); sp.removeSpan(mEditor.mSuggestionRangeSpan); } ss.text = sp; } else { ss.text = mText.toString(); } } if (hasSelection) { // XXX Should also save the current scroll position! ss.selStart = start; ss.selEnd = end; } if (isFocused() && start >= 0 && end >= 0) { ss.frozenWithFocus = true; } ss.error = getError(); if (mEditor != null) { ss.editorState = mEditor.saveInstanceState(); } return ss; } return superState; } void removeMisspelledSpans(Spannable spannable) { SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), SuggestionSpan.class); for (int i = 0; i < suggestionSpans.length; i++) { int flags = suggestionSpans[i].getFlags(); if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { spannable.removeSpan(suggestionSpans[i]); } } } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.getSuperState()); // XXX restore buffer type too, as well as lots of other stuff if (ss.text != null) { setText(ss.text); } if (ss.selStart >= 0 && ss.selEnd >= 0) { if (mSpannable != null) { int len = mText.length(); if (ss.selStart > len || ss.selEnd > len) { String restored = ""; if (ss.text != null) { restored = "(restored) "; } Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd + " out of range for " + restored + "text " + mText); } else { Selection.setSelection(mSpannable, ss.selStart, ss.selEnd); if (ss.frozenWithFocus) { createEditorIfNeeded(); mEditor.mFrozenWithFocus = true; } } } } if (ss.error != null) { final CharSequence error = ss.error; // Display the error later, after the first layout pass post(new Runnable() { public void run() { if (mEditor == null || !mEditor.mErrorWasChanged) { setError(error); } } }); } if (ss.editorState != null) { createEditorIfNeeded(); mEditor.restoreInstanceState(ss.editorState); } }
Control whether this text view saves its entire text contents when freezing to an icicle, in addition to dynamic state such as cursor position. By default this is false, not saving the text. Set to true if the text in the text view is not being saved somewhere else in persistent storage (such as in a content provider) so that if the view is later thawed the user will not lose their data. For EditText it is always enabled, regardless of the value of the attribute.
Params:
  • freezesText – Controls whether a frozen icicle should include the entire text data: true to include it, false to not.
@attrref android.R.styleable#TextView_freezesText
/** * Control whether this text view saves its entire text contents when * freezing to an icicle, in addition to dynamic state such as cursor * position. By default this is false, not saving the text. Set to true * if the text in the text view is not being saved somewhere else in * persistent storage (such as in a content provider) so that if the * view is later thawed the user will not lose their data. For * {@link android.widget.EditText} it is always enabled, regardless of * the value of the attribute. * * @param freezesText Controls whether a frozen icicle should include the * entire text data: true to include it, false to not. * * @attr ref android.R.styleable#TextView_freezesText */
@android.view.RemotableViewMethod public void setFreezesText(boolean freezesText) { mFreezesText = freezesText; }
Return whether this text view is including its entire text contents in frozen icicles. For EditText it always returns true.
See Also:
Returns:Returns true if text is included, false if it isn't.
/** * Return whether this text view is including its entire text contents * in frozen icicles. For {@link android.widget.EditText} it always returns true. * * @return Returns true if text is included, false if it isn't. * * @see #setFreezesText */
public boolean getFreezesText() { return mFreezesText; } ///////////////////////////////////////////////////////////////////////////
Sets the Factory used to create new Editables.
Params:
See Also:
/** * Sets the Factory used to create new {@link Editable Editables}. * * @param factory {@link android.text.Editable.Factory Editable.Factory} to be used * * @see android.text.Editable.Factory * @see android.widget.TextView.BufferType#EDITABLE */
public final void setEditableFactory(Editable.Factory factory) { mEditableFactory = factory; setText(mText); }
Sets the Factory used to create new Spannables.
Params:
See Also:
/** * Sets the Factory used to create new {@link Spannable Spannables}. * * @param factory {@link android.text.Spannable.Factory Spannable.Factory} to be used * * @see android.text.Spannable.Factory * @see android.widget.TextView.BufferType#SPANNABLE */
public final void setSpannableFactory(Spannable.Factory factory) { mSpannableFactory = factory; setText(mText); }
Sets the text to be displayed. TextView does not accept HTML-like formatting, which you can do with text strings in XML resource files. To style your strings, attach android.text.style.* objects to a SpannableString, or see the Available Resource Types documentation for an example of setting formatted text in the XML resource file.

When required, TextView will use Factory to create final or intermediate Spannables. Likewise it will use Factory to create final or intermediate Editables. If the passed text is a PrecomputedText but the parameters used to create the PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure the parameters match, you can call setTextMetricsParams before calling this.
Params:
  • text – text to be displayed
Throws:
@attrref android.R.styleable#TextView_text
/** * Sets the text to be displayed. TextView <em>does not</em> accept * HTML-like formatting, which you can do with text strings in XML resource files. * To style your strings, attach android.text.style.* objects to a * {@link android.text.SpannableString}, or see the * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> * Available Resource Types</a> documentation for an example of setting * formatted text in the XML resource file. * <p/> * When required, TextView will use {@link android.text.Spannable.Factory} to create final or * intermediate {@link Spannable Spannables}. Likewise it will use * {@link android.text.Editable.Factory} to create final or intermediate * {@link Editable Editables}. * * If the passed text is a {@link PrecomputedText} but the parameters used to create the * PrecomputedText mismatches with this TextView, IllegalArgumentException is thrown. To ensure * the parameters match, you can call {@link TextView#setTextMetricsParams} before calling this. * * @param text text to be displayed * * @attr ref android.R.styleable#TextView_text * @throws IllegalArgumentException if the passed text is a {@link PrecomputedText} but the * parameters used to create the PrecomputedText mismatches * with this TextView. */
@android.view.RemotableViewMethod public final void setText(CharSequence text) { setText(text, mBufferType); }
Sets the text to be displayed but retains the cursor position. Same as setText(CharSequence) except that the cursor position (if any) is retained in the new text.

When required, TextView will use Factory to create final or intermediate Spannables. Likewise it will use Factory to create final or intermediate Editables.
Params:
  • text – text to be displayed
See Also:
/** * Sets the text to be displayed but retains the cursor position. Same as * {@link #setText(CharSequence)} except that the cursor position (if any) is retained in the * new text. * <p/> * When required, TextView will use {@link android.text.Spannable.Factory} to create final or * intermediate {@link Spannable Spannables}. Likewise it will use * {@link android.text.Editable.Factory} to create final or intermediate * {@link Editable Editables}. * * @param text text to be displayed * * @see #setText(CharSequence) */
@android.view.RemotableViewMethod public final void setTextKeepState(CharSequence text) { setTextKeepState(text, mBufferType); }
Sets the text to be displayed and the BufferType.

When required, TextView will use Factory to create final or intermediate Spannables. Likewise it will use Factory to create final or intermediate Editables.
Params:
  • text – text to be displayed
  • type – a BufferType which defines whether the text is stored as a static text, styleable/spannable text, or editable text
See Also:
@attrref android.R.styleable#TextView_text
@attrref android.R.styleable#TextView_bufferType
/** * Sets the text to be displayed and the {@link android.widget.TextView.BufferType}. * <p/> * When required, TextView will use {@link android.text.Spannable.Factory} to create final or * intermediate {@link Spannable Spannables}. Likewise it will use * {@link android.text.Editable.Factory} to create final or intermediate * {@link Editable Editables}. * * @param text text to be displayed * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is * stored as a static text, styleable/spannable text, or editable text * * @see #setText(CharSequence) * @see android.widget.TextView.BufferType * @see #setSpannableFactory(Spannable.Factory) * @see #setEditableFactory(Editable.Factory) * * @attr ref android.R.styleable#TextView_text * @attr ref android.R.styleable#TextView_bufferType */
public void setText(CharSequence text, BufferType type) { setText(text, type, true, 0); if (mCharWrapper != null) { mCharWrapper.mChars = null; } } private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) { mTextSetFromXmlOrResourceId = false; if (text == null) { text = ""; } // If suggestions are not enabled, remove the suggestion spans from the text if (!isSuggestionsEnabled()) { text = removeSuggestionSpans(text); } if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); if (text instanceof Spanned && ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { setHorizontalFadingEdgeEnabled(true); mMarqueeFadeMode = MARQUEE_FADE_NORMAL; } else { setHorizontalFadingEdgeEnabled(false); mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; } setEllipsize(TextUtils.TruncateAt.MARQUEE); } int n = mFilters.length; for (int i = 0; i < n; i++) { CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); if (out != null) { text = out; } } if (notifyBefore) { if (mText != null) { oldlen = mText.length(); sendBeforeTextChanged(mText, 0, oldlen, text.length()); } else { sendBeforeTextChanged("", 0, 0, text.length()); } } boolean needEditableForNotification = false; if (mListeners != null && mListeners.size() != 0) { needEditableForNotification = true; } PrecomputedText precomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null; if (type == BufferType.EDITABLE || getKeyListener() != null || needEditableForNotification) { createEditorIfNeeded(); mEditor.forgetUndoRedo(); Editable t = mEditableFactory.newEditable(text); text = t; setFilters(t, mFilters); InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) imm.restartInput(this); } else if (precomputed != null) { if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } if (!precomputed.getParams().isSameTextMetricsInternal( getPaint(), mTextDir, mBreakStrategy, mHyphenationFrequency)) { throw new IllegalArgumentException( "PrecomputedText's Parameters don't match the parameters of this TextView." + "Consider using setTextMetricsParams(precomputedText.getParams()) " + "to override the settings of this TextView: " + "PrecomputedText: " + precomputed.getParams() + "TextView: " + getTextMetricsParams()); } } else if (type == BufferType.SPANNABLE || mMovement != null) { text = mSpannableFactory.newSpannable(text); } else if (!(text instanceof CharWrapper)) { text = TextUtils.stringOrSpannedString(text); } if (mAutoLinkMask != 0) { Spannable s2; if (type == BufferType.EDITABLE || text instanceof Spannable) { s2 = (Spannable) text; } else { s2 = mSpannableFactory.newSpannable(text); } if (Linkify.addLinks(s2, mAutoLinkMask)) { text = s2; type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; /* * We must go ahead and set the text before changing the * movement method, because setMovementMethod() may call * setText() again to try to upgrade the buffer type. */ setTextInternal(text); // Do not change the movement method for text that support text selection as it // would prevent an arbitrary cursor displacement. if (mLinksClickable && !textCanBeSelected()) { setMovementMethod(LinkMovementMethod.getInstance()); } } } mBufferType = type; setTextInternal(text); if (mTransformation == null) { mTransformed = text; } else { mTransformed = mTransformation.getTransformation(text, this); } final int textLength = text.length(); if (text instanceof Spannable && !mAllowTransformationLengthChange) { Spannable sp = (Spannable) text; // Remove any ChangeWatchers that might have come from other TextViews. final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); final int count = watchers.length; for (int i = 0; i < count; i++) { sp.removeSpan(watchers[i]); } if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); if (mEditor != null) mEditor.addSpanWatchers(sp); if (mTransformation != null) { sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); } if (mMovement != null) { mMovement.initialize(this, (Spannable) text); /* * Initializing the movement method will have set the * selection, so reset mSelectionMoved to keep that from * interfering with the normal on-focus selection-setting. */ if (mEditor != null) mEditor.mSelectionMoved = false; } } if (mLayout != null) { checkForRelayout(); } sendOnTextChanged(text, 0, oldlen, textLength); onTextChanged(text, 0, oldlen, textLength); notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); if (needEditableForNotification) { sendAfterTextChanged((Editable) text); } else { notifyAutoFillManagerAfterTextChangedIfNeeded(); } // SelectionModifierCursorController depends on textCanBeSelected, which depends on text if (mEditor != null) mEditor.prepareCursorControllers(); }
Sets the TextView to display the specified slice of the specified char array. You must promise that you will not change the contents of the array except for right before another call to setText(), since the TextView has no way to know that the text has changed and that it needs to invalidate and re-layout.
Params:
  • text – char array to be displayed
  • start – start index in the char array
  • len – length of char count after start
/** * Sets the TextView to display the specified slice of the specified * char array. You must promise that you will not change the contents * of the array except for right before another call to setText(), * since the TextView has no way to know that the text * has changed and that it needs to invalidate and re-layout. * * @param text char array to be displayed * @param start start index in the char array * @param len length of char count after {@code start} */
public final void setText(char[] text, int start, int len) { int oldlen = 0; if (start < 0 || len < 0 || start + len > text.length) { throw new IndexOutOfBoundsException(start + ", " + len); } /* * We must do the before-notification here ourselves because if * the old text is a CharWrapper we destroy it before calling * into the normal path. */ if (mText != null) { oldlen = mText.length(); sendBeforeTextChanged(mText, 0, oldlen, len); } else { sendBeforeTextChanged("", 0, 0, len); } if (mCharWrapper == null) { mCharWrapper = new CharWrapper(text, start, len); } else { mCharWrapper.set(text, start, len); } setText(mCharWrapper, mBufferType, false, oldlen); }
Sets the text to be displayed and the BufferType but retains the cursor position. Same as setText(CharSequence, BufferType) except that the cursor position (if any) is retained in the new text.

When required, TextView will use Factory to create final or intermediate Spannables. Likewise it will use Factory to create final or intermediate Editables.
Params:
  • text – text to be displayed
  • type – a BufferType which defines whether the text is stored as a static text, styleable/spannable text, or editable text
See Also:
/** * Sets the text to be displayed and the {@link android.widget.TextView.BufferType} but retains * the cursor position. Same as * {@link #setText(CharSequence, android.widget.TextView.BufferType)} except that the cursor * position (if any) is retained in the new text. * <p/> * When required, TextView will use {@link android.text.Spannable.Factory} to create final or * intermediate {@link Spannable Spannables}. Likewise it will use * {@link android.text.Editable.Factory} to create final or intermediate * {@link Editable Editables}. * * @param text text to be displayed * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is * stored as a static text, styleable/spannable text, or editable text * * @see #setText(CharSequence, android.widget.TextView.BufferType) */
public final void setTextKeepState(CharSequence text, BufferType type) { int start = getSelectionStart(); int end = getSelectionEnd(); int len = text.length(); setText(text, type); if (start >= 0 || end >= 0) { if (mSpannable != null) { Selection.setSelection(mSpannable, Math.max(0, Math.min(start, len)), Math.max(0, Math.min(end, len))); } } }
Sets the text to be displayed using a string resource identifier.
Params:
  • resid – the resource identifier of the string resource to be displayed
See Also:
@attrref android.R.styleable#TextView_text
/** * Sets the text to be displayed using a string resource identifier. * * @param resid the resource identifier of the string resource to be displayed * * @see #setText(CharSequence) * * @attr ref android.R.styleable#TextView_text */
@android.view.RemotableViewMethod public final void setText(@StringRes int resid) { setText(getContext().getResources().getText(resid)); mTextSetFromXmlOrResourceId = true; mTextId = resid; }
Sets the text to be displayed using a string resource identifier and the BufferType.

When required, TextView will use Factory to create final or intermediate Spannables. Likewise it will use Factory to create final or intermediate Editables.
Params:
  • resid – the resource identifier of the string resource to be displayed
  • type – a BufferType which defines whether the text is stored as a static text, styleable/spannable text, or editable text
See Also:
@attrref android.R.styleable#TextView_text
@attrref android.R.styleable#TextView_bufferType
/** * Sets the text to be displayed using a string resource identifier and the * {@link android.widget.TextView.BufferType}. * <p/> * When required, TextView will use {@link android.text.Spannable.Factory} to create final or * intermediate {@link Spannable Spannables}. Likewise it will use * {@link android.text.Editable.Factory} to create final or intermediate * {@link Editable Editables}. * * @param resid the resource identifier of the string resource to be displayed * @param type a {@link android.widget.TextView.BufferType} which defines whether the text is * stored as a static text, styleable/spannable text, or editable text * * @see #setText(int) * @see #setText(CharSequence) * @see android.widget.TextView.BufferType * @see #setSpannableFactory(Spannable.Factory) * @see #setEditableFactory(Editable.Factory) * * @attr ref android.R.styleable#TextView_text * @attr ref android.R.styleable#TextView_bufferType */
public final void setText(@StringRes int resid, BufferType type) { setText(getContext().getResources().getText(resid), type); mTextSetFromXmlOrResourceId = true; mTextId = resid; }
Sets the text to be displayed when the text of the TextView is empty. Null means to use the normal empty text. The hint does not currently participate in determining the size of the view.
@attrref android.R.styleable#TextView_hint
/** * Sets the text to be displayed when the text of the TextView is empty. * Null means to use the normal empty text. The hint does not currently * participate in determining the size of the view. * * @attr ref android.R.styleable#TextView_hint */
@android.view.RemotableViewMethod public final void setHint(CharSequence hint) { setHintInternal(hint); if (mEditor != null && isInputMethodTarget()) { mEditor.reportExtractedText(); } } private void setHintInternal(CharSequence hint) { mHint = TextUtils.stringOrSpannedString(hint); if (mLayout != null) { checkForRelayout(); } if (mText.length() == 0) { invalidate(); } // Invalidate display list if hint is currently used if (mEditor != null && mText.length() == 0 && mHint != null) { mEditor.invalidateTextDisplayList(); } }
Sets the text to be displayed when the text of the TextView is empty, from a resource.
@attrref android.R.styleable#TextView_hint
/** * Sets the text to be displayed when the text of the TextView is empty, * from a resource. * * @attr ref android.R.styleable#TextView_hint */
@android.view.RemotableViewMethod public final void setHint(@StringRes int resid) { setHint(getContext().getResources().getText(resid)); }
Returns the hint that is displayed when the text of the TextView is empty.
@attrref android.R.styleable#TextView_hint
/** * Returns the hint that is displayed when the text of the TextView * is empty. * * @attr ref android.R.styleable#TextView_hint */
@ViewDebug.CapturedViewProperty public CharSequence getHint() { return mHint; } boolean isSingleLine() { return mSingleLine; } private static boolean isMultilineInputType(int type) { return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); }
Removes the suggestion spans.
/** * Removes the suggestion spans. */
CharSequence removeSuggestionSpans(CharSequence text) { if (text instanceof Spanned) { Spannable spannable; if (text instanceof Spannable) { spannable = (Spannable) text; } else { spannable = mSpannableFactory.newSpannable(text); } SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); if (spans.length == 0) { return text; } else { text = spannable; } for (int i = 0; i < spans.length; i++) { spannable.removeSpan(spans[i]); } } return text; }
Set the type of the content with a constant as defined for EditorInfo.inputType. This will take care of changing the key listener, by calling setKeyListener(KeyListener), to match the given content type. If the given content type is InputType.TYPE_NULL then a soft keyboard will not be displayed for this text view. Note that the maximum number of displayed lines (see setMaxLines(int)) will be modified if you change the InputType.TYPE_TEXT_FLAG_MULTI_LINE flag of the input type.
See Also:
@attrref android.R.styleable#TextView_inputType
/** * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} * then a soft keyboard will not be displayed for this text view. * * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input * type. * * @see #getInputType() * @see #setRawInputType(int) * @see android.text.InputType * @attr ref android.R.styleable#TextView_inputType */
public void setInputType(int type) { final boolean wasPassword = isPasswordInputType(getInputType()); final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); setInputType(type, false); final boolean isPassword = isPasswordInputType(type); final boolean isVisiblePassword = isVisiblePasswordInputType(type); boolean forceUpdate = false; if (isPassword) { setTransformationMethod(PasswordTransformationMethod.getInstance()); setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, Typeface.NORMAL, -1 /* weight, not specifeid */); } else if (isVisiblePassword) { if (mTransformation == PasswordTransformationMethod.getInstance()) { forceUpdate = true; } setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, MONOSPACE, Typeface.NORMAL, -1 /* weight, not specified */); } else if (wasPassword || wasVisiblePassword) { // not in password mode, clean up typeface and transformation setTypefaceFromAttrs(null/* fontTypeface */, null /* fontFamily */, DEFAULT_TYPEFACE /* typeface index */, Typeface.NORMAL, -1 /* weight, not specified */); if (mTransformation == PasswordTransformationMethod.getInstance()) { forceUpdate = true; } } boolean singleLine = !isMultilineInputType(type); // We need to update the single line mode if it has changed or we // were previously in password mode. if (mSingleLine != singleLine || forceUpdate) { // Change single line mode, but only change the transformation if // we are not in password mode. applySingleLine(singleLine, !isPassword, true); } if (!isSuggestionsEnabled()) { setTextInternal(removeSuggestionSpans(mText)); } InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) imm.restartInput(this); }
It would be better to rely on the input type for everything. A password inputType should have a password transformation. We should hence use isPasswordInputType instead of this method. We should: - Call setInputType in setKeyListener instead of changing the input type directly (which would install the correct transformation). - Refuse the installation of a non-password transformation in setTransformation if the input type is password. However, this is like this for legacy reasons and we cannot break existing apps. This method is useful since it matches what the user can see (obfuscated text or not).
Returns:true if the current transformation method is of the password type.
/** * It would be better to rely on the input type for everything. A password inputType should have * a password transformation. We should hence use isPasswordInputType instead of this method. * * We should: * - Call setInputType in setKeyListener instead of changing the input type directly (which * would install the correct transformation). * - Refuse the installation of a non-password transformation in setTransformation if the input * type is password. * * However, this is like this for legacy reasons and we cannot break existing apps. This method * is useful since it matches what the user can see (obfuscated text or not). * * @return true if the current transformation method is of the password type. */
boolean hasPasswordTransformationMethod() { return mTransformation instanceof PasswordTransformationMethod; } static boolean isPasswordInputType(int inputType) { final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); return variation == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) || variation == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) || variation == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); } private static boolean isVisiblePasswordInputType(int inputType) { final int variation = inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); return variation == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); }
Directly change the content type integer of the text view, without modifying any other state.
See Also:
@attrref android.R.styleable#TextView_inputType
/** * Directly change the content type integer of the text view, without * modifying any other state. * @see #setInputType(int) * @see android.text.InputType * @attr ref android.R.styleable#TextView_inputType */
public void setRawInputType(int type) { if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value createEditorIfNeeded(); mEditor.mInputType = type; }
See Also:
Returns:null if the key listener should use pre-O (locale-independent). Otherwise a Locale object that can be used to customize key various listeners.
/** * @return {@code null} if the key listener should use pre-O (locale-independent). Otherwise * a {@code Locale} object that can be used to customize key various listeners. * @see DateKeyListener#getInstance(Locale) * @see DateTimeKeyListener#getInstance(Locale) * @see DigitsKeyListener#getInstance(Locale) * @see TimeKeyListener#getInstance(Locale) */
@Nullable private Locale getCustomLocaleForKeyListenerOrNull() { if (!mUseInternationalizedInput) { // If the application does not target O, stick to the previous behavior. return null; } final LocaleList locales = getImeHintLocales(); if (locales == null) { // If the application does not explicitly specify IME hint locale, also stick to the // previous behavior. return null; } return locales.get(0); } private void setInputType(int type, boolean direct) { final int cls = type & EditorInfo.TYPE_MASK_CLASS; KeyListener input; if (cls == EditorInfo.TYPE_CLASS_TEXT) { boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; TextKeyListener.Capitalize cap; if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { cap = TextKeyListener.Capitalize.CHARACTERS; } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { cap = TextKeyListener.Capitalize.WORDS; } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { cap = TextKeyListener.Capitalize.SENTENCES; } else { cap = TextKeyListener.Capitalize.NONE; } input = TextKeyListener.getInstance(autotext, cap); } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { final Locale locale = getCustomLocaleForKeyListenerOrNull(); input = DigitsKeyListener.getInstance( locale, (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); if (locale != null) { // Override type, if necessary for i18n. int newType = input.getInputType(); final int newClass = newType & EditorInfo.TYPE_MASK_CLASS; if (newClass != EditorInfo.TYPE_CLASS_NUMBER) { // The class is different from the original class. So we need to override // 'type'. But we want to keep the password flag if it's there. if ((type & EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD) != 0) { newType |= EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; } type = newType; } } } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { final Locale locale = getCustomLocaleForKeyListenerOrNull(); switch (type & EditorInfo.TYPE_MASK_VARIATION) { case EditorInfo.TYPE_DATETIME_VARIATION_DATE: input = DateKeyListener.getInstance(locale); break; case EditorInfo.TYPE_DATETIME_VARIATION_TIME: input = TimeKeyListener.getInstance(locale); break; default: input = DateTimeKeyListener.getInstance(locale); break; } if (mUseInternationalizedInput) { type = input.getInputType(); // Override type, if necessary for i18n. } } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { input = DialerKeyListener.getInstance(); } else { input = TextKeyListener.getInstance(); } setRawInputType(type); mListenerChanged = false; if (direct) { createEditorIfNeeded(); mEditor.mKeyListener = input; } else { setKeyListenerOnly(input); } }
Get the type of the editable content.
See Also:
/** * Get the type of the editable content. * * @see #setInputType(int) * @see android.text.InputType */
public int getInputType() { return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; }
Change the editor type integer associated with the text view, which is reported to an Input Method Editor (IME) with EditorInfo.imeOptions when it has focus.
See Also:
@attrref android.R.styleable#TextView_imeOptions
/** * Change the editor type integer associated with the text view, which * is reported to an Input Method Editor (IME) with {@link EditorInfo#imeOptions} * when it has focus. * @see #getImeOptions * @see android.view.inputmethod.EditorInfo * @attr ref android.R.styleable#TextView_imeOptions */
public void setImeOptions(int imeOptions) { createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeOptions = imeOptions; }
Get the type of the Input Method Editor (IME).
See Also:
Returns:the type of the IME
/** * Get the type of the Input Method Editor (IME). * @return the type of the IME * @see #setImeOptions(int) * @see android.view.inputmethod.EditorInfo */
public int getImeOptions() { return mEditor != null && mEditor.mInputContentType != null ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; }
Change the custom IME action associated with the text view, which will be reported to an IME with EditorInfo.actionLabel and EditorInfo.actionId when it has focus.
See Also:
@attrref android.R.styleable#TextView_imeActionLabel
@attrref android.R.styleable#TextView_imeActionId
/** * Change the custom IME action associated with the text view, which * will be reported to an IME with {@link EditorInfo#actionLabel} * and {@link EditorInfo#actionId} when it has focus. * @see #getImeActionLabel * @see #getImeActionId * @see android.view.inputmethod.EditorInfo * @attr ref android.R.styleable#TextView_imeActionLabel * @attr ref android.R.styleable#TextView_imeActionId */
public void setImeActionLabel(CharSequence label, int actionId) { createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeActionLabel = label; mEditor.mInputContentType.imeActionId = actionId; }
Get the IME action label previous set with setImeActionLabel.
See Also:
/** * Get the IME action label previous set with {@link #setImeActionLabel}. * * @see #setImeActionLabel * @see android.view.inputmethod.EditorInfo */
public CharSequence getImeActionLabel() { return mEditor != null && mEditor.mInputContentType != null ? mEditor.mInputContentType.imeActionLabel : null; }
Get the IME action ID previous set with setImeActionLabel.
See Also:
/** * Get the IME action ID previous set with {@link #setImeActionLabel}. * * @see #setImeActionLabel * @see android.view.inputmethod.EditorInfo */
public int getImeActionId() { return mEditor != null && mEditor.mInputContentType != null ? mEditor.mInputContentType.imeActionId : 0; }
Set a special listener to be called when an action is performed on the text view. This will be called when the enter key is pressed, or when an action supplied to the IME is selected by the user. Setting this means that the normal hard key event will not insert a newline into the text view, even if it is multi-line; holding down the ALT modifier will, however, allow the user to insert a newline character.
/** * Set a special listener to be called when an action is performed * on the text view. This will be called when the enter key is pressed, * or when an action supplied to the IME is selected by the user. Setting * this means that the normal hard key event will not insert a newline * into the text view, even if it is multi-line; holding down the ALT * modifier will, however, allow the user to insert a newline character. */
public void setOnEditorActionListener(OnEditorActionListener l) { createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.onEditorActionListener = l; }
Called when an attached input method calls InputConnection.performEditorAction() for this text view. The default implementation will call your action listener supplied to setOnEditorActionListener, or perform a standard operation for EditorInfo.IME_ACTION_NEXT, EditorInfo.IME_ACTION_PREVIOUS, or EditorInfo.IME_ACTION_DONE.

For backwards compatibility, if no IME options have been set and the text view would not normally advance focus on enter, then the NEXT and DONE actions received here will be turned into an enter key down/up pair to go through the normal key handling.

Params:
  • actionCode – The code of the action being performed.
See Also:
/** * Called when an attached input method calls * {@link InputConnection#performEditorAction(int) * InputConnection.performEditorAction()} * for this text view. The default implementation will call your action * listener supplied to {@link #setOnEditorActionListener}, or perform * a standard operation for {@link EditorInfo#IME_ACTION_NEXT * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE * EditorInfo.IME_ACTION_DONE}. * * <p>For backwards compatibility, if no IME options have been set and the * text view would not normally advance focus on enter, then * the NEXT and DONE actions received here will be turned into an enter * key down/up pair to go through the normal key handling. * * @param actionCode The code of the action being performed. * * @see #setOnEditorActionListener */
public void onEditorAction(int actionCode) { final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; if (ict != null) { if (ict.onEditorActionListener != null) { if (ict.onEditorActionListener.onEditorAction(this, actionCode, null)) { return; } } // This is the handling for some default action. // Note that for backwards compatibility we don't do this // default handling if explicit ime options have not been given, // instead turning this into the normal enter key codes that an // app may be expecting. if (actionCode == EditorInfo.IME_ACTION_NEXT) { View v = focusSearch(FOCUS_FORWARD); if (v != null) { if (!v.requestFocus(FOCUS_FORWARD)) { throw new IllegalStateException("focus search returned a view " + "that wasn't able to take focus!"); } } return; } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { View v = focusSearch(FOCUS_BACKWARD); if (v != null) { if (!v.requestFocus(FOCUS_BACKWARD)) { throw new IllegalStateException("focus search returned a view " + "that wasn't able to take focus!"); } } return; } else if (actionCode == EditorInfo.IME_ACTION_DONE) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null && imm.isActive(this)) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } return; } } ViewRootImpl viewRootImpl = getViewRootImpl(); if (viewRootImpl != null) { long eventTime = SystemClock.uptimeMillis(); viewRootImpl.dispatchKeyFromIme( new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE | KeyEvent.FLAG_EDITOR_ACTION)); viewRootImpl.dispatchKeyFromIme( new KeyEvent(SystemClock.uptimeMillis(), eventTime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE | KeyEvent.FLAG_EDITOR_ACTION)); } }
Set the private content type of the text, which is the EditorInfo.privateImeOptions field that will be filled in when creating an input connection.
See Also:
@attrref android.R.styleable#TextView_privateImeOptions
/** * Set the private content type of the text, which is the * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} * field that will be filled in when creating an input connection. * * @see #getPrivateImeOptions() * @see EditorInfo#privateImeOptions * @attr ref android.R.styleable#TextView_privateImeOptions */
public void setPrivateImeOptions(String type) { createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.privateImeOptions = type; }
Get the private type of the content.
See Also:
/** * Get the private type of the content. * * @see #setPrivateImeOptions(String) * @see EditorInfo#privateImeOptions */
public String getPrivateImeOptions() { return mEditor != null && mEditor.mInputContentType != null ? mEditor.mInputContentType.privateImeOptions : null; }
Set the extra input data of the text, which is the TextBoxAttribute.extras Bundle that will be filled in when creating an input connection. The given integer is the resource identifier of an XML resource holding an <input-extras> XML tree.
See Also:
@attrref android.R.styleable#TextView_editorExtras
/** * Set the extra input data of the text, which is the * {@link EditorInfo#extras TextBoxAttribute.extras} * Bundle that will be filled in when creating an input connection. The * given integer is the resource identifier of an XML resource holding an * {@link android.R.styleable#InputExtras &lt;input-extras&gt;} XML tree. * * @see #getInputExtras(boolean) * @see EditorInfo#extras * @attr ref android.R.styleable#TextView_editorExtras */
public void setInputExtras(@XmlRes int xmlResId) throws XmlPullParserException, IOException { createEditorIfNeeded(); XmlResourceParser parser = getResources().getXml(xmlResId); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.extras = new Bundle(); getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); }
Retrieve the input extras currently associated with the text view, which can be viewed as well as modified.
Params:
  • create – If true, the extras will be created if they don't already exist. Otherwise, null will be returned if none have been created.
See Also:
@attrref android.R.styleable#TextView_editorExtras
/** * Retrieve the input extras currently associated with the text view, which * can be viewed as well as modified. * * @param create If true, the extras will be created if they don't already * exist. Otherwise, null will be returned if none have been created. * @see #setInputExtras(int) * @see EditorInfo#extras * @attr ref android.R.styleable#TextView_editorExtras */
public Bundle getInputExtras(boolean create) { if (mEditor == null && !create) return null; createEditorIfNeeded(); if (mEditor.mInputContentType == null) { if (!create) return null; mEditor.createInputContentTypeIfNeeded(); } if (mEditor.mInputContentType.extras == null) { if (!create) return null; mEditor.mInputContentType.extras = new Bundle(); } return mEditor.mInputContentType.extras; }
Change "hint" locales associated with the text view, which will be reported to an IME with EditorInfo.hintLocales when it has focus. Starting with Android O, this also causes internationalized listeners to be created (or change locale) based on the first locale in the input locale list.

Note: If you want new "hint" to take effect immediately you need to call InputMethodManager.restartInput(View).

Params:
  • hintLocales – List of the languages that the user is supposed to switch to no matter what input method subtype is currently used. Set null to clear the current "hint".
See Also:
/** * Change "hint" locales associated with the text view, which will be reported to an IME with * {@link EditorInfo#hintLocales} when it has focus. * * Starting with Android O, this also causes internationalized listeners to be created (or * change locale) based on the first locale in the input locale list. * * <p><strong>Note:</strong> If you want new "hint" to take effect immediately you need to * call {@link InputMethodManager#restartInput(View)}.</p> * @param hintLocales List of the languages that the user is supposed to switch to no matter * what input method subtype is currently used. Set {@code null} to clear the current "hint". * @see #getImeHintLocales() * @see android.view.inputmethod.EditorInfo#hintLocales */
public void setImeHintLocales(@Nullable LocaleList hintLocales) { createEditorIfNeeded(); mEditor.createInputContentTypeIfNeeded(); mEditor.mInputContentType.imeHintLocales = hintLocales; if (mUseInternationalizedInput) { changeListenerLocaleTo(hintLocales == null ? null : hintLocales.get(0)); } }
See Also:
Returns:The current languages list "hint". null when no "hint" is available.
/** * @return The current languages list "hint". {@code null} when no "hint" is available. * @see #setImeHintLocales(LocaleList) * @see android.view.inputmethod.EditorInfo#hintLocales */
@Nullable public LocaleList getImeHintLocales() { if (mEditor == null) { return null; } if (mEditor.mInputContentType == null) { return null; } return mEditor.mInputContentType.imeHintLocales; }
Returns the error message that was set to be displayed with setError, or null if no error was set or if it the error was cleared by the widget after user input.
/** * Returns the error message that was set to be displayed with * {@link #setError}, or <code>null</code> if no error was set * or if it the error was cleared by the widget after user input. */
public CharSequence getError() { return mEditor == null ? null : mEditor.mError; }
Sets the right-hand compound drawable of the TextView to the "error" icon and sets an error message that will be displayed in a popup when the TextView has focus. The icon and error message will be reset to null when any key events cause changes to the TextView's text. If the error is null, the error message and icon will be cleared.
/** * Sets the right-hand compound drawable of the TextView to the "error" * icon and sets an error message that will be displayed in a popup when * the TextView has focus. The icon and error message will be reset to * null when any key events cause changes to the TextView's text. If the * <code>error</code> is <code>null</code>, the error message and icon * will be cleared. */
@android.view.RemotableViewMethod public void setError(CharSequence error) { if (error == null) { setError(null, null); } else { Drawable dr = getContext().getDrawable( com.android.internal.R.drawable.indicator_input_error); dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); setError(error, dr); } }
Sets the right-hand compound drawable of the TextView to the specified icon and sets an error message that will be displayed in a popup when the TextView has focus. The icon and error message will be reset to null when any key events cause changes to the TextView's text. The drawable must already have had Drawable.setBounds set on it. If the error is null, the error message will be cleared (and you should provide a null icon as well).
/** * Sets the right-hand compound drawable of the TextView to the specified * icon and sets an error message that will be displayed in a popup when * the TextView has focus. The icon and error message will be reset to * null when any key events cause changes to the TextView's text. The * drawable must already have had {@link Drawable#setBounds} set on it. * If the <code>error</code> is <code>null</code>, the error message will * be cleared (and you should provide a <code>null</code> icon as well). */
public void setError(CharSequence error, Drawable icon) { createEditorIfNeeded(); mEditor.setError(error, icon); notifyViewAccessibilityStateChangedIfNeeded( AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @Override protected boolean setFrame(int l, int t, int r, int b) { boolean result = super.setFrame(l, t, r, b); if (mEditor != null) mEditor.setFrame(); restartMarqueeIfNeeded(); return result; } private void restartMarqueeIfNeeded() { if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { mRestartMarquee = false; startMarquee(); } }
Sets the list of input filters that will be used if the buffer is Editable. Has no effect otherwise.
@attrref android.R.styleable#TextView_maxLength
/** * Sets the list of input filters that will be used if the buffer is * Editable. Has no effect otherwise. * * @attr ref android.R.styleable#TextView_maxLength */
public void setFilters(InputFilter[] filters) { if (filters == null) { throw new IllegalArgumentException(); } mFilters = filters; if (mText instanceof Editable) { setFilters((Editable) mText, filters); } }
Sets the list of input filters on the specified Editable, and includes mInput in the list if it is an InputFilter.
/** * Sets the list of input filters on the specified Editable, * and includes mInput in the list if it is an InputFilter. */
private void setFilters(Editable e, InputFilter[] filters) { if (mEditor != null) { final boolean undoFilter = mEditor.mUndoInputFilter != null; final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; int num = 0; if (undoFilter) num++; if (keyFilter) num++; if (num > 0) { InputFilter[] nf = new InputFilter[filters.length + num]; System.arraycopy(filters, 0, nf, 0, filters.length); num = 0; if (undoFilter) { nf[filters.length] = mEditor.mUndoInputFilter; num++; } if (keyFilter) { nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; } e.setFilters(nf); return; } } e.setFilters(filters); }
Returns the current list of input filters.
@attrref android.R.styleable#TextView_maxLength
/** * Returns the current list of input filters. * * @attr ref android.R.styleable#TextView_maxLength */
public InputFilter[] getFilters() { return mFilters; } ///////////////////////////////////////////////////////////////////////// private int getBoxHeight(Layout l) { Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; int padding = (l == mHintLayout) ? getCompoundPaddingTop() + getCompoundPaddingBottom() : getExtendedPaddingTop() + getExtendedPaddingBottom(); return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; } int getVerticalOffset(boolean forceNormal) { int voffset = 0; final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; Layout l = mLayout; if (!forceNormal && mText.length() == 0 && mHintLayout != null) { l = mHintLayout; } if (gravity != Gravity.TOP) { int boxht = getBoxHeight(l); int textht = l.getHeight(); if (textht < boxht) { if (gravity == Gravity.BOTTOM) { voffset = boxht - textht; } else { // (gravity == Gravity.CENTER_VERTICAL) voffset = (boxht - textht) >> 1; } } } return voffset; } private int getBottomVerticalOffset(boolean forceNormal) { int voffset = 0; final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; Layout l = mLayout; if (!forceNormal && mText.length() == 0 && mHintLayout != null) { l = mHintLayout; } if (gravity != Gravity.BOTTOM) { int boxht = getBoxHeight(l); int textht = l.getHeight(); if (textht < boxht) { if (gravity == Gravity.TOP) { voffset = boxht - textht; } else { // (gravity == Gravity.CENTER_VERTICAL) voffset = (boxht - textht) >> 1; } } } return voffset; } void invalidateCursorPath() { if (mHighlightPathBogus) { invalidateCursor(); } else { final int horizontalPadding = getCompoundPaddingLeft(); final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); if (mEditor.mDrawableForCursor == null) { synchronized (TEMP_RECTF) { /* * The reason for this concern about the thickness of the * cursor and doing the floor/ceil on the coordinates is that * some EditTexts (notably textfields in the Browser) have * anti-aliased text where not all the characters are * necessarily at integer-multiple locations. This should * make sure the entire cursor gets invalidated instead of * sometimes missing half a pixel. */ float thick = (float) Math.ceil(mTextPaint.getStrokeWidth()); if (thick < 1.0f) { thick = 1.0f; } thick /= 2.0f; // mHighlightPath is guaranteed to be non null at that point. mHighlightPath.computeBounds(TEMP_RECTF, false); invalidate((int) Math.floor(horizontalPadding + TEMP_RECTF.left - thick), (int) Math.floor(verticalPadding + TEMP_RECTF.top - thick), (int) Math.ceil(horizontalPadding + TEMP_RECTF.right + thick), (int) Math.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); } } else { final Rect bounds = mEditor.mDrawableForCursor.getBounds(); invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, bounds.right + horizontalPadding, bounds.bottom + verticalPadding); } } } void invalidateCursor() { int where = getSelectionEnd(); invalidateCursor(where, where, where); } private void invalidateCursor(int a, int b, int c) { if (a >= 0 || b >= 0 || c >= 0) { int start = Math.min(Math.min(a, b), c); int end = Math.max(Math.max(a, b), c); invalidateRegion(start, end, true /* Also invalidates blinking cursor */); } }
Invalidates the region of text enclosed between the start and end text offsets.
/** * Invalidates the region of text enclosed between the start and end text offsets. */
void invalidateRegion(int start, int end, boolean invalidateCursor) { if (mLayout == null) { invalidate(); } else { int lineStart = mLayout.getLineForOffset(start); int top = mLayout.getLineTop(lineStart); // This is ridiculous, but the descent from the line above // can hang down into the line we really want to redraw, // so we have to invalidate part of the line above to make // sure everything that needs to be redrawn really is. // (But not the whole line above, because that would cause // the same problem with the descenders on the line above it!) if (lineStart > 0) { top -= mLayout.getLineDescent(lineStart - 1); } int lineEnd; if (start == end) { lineEnd = lineStart; } else { lineEnd = mLayout.getLineForOffset(end); } int bottom = mLayout.getLineBottom(lineEnd); // mEditor can be null in case selection is set programmatically. if (invalidateCursor && mEditor != null && mEditor.mDrawableForCursor != null) { final Rect bounds = mEditor.mDrawableForCursor.getBounds(); top = Math.min(top, bounds.top); bottom = Math.max(bottom, bounds.bottom); } final int compoundPaddingLeft = getCompoundPaddingLeft(); final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); int left, right; if (lineStart == lineEnd && !invalidateCursor) { left = (int) mLayout.getPrimaryHorizontal(start); right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); left += compoundPaddingLeft; right += compoundPaddingLeft; } else { // Rectangle bounding box when the region spans several lines left = compoundPaddingLeft; right = getWidth() - getCompoundPaddingRight(); } invalidate(mScrollX + left, verticalPadding + top, mScrollX + right, verticalPadding + bottom); } } private void registerForPreDraw() { if (!mPreDrawRegistered) { getViewTreeObserver().addOnPreDrawListener(this); mPreDrawRegistered = true; } } private void unregisterForPreDraw() { getViewTreeObserver().removeOnPreDrawListener(this); mPreDrawRegistered = false; mPreDrawListenerDetached = false; }
{@inheritDoc}
/** * {@inheritDoc} */
@Override public boolean onPreDraw() { if (mLayout == null) { assumeLayout(); } if (mMovement != null) { /* This code also provides auto-scrolling when a cursor is moved using a * CursorController (insertion point or selection limits). * For selection, ensure start or end is visible depending on controller's state. */ int curs = getSelectionEnd(); // Do not create the controller if it is not already created. if (mEditor != null && mEditor.mSelectionModifierCursorController != null && mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { curs = getSelectionStart(); } /* * TODO: This should really only keep the end in view if * it already was before the text changed. I'm not sure * of a good way to tell from here if it was. */ if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { curs = mText.length(); } if (curs >= 0) { bringPointIntoView(curs); } } else { bringTextIntoView(); } // This has to be checked here since: // - onFocusChanged cannot start it when focus is given to a view with selected text (after // a screen rotation) since layout is not yet initialized at that point. if (mEditor != null && mEditor.mCreatedWithASelection) { mEditor.refreshTextActionMode(); mEditor.mCreatedWithASelection = false; } unregisterForPreDraw(); return true; } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); if (mEditor != null) mEditor.onAttachedToWindow(); if (mPreDrawListenerDetached) { getViewTreeObserver().addOnPreDrawListener(this); mPreDrawListenerDetached = false; } }
@hide
/** @hide */
@Override protected void onDetachedFromWindowInternal() { if (mPreDrawRegistered) { getViewTreeObserver().removeOnPreDrawListener(this); mPreDrawListenerDetached = true; } resetResolvedDrawables(); if (mEditor != null) mEditor.onDetachedFromWindow(); super.onDetachedFromWindowInternal(); } @Override public void onScreenStateChanged(int screenState) { super.onScreenStateChanged(screenState); if (mEditor != null) mEditor.onScreenStateChanged(screenState); } @Override protected boolean isPaddingOffsetRequired() { return mShadowRadius != 0 || mDrawables != null; } @Override protected int getLeftPaddingOffset() { return getCompoundPaddingLeft() - mPaddingLeft + (int) Math.min(0, mShadowDx - mShadowRadius); } @Override protected int getTopPaddingOffset() { return (int) Math.min(0, mShadowDy - mShadowRadius); } @Override protected int getBottomPaddingOffset() { return (int) Math.max(0, mShadowDy + mShadowRadius); } @Override protected int getRightPaddingOffset() { return -(getCompoundPaddingRight() - mPaddingRight) + (int) Math.max(0, mShadowDx + mShadowRadius); } @Override protected boolean verifyDrawable(@NonNull Drawable who) { final boolean verified = super.verifyDrawable(who); if (!verified && mDrawables != null) { for (Drawable dr : mDrawables.mShowing) { if (who == dr) { return true; } } } return verified; } @Override public void jumpDrawablesToCurrentState() { super.jumpDrawablesToCurrentState(); if (mDrawables != null) { for (Drawable dr : mDrawables.mShowing) { if (dr != null) { dr.jumpToCurrentState(); } } } } @Override public void invalidateDrawable(@NonNull Drawable drawable) { boolean handled = false; if (verifyDrawable(drawable)) { final Rect dirty = drawable.getBounds(); int scrollX = mScrollX; int scrollY = mScrollY; // IMPORTANT: The coordinates below are based on the coordinates computed // for each compound drawable in onDraw(). Make sure to update each section // accordingly. final TextView.Drawables drawables = mDrawables; if (drawables != null) { if (drawable == drawables.mShowing[Drawables.LEFT]) { final int compoundPaddingTop = getCompoundPaddingTop(); final int compoundPaddingBottom = getCompoundPaddingBottom(); final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; scrollX += mPaddingLeft; scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; handled = true; } else if (drawable == drawables.mShowing[Drawables.RIGHT]) { final int compoundPaddingTop = getCompoundPaddingTop(); final int compoundPaddingBottom = getCompoundPaddingBottom(); final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; handled = true; } else if (drawable == drawables.mShowing[Drawables.TOP]) { final int compoundPaddingLeft = getCompoundPaddingLeft(); final int compoundPaddingRight = getCompoundPaddingRight(); final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; scrollY += mPaddingTop; handled = true; } else if (drawable == drawables.mShowing[Drawables.BOTTOM]) { final int compoundPaddingLeft = getCompoundPaddingLeft(); final int compoundPaddingRight = getCompoundPaddingRight(); final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); handled = true; } } if (handled) { invalidate(dirty.left + scrollX, dirty.top + scrollY, dirty.right + scrollX, dirty.bottom + scrollY); } } if (!handled) { super.invalidateDrawable(drawable); } } @Override public boolean hasOverlappingRendering() { // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation return ((getBackground() != null && getBackground().getCurrent() != null) || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled()); }
Returns the state of the textIsSelectable flag (See setTextIsSelectable()). Although you have to set this flag to allow users to select and copy text in a non-editable TextView, the content of an EditText can always be selected, independently of the value of this flag.

Returns:True if the text displayed in this TextView can be selected by the user.
@attrref android.R.styleable#TextView_textIsSelectable
/** * * Returns the state of the {@code textIsSelectable} flag (See * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag * to allow users to select and copy text in a non-editable TextView, the content of an * {@link EditText} can always be selected, independently of the value of this flag. * <p> * * @return True if the text displayed in this TextView can be selected by the user. * * @attr ref android.R.styleable#TextView_textIsSelectable */
public boolean isTextSelectable() { return mEditor == null ? false : mEditor.mTextIsSelectable; }
Sets whether the content of this view is selectable by the user. The default is false, meaning that the content is not selectable.

When you use a TextView to display a useful piece of information to the user (such as a contact's address), make it selectable, so that the user can select and copy its content. You can also use set the XML attribute TextView_textIsSelectable.TextView_textIsSelectable to "true".

When you call this method to set the value of textIsSelectable, it sets the flags focusable, focusableInTouchMode, clickable, and longClickable to the same value. These flags correspond to the attributes android:focusable, android:focusableInTouchMode, android:clickable, and android:longClickable. To restore any of these flags to a state you had set previously, call one or more of the following methods: setFocusable(), setFocusableInTouchMode(), setClickable() or setLongClickable().

Params:
  • selectable – Whether the content of this TextView should be selectable.
/** * Sets whether the content of this view is selectable by the user. The default is * {@code false}, meaning that the content is not selectable. * <p> * When you use a TextView to display a useful piece of information to the user (such as a * contact's address), make it selectable, so that the user can select and copy its * content. You can also use set the XML attribute * {@link android.R.styleable#TextView_textIsSelectable} to "true". * <p> * When you call this method to set the value of {@code textIsSelectable}, it sets * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, * and {@code longClickable} to the same value. These flags correspond to the attributes * {@link android.R.styleable#View_focusable android:focusable}, * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, * {@link android.R.styleable#View_clickable android:clickable}, and * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these * flags to a state you had set previously, call one or more of the following methods: * {@link #setFocusable(boolean) setFocusable()}, * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, * {@link #setClickable(boolean) setClickable()} or * {@link #setLongClickable(boolean) setLongClickable()}. * * @param selectable Whether the content of this TextView should be selectable. */
public void setTextIsSelectable(boolean selectable) { if (!selectable && mEditor == null) return; // false is default value with no edit data createEditorIfNeeded(); if (mEditor.mTextIsSelectable == selectable) return; mEditor.mTextIsSelectable = selectable; setFocusableInTouchMode(selectable); setFocusable(FOCUSABLE_AUTO); setClickable(selectable); setLongClickable(selectable); // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); // Called by setText above, but safer in case of future code changes mEditor.prepareCursorControllers(); } @Override protected int[] onCreateDrawableState(int extraSpace) { final int[] drawableState; if (mSingleLine) { drawableState = super.onCreateDrawableState(extraSpace); } else { drawableState = super.onCreateDrawableState(extraSpace + 1); mergeDrawableStates(drawableState, MULTILINE_STATE_SET); } if (isTextSelectable()) { // Disable pressed state, which was introduced when TextView was made clickable. // Prevents text color change. // setClickable(false) would have a similar effect, but it also disables focus changes // and long press actions, which are both needed by text selection. final int length = drawableState.length; for (int i = 0; i < length; i++) { if (drawableState[i] == R.attr.state_pressed) { final int[] nonPressedState = new int[length - 1]; System.arraycopy(drawableState, 0, nonPressedState, 0, i); System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); return nonPressedState; } } } return drawableState; } private Path getUpdatedHighlightPath() { Path highlight = null; Paint highlightPaint = mHighlightPaint; final int selStart = getSelectionStart(); final int selEnd = getSelectionEnd(); if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { if (selStart == selEnd) { if (mEditor != null && mEditor.shouldRenderCursor()) { if (mHighlightPathBogus) { if (mHighlightPath == null) mHighlightPath = new Path(); mHighlightPath.reset(); mLayout.getCursorPath(selStart, mHighlightPath, mText); mEditor.updateCursorPosition(); mHighlightPathBogus = false; } // XXX should pass to skin instead of drawing directly highlightPaint.setColor(mCurTextColor); highlightPaint.setStyle(Paint.Style.STROKE); highlight = mHighlightPath; } } else { if (mHighlightPathBogus) { if (mHighlightPath == null) mHighlightPath = new Path(); mHighlightPath.reset(); mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); mHighlightPathBogus = false; } // XXX should pass to skin instead of drawing directly highlightPaint.setColor(mHighlightColor); highlightPaint.setStyle(Paint.Style.FILL); highlight = mHighlightPath; } } return highlight; }
@hide
/** * @hide */
public int getHorizontalOffsetForDrawables() { return 0; } @Override protected void onDraw(Canvas canvas) { restartMarqueeIfNeeded(); // Draw the background for this view super.onDraw(canvas); final int compoundPaddingLeft = getCompoundPaddingLeft(); final int compoundPaddingTop = getCompoundPaddingTop(); final int compoundPaddingRight = getCompoundPaddingRight(); final int compoundPaddingBottom = getCompoundPaddingBottom(); final int scrollX = mScrollX; final int scrollY = mScrollY; final int right = mRight; final int left = mLeft; final int bottom = mBottom; final int top = mTop; final boolean isLayoutRtl = isLayoutRtl(); final int offset = getHorizontalOffsetForDrawables(); final int leftOffset = isLayoutRtl ? 0 : offset; final int rightOffset = isLayoutRtl ? offset : 0; final Drawables dr = mDrawables; if (dr != null) { /* * Compound, not extended, because the icon is not clipped * if the text height is smaller. */ int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. if (dr.mShowing[Drawables.LEFT] != null) { canvas.save(); canvas.translate(scrollX + mPaddingLeft + leftOffset, scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightLeft) / 2); dr.mShowing[Drawables.LEFT].draw(canvas); canvas.restore(); } // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. if (dr.mShowing[Drawables.RIGHT] != null) { canvas.save(); canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight - rightOffset, scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); dr.mShowing[Drawables.RIGHT].draw(canvas); canvas.restore(); } // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. if (dr.mShowing[Drawables.TOP] != null) { canvas.save(); canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); dr.mShowing[Drawables.TOP].draw(canvas); canvas.restore(); } // IMPORTANT: The coordinates computed are also used in invalidateDrawable() // Make sure to update invalidateDrawable() when changing this code. if (dr.mShowing[Drawables.BOTTOM] != null) { canvas.save(); canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthBottom) / 2, scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); dr.mShowing[Drawables.BOTTOM].draw(canvas); canvas.restore(); } } int color = mCurTextColor; if (mLayout == null) { assumeLayout(); } Layout layout = mLayout; if (mHint != null && mText.length() == 0) { if (mHintTextColor != null) { color = mCurHintTextColor; } layout = mHintLayout; } mTextPaint.setColor(color); mTextPaint.drawableState = getDrawableState(); canvas.save(); /* Would be faster if we didn't have to do this. Can we chop the (displayable) text so that we don't need to do this ever? */ int extendedPaddingTop = getExtendedPaddingTop(); int extendedPaddingBottom = getExtendedPaddingBottom(); final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; final int maxScrollY = mLayout.getHeight() - vspace; float clipLeft = compoundPaddingLeft + scrollX; float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; float clipRight = right - left - getCompoundPaddingRight() + scrollX; float clipBottom = bottom - top + scrollY - ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); if (mShadowRadius != 0) { clipLeft += Math.min(0, mShadowDx - mShadowRadius); clipRight += Math.max(0, mShadowDx + mShadowRadius); clipTop += Math.min(0, mShadowDy - mShadowRadius); clipBottom += Math.max(0, mShadowDy + mShadowRadius); } canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); int voffsetText = 0; int voffsetCursor = 0; // translate in by our padding /* shortcircuit calling getVerticaOffset() */ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { voffsetText = getVerticalOffset(false); voffsetCursor = getVerticalOffset(true); } canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); if (isMarqueeFadeEnabled()) { if (!mSingleLine && getLineCount() == 1 && canMarquee() && (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { final int width = mRight - mLeft; final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); final float dx = mLayout.getLineRight(0) - (width - padding); canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); } if (mMarquee != null && mMarquee.isRunning()) { final float dx = -mMarquee.getScroll(); canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); } } final int cursorOffsetVertical = voffsetCursor - voffsetText; Path highlight = getUpdatedHighlightPath(); if (mEditor != null) { mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); } else { layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } if (mMarquee != null && mMarquee.shouldDrawGhost()) { final float dx = mMarquee.getGhostOffset(); canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); } canvas.restore(); } @Override public void getFocusedRect(Rect r) { if (mLayout == null) { super.getFocusedRect(r); return; } int selEnd = getSelectionEnd(); if (selEnd < 0) { super.getFocusedRect(r); return; } int selStart = getSelectionStart(); if (selStart < 0 || selStart >= selEnd) { int line = mLayout.getLineForOffset(selEnd); r.top = mLayout.getLineTop(line); r.bottom = mLayout.getLineBottom(line); r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; r.right = r.left + 4; } else { int lineStart = mLayout.getLineForOffset(selStart); int lineEnd = mLayout.getLineForOffset(selEnd); r.top = mLayout.getLineTop(lineStart); r.bottom = mLayout.getLineBottom(lineEnd); if (lineStart == lineEnd) { r.left = (int) mLayout.getPrimaryHorizontal(selStart); r.right = (int) mLayout.getPrimaryHorizontal(selEnd); } else { // Selection extends across multiple lines -- make the focused // rect cover the entire width. if (mHighlightPathBogus) { if (mHighlightPath == null) mHighlightPath = new Path(); mHighlightPath.reset(); mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); mHighlightPathBogus = false; } synchronized (TEMP_RECTF) { mHighlightPath.computeBounds(TEMP_RECTF, true); r.left = (int) TEMP_RECTF.left - 1; r.right = (int) TEMP_RECTF.right + 1; } } } // Adjust for padding and gravity. int paddingLeft = getCompoundPaddingLeft(); int paddingTop = getExtendedPaddingTop(); if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { paddingTop += getVerticalOffset(false); } r.offset(paddingLeft, paddingTop); int paddingBottom = getExtendedPaddingBottom(); r.bottom += paddingBottom; }
Return the number of lines of text, or 0 if the internal Layout has not been built.
/** * Return the number of lines of text, or 0 if the internal Layout has not * been built. */
public int getLineCount() { return mLayout != null ? mLayout.getLineCount() : 0; }
Return the baseline for the specified line (0...getLineCount() - 1) If bounds is not null, return the top, left, right, bottom extents of the specified line in it. If the internal Layout has not been built, return 0 and set bounds to (0, 0, 0, 0)
Params:
  • line – which line to examine (0..getLineCount() - 1)
  • bounds – Optional. If not null, it returns the extent of the line
Returns:the Y-coordinate of the baseline
/** * Return the baseline for the specified line (0...getLineCount() - 1) * If bounds is not null, return the top, left, right, bottom extents * of the specified line in it. If the internal Layout has not been built, * return 0 and set bounds to (0, 0, 0, 0) * @param line which line to examine (0..getLineCount() - 1) * @param bounds Optional. If not null, it returns the extent of the line * @return the Y-coordinate of the baseline */
public int getLineBounds(int line, Rect bounds) { if (mLayout == null) { if (bounds != null) { bounds.set(0, 0, 0, 0); } return 0; } else { int baseline = mLayout.getLineBounds(line, bounds); int voffset = getExtendedPaddingTop(); if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { voffset += getVerticalOffset(true); } if (bounds != null) { bounds.offset(getCompoundPaddingLeft(), voffset); } return baseline + voffset; } } @Override public int getBaseline() { if (mLayout == null) { return super.getBaseline(); } return getBaselineOffset() + mLayout.getLineBaseline(0); } int getBaselineOffset() { int voffset = 0; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { voffset = getVerticalOffset(true); } if (isLayoutModeOptical(mParent)) { voffset -= getOpticalInsets().top; } return getExtendedPaddingTop() + voffset; }
@hide
/** * @hide */
@Override protected int getFadeTop(boolean offsetRequired) { if (mLayout == null) return 0; int voffset = 0; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { voffset = getVerticalOffset(true); } if (offsetRequired) voffset += getTopPaddingOffset(); return getExtendedPaddingTop() + voffset; }
@hide
/** * @hide */
@Override protected int getFadeHeight(boolean offsetRequired) { return mLayout != null ? mLayout.getHeight() : 0; } @Override public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) { if (mSpannable != null && mLinksClickable) { final float x = event.getX(pointerIndex); final float y = event.getY(pointerIndex); final int offset = getOffsetForPosition(x, y); final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset, ClickableSpan.class); if (clickables.length > 0) { return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND); } } if (isTextSelectable() || isTextEditable()) { return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_TEXT); } return super.onResolvePointerIcon(event, pointerIndex); } @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { // Note: If the IME is in fullscreen mode and IMS#mExtractEditText is in text action mode, // InputMethodService#onKeyDown and InputMethodService#onKeyUp are responsible to call // InputMethodService#mExtractEditText.maybeHandleBackInTextActionMode(event). if (keyCode == KeyEvent.KEYCODE_BACK && handleBackInTextActionModeIfNeeded(event)) { return true; } return super.onKeyPreIme(keyCode, event); }
@hide
/** * @hide */
public boolean handleBackInTextActionModeIfNeeded(KeyEvent event) { // Do nothing unless mEditor is in text action mode. if (mEditor == null || mEditor.getTextActionMode() == null) { return false; } if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.startTracking(event, this); } return true; } else if (event.getAction() == KeyEvent.ACTION_UP) { KeyEvent.DispatcherState state = getKeyDispatcherState(); if (state != null) { state.handleUpEvent(event); } if (event.isTracking() && !event.isCanceled()) { stopTextActionMode(); return true; } } return false; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { final int which = doKeyDown(keyCode, event, null); if (which == KEY_EVENT_NOT_HANDLED) { return super.onKeyDown(keyCode, event); } return true; } @Override public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); final int which = doKeyDown(keyCode, down, event); if (which == KEY_EVENT_NOT_HANDLED) { // Go through default dispatching. return super.onKeyMultiple(keyCode, repeatCount, event); } if (which == KEY_EVENT_HANDLED) { // Consumed the whole thing. return true; } repeatCount--; // We are going to dispatch the remaining events to either the input // or movement method. To do this, we will just send a repeated stream // of down and up events until we have done the complete repeatCount. // It would be nice if those interfaces had an onKeyMultiple() method, // but adding that is a more complicated change. KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); if (which == KEY_DOWN_HANDLED_BY_KEY_LISTENER) { // mEditor and mEditor.mInput are not null from doKeyDown mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); while (--repeatCount > 0) { mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, down); mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, up); } hideErrorIfUnchanged(); } else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) { // mMovement is not null from doKeyDown mMovement.onKeyUp(this, mSpannable, keyCode, up); while (--repeatCount > 0) { mMovement.onKeyDown(this, mSpannable, keyCode, down); mMovement.onKeyUp(this, mSpannable, keyCode, up); } } return true; }
Returns true if pressing ENTER in this field advances focus instead of inserting the character. This is true mostly in single-line fields, but also in mail addresses and subjects which will display on multiple lines but where it doesn't make sense to insert newlines.
/** * Returns true if pressing ENTER in this field advances focus instead * of inserting the character. This is true mostly in single-line fields, * but also in mail addresses and subjects which will display on multiple * lines but where it doesn't make sense to insert newlines. */
private boolean shouldAdvanceFocusOnEnter() { if (getKeyListener() == null) { return false; } if (mSingleLine) { return true; } if (mEditor != null && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { return true; } } return false; }
Returns true if pressing TAB in this field advances focus instead of inserting the character. Insert tabs only in multi-line editors.
/** * Returns true if pressing TAB in this field advances focus instead * of inserting the character. Insert tabs only in multi-line editors. */
private boolean shouldAdvanceFocusOnTab() { if (getKeyListener() != null && !mSingleLine && mEditor != null && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { return false; } } return true; } private boolean isDirectionalNavigationKey(int keyCode) { switch(keyCode) { case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_RIGHT: return true; } return false; } private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { if (!isEnabled()) { return KEY_EVENT_NOT_HANDLED; } // If this is the initial keydown, we don't want to prevent a movement away from this view. // While this shouldn't be necessary because any time we're preventing default movement we // should be restricting the focus to remain within this view, thus we'll also receive // the key up event, occasionally key up events will get dropped and we don't want to // prevent the user from traversing out of this on the next key down. if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { mPreventDefaultMovement = false; } switch (keyCode) { case KeyEvent.KEYCODE_ENTER: if (event.hasNoModifiers()) { // When mInputContentType is set, we know that we are // running in a "modern" cupcake environment, so don't need // to worry about the application trying to capture // enter key events. if (mEditor != null && mEditor.mInputContentType != null) { // If there is an action listener, given them a // chance to consume the event. if (mEditor.mInputContentType.onEditorActionListener != null && mEditor.mInputContentType.onEditorActionListener.onEditorAction( this, EditorInfo.IME_NULL, event)) { mEditor.mInputContentType.enterDown = true; // We are consuming the enter key for them. return KEY_EVENT_HANDLED; } } // If our editor should move focus when enter is pressed, or // this is a generated event from an IME action button, then // don't let it be inserted into the text. if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 || shouldAdvanceFocusOnEnter()) { if (hasOnClickListeners()) { return KEY_EVENT_NOT_HANDLED; } return KEY_EVENT_HANDLED; } } break; case KeyEvent.KEYCODE_DPAD_CENTER: if (event.hasNoModifiers()) { if (shouldAdvanceFocusOnEnter()) { return KEY_EVENT_NOT_HANDLED; } } break; case KeyEvent.KEYCODE_TAB: if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { if (shouldAdvanceFocusOnTab()) { return KEY_EVENT_NOT_HANDLED; } } break; // Has to be done on key down (and not on key up) to correctly be intercepted. case KeyEvent.KEYCODE_BACK: if (mEditor != null && mEditor.getTextActionMode() != null) { stopTextActionMode(); return KEY_EVENT_HANDLED; } break; case KeyEvent.KEYCODE_CUT: if (event.hasNoModifiers() && canCut()) { if (onTextContextMenuItem(ID_CUT)) { return KEY_EVENT_HANDLED; } } break; case KeyEvent.KEYCODE_COPY: if (event.hasNoModifiers() && canCopy()) { if (onTextContextMenuItem(ID_COPY)) { return KEY_EVENT_HANDLED; } } break; case KeyEvent.KEYCODE_PASTE: if (event.hasNoModifiers() && canPaste()) { if (onTextContextMenuItem(ID_PASTE)) { return KEY_EVENT_HANDLED; } } break; } if (mEditor != null && mEditor.mKeyListener != null) { boolean doDown = true; if (otherEvent != null) { try { beginBatchEdit(); final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, otherEvent); hideErrorIfUnchanged(); doDown = false; if (handled) { return KEY_EVENT_HANDLED; } } catch (AbstractMethodError e) { // onKeyOther was added after 1.0, so if it isn't // implemented we need to try to dispatch as a regular down. } finally { endBatchEdit(); } } if (doDown) { beginBatchEdit(); final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, keyCode, event); endBatchEdit(); hideErrorIfUnchanged(); if (handled) return KEY_DOWN_HANDLED_BY_KEY_LISTENER; } } // bug 650865: sometimes we get a key event before a layout. // don't try to move around if we don't know the layout. if (mMovement != null && mLayout != null) { boolean doDown = true; if (otherEvent != null) { try { boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent); doDown = false; if (handled) { return KEY_EVENT_HANDLED; } } catch (AbstractMethodError e) { // onKeyOther was added after 1.0, so if it isn't // implemented we need to try to dispatch as a regular down. } } if (doDown) { if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) { if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { mPreventDefaultMovement = true; } return KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD; } } // Consume arrows from keyboard devices to prevent focus leaving the editor. // DPAD/JOY devices (Gamepads, TV remotes) often lack a TAB key so allow those // to move focus with arrows. if (event.getSource() == InputDevice.SOURCE_KEYBOARD && isDirectionalNavigationKey(keyCode)) { return KEY_EVENT_HANDLED; } } return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? KEY_EVENT_HANDLED : KEY_EVENT_NOT_HANDLED; }
Resets the mErrorWasChanged flag, so that future calls to setError(CharSequence) can be recorded.
@hide
/** * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} * can be recorded. * @hide */
public void resetErrorChangedFlag() { /* * Keep track of what the error was before doing the input * so that if an input filter changed the error, we leave * that error showing. Otherwise, we take down whatever * error was showing when the user types something. */ if (mEditor != null) mEditor.mErrorWasChanged = false; }
@hide
/** * @hide */
public void hideErrorIfUnchanged() { if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { setError(null, null); } } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (!isEnabled()) { return super.onKeyUp(keyCode, event); } if (!KeyEvent.isModifierKey(keyCode)) { mPreventDefaultMovement = false; } switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: if (event.hasNoModifiers()) { /* * If there is a click listener, just call through to * super, which will invoke it. * * If there isn't a click listener, try to show the soft * input method. (It will also * call performClick(), but that won't do anything in * this case.) */ if (!hasOnClickListeners()) { if (mMovement != null && mText instanceof Editable && mLayout != null && onCheckIsTextEditor()) { InputMethodManager imm = InputMethodManager.peekInstance(); viewClicked(imm); if (imm != null && getShowSoftInputOnFocus()) { imm.showSoftInput(this, 0); } } } } return super.onKeyUp(keyCode, event); case KeyEvent.KEYCODE_ENTER: if (event.hasNoModifiers()) { if (mEditor != null && mEditor.mInputContentType != null && mEditor.mInputContentType.onEditorActionListener != null && mEditor.mInputContentType.enterDown) { mEditor.mInputContentType.enterDown = false; if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( this, EditorInfo.IME_NULL, event)) { return true; } } if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 || shouldAdvanceFocusOnEnter()) { /* * If there is a click listener, just call through to * super, which will invoke it. * * If there isn't a click listener, try to advance focus, * but still call through to super, which will reset the * pressed state and longpress state. (It will also * call performClick(), but that won't do anything in * this case.) */ if (!hasOnClickListeners()) { View v = focusSearch(FOCUS_DOWN); if (v != null) { if (!v.requestFocus(FOCUS_DOWN)) { throw new IllegalStateException("focus search returned a view " + "that wasn't able to take focus!"); } /* * Return true because we handled the key; super * will return false because there was no click * listener. */ super.onKeyUp(keyCode, event); return true; } else if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0) { // No target for next focus, but make sure the IME // if this came from it. InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null && imm.isActive(this)) { imm.hideSoftInputFromWindow(getWindowToken(), 0); } } } } return super.onKeyUp(keyCode, event); } break; } if (mEditor != null && mEditor.mKeyListener != null) { if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) { return true; } } if (mMovement != null && mLayout != null) { if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) { return true; } } return super.onKeyUp(keyCode, event); } @Override public boolean onCheckIsTextEditor() { return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; } @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (onCheckIsTextEditor() && isEnabled()) { mEditor.createInputMethodStateIfNeeded(); outAttrs.inputType = getInputType(); if (mEditor.mInputContentType != null) { outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; outAttrs.actionId = mEditor.mInputContentType.imeActionId; outAttrs.extras = mEditor.mInputContentType.extras; outAttrs.hintLocales = mEditor.mInputContentType.imeHintLocales; } else { outAttrs.imeOptions = EditorInfo.IME_NULL; outAttrs.hintLocales = null; } if (focusSearch(FOCUS_DOWN) != null) { outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; } if (focusSearch(FOCUS_UP) != null) { outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; } if ((outAttrs.imeOptions & EditorInfo.IME_MASK_ACTION) == EditorInfo.IME_ACTION_UNSPECIFIED) { if ((outAttrs.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { // An action has not been set, but the enter key will move to // the next focus, so set the action to that. outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; } else { // An action has not been set, and there is no focus to move // to, so let's just supply a "done" action. outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; } if (!shouldAdvanceFocusOnEnter()) { outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; } } if (isMultilineInputType(outAttrs.inputType)) { // Multi-line text editors should always show an enter key. outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; } outAttrs.hintText = mHint; if (mText instanceof Editable) { InputConnection ic = new EditableInputConnection(this); outAttrs.initialSelStart = getSelectionStart(); outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); return ic; } } return null; }
If this TextView contains editable content, extract a portion of it based on the information in request in to outText.
Returns:Returns true if the text was successfully extracted, else false.
/** * If this TextView contains editable content, extract a portion of it * based on the information in <var>request</var> in to <var>outText</var>. * @return Returns true if the text was successfully extracted, else false. */
public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { createEditorIfNeeded(); return mEditor.extractText(request, outText); }
This is used to remove all style-impacting spans from text before new extracted text is being replaced into it, so that we don't have any lingering spans applied during the replace.
/** * This is used to remove all style-impacting spans from text before new * extracted text is being replaced into it, so that we don't have any * lingering spans applied during the replace. */
static void removeParcelableSpans(Spannable spannable, int start, int end) { Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); int i = spans.length; while (i > 0) { i--; spannable.removeSpan(spans[i]); } }
Apply to this text view the given extracted text, as previously returned by extractText(ExtractedTextRequest, ExtractedText).
/** * Apply to this text view the given extracted text, as previously * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. */
public void setExtractedText(ExtractedText text) { Editable content = getEditableText(); if (text.text != null) { if (content == null) { setText(text.text, TextView.BufferType.EDITABLE); } else { int start = 0; int end = content.length(); if (text.partialStartOffset >= 0) { final int N = content.length(); start = text.partialStartOffset; if (start > N) start = N; end = text.partialEndOffset; if (end > N) end = N; } removeParcelableSpans(content, start, end); if (TextUtils.equals(content.subSequence(start, end), text.text)) { if (text.text instanceof Spanned) { // OK to copy spans only. TextUtils.copySpansFrom((Spanned) text.text, 0, end - start, Object.class, content, start); } } else { content.replace(start, end, text.text); } } } // Now set the selection position... make sure it is in range, to // avoid crashes. If this is a partial update, it is possible that // the underlying text may have changed, causing us problems here. // Also we just don't want to trust clients to do the right thing. Spannable sp = (Spannable) getText(); final int N = sp.length(); int start = text.selectionStart; if (start < 0) { start = 0; } else if (start > N) { start = N; } int end = text.selectionEnd; if (end < 0) { end = 0; } else if (end > N) { end = N; } Selection.setSelection(sp, start, end); // Finally, update the selection mode. if ((text.flags & ExtractedText.FLAG_SELECTING) != 0) { MetaKeyKeyListener.startSelecting(this, sp); } else { MetaKeyKeyListener.stopSelecting(this, sp); } setHintInternal(text.hint); }
@hide
/** * @hide */
public void setExtracting(ExtractedTextRequest req) { if (mEditor.mInputMethodState != null) { mEditor.mInputMethodState.mExtractedTextRequest = req; } // This would stop a possible selection mode, but no such mode is started in case // extracted mode will start. Some text is selected though, and will trigger an action mode // in the extracted view. mEditor.hideCursorAndSpanControllers(); stopTextActionMode(); if (mEditor.mSelectionModifierCursorController != null) { mEditor.mSelectionModifierCursorController.resetTouchOffsets(); } }
Called by the framework in response to a text completion from the current input method, provided by it calling InputConnection.commitCompletion(). The default implementation does nothing; text views that are supporting auto-completion should override this to do their desired behavior.
Params:
  • text – The auto complete text the user has selected.
/** * Called by the framework in response to a text completion from * the current input method, provided by it calling * {@link InputConnection#commitCompletion * InputConnection.commitCompletion()}. The default implementation does * nothing; text views that are supporting auto-completion should override * this to do their desired behavior. * * @param text The auto complete text the user has selected. */
public void onCommitCompletion(CompletionInfo text) { // intentionally empty }
Called by the framework in response to a text auto-correction (such as fixing a typo using a dictionary) from the current input method, provided by it calling InputConnection.commitCorrection(). The default implementation flashes the background of the corrected word to provide feedback to the user.
Params:
  • info – The auto correct info about the text that was corrected.
/** * Called by the framework in response to a text auto-correction (such as fixing a typo using a * dictionary) from the current input method, provided by it calling * {@link InputConnection#commitCorrection(CorrectionInfo) InputConnection.commitCorrection()}. * The default implementation flashes the background of the corrected word to provide * feedback to the user. * * @param info The auto correct info about the text that was corrected. */
public void onCommitCorrection(CorrectionInfo info) { if (mEditor != null) mEditor.onCommitCorrection(info); } public void beginBatchEdit() { if (mEditor != null) mEditor.beginBatchEdit(); } public void endBatchEdit() { if (mEditor != null) mEditor.endBatchEdit(); }
Called by the framework in response to a request to begin a batch of edit operations through a call to link beginBatchEdit().
/** * Called by the framework in response to a request to begin a batch * of edit operations through a call to link {@link #beginBatchEdit()}. */
public void onBeginBatchEdit() { // intentionally empty }
Called by the framework in response to a request to end a batch of edit operations through a call to link endBatchEdit.
/** * Called by the framework in response to a request to end a batch * of edit operations through a call to link {@link #endBatchEdit}. */
public void onEndBatchEdit() { // intentionally empty }
Called by the framework in response to a private command from the current method, provided by it calling InputConnection.performPrivateCommand().
Params:
  • action – The action name of the command.
  • data – Any additional data for the command. This may be null.
Returns:Return true if you handled the command, else false.
/** * Called by the framework in response to a private command from the * current method, provided by it calling * {@link InputConnection#performPrivateCommand * InputConnection.performPrivateCommand()}. * * @param action The action name of the command. * @param data Any additional data for the command. This may be null. * @return Return true if you handled the command, else false. */
public boolean onPrivateIMECommand(String action, Bundle data) { return false; }
@hide
/** @hide */
@VisibleForTesting public void nullLayouts() { if (mLayout instanceof BoringLayout && mSavedLayout == null) { mSavedLayout = (BoringLayout) mLayout; } if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { mSavedHintLayout = (BoringLayout) mHintLayout; } mSavedMarqueeModeLayout = mLayout = mHintLayout = null; mBoring = mHintBoring = null; // Since it depends on the value of mLayout if (mEditor != null) mEditor.prepareCursorControllers(); }
Make a new Layout based on the already-measured size of the view, on the assumption that it was measured correctly at some point.
/** * Make a new Layout based on the already-measured size of the view, * on the assumption that it was measured correctly at some point. */
private void assumeLayout() { int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); if (width < 1) { width = 0; } int physicalWidth = width; if (mHorizontallyScrolling) { width = VERY_WIDE; } makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, physicalWidth, false); } private Layout.Alignment getLayoutAlignment() { Layout.Alignment alignment; switch (getTextAlignment()) { case TEXT_ALIGNMENT_GRAVITY: switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { case Gravity.START: alignment = Layout.Alignment.ALIGN_NORMAL; break; case Gravity.END: alignment = Layout.Alignment.ALIGN_OPPOSITE; break; case Gravity.LEFT: alignment = Layout.Alignment.ALIGN_LEFT; break; case Gravity.RIGHT: alignment = Layout.Alignment.ALIGN_RIGHT; break; case Gravity.CENTER_HORIZONTAL: alignment = Layout.Alignment.ALIGN_CENTER; break; default: alignment = Layout.Alignment.ALIGN_NORMAL; break; } break; case TEXT_ALIGNMENT_TEXT_START: alignment = Layout.Alignment.ALIGN_NORMAL; break; case TEXT_ALIGNMENT_TEXT_END: alignment = Layout.Alignment.ALIGN_OPPOSITE; break; case TEXT_ALIGNMENT_CENTER: alignment = Layout.Alignment.ALIGN_CENTER; break; case TEXT_ALIGNMENT_VIEW_START: alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; break; case TEXT_ALIGNMENT_VIEW_END: alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; break; case TEXT_ALIGNMENT_INHERIT: // This should never happen as we have already resolved the text alignment // but better safe than sorry so we just fall through default: alignment = Layout.Alignment.ALIGN_NORMAL; break; } return alignment; }
The width passed in is now the desired layout width, not the full view width with padding. {@hide}
/** * The width passed in is now the desired layout width, * not the full view width with padding. * {@hide} */
@VisibleForTesting public void makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView) { stopMarquee(); // Update "old" cached values mOldMaximum = mMaximum; mOldMaxMode = mMaxMode; mHighlightPathBogus = true; if (wantWidth < 0) { wantWidth = 0; } if (hintWidth < 0) { hintWidth = 0; } Layout.Alignment alignment = getLayoutAlignment(); final boolean testDirChange = mSingleLine && mLayout != null && (alignment == Layout.Alignment.ALIGN_NORMAL || alignment == Layout.Alignment.ALIGN_OPPOSITE); int oldDir = 0; if (testDirChange) oldDir = mLayout.getParagraphDirection(0); boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE && mMarqueeFadeMode != MARQUEE_FADE_NORMAL; TruncateAt effectiveEllipsize = mEllipsize; if (mEllipsize == TruncateAt.MARQUEE && mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { effectiveEllipsize = TruncateAt.END_SMALL; } if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, effectiveEllipsize, effectiveEllipsize == mEllipsize); if (switchEllipsize) { TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ? TruncateAt.END : TruncateAt.MARQUEE; mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); } shouldEllipsize = mEllipsize != null; mHintLayout = null; if (mHint != null) { if (shouldEllipsize) hintWidth = wantWidth; if (hintBoring == UNKNOWN_BORING) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } } if (hintBoring != null) { if (hintBoring.width <= hintWidth && (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { if (mSavedHintLayout != null) { mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, hintWidth, alignment, mSpacingMult, mSpacingAdd, hintBoring, mIncludePad); } else { mHintLayout = BoringLayout.make(mHint, mTextPaint, hintWidth, alignment, mSpacingMult, mSpacingAdd, hintBoring, mIncludePad); } mSavedHintLayout = (BoringLayout) mHintLayout; } else if (shouldEllipsize && hintBoring.width <= hintWidth) { if (mSavedHintLayout != null) { mHintLayout = mSavedHintLayout.replaceOrMake(mHint, mTextPaint, hintWidth, alignment, mSpacingMult, mSpacingAdd, hintBoring, mIncludePad, mEllipsize, ellipsisWidth); } else { mHintLayout = BoringLayout.make(mHint, mTextPaint, hintWidth, alignment, mSpacingMult, mSpacingAdd, hintBoring, mIncludePad, mEllipsize, ellipsisWidth); } } } // TODO: code duplication with makeSingleLayout() if (mHintLayout == null) { StaticLayout.Builder builder = StaticLayout.Builder.obtain(mHint, 0, mHint.length(), mTextPaint, hintWidth) .setAlignment(alignment) .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); if (shouldEllipsize) { builder.setEllipsize(mEllipsize) .setEllipsizedWidth(ellipsisWidth); } mHintLayout = builder.build(); } } if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { registerForPreDraw(); } if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { if (!compressText(ellipsisWidth)) { final int height = mLayoutParams.height; // If the size of the view does not depend on the size of the text, try to // start the marquee immediately if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { startMarquee(); } else { // Defer the start of the marquee until we know our width (see setFrame()) mRestartMarquee = true; } } } // CursorControllers need a non-null mLayout if (mEditor != null) mEditor.prepareCursorControllers(); }
Returns true if DynamicLayout is required
@hide
/** * Returns true if DynamicLayout is required * * @hide */
@VisibleForTesting public boolean useDynamicLayout() { return isTextSelectable() || (mSpannable != null && mPrecomputed == null); }
@hide
/** * @hide */
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved) { Layout result = null; if (useDynamicLayout()) { final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint, wantWidth) .setDisplayText(mTransformed) .setAlignment(alignment) .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) .setEllipsize(getKeyListener() == null ? effectiveEllipsize : null) .setEllipsizedWidth(ellipsisWidth); result = builder.build(); } else { if (boring == UNKNOWN_BORING) { boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); if (boring != null) { mBoring = boring; } } if (boring != null) { if (boring.width <= wantWidth && (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { if (useSaved && mSavedLayout != null) { result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad); } else { result = BoringLayout.make(mTransformed, mTextPaint, wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad); } if (useSaved) { mSavedLayout = (BoringLayout) result; } } else if (shouldEllipsize && boring.width <= wantWidth) { if (useSaved && mSavedLayout != null) { result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad, effectiveEllipsize, ellipsisWidth); } else { result = BoringLayout.make(mTransformed, mTextPaint, wantWidth, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad, effectiveEllipsize, ellipsisWidth); } } } } if (result == null) { StaticLayout.Builder builder = StaticLayout.Builder.obtain(mTransformed, 0, mTransformed.length(), mTextPaint, wantWidth) .setAlignment(alignment) .setTextDirection(mTextDir) .setLineSpacing(mSpacingAdd, mSpacingMult) .setIncludePad(mIncludePad) .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(mBreakStrategy) .setHyphenationFrequency(mHyphenationFrequency) .setJustificationMode(mJustificationMode) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); if (shouldEllipsize) { builder.setEllipsize(effectiveEllipsize) .setEllipsizedWidth(ellipsisWidth); } result = builder.build(); } return result; } private boolean compressText(float width) { if (isHardwareAccelerated()) return false; // Only compress the text if it hasn't been compressed by the previous pass if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && mTextPaint.getTextScaleX() == 1.0f) { final float textWidth = mLayout.getLineWidth(0); final float overflow = (textWidth + 1.0f - width) / width; if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); post(new Runnable() { public void run() { requestLayout(); } }); return true; } } return false; } private static int desired(Layout layout) { int n = layout.getLineCount(); CharSequence text = layout.getText(); float max = 0; // if any line was wrapped, we can't use it. // but it's ok for the last line not to have a newline for (int i = 0; i < n - 1; i++) { if (text.charAt(layout.getLineEnd(i) - 1) != '\n') { return -1; } } for (int i = 0; i < n; i++) { max = Math.max(max, layout.getLineWidth(i)); } return (int) Math.ceil(max); }
Set whether the TextView includes extra top and bottom padding to make room for accents that go above the normal ascent and descent. The default is true.
See Also:
  • getIncludeFontPadding()
@attrref android.R.styleable#TextView_includeFontPadding
/** * Set whether the TextView includes extra top and bottom padding to make * room for accents that go above the normal ascent and descent. * The default is true. * * @see #getIncludeFontPadding() * * @attr ref android.R.styleable#TextView_includeFontPadding */
public void setIncludeFontPadding(boolean includepad) { if (mIncludePad != includepad) { mIncludePad = includepad; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Gets whether the TextView includes extra top and bottom padding to make room for accents that go above the normal ascent and descent.
See Also:
  • setIncludeFontPadding(boolean)
@attrref android.R.styleable#TextView_includeFontPadding
/** * Gets whether the TextView includes extra top and bottom padding to make * room for accents that go above the normal ascent and descent. * * @see #setIncludeFontPadding(boolean) * * @attr ref android.R.styleable#TextView_includeFontPadding */
public boolean getIncludeFontPadding() { return mIncludePad; }
@hide
/** @hide */
@VisibleForTesting public static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; BoringLayout.Metrics boring = UNKNOWN_BORING; BoringLayout.Metrics hintBoring = UNKNOWN_BORING; if (mTextDir == null) { mTextDir = getTextDirectionHeuristic(); } int des = -1; boolean fromexisting = false; final float widthLimit = (widthMode == MeasureSpec.AT_MOST) ? (float) widthSize : Float.MAX_VALUE; if (widthMode == MeasureSpec.EXACTLY) { // Parent has told us how big to be. So be it. width = widthSize; } else { if (mLayout != null && mEllipsize == null) { des = desired(mLayout); } if (des < 0) { boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); if (boring != null) { mBoring = boring; } } else { fromexisting = true; } if (boring == null || boring == UNKNOWN_BORING) { if (des < 0) { des = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mTransformed, 0, mTransformed.length(), mTextPaint, mTextDir, widthLimit)); } width = des; } else { width = boring.width; } final Drawables dr = mDrawables; if (dr != null) { width = Math.max(width, dr.mDrawableWidthTop); width = Math.max(width, dr.mDrawableWidthBottom); } if (mHint != null) { int hintDes = -1; int hintWidth; if (mHintLayout != null && mEllipsize == null) { hintDes = desired(mHintLayout); } if (hintDes < 0) { hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); if (hintBoring != null) { mHintBoring = hintBoring; } } if (hintBoring == null || hintBoring == UNKNOWN_BORING) { if (hintDes < 0) { hintDes = (int) Math.ceil(Layout.getDesiredWidthWithLimit(mHint, 0, mHint.length(), mTextPaint, mTextDir, widthLimit)); } hintWidth = hintDes; } else { hintWidth = hintBoring.width; } if (hintWidth > width) { width = hintWidth; } } width += getCompoundPaddingLeft() + getCompoundPaddingRight(); if (mMaxWidthMode == EMS) { width = Math.min(width, mMaxWidth * getLineHeight()); } else { width = Math.min(width, mMaxWidth); } if (mMinWidthMode == EMS) { width = Math.max(width, mMinWidth * getLineHeight()); } else { width = Math.max(width, mMinWidth); } // Check against our minimum width width = Math.max(width, getSuggestedMinimumWidth()); if (widthMode == MeasureSpec.AT_MOST) { width = Math.min(widthSize, width); } } int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); int unpaddedWidth = want; if (mHorizontallyScrolling) want = VERY_WIDE; int hintWant = want; int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); if (mLayout == null) { makeNewLayout(want, hintWant, boring, hintBoring, width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); } else { final boolean layoutChanged = (mLayout.getWidth() != want) || (hintWidth != hintWant) || (mLayout.getEllipsizedWidth() != width - getCompoundPaddingLeft() - getCompoundPaddingRight()); final boolean widthChanged = (mHint == null) && (mEllipsize == null) && (want > mLayout.getWidth()) && (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want)); final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); if (layoutChanged || maximumChanged) { if (!maximumChanged && widthChanged) { mLayout.increaseWidthTo(want); } else { makeNewLayout(want, hintWant, boring, hintBoring, width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); } } else { // Nothing has changed } } if (heightMode == MeasureSpec.EXACTLY) { // Parent has told us how big to be. So be it. height = heightSize; mDesiredHeightAtMeasure = -1; } else { int desired = getDesiredHeight(); height = desired; mDesiredHeightAtMeasure = desired; if (heightMode == MeasureSpec.AT_MOST) { height = Math.min(desired, heightSize); } } int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); } /* * We didn't let makeNewLayout() register to bring the cursor into view, * so do it here if there is any possibility that it is needed. */ if (mMovement != null || mLayout.getWidth() > unpaddedWidth || mLayout.getHeight() > unpaddedHeight) { registerForPreDraw(); } else { scrollTo(0, 0); } setMeasuredDimension(width, height); }
Automatically computes and sets the text size.
/** * Automatically computes and sets the text size. */
private void autoSizeText() { if (!isAutoSizeEnabled()) { return; } if (mNeedsAutoSizeText) { if (getMeasuredWidth() <= 0 || getMeasuredHeight() <= 0) { return; } final int availableWidth = mHorizontallyScrolling ? VERY_WIDE : getMeasuredWidth() - getTotalPaddingLeft() - getTotalPaddingRight(); final int availableHeight = getMeasuredHeight() - getExtendedPaddingBottom() - getExtendedPaddingTop(); if (availableWidth <= 0 || availableHeight <= 0) { return; } synchronized (TEMP_RECTF) { TEMP_RECTF.setEmpty(); TEMP_RECTF.right = availableWidth; TEMP_RECTF.bottom = availableHeight; final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); if (optimalTextSize != getTextSize()) { setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize, false /* shouldRequestLayout */); makeNewLayout(availableWidth, 0 /* hintWidth */, UNKNOWN_BORING, UNKNOWN_BORING, mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false /* bringIntoView */); } } } // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing // after the next layout pass should set this to false. mNeedsAutoSizeText = true; }
Performs a binary search to find the largest text size that will still fit within the size available to this view.
/** * Performs a binary search to find the largest text size that will still fit within the size * available to this view. */
private int findLargestTextSizeWhichFits(RectF availableSpace) { final int sizesCount = mAutoSizeTextSizesInPx.length; if (sizesCount == 0) { throw new IllegalStateException("No available text sizes to choose from."); } int bestSizeIndex = 0; int lowIndex = bestSizeIndex + 1; int highIndex = sizesCount - 1; int sizeToTryIndex; while (lowIndex <= highIndex) { sizeToTryIndex = (lowIndex + highIndex) / 2; if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { bestSizeIndex = lowIndex; lowIndex = sizeToTryIndex + 1; } else { highIndex = sizeToTryIndex - 1; bestSizeIndex = highIndex; } } return mAutoSizeTextSizesInPx[bestSizeIndex]; } private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { final CharSequence text = mTransformed != null ? mTransformed : getText(); final int maxLines = getMaxLines(); if (mTempTextPaint == null) { mTempTextPaint = new TextPaint(); } else { mTempTextPaint.reset(); } mTempTextPaint.set(getPaint()); mTempTextPaint.setTextSize(suggestedSizeInPx); final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain( text, 0, text.length(), mTempTextPaint, Math.round(availableSpace.right)); layoutBuilder.setAlignment(getLayoutAlignment()) .setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()) .setIncludePad(getIncludeFontPadding()) .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing) .setBreakStrategy(getBreakStrategy()) .setHyphenationFrequency(getHyphenationFrequency()) .setJustificationMode(getJustificationMode()) .setMaxLines(mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE) .setTextDirection(getTextDirectionHeuristic()); final StaticLayout layout = layoutBuilder.build(); // Lines overflow. if (maxLines != -1 && layout.getLineCount() > maxLines) { return false; } // Height overflow. if (layout.getHeight() > availableSpace.bottom) { return false; } return true; } private int getDesiredHeight() { return Math.max( getDesiredHeight(mLayout, true), getDesiredHeight(mHintLayout, mEllipsize != null)); } private int getDesiredHeight(Layout layout, boolean cap) { if (layout == null) { return 0; } /* * Don't cap the hint to a certain number of lines. * (Do cap it, though, if we have a maximum pixel height.) */ int desired = layout.getHeight(cap); final Drawables dr = mDrawables; if (dr != null) { desired = Math.max(desired, dr.mDrawableHeightLeft); desired = Math.max(desired, dr.mDrawableHeightRight); } int linecount = layout.getLineCount(); final int padding = getCompoundPaddingTop() + getCompoundPaddingBottom(); desired += padding; if (mMaxMode != LINES) { desired = Math.min(desired, mMaximum); } else if (cap && linecount > mMaximum && (layout instanceof DynamicLayout || layout instanceof BoringLayout)) { desired = layout.getLineTop(mMaximum); if (dr != null) { desired = Math.max(desired, dr.mDrawableHeightLeft); desired = Math.max(desired, dr.mDrawableHeightRight); } desired += padding; linecount = mMaximum; } if (mMinMode == LINES) { if (linecount < mMinimum) { desired += getLineHeight() * (mMinimum - linecount); } } else { desired = Math.max(desired, mMinimum); } // Check against our minimum height desired = Math.max(desired, getSuggestedMinimumHeight()); return desired; }
Check whether a change to the existing text layout requires a new view layout.
/** * Check whether a change to the existing text layout requires a * new view layout. */
private void checkForResize() { boolean sizeChanged = false; if (mLayout != null) { // Check if our width changed if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { sizeChanged = true; invalidate(); } // Check if our height changed if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { int desiredHeight = getDesiredHeight(); if (desiredHeight != this.getHeight()) { sizeChanged = true; } } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { if (mDesiredHeightAtMeasure >= 0) { int desiredHeight = getDesiredHeight(); if (desiredHeight != mDesiredHeightAtMeasure) { sizeChanged = true; } } } } if (sizeChanged) { requestLayout(); // caller will have already invalidated } }
Check whether entirely new text requires a new view layout or merely a new text layout.
/** * Check whether entirely new text requires a new view layout * or merely a new text layout. */
private void checkForRelayout() { // If we have a fixed width, we can just swap in a new text layout // if the text height stays the same or if the view height is fixed. if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && (mHint == null || mHintLayout != null) && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { // Static width, so try making a new text layout. int oldht = mLayout.getHeight(); int want = mLayout.getWidth(); int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); /* * No need to bring the text into view, since the size is not * changing (unless we do the requestLayout(), in which case it * will happen at measure). */ makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { // In a fixed-height view, so use our new text layout. if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && mLayoutParams.height != LayoutParams.MATCH_PARENT) { autoSizeText(); invalidate(); return; } // Dynamic height, but height has stayed the same, // so use our new text layout. if (mLayout.getHeight() == oldht && (mHintLayout == null || mHintLayout.getHeight() == oldht)) { autoSizeText(); invalidate(); return; } } // We lose: the height has changed and we have a dynamic height. // Request a new view layout using our new text layout. requestLayout(); invalidate(); } else { // Dynamic width, so we have no choice but to request a new // view layout with a new text layout. nullLayouts(); requestLayout(); invalidate(); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (mDeferScroll >= 0) { int curs = mDeferScroll; mDeferScroll = -1; bringPointIntoView(Math.min(curs, mText.length())); } // Call auto-size after the width and height have been calculated. autoSizeText(); } private boolean isShowingHint() { return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); }
Returns true if anything changed.
/** * Returns true if anything changed. */
private boolean bringTextIntoView() { Layout layout = isShowingHint() ? mHintLayout : mLayout; int line = 0; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { line = layout.getLineCount() - 1; } Layout.Alignment a = layout.getParagraphAlignment(line); int dir = layout.getParagraphDirection(line); int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); int ht = layout.getHeight(); int scrollx, scrolly; // Convert to left, center, or right alignment. if (a == Layout.Alignment.ALIGN_NORMAL) { a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; } else if (a == Layout.Alignment.ALIGN_OPPOSITE) { a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; } if (a == Layout.Alignment.ALIGN_CENTER) { /* * Keep centered if possible, or, if it is too wide to fit, * keep leading edge in view. */ int left = (int) Math.floor(layout.getLineLeft(line)); int right = (int) Math.ceil(layout.getLineRight(line)); if (right - left < hspace) { scrollx = (right + left) / 2 - hspace / 2; } else { if (dir < 0) { scrollx = right - hspace; } else { scrollx = left; } } } else if (a == Layout.Alignment.ALIGN_RIGHT) { int right = (int) Math.ceil(layout.getLineRight(line)); scrollx = right - hspace; } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) scrollx = (int) Math.floor(layout.getLineLeft(line)); } if (ht < vspace) { scrolly = 0; } else { if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { scrolly = ht - vspace; } else { scrolly = 0; } } if (scrollx != mScrollX || scrolly != mScrollY) { scrollTo(scrollx, scrolly); return true; } else { return false; } }
Move the point, specified by the offset, into the view if it is needed. This has to be called after layout. Returns true if anything changed.
/** * Move the point, specified by the offset, into the view if it is needed. * This has to be called after layout. Returns true if anything changed. */
public boolean bringPointIntoView(int offset) { if (isLayoutRequested()) { mDeferScroll = offset; return false; } boolean changed = false; Layout layout = isShowingHint() ? mHintLayout : mLayout; if (layout == null) return changed; int line = layout.getLineForOffset(offset); int grav; switch (layout.getParagraphAlignment(line)) { case ALIGN_LEFT: grav = 1; break; case ALIGN_RIGHT: grav = -1; break; case ALIGN_NORMAL: grav = layout.getParagraphDirection(line); break; case ALIGN_OPPOSITE: grav = -layout.getParagraphDirection(line); break; case ALIGN_CENTER: default: grav = 0; break; } // We only want to clamp the cursor to fit within the layout width // in left-to-right modes, because in a right to left alignment, // we want to scroll to keep the line-right on the screen, as other // lines are likely to have text flush with the right margin, which // we want to keep visible. // A better long-term solution would probably be to measure both // the full line and a blank-trimmed version, and, for example, use // the latter measurement for centering and right alignment, but for // the time being we only implement the cursor clamping in left to // right where it is most likely to be annoying. final boolean clamped = grav > 0; // FIXME: Is it okay to truncate this, or should we round? final int x = (int) layout.getPrimaryHorizontal(offset, clamped); final int top = layout.getLineTop(line); final int bottom = layout.getLineTop(line + 1); int left = (int) Math.floor(layout.getLineLeft(line)); int right = (int) Math.ceil(layout.getLineRight(line)); int ht = layout.getHeight(); int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); if (!mHorizontallyScrolling && right - left > hspace && right > x) { // If cursor has been clamped, make sure we don't scroll. right = Math.max(x, left + hspace); } int hslack = (bottom - top) / 2; int vslack = hslack; if (vslack > vspace / 4) { vslack = vspace / 4; } if (hslack > hspace / 4) { hslack = hspace / 4; } int hs = mScrollX; int vs = mScrollY; if (top - vs < vslack) { vs = top - vslack; } if (bottom - vs > vspace - vslack) { vs = bottom - (vspace - vslack); } if (ht - vs < vspace) { vs = ht - vspace; } if (0 - vs > 0) { vs = 0; } if (grav != 0) { if (x - hs < hslack) { hs = x - hslack; } if (x - hs > hspace - hslack) { hs = x - (hspace - hslack); } } if (grav < 0) { if (left - hs > 0) { hs = left; } if (right - hs < hspace) { hs = right - hspace; } } else if (grav > 0) { if (right - hs < hspace) { hs = right - hspace; } if (left - hs > 0) { hs = left; } } else /* grav == 0 */ { if (right - left <= hspace) { /* * If the entire text fits, center it exactly. */ hs = left - (hspace - (right - left)) / 2; } else if (x > right - hslack) { /* * If we are near the right edge, keep the right edge * at the edge of the view. */ hs = right - hspace; } else if (x < left + hslack) { /* * If we are near the left edge, keep the left edge * at the edge of the view. */ hs = left; } else if (left > hs) { /* * Is there whitespace visible at the left? Fix it if so. */ hs = left; } else if (right < hs + hspace) { /* * Is there whitespace visible at the right? Fix it if so. */ hs = right - hspace; } else { /* * Otherwise, float as needed. */ if (x - hs < hslack) { hs = x - hslack; } if (x - hs > hspace - hslack) { hs = x - (hspace - hslack); } } } if (hs != mScrollX || vs != mScrollY) { if (mScroller == null) { scrollTo(hs, vs); } else { long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; int dx = hs - mScrollX; int dy = vs - mScrollY; if (duration > ANIMATED_SCROLL_GAP) { mScroller.startScroll(mScrollX, mScrollY, dx, dy); awakenScrollBars(mScroller.getDuration()); invalidate(); } else { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } scrollBy(dx, dy); } mLastScroll = AnimationUtils.currentAnimationTimeMillis(); } changed = true; } if (isFocused()) { // This offsets because getInterestingRect() is in terms of viewport coordinates, but // requestRectangleOnScreen() is in terms of content coordinates. // The offsets here are to ensure the rectangle we are using is // within our view bounds, in case the cursor is on the far left // or right. If it isn't withing the bounds, then this request // will be ignored. if (mTempRect == null) mTempRect = new Rect(); mTempRect.set(x - 2, top, x + 2, bottom); getInterestingRect(mTempRect, line); mTempRect.offset(mScrollX, mScrollY); if (requestRectangleOnScreen(mTempRect)) { changed = true; } } return changed; }
Move the cursor, if needed, so that it is at an offset that is visible to the user. This will not move the cursor if it represents more than one character (a selection range). This will only work if the TextView contains spannable text; otherwise it will do nothing.
Returns:True if the cursor was actually moved, false otherwise.
/** * Move the cursor, if needed, so that it is at an offset that is visible * to the user. This will not move the cursor if it represents more than * one character (a selection range). This will only work if the * TextView contains spannable text; otherwise it will do nothing. * * @return True if the cursor was actually moved, false otherwise. */
public boolean moveCursorToVisibleOffset() { if (!(mText instanceof Spannable)) { return false; } int start = getSelectionStart(); int end = getSelectionEnd(); if (start != end) { return false; } // First: make sure the line is visible on screen: int line = mLayout.getLineForOffset(start); final int top = mLayout.getLineTop(line); final int bottom = mLayout.getLineTop(line + 1); final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); int vslack = (bottom - top) / 2; if (vslack > vspace / 4) { vslack = vspace / 4; } final int vs = mScrollY; if (top < (vs + vslack)) { line = mLayout.getLineForVertical(vs + vslack + (bottom - top)); } else if (bottom > (vspace + vs - vslack)) { line = mLayout.getLineForVertical(vspace + vs - vslack - (bottom - top)); } // Next: make sure the character is visible on screen: final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); final int hs = mScrollX; final int leftChar = mLayout.getOffsetForHorizontal(line, hs); final int rightChar = mLayout.getOffsetForHorizontal(line, hspace + hs); // line might contain bidirectional text final int lowChar = leftChar < rightChar ? leftChar : rightChar; final int highChar = leftChar > rightChar ? leftChar : rightChar; int newStart = start; if (newStart < lowChar) { newStart = lowChar; } else if (newStart > highChar) { newStart = highChar; } if (newStart != start) { Selection.setSelection(mSpannable, newStart); return true; } return false; } @Override public void computeScroll() { if (mScroller != null) { if (mScroller.computeScrollOffset()) { mScrollX = mScroller.getCurrX(); mScrollY = mScroller.getCurrY(); invalidateParentCaches(); postInvalidate(); // So we draw again } } } private void getInterestingRect(Rect r, int line) { convertFromViewportToContentCoordinates(r); // Rectangle can can be expanded on first and last line to take // padding into account. // TODO Take left/right padding into account too? if (line == 0) r.top -= getExtendedPaddingTop(); if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); } private void convertFromViewportToContentCoordinates(Rect r) { final int horizontalOffset = viewportToContentHorizontalOffset(); r.left += horizontalOffset; r.right += horizontalOffset; final int verticalOffset = viewportToContentVerticalOffset(); r.top += verticalOffset; r.bottom += verticalOffset; } int viewportToContentHorizontalOffset() { return getCompoundPaddingLeft() - mScrollX; } int viewportToContentVerticalOffset() { int offset = getExtendedPaddingTop() - mScrollY; if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { offset += getVerticalOffset(false); } return offset; } @Override public void debug(int depth) { super.debug(depth); String output = debugIndent(depth); output += "frame={" + mLeft + ", " + mTop + ", " + mRight + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY + "} "; if (mText != null) { output += "mText=\"" + mText + "\" "; if (mLayout != null) { output += "mLayout width=" + mLayout.getWidth() + " height=" + mLayout.getHeight(); } } else { output += "mText=NULL"; } Log.d(VIEW_LOG_TAG, output); }
Convenience for Selection.getSelectionStart.
/** * Convenience for {@link Selection#getSelectionStart}. */
@ViewDebug.ExportedProperty(category = "text") public int getSelectionStart() { return Selection.getSelectionStart(getText()); }
Convenience for Selection.getSelectionEnd.
/** * Convenience for {@link Selection#getSelectionEnd}. */
@ViewDebug.ExportedProperty(category = "text") public int getSelectionEnd() { return Selection.getSelectionEnd(getText()); }
Return true iff there is a selection inside this text view.
/** * Return true iff there is a selection inside this text view. */
public boolean hasSelection() { final int selectionStart = getSelectionStart(); final int selectionEnd = getSelectionEnd(); return selectionStart >= 0 && selectionEnd > 0 && selectionStart != selectionEnd; } String getSelectedText() { if (!hasSelection()) { return null; } final int start = getSelectionStart(); final int end = getSelectionEnd(); return String.valueOf( start > end ? mText.subSequence(end, start) : mText.subSequence(start, end)); }
Sets the properties of this field (lines, horizontally scrolling, transformation method) to be for a single-line input.
@attrref android.R.styleable#TextView_singleLine
/** * Sets the properties of this field (lines, horizontally scrolling, * transformation method) to be for a single-line input. * * @attr ref android.R.styleable#TextView_singleLine */
public void setSingleLine() { setSingleLine(true); }
Sets the properties of this field to transform input to ALL CAPS display. This may use a "small caps" formatting if available. This setting will be ignored if this field is editable or selectable. This call replaces the current transformation method. Disabling this will not necessarily restore the previous behavior from before this was enabled.
See Also:
  • setTransformationMethod(TransformationMethod)
@attrref android.R.styleable#TextView_textAllCaps
/** * Sets the properties of this field to transform input to ALL CAPS * display. This may use a "small caps" formatting if available. * This setting will be ignored if this field is editable or selectable. * * This call replaces the current transformation method. Disabling this * will not necessarily restore the previous behavior from before this * was enabled. * * @see #setTransformationMethod(TransformationMethod) * @attr ref android.R.styleable#TextView_textAllCaps */
public void setAllCaps(boolean allCaps) { if (allCaps) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); } else { setTransformationMethod(null); } }
Checks whether the transformation method applied to this TextView is set to ALL CAPS.
See Also:
Returns:Whether the current transformation method is for ALL CAPS.
/** * * Checks whether the transformation method applied to this TextView is set to ALL CAPS. * @return Whether the current transformation method is for ALL CAPS. * * @see #setAllCaps(boolean) * @see #setTransformationMethod(TransformationMethod) */
public boolean isAllCaps() { final TransformationMethod method = getTransformationMethod(); return method != null && method instanceof AllCapsTransformationMethod; }
If true, sets the properties of this field (number of lines, horizontally scrolling, transformation method) to be for a single-line input; if false, restores these to the default conditions. Note that the default conditions are not necessarily those that were in effect prior this method, and you may want to reset these properties to your custom values.
@attrref android.R.styleable#TextView_singleLine
/** * If true, sets the properties of this field (number of lines, horizontally scrolling, * transformation method) to be for a single-line input; if false, restores these to the default * conditions. * * Note that the default conditions are not necessarily those that were in effect prior this * method, and you may want to reset these properties to your custom values. * * @attr ref android.R.styleable#TextView_singleLine */
@android.view.RemotableViewMethod public void setSingleLine(boolean singleLine) { // Could be used, but may break backward compatibility. // if (mSingleLine == singleLine) return; setInputTypeSingleLine(singleLine); applySingleLine(singleLine, true, true); }
Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType.
Params:
  • singleLine –
/** * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. * @param singleLine */
private void setInputTypeSingleLine(boolean singleLine) { if (mEditor != null && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { if (singleLine) { mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; } else { mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; } } } private void applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines) { mSingleLine = singleLine; if (singleLine) { setLines(1); setHorizontallyScrolling(true); if (applyTransformation) { setTransformationMethod(SingleLineTransformationMethod.getInstance()); } } else { if (changeMaxLines) { setMaxLines(Integer.MAX_VALUE); } setHorizontallyScrolling(false); if (applyTransformation) { setTransformationMethod(null); } } }
Causes words in the text that are longer than the view's width to be ellipsized instead of broken in the middle. You may also want to setSingleLine or setHorizontallyScrolling to constrain the text to a single line. Use null to turn off ellipsizing. If setMaxLines has been used to set two or more lines, only TruncateAt.END and TruncateAt.MARQUEE are supported (other ellipsizing types will not do anything).
@attrref android.R.styleable#TextView_ellipsize
/** * Causes words in the text that are longer than the view's width * to be ellipsized instead of broken in the middle. You may also * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} * to constrain the text to a single line. Use <code>null</code> * to turn off ellipsizing. * * If {@link #setMaxLines} has been used to set two or more lines, * only {@link android.text.TextUtils.TruncateAt#END} and * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported * (other ellipsizing types will not do anything). * * @attr ref android.R.styleable#TextView_ellipsize */
public void setEllipsize(TextUtils.TruncateAt where) { // TruncateAt is an enum. != comparison is ok between these singleton objects. if (mEllipsize != where) { mEllipsize = where; if (mLayout != null) { nullLayouts(); requestLayout(); invalidate(); } } }
Sets how many times to repeat the marquee animation. Only applied if the TextView has marquee enabled. Set to -1 to repeat indefinitely.
See Also:
  • getMarqueeRepeatLimit()
@attrref android.R.styleable#TextView_marqueeRepeatLimit
/** * Sets how many times to repeat the marquee animation. Only applied if the * TextView has marquee enabled. Set to -1 to repeat indefinitely. * * @see #getMarqueeRepeatLimit() * * @attr ref android.R.styleable#TextView_marqueeRepeatLimit */
public void setMarqueeRepeatLimit(int marqueeLimit) { mMarqueeRepeatLimit = marqueeLimit; }
Gets the number of times the marquee animation is repeated. Only meaningful if the TextView has marquee enabled.
See Also:
Returns:the number of times the marquee animation is repeated. -1 if the animation repeats indefinitely
@attrref android.R.styleable#TextView_marqueeRepeatLimit
/** * Gets the number of times the marquee animation is repeated. Only meaningful if the * TextView has marquee enabled. * * @return the number of times the marquee animation is repeated. -1 if the animation * repeats indefinitely * * @see #setMarqueeRepeatLimit(int) * * @attr ref android.R.styleable#TextView_marqueeRepeatLimit */
public int getMarqueeRepeatLimit() { return mMarqueeRepeatLimit; }
Returns where, if anywhere, words that are longer than the view is wide should be ellipsized.
/** * Returns where, if anywhere, words that are longer than the view * is wide should be ellipsized. */
@ViewDebug.ExportedProperty public TextUtils.TruncateAt getEllipsize() { return mEllipsize; }
Set the TextView so that when it takes focus, all the text is selected.
@attrref android.R.styleable#TextView_selectAllOnFocus
/** * Set the TextView so that when it takes focus, all the text is * selected. * * @attr ref android.R.styleable#TextView_selectAllOnFocus */
@android.view.RemotableViewMethod public void setSelectAllOnFocus(boolean selectAllOnFocus) { createEditorIfNeeded(); mEditor.mSelectAllOnFocus = selectAllOnFocus; if (selectAllOnFocus && !(mText instanceof Spannable)) { setText(mText, BufferType.SPANNABLE); } }
Set whether the cursor is visible. The default is true. Note that this property only makes sense for editable TextView.
See Also:
  • isCursorVisible()
@attrref android.R.styleable#TextView_cursorVisible
/** * Set whether the cursor is visible. The default is true. Note that this property only * makes sense for editable TextView. * * @see #isCursorVisible() * * @attr ref android.R.styleable#TextView_cursorVisible */
@android.view.RemotableViewMethod public void setCursorVisible(boolean visible) { if (visible && mEditor == null) return; // visible is the default value with no edit data createEditorIfNeeded(); if (mEditor.mCursorVisible != visible) { mEditor.mCursorVisible = visible; invalidate(); mEditor.makeBlink(); // InsertionPointCursorController depends on mCursorVisible mEditor.prepareCursorControllers(); } }
See Also:
Returns:whether or not the cursor is visible (assuming this TextView is editable)
@attrref android.R.styleable#TextView_cursorVisible
/** * @return whether or not the cursor is visible (assuming this TextView is editable) * * @see #setCursorVisible(boolean) * * @attr ref android.R.styleable#TextView_cursorVisible */
public boolean isCursorVisible() { // true is the default value return mEditor == null ? true : mEditor.mCursorVisible; } private boolean canMarquee() { int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); return width > 0 && (mLayout.getLineWidth(0) > width || (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null && mSavedMarqueeModeLayout.getLineWidth(0) > width)); } private void startMarquee() { // Do not ellipsize EditText if (getKeyListener() != null) return; if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { return; } if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && getLineCount() == 1 && canMarquee()) { if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; final Layout tmp = mLayout; mLayout = mSavedMarqueeModeLayout; mSavedMarqueeModeLayout = tmp; setHorizontalFadingEdgeEnabled(true); requestLayout(); invalidate(); } if (mMarquee == null) mMarquee = new Marquee(this); mMarquee.start(mMarqueeRepeatLimit); } } private void stopMarquee() { if (mMarquee != null && !mMarquee.isStopped()) { mMarquee.stop(); } if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; final Layout tmp = mSavedMarqueeModeLayout; mSavedMarqueeModeLayout = mLayout; mLayout = tmp; setHorizontalFadingEdgeEnabled(false); requestLayout(); invalidate(); } } private void startStopMarquee(boolean start) { if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { if (start) { startMarquee(); } else { stopMarquee(); } } }
This method is called when the text is changed, in case any subclasses would like to know. Within text, the lengthAfter characters beginning at start have just replaced old text that had length lengthBefore. It is an error to attempt to make changes to text from this callback.
Params:
  • text – The text the TextView is displaying
  • start – The offset of the start of the range of the text that was modified
  • lengthBefore – The length of the former text that has been replaced
  • lengthAfter – The length of the replacement modified text
/** * This method is called when the text is changed, in case any subclasses * would like to know. * * Within <code>text</code>, the <code>lengthAfter</code> characters * beginning at <code>start</code> have just replaced old text that had * length <code>lengthBefore</code>. It is an error to attempt to make * changes to <code>text</code> from this callback. * * @param text The text the TextView is displaying * @param start The offset of the start of the range of the text that was * modified * @param lengthBefore The length of the former text that has been replaced * @param lengthAfter The length of the replacement modified text */
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { // intentionally empty, template pattern method can be overridden by subclasses }
This method is called when the selection has changed, in case any subclasses would like to know.
Params:
  • selStart – The new selection start location.
  • selEnd – The new selection end location.
/** * This method is called when the selection has changed, in case any * subclasses would like to know. * * @param selStart The new selection start location. * @param selEnd The new selection end location. */
protected void onSelectionChanged(int selStart, int selEnd) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); }
Adds a TextWatcher to the list of those whose methods are called whenever this TextView's text changes.

In 1.0, the TextWatcher.afterTextChanged method was erroneously not called after setText calls. Now, doing setText if there are any text changed listeners forces the buffer type to Editable if it would not otherwise be and does call this method.

/** * Adds a TextWatcher to the list of those whose methods are called * whenever this TextView's text changes. * <p> * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously * not called after {@link #setText} calls. Now, doing {@link #setText} * if there are any text changed listeners forces the buffer type to * Editable if it would not otherwise be and does call this method. */
public void addTextChangedListener(TextWatcher watcher) { if (mListeners == null) { mListeners = new ArrayList<TextWatcher>(); } mListeners.add(watcher); }
Removes the specified TextWatcher from the list of those whose methods are called whenever this TextView's text changes.
/** * Removes the specified TextWatcher from the list of those whose * methods are called * whenever this TextView's text changes. */
public void removeTextChangedListener(TextWatcher watcher) { if (mListeners != null) { int i = mListeners.indexOf(watcher); if (i >= 0) { mListeners.remove(i); } } } private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { if (mListeners != null) { final ArrayList<TextWatcher> list = mListeners; final int count = list.size(); for (int i = 0; i < count; i++) { list.get(i).beforeTextChanged(text, start, before, after); } } // The spans that are inside or intersect the modified region no longer make sense removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); } // Removes all spans that are inside or actually overlap the start..end range private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { if (!(mText instanceof Editable)) return; Editable text = (Editable) mText; T[] spans = text.getSpans(start, end, type); final int length = spans.length; for (int i = 0; i < length; i++) { final int spanStart = text.getSpanStart(spans[i]); final int spanEnd = text.getSpanEnd(spans[i]); if (spanEnd == start || spanStart == end) break; text.removeSpan(spans[i]); } } void removeAdjacentSuggestionSpans(final int pos) { if (!(mText instanceof Editable)) return; final Editable text = (Editable) mText; final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); final int length = spans.length; for (int i = 0; i < length; i++) { final int spanStart = text.getSpanStart(spans[i]); final int spanEnd = text.getSpanEnd(spans[i]); if (spanEnd == pos || spanStart == pos) { if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { text.removeSpan(spans[i]); } } } }
Not private so it can be called from an inner class without going through a thunk.
/** * Not private so it can be called from an inner class without going * through a thunk. */
void sendOnTextChanged(CharSequence text, int start, int before, int after) { if (mListeners != null) { final ArrayList<TextWatcher> list = mListeners; final int count = list.size(); for (int i = 0; i < count; i++) { list.get(i).onTextChanged(text, start, before, after); } } if (mEditor != null) mEditor.sendOnTextChanged(start, before, after); }
Not private so it can be called from an inner class without going through a thunk.
/** * Not private so it can be called from an inner class without going * through a thunk. */
void sendAfterTextChanged(Editable text) { if (mListeners != null) { final ArrayList<TextWatcher> list = mListeners; final int count = list.size(); for (int i = 0; i < count; i++) { list.get(i).afterTextChanged(text); } } // Always notify AutoFillManager - it will return right away if autofill is disabled. notifyAutoFillManagerAfterTextChangedIfNeeded(); hideErrorIfUnchanged(); } private void notifyAutoFillManagerAfterTextChangedIfNeeded() { // It is important to not check whether the view is important for autofill // since the user can trigger autofill manually on not important views. if (!isAutofillable()) { return; } final AutofillManager afm = mContext.getSystemService(AutofillManager.class); if (afm == null) { return; } if (mLastValueSentToAutofillManager == null || !mLastValueSentToAutofillManager.equals(mText)) { if (android.view.autofill.Helper.sVerbose) { Log.v(LOG_TAG, "notifying AFM after text changed"); } afm.notifyValueChanged(TextView.this); mLastValueSentToAutofillManager = mText; } else { if (android.view.autofill.Helper.sVerbose) { Log.v(LOG_TAG, "not notifying AFM on unchanged text"); } } } private boolean isAutofillable() { // It is important to not check whether the view is important for autofill // since the user can trigger autofill manually on not important views. return getAutofillType() != AUTOFILL_TYPE_NONE; } void updateAfterEdit() { invalidate(); int curs = getSelectionStart(); if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { registerForPreDraw(); } checkForResize(); if (curs >= 0) { mHighlightPathBogus = true; if (mEditor != null) mEditor.makeBlink(); bringPointIntoView(curs); } }
Not private so it can be called from an inner class without going through a thunk.
/** * Not private so it can be called from an inner class without going * through a thunk. */
void handleTextChanged(CharSequence buffer, int start, int before, int after) { sLastCutCopyOrTextChangedTime = 0; final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; if (ims == null || ims.mBatchEditNesting == 0) { updateAfterEdit(); } if (ims != null) { ims.mContentChanged = true; if (ims.mChangedStart < 0) { ims.mChangedStart = start; ims.mChangedEnd = start + before; } else { ims.mChangedStart = Math.min(ims.mChangedStart, start); ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); } ims.mChangedDelta += after - before; } resetErrorChangedFlag(); sendOnTextChanged(buffer, start, before, after); onTextChanged(buffer, start, before, after); }
Not private so it can be called from an inner class without going through a thunk.
/** * Not private so it can be called from an inner class without going * through a thunk. */
void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { // XXX Make the start and end move together if this ends up // spending too much time invalidating. boolean selChanged = false; int newSelStart = -1, newSelEnd = -1; final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; if (what == Selection.SELECTION_END) { selChanged = true; newSelEnd = newStart; if (oldStart >= 0 || newStart >= 0) { invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); checkForResize(); registerForPreDraw(); if (mEditor != null) mEditor.makeBlink(); } } if (what == Selection.SELECTION_START) { selChanged = true; newSelStart = newStart; if (oldStart >= 0 || newStart >= 0) { int end = Selection.getSelectionEnd(buf); invalidateCursor(end, oldStart, newStart); } } if (selChanged) { mHighlightPathBogus = true; if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; if ((buf.getSpanFlags(what) & Spanned.SPAN_INTERMEDIATE) == 0) { if (newSelStart < 0) { newSelStart = Selection.getSelectionStart(buf); } if (newSelEnd < 0) { newSelEnd = Selection.getSelectionEnd(buf); } if (mEditor != null) { mEditor.refreshTextActionMode(); if (!hasSelection() && mEditor.getTextActionMode() == null && hasTransientState()) { // User generated selection has been removed. setHasTransientState(false); } } onSelectionChanged(newSelStart, newSelEnd); } } if (what instanceof UpdateAppearance || what instanceof ParagraphStyle || what instanceof CharacterStyle) { if (ims == null || ims.mBatchEditNesting == 0) { invalidate(); mHighlightPathBogus = true; checkForResize(); } else { ims.mContentChanged = true; } if (mEditor != null) { if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); mEditor.invalidateHandlesAndActionMode(); } } if (MetaKeyKeyListener.isMetaTracker(buf, what)) { mHighlightPathBogus = true; if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { ims.mSelectionModeChanged = true; } if (Selection.getSelectionStart(buf) >= 0) { if (ims == null || ims.mBatchEditNesting == 0) { invalidateCursor(); } else { ims.mCursorChanged = true; } } } if (what instanceof ParcelableSpan) { // If this is a span that can be sent to a remote process, // the current extract editor would be interested in it. if (ims != null && ims.mExtractedTextRequest != null) { if (ims.mBatchEditNesting != 0) { if (oldStart >= 0) { if (ims.mChangedStart > oldStart) { ims.mChangedStart = oldStart; } if (ims.mChangedStart > oldEnd) { ims.mChangedStart = oldEnd; } } if (newStart >= 0) { if (ims.mChangedStart > newStart) { ims.mChangedStart = newStart; } if (ims.mChangedStart > newEnd) { ims.mChangedStart = newEnd; } } } else { if (DEBUG_EXTRACT) { Log.v(LOG_TAG, "Span change outside of batch: " + oldStart + "-" + oldEnd + "," + newStart + "-" + newEnd + " " + what); } ims.mContentChanged = true; } } } if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) { mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); } } @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (isTemporarilyDetached()) { // If we are temporarily in the detach state, then do nothing. super.onFocusChanged(focused, direction, previouslyFocusedRect); return; } if (mEditor != null) mEditor.onFocusChanged(focused, direction); if (focused) { if (mSpannable != null) { MetaKeyKeyListener.resetMetaState(mSpannable); } } startStopMarquee(focused); if (mTransformation != null) { mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); } super.onFocusChanged(focused, direction, previouslyFocusedRect); } @Override public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); startStopMarquee(hasWindowFocus); } @Override protected void onVisibilityChanged(View changedView, int visibility) { super.onVisibilityChanged(changedView, visibility); if (mEditor != null && visibility != VISIBLE) { mEditor.hideCursorAndSpanControllers(); stopTextActionMode(); } }
Use BaseInputConnection.removeComposingSpans() to remove any IME composing state from this text view.
/** * Use {@link BaseInputConnection#removeComposingSpans * BaseInputConnection.removeComposingSpans()} to remove any IME composing * state from this text view. */
public void clearComposingText() { if (mText instanceof Spannable) { BaseInputConnection.removeComposingSpans(mSpannable); } } @Override public void setSelected(boolean selected) { boolean wasSelected = isSelected(); super.setSelected(selected); if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { if (selected) { startMarquee(); } else { stopMarquee(); } } } @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getActionMasked(); if (mEditor != null) { mEditor.onTouchEvent(event); if (mEditor.mSelectionModifierCursorController != null && mEditor.mSelectionModifierCursorController.isDragAcceleratorActive()) { return true; } } final boolean superResult = super.onTouchEvent(event); /* * Don't handle the release after a long press, because it will move the selection away from * whatever the menu action was trying to affect. If the long press should have triggered an * insertion action mode, we can now actually show it. */ if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { mEditor.mDiscardNextActionUp = false; if (mEditor.mIsInsertionActionModeStartPending) { mEditor.startInsertionActionMode(); mEditor.mIsInsertionActionModeStartPending = false; } return superResult; } final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) && (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() && mText instanceof Spannable && mLayout != null) { boolean handled = false; if (mMovement != null) { handled |= mMovement.onTouchEvent(this, mSpannable, event); } final boolean textIsSelectable = isTextSelectable(); if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { // The LinkMovementMethod which should handle taps on links has not been installed // on non editable text that support text selection. // We reproduce its behavior here to open links for these. ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(), getSelectionEnd(), ClickableSpan.class); if (links.length > 0) { links[0].onClick(this); handled = true; } } if (touchIsFinished && (isTextEditable() || textIsSelectable)) { // Show the IME, except when selecting in read-only text. final InputMethodManager imm = InputMethodManager.peekInstance(); viewClicked(imm); if (isTextEditable() && mEditor.mShowSoftInputOnFocus && imm != null) { imm.showSoftInput(this, 0); } // The above condition ensures that the mEditor is not null mEditor.onTouchUpEvent(event); handled = true; } if (handled) { return true; } } return superResult; } @Override public boolean onGenericMotionEvent(MotionEvent event) { if (mMovement != null && mText instanceof Spannable && mLayout != null) { try { if (mMovement.onGenericMotionEvent(this, mSpannable, event)) { return true; } } catch (AbstractMethodError ex) { // onGenericMotionEvent was added to the MovementMethod interface in API 12. // Ignore its absence in case third party applications implemented the // interface directly. } } return super.onGenericMotionEvent(event); } @Override protected void onCreateContextMenu(ContextMenu menu) { if (mEditor != null) { mEditor.onCreateContextMenu(menu); } } @Override public boolean showContextMenu() { if (mEditor != null) { mEditor.setContextMenuAnchor(Float.NaN, Float.NaN); } return super.showContextMenu(); } @Override public boolean showContextMenu(float x, float y) { if (mEditor != null) { mEditor.setContextMenuAnchor(x, y); } return super.showContextMenu(x, y); }
Returns:True iff this TextView contains a text that can be edited, or if this is a selectable TextView.
/** * @return True iff this TextView contains a text that can be edited, or if this is * a selectable TextView. */
boolean isTextEditable() { return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); }
Returns true, only while processing a touch gesture, if the initial touch down event caused focus to move to the text view and as a result its selection changed. Only valid while processing the touch gesture of interest, in an editable text view.
/** * Returns true, only while processing a touch gesture, if the initial * touch down event caused focus to move to the text view and as a result * its selection changed. Only valid while processing the touch gesture * of interest, in an editable text view. */
public boolean didTouchFocusSelect() { return mEditor != null && mEditor.mTouchFocusSelected; } @Override public void cancelLongPress() { super.cancelLongPress(); if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; } @Override public boolean onTrackballEvent(MotionEvent event) { if (mMovement != null && mSpannable != null && mLayout != null) { if (mMovement.onTrackballEvent(this, mSpannable, event)) { return true; } } return super.onTrackballEvent(event); }
Sets the Scroller used for producing a scrolling animation
Params:
  • s – A Scroller instance
/** * Sets the Scroller used for producing a scrolling animation * * @param s A Scroller instance */
public void setScroller(Scroller s) { mScroller = s; } @Override protected float getLeftFadingEdgeStrength() { if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { final Marquee marquee = mMarquee; if (marquee.shouldDrawLeftFade()) { return getHorizontalFadingEdgeStrength(marquee.getScroll(), 0.0f); } else { return 0.0f; } } else if (getLineCount() == 1) { final float lineLeft = getLayout().getLineLeft(0); if (lineLeft > mScrollX) return 0.0f; return getHorizontalFadingEdgeStrength(mScrollX, lineLeft); } return super.getLeftFadingEdgeStrength(); } @Override protected float getRightFadingEdgeStrength() { if (isMarqueeFadeEnabled() && mMarquee != null && !mMarquee.isStopped()) { final Marquee marquee = mMarquee; return getHorizontalFadingEdgeStrength(marquee.getMaxFadeScroll(), marquee.getScroll()); } else if (getLineCount() == 1) { final float rightEdge = mScrollX + (getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight()); final float lineRight = getLayout().getLineRight(0); if (lineRight < rightEdge) return 0.0f; return getHorizontalFadingEdgeStrength(rightEdge, lineRight); } return super.getRightFadingEdgeStrength(); }
Calculates the fading edge strength as the ratio of the distance between two horizontal positions to View.getHorizontalFadingEdgeLength(). Uses the absolute value for the distance calculation.
Params:
  • position1 – A horizontal position.
  • position2 – A horizontal position.
Returns:Fading edge strength between [0.0f, 1.0f].
/** * Calculates the fading edge strength as the ratio of the distance between two * horizontal positions to {@link View#getHorizontalFadingEdgeLength()}. Uses the absolute * value for the distance calculation. * * @param position1 A horizontal position. * @param position2 A horizontal position. * @return Fading edge strength between [0.0f, 1.0f]. */
@FloatRange(from = 0.0, to = 1.0) private float getHorizontalFadingEdgeStrength(float position1, float position2) { final int horizontalFadingEdgeLength = getHorizontalFadingEdgeLength(); if (horizontalFadingEdgeLength == 0) return 0.0f; final float diff = Math.abs(position1 - position2); if (diff > horizontalFadingEdgeLength) return 1.0f; return diff / horizontalFadingEdgeLength; } private boolean isMarqueeFadeEnabled() { return mEllipsize == TextUtils.TruncateAt.MARQUEE && mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; } @Override protected int computeHorizontalScrollRange() { if (mLayout != null) { return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ? (int) mLayout.getLineWidth(0) : mLayout.getWidth(); } return super.computeHorizontalScrollRange(); } @Override protected int computeVerticalScrollRange() { if (mLayout != null) { return mLayout.getHeight(); } return super.computeVerticalScrollRange(); } @Override protected int computeVerticalScrollExtent() { return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); } @Override public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { super.findViewsWithText(outViews, searched, flags); if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { String searchedLowerCase = searched.toString().toLowerCase(); String textLowerCase = mText.toString().toLowerCase(); if (textLowerCase.contains(searchedLowerCase)) { outViews.add(this); } } }
Type of the text buffer that defines the characteristics of the text such as static, styleable, or editable.
/** * Type of the text buffer that defines the characteristics of the text such as static, * styleable, or editable. */
public enum BufferType { NORMAL, SPANNABLE, EDITABLE }
Returns the TextView_textColor attribute from the TypedArray, if set, or the TextAppearance_textColor from the TextView_textAppearance attribute, if TextView_textColor was not set directly.
@removed
/** * Returns the TextView_textColor attribute from the TypedArray, if set, or * the TextAppearance_textColor from the TextView_textAppearance attribute, * if TextView_textColor was not set directly. * * @removed */
public static ColorStateList getTextColors(Context context, TypedArray attrs) { if (attrs == null) { // Preserve behavior prior to removal of this API. throw new NullPointerException(); } // It's not safe to use this method from apps. The parameter 'attrs' // must have been obtained using the TextView filter array which is not // available to the SDK. As such, we grab a default TypedArray with the // right filter instead here. final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); if (colors == null) { final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); if (ap != 0) { final TypedArray appearance = context.obtainStyledAttributes( ap, R.styleable.TextAppearance); colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); appearance.recycle(); } } a.recycle(); return colors; }
Returns the default color from the TextView_textColor attribute from the AttributeSet, if set, or the default color from the TextAppearance_textColor from the TextView_textAppearance attribute, if TextView_textColor was not set directly.
@removed
/** * Returns the default color from the TextView_textColor attribute from the * AttributeSet, if set, or the default color from the * TextAppearance_textColor from the TextView_textAppearance attribute, if * TextView_textColor was not set directly. * * @removed */
public static int getTextColor(Context context, TypedArray attrs, int def) { final ColorStateList colors = getTextColors(context, attrs); if (colors == null) { return def; } else { return colors.getDefaultColor(); } } @Override public boolean onKeyShortcut(int keyCode, KeyEvent event) { if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { // Handle Ctrl-only shortcuts. switch (keyCode) { case KeyEvent.KEYCODE_A: if (canSelectText()) { return onTextContextMenuItem(ID_SELECT_ALL); } break; case KeyEvent.KEYCODE_Z: if (canUndo()) { return onTextContextMenuItem(ID_UNDO); } break; case KeyEvent.KEYCODE_X: if (canCut()) { return onTextContextMenuItem(ID_CUT); } break; case KeyEvent.KEYCODE_C: if (canCopy()) { return onTextContextMenuItem(ID_COPY); } break; case KeyEvent.KEYCODE_V: if (canPaste()) { return onTextContextMenuItem(ID_PASTE); } break; } } else if (event.hasModifiers(KeyEvent.META_CTRL_ON | KeyEvent.META_SHIFT_ON)) { // Handle Ctrl-Shift shortcuts. switch (keyCode) { case KeyEvent.KEYCODE_Z: if (canRedo()) { return onTextContextMenuItem(ID_REDO); } break; case KeyEvent.KEYCODE_V: if (canPaste()) { return onTextContextMenuItem(ID_PASTE_AS_PLAIN_TEXT); } } } return super.onKeyShortcut(keyCode, event); }
Unlike textCanBeSelected(), this method is based on the current state of the TextView. textCanBeSelected() has to be true (this is one of the conditions to have a selection controller (see Editor.prepareCursorControllers()), but this is not sufficient.
/** * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not * sufficient. */
boolean canSelectText() { return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); }
Test based on the intrinsic charateristics of the TextView. The text must be spannable and the movement method must allow for arbitary selection. See also canSelectText().
/** * Test based on the <i>intrinsic</i> charateristics of the TextView. * The text must be spannable and the movement method must allow for arbitary selection. * * See also {@link #canSelectText()}. */
boolean textCanBeSelected() { // prepareCursorController() relies on this method. // If you change this condition, make sure prepareCursorController is called anywhere // the value of this condition might be changed. if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; return isTextEditable() || (isTextSelectable() && mText instanceof Spannable && isEnabled()); } private Locale getTextServicesLocale(boolean allowNullLocale) { // Start fetching the text services locale asynchronously. updateTextServicesLocaleAsync(); // If !allowNullLocale and there is no cached text services locale, just return the default // locale. return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() : mCurrentSpellCheckerLocaleCache; }
This is a temporary method. Future versions may support multi-locale text. Caveat: This method may not return the latest text services locale, but this should be acceptable and it's more important to make this method asynchronous.
Returns:The locale that should be used for a word iterator in this TextView, based on the current spell checker settings, the current IME's locale, or the system default locale. Please note that a word iterator in this TextView is different from another word iterator used by SpellChecker.java of TextView. This method should be used for the former.
@hide
/** * This is a temporary method. Future versions may support multi-locale text. * Caveat: This method may not return the latest text services locale, but this should be * acceptable and it's more important to make this method asynchronous. * * @return The locale that should be used for a word iterator * in this TextView, based on the current spell checker settings, * the current IME's locale, or the system default locale. * Please note that a word iterator in this TextView is different from another word iterator * used by SpellChecker.java of TextView. This method should be used for the former. * @hide */
// TODO: Support multi-locale // TODO: Update the text services locale immediately after the keyboard locale is switched // by catching intent of keyboard switch event public Locale getTextServicesLocale() { return getTextServicesLocale(false /* allowNullLocale */); }
Returns:true if this TextView is specialized for showing and interacting with the extracted text in a full-screen input method.
@hide
/** * @return {@code true} if this TextView is specialized for showing and interacting with the * extracted text in a full-screen input method. * @hide */
public boolean isInExtractedMode() { return false; }
Returns:true if this widget supports auto-sizing text and has been configured to auto-size.
/** * @return {@code true} if this widget supports auto-sizing text and has been configured to * auto-size. */
private boolean isAutoSizeEnabled() { return supportsAutoSizeText() && mAutoSizeTextType != AUTO_SIZE_TEXT_TYPE_NONE; }
Returns:true if this TextView supports auto-sizing text to fit within its container.
@hide
/** * @return {@code true} if this TextView supports auto-sizing text to fit within its container. * @hide */
protected boolean supportsAutoSizeText() { return true; }
This is a temporary method. Future versions may support multi-locale text. Caveat: This method may not return the latest spell checker locale, but this should be acceptable and it's more important to make this method asynchronous.
Returns:The locale that should be used for a spell checker in this TextView, based on the current spell checker settings, the current IME's locale, or the system default locale.
@hide
/** * This is a temporary method. Future versions may support multi-locale text. * Caveat: This method may not return the latest spell checker locale, but this should be * acceptable and it's more important to make this method asynchronous. * * @return The locale that should be used for a spell checker in this TextView, * based on the current spell checker settings, the current IME's locale, or the system default * locale. * @hide */
public Locale getSpellCheckerLocale() { return getTextServicesLocale(true /* allowNullLocale */); } private void updateTextServicesLocaleAsync() { // AsyncTask.execute() uses a serial executor which means we don't have // to lock around updateTextServicesLocaleLocked() to prevent it from // being executed n times in parallel. AsyncTask.execute(new Runnable() { @Override public void run() { updateTextServicesLocaleLocked(); } }); } private void updateTextServicesLocaleLocked() { final TextServicesManager textServicesManager = (TextServicesManager) mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); final Locale locale; if (subtype != null) { locale = subtype.getLocaleObject(); } else { locale = null; } mCurrentSpellCheckerLocaleCache = locale; } void onLocaleChanged() { mEditor.onLocaleChanged(); }
This method is used by the ArrowKeyMovementMethod to jump from one word to the other. Made available to achieve a consistent behavior.
@hide
/** * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. * Made available to achieve a consistent behavior. * @hide */
public WordIterator getWordIterator() { if (mEditor != null) { return mEditor.getWordIterator(); } else { return null; } }
@hide
/** @hide */
@Override public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) { super.onPopulateAccessibilityEventInternal(event); final CharSequence text = getTextForAccessibility(); if (!TextUtils.isEmpty(text)) { event.getText().add(text); } } @Override public CharSequence getAccessibilityClassName() { return TextView.class.getName(); } @Override public void onProvideStructure(ViewStructure structure) { super.onProvideStructure(structure); onProvideAutoStructureForAssistOrAutofill(structure, false); } @Override public void onProvideAutofillStructure(ViewStructure structure, int flags) { super.onProvideAutofillStructure(structure, flags); onProvideAutoStructureForAssistOrAutofill(structure, true); } private void onProvideAutoStructureForAssistOrAutofill(ViewStructure structure, boolean forAutofill) { final boolean isPassword = hasPasswordTransformationMethod() || isPasswordInputType(getInputType()); if (forAutofill) { structure.setDataIsSensitive(!mTextSetFromXmlOrResourceId); if (mTextId != ResourceId.ID_NULL) { try { structure.setTextIdEntry(getResources().getResourceEntryName(mTextId)); } catch (Resources.NotFoundException e) { if (android.view.autofill.Helper.sVerbose) { Log.v(LOG_TAG, "onProvideAutofillStructure(): cannot set name for text id " + mTextId + ": " + e.getMessage()); } } } } if (!isPassword || forAutofill) { if (mLayout == null) { assumeLayout(); } Layout layout = mLayout; final int lineCount = layout.getLineCount(); if (lineCount <= 1) { // Simple case: this is a single line. final CharSequence text = getText(); if (forAutofill) { structure.setText(text); } else { structure.setText(text, getSelectionStart(), getSelectionEnd()); } } else { // Complex case: multi-line, could be scrolled or within a scroll container // so some lines are not visible. final int[] tmpCords = new int[2]; getLocationInWindow(tmpCords); final int topWindowLocation = tmpCords[1]; View root = this; ViewParent viewParent = getParent(); while (viewParent instanceof View) { root = (View) viewParent; viewParent = root.getParent(); } final int windowHeight = root.getHeight(); final int topLine; final int bottomLine; if (topWindowLocation >= 0) { // The top of the view is fully within its window; start text at line 0. topLine = getLineAtCoordinateUnclamped(0); bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1); } else { // The top of hte window has scrolled off the top of the window; figure out // the starting line for this. topLine = getLineAtCoordinateUnclamped(-topWindowLocation); bottomLine = getLineAtCoordinateUnclamped(windowHeight - 1 - topWindowLocation); } // We want to return some contextual lines above/below the lines that are // actually visible. int expandedTopLine = topLine - (bottomLine - topLine) / 2; if (expandedTopLine < 0) { expandedTopLine = 0; } int expandedBottomLine = bottomLine + (bottomLine - topLine) / 2; if (expandedBottomLine >= lineCount) { expandedBottomLine = lineCount - 1; } // Convert lines into character offsets. int expandedTopChar = layout.getLineStart(expandedTopLine); int expandedBottomChar = layout.getLineEnd(expandedBottomLine); // Take into account selection -- if there is a selection, we need to expand // the text we are returning to include that selection. final int selStart = getSelectionStart(); final int selEnd = getSelectionEnd(); if (selStart < selEnd) { if (selStart < expandedTopChar) { expandedTopChar = selStart; } if (selEnd > expandedBottomChar) { expandedBottomChar = selEnd; } } // Get the text and trim it to the range we are reporting. CharSequence text = getText(); if (expandedTopChar > 0 || expandedBottomChar < text.length()) { text = text.subSequence(expandedTopChar, expandedBottomChar); } if (forAutofill) { structure.setText(text); } else { structure.setText(text, selStart - expandedTopChar, selEnd - expandedTopChar); final int[] lineOffsets = new int[bottomLine - topLine + 1]; final int[] lineBaselines = new int[bottomLine - topLine + 1]; final int baselineOffset = getBaselineOffset(); for (int i = topLine; i <= bottomLine; i++) { lineOffsets[i - topLine] = layout.getLineStart(i); lineBaselines[i - topLine] = layout.getLineBaseline(i) + baselineOffset; } structure.setTextLines(lineOffsets, lineBaselines); } } if (!forAutofill) { // Extract style information that applies to the TextView as a whole. int style = 0; int typefaceStyle = getTypefaceStyle(); if ((typefaceStyle & Typeface.BOLD) != 0) { style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; } if ((typefaceStyle & Typeface.ITALIC) != 0) { style |= AssistStructure.ViewNode.TEXT_STYLE_ITALIC; } // Global styles can also be set via TextView.setPaintFlags(). int paintFlags = mTextPaint.getFlags(); if ((paintFlags & Paint.FAKE_BOLD_TEXT_FLAG) != 0) { style |= AssistStructure.ViewNode.TEXT_STYLE_BOLD; } if ((paintFlags & Paint.UNDERLINE_TEXT_FLAG) != 0) { style |= AssistStructure.ViewNode.TEXT_STYLE_UNDERLINE; } if ((paintFlags & Paint.STRIKE_THRU_TEXT_FLAG) != 0) { style |= AssistStructure.ViewNode.TEXT_STYLE_STRIKE_THRU; } // TextView does not have its own text background color. A background is either part // of the View (and can be any drawable) or a BackgroundColorSpan inside the text. structure.setTextStyle(getTextSize(), getCurrentTextColor(), AssistStructure.ViewNode.TEXT_COLOR_UNDEFINED /* bgColor */, style); } else { structure.setMinTextEms(getMinEms()); structure.setMaxTextEms(getMaxEms()); int maxLength = -1; for (InputFilter filter: getFilters()) { if (filter instanceof InputFilter.LengthFilter) { maxLength = ((InputFilter.LengthFilter) filter).getMax(); break; } } structure.setMaxTextLength(maxLength); } } structure.setHint(getHint()); structure.setInputType(getInputType()); } boolean canRequestAutofill() { if (!isAutofillable()) { return false; } final AutofillManager afm = mContext.getSystemService(AutofillManager.class); if (afm != null) { return afm.isEnabled(); } return false; } private void requestAutofill() { final AutofillManager afm = mContext.getSystemService(AutofillManager.class); if (afm != null) { afm.requestAutofill(this); } } @Override public void autofill(AutofillValue value) { if (!value.isText() || !isTextEditable()) { Log.w(LOG_TAG, value + " could not be autofilled into " + this); return; } final CharSequence autofilledValue = value.getTextValue(); // First autofill it... setText(autofilledValue, mBufferType, true, 0); // ...then move cursor to the end. final CharSequence text = getText(); if ((text instanceof Spannable)) { Selection.setSelection((Spannable) text, text.length()); } } @Override public @AutofillType int getAutofillType() { return isTextEditable() ? AUTOFILL_TYPE_TEXT : AUTOFILL_TYPE_NONE; }
Gets the TextView's current text for AutoFill. The value is trimmed to 100K chars if longer.
See Also:
Returns:current text, null if the text is not editable
/** * Gets the {@link TextView}'s current text for AutoFill. The value is trimmed to 100K * {@code char}s if longer. * * @return current text, {@code null} if the text is not editable * * @see View#getAutofillValue() */
@Override @Nullable public AutofillValue getAutofillValue() { if (isTextEditable()) { final CharSequence text = TextUtils.trimToParcelableSize(getText()); return AutofillValue.forText(text); } return null; }
@hide
/** @hide */
@Override public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { super.onInitializeAccessibilityEventInternal(event); final boolean isPassword = hasPasswordTransformationMethod(); event.setPassword(isPassword); if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { event.setFromIndex(Selection.getSelectionStart(mText)); event.setToIndex(Selection.getSelectionEnd(mText)); event.setItemCount(mText.length()); } }
@hide
/** @hide */
@Override public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); final boolean isPassword = hasPasswordTransformationMethod(); info.setPassword(isPassword); info.setText(getTextForAccessibility()); info.setHintText(mHint); info.setShowingHintText(isShowingHint()); if (mBufferType == BufferType.EDITABLE) { info.setEditable(true); if (isEnabled()) { info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SET_TEXT); } } if (mEditor != null) { info.setInputType(mEditor.mInputType); if (mEditor.mError != null) { info.setContentInvalid(true); info.setError(mEditor.mError); } } if (!TextUtils.isEmpty(mText)) { info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); info.setAvailableExtraData( Arrays.asList(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)); } if (isFocused()) { if (canCopy()) { info.addAction(AccessibilityNodeInfo.ACTION_COPY); } if (canPaste()) { info.addAction(AccessibilityNodeInfo.ACTION_PASTE); } if (canCut()) { info.addAction(AccessibilityNodeInfo.ACTION_CUT); } if (canShare()) { info.addAction(new AccessibilityNodeInfo.AccessibilityAction( ACCESSIBILITY_ACTION_SHARE, getResources().getString(com.android.internal.R.string.share))); } if (canProcessText()) { // also implies mEditor is not null. mEditor.mProcessTextIntentActionsHandler.onInitializeAccessibilityNodeInfo(info); } } // Check for known input filter types. final int numFilters = mFilters.length; for (int i = 0; i < numFilters; i++) { final InputFilter filter = mFilters[i]; if (filter instanceof InputFilter.LengthFilter) { info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); } } if (!isSingleLine()) { info.setMultiLine(true); } } @Override public void addExtraDataToAccessibilityNodeInfo( AccessibilityNodeInfo info, String extraDataKey, Bundle arguments) { // The only extra data we support requires arguments. if (arguments == null) { return; } if (extraDataKey.equals(EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY)) { int positionInfoStartIndex = arguments.getInt( EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX, -1); int positionInfoLength = arguments.getInt( EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH, -1); if ((positionInfoLength <= 0) || (positionInfoStartIndex < 0) || (positionInfoStartIndex >= mText.length())) { Log.e(LOG_TAG, "Invalid arguments for accessibility character locations"); return; } RectF[] boundingRects = new RectF[positionInfoLength]; final CursorAnchorInfo.Builder builder = new CursorAnchorInfo.Builder(); populateCharacterBounds(builder, positionInfoStartIndex, positionInfoStartIndex + positionInfoLength, viewportToContentHorizontalOffset(), viewportToContentVerticalOffset()); CursorAnchorInfo cursorAnchorInfo = builder.setMatrix(null).build(); for (int i = 0; i < positionInfoLength; i++) { int flags = cursorAnchorInfo.getCharacterBoundsFlags(positionInfoStartIndex + i); if ((flags & FLAG_HAS_VISIBLE_REGION) == FLAG_HAS_VISIBLE_REGION) { RectF bounds = cursorAnchorInfo .getCharacterBounds(positionInfoStartIndex + i); if (bounds != null) { mapRectFromViewToScreenCoords(bounds, true); boundingRects[i] = bounds; } } } info.getExtras().putParcelableArray(extraDataKey, boundingRects); } }
Populate requested character bounds in a Builder
Params:
  • builder – The builder to populate
  • startIndex – The starting character index to populate
  • endIndex – The ending character index to populate
  • viewportToContentHorizontalOffset – The horizontal offset from the viewport to the content
  • viewportToContentVerticalOffset – The vertical offset from the viewport to the content
@hide
/** * Populate requested character bounds in a {@link CursorAnchorInfo.Builder} * * @param builder The builder to populate * @param startIndex The starting character index to populate * @param endIndex The ending character index to populate * @param viewportToContentHorizontalOffset The horizontal offset from the viewport to the * content * @param viewportToContentVerticalOffset The vertical offset from the viewport to the content * @hide */
public void populateCharacterBounds(CursorAnchorInfo.Builder builder, int startIndex, int endIndex, float viewportToContentHorizontalOffset, float viewportToContentVerticalOffset) { final int minLine = mLayout.getLineForOffset(startIndex); final int maxLine = mLayout.getLineForOffset(endIndex - 1); for (int line = minLine; line <= maxLine; ++line) { final int lineStart = mLayout.getLineStart(line); final int lineEnd = mLayout.getLineEnd(line); final int offsetStart = Math.max(lineStart, startIndex); final int offsetEnd = Math.min(lineEnd, endIndex); final boolean ltrLine = mLayout.getParagraphDirection(line) == Layout.DIR_LEFT_TO_RIGHT; final float[] widths = new float[offsetEnd - offsetStart]; mLayout.getPaint().getTextWidths(mTransformed, offsetStart, offsetEnd, widths); final float top = mLayout.getLineTop(line); final float bottom = mLayout.getLineBottom(line); for (int offset = offsetStart; offset < offsetEnd; ++offset) { final float charWidth = widths[offset - offsetStart]; final boolean isRtl = mLayout.isRtlCharAt(offset); final float primary = mLayout.getPrimaryHorizontal(offset); final float secondary = mLayout.getSecondaryHorizontal(offset); // TODO: This doesn't work perfectly for text with custom styles and // TAB chars. final float left; final float right; if (ltrLine) { if (isRtl) { left = secondary - charWidth; right = secondary; } else { left = primary; right = primary + charWidth; } } else { if (!isRtl) { left = secondary; right = secondary + charWidth; } else { left = primary - charWidth; right = primary; } } // TODO: Check top-right and bottom-left as well. final float localLeft = left + viewportToContentHorizontalOffset; final float localRight = right + viewportToContentHorizontalOffset; final float localTop = top + viewportToContentVerticalOffset; final float localBottom = bottom + viewportToContentVerticalOffset; final boolean isTopLeftVisible = isPositionVisible(localLeft, localTop); final boolean isBottomRightVisible = isPositionVisible(localRight, localBottom); int characterBoundsFlags = 0; if (isTopLeftVisible || isBottomRightVisible) { characterBoundsFlags |= FLAG_HAS_VISIBLE_REGION; } if (!isTopLeftVisible || !isBottomRightVisible) { characterBoundsFlags |= CursorAnchorInfo.FLAG_HAS_INVISIBLE_REGION; } if (isRtl) { characterBoundsFlags |= CursorAnchorInfo.FLAG_IS_RTL; } // Here offset is the index in Java chars. builder.addCharacterBounds(offset, localLeft, localTop, localRight, localBottom, characterBoundsFlags); } } }
@hide
/** * @hide */
public boolean isPositionVisible(final float positionX, final float positionY) { synchronized (TEMP_POSITION) { final float[] position = TEMP_POSITION; position[0] = positionX; position[1] = positionY; View view = this; while (view != null) { if (view != this) { // Local scroll is already taken into account in positionX/Y position[0] -= view.getScrollX(); position[1] -= view.getScrollY(); } if (position[0] < 0 || position[1] < 0 || position[0] > view.getWidth() || position[1] > view.getHeight()) { return false; } if (!view.getMatrix().isIdentity()) { view.getMatrix().mapPoints(position); } position[0] += view.getLeft(); position[1] += view.getTop(); final ViewParent parent = view.getParent(); if (parent instanceof View) { view = (View) parent; } else { // We've reached the ViewRoot, stop iterating view = null; } } } // We've been able to walk up the view hierarchy and the position was never clipped return true; }
Performs an accessibility action after it has been offered to the delegate.
@hide
/** * Performs an accessibility action after it has been offered to the * delegate. * * @hide */
@Override public boolean performAccessibilityActionInternal(int action, Bundle arguments) { if (mEditor != null && mEditor.mProcessTextIntentActionsHandler.performAccessibilityAction(action)) { return true; } switch (action) { case AccessibilityNodeInfo.ACTION_CLICK: { return performAccessibilityActionClick(arguments); } case AccessibilityNodeInfo.ACTION_COPY: { if (isFocused() && canCopy()) { if (onTextContextMenuItem(ID_COPY)) { return true; } } } return false; case AccessibilityNodeInfo.ACTION_PASTE: { if (isFocused() && canPaste()) { if (onTextContextMenuItem(ID_PASTE)) { return true; } } } return false; case AccessibilityNodeInfo.ACTION_CUT: { if (isFocused() && canCut()) { if (onTextContextMenuItem(ID_CUT)) { return true; } } } return false; case AccessibilityNodeInfo.ACTION_SET_SELECTION: { ensureIterableTextForAccessibilitySelectable(); CharSequence text = getIterableTextForAccessibility(); if (text == null) { return false; } final int start = (arguments != null) ? arguments.getInt( AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; final int end = (arguments != null) ? arguments.getInt( AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; if ((getSelectionStart() != start || getSelectionEnd() != end)) { // No arguments clears the selection. if (start == end && end == -1) { Selection.removeSelection((Spannable) text); return true; } if (start >= 0 && start <= end && end <= text.length()) { Selection.setSelection((Spannable) text, start, end); // Make sure selection mode is engaged. if (mEditor != null) { mEditor.startSelectionActionModeAsync(false); } return true; } } } return false; case AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY: case AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY: { ensureIterableTextForAccessibilitySelectable(); return super.performAccessibilityActionInternal(action, arguments); } case ACCESSIBILITY_ACTION_SHARE: { if (isFocused() && canShare()) { if (onTextContextMenuItem(ID_SHARE)) { return true; } } } return false; case AccessibilityNodeInfo.ACTION_SET_TEXT: { if (!isEnabled() || (mBufferType != BufferType.EDITABLE)) { return false; } CharSequence text = (arguments != null) ? arguments.getCharSequence( AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE) : null; setText(text); if (mText != null) { int updatedTextLength = mText.length(); if (updatedTextLength > 0) { Selection.setSelection(mSpannable, updatedTextLength); } } } return true; default: { return super.performAccessibilityActionInternal(action, arguments); } } } private boolean performAccessibilityActionClick(Bundle arguments) { boolean handled = false; if (!isEnabled()) { return false; } if (isClickable() || isLongClickable()) { // Simulate View.onTouchEvent for an ACTION_UP event if (isFocusable() && !isFocused()) { requestFocus(); } performClick(); handled = true; } // Show the IME, except when selecting in read-only text. if ((mMovement != null || onCheckIsTextEditor()) && hasSpannableText() && mLayout != null && (isTextEditable() || isTextSelectable()) && isFocused()) { final InputMethodManager imm = InputMethodManager.peekInstance(); viewClicked(imm); if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { handled |= imm.showSoftInput(this, 0); } } return handled; } private boolean hasSpannableText() { return mText != null && mText instanceof Spannable; }
@hide
/** @hide */
@Override public void sendAccessibilityEventInternal(int eventType) { if (eventType == AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED && mEditor != null) { mEditor.mProcessTextIntentActionsHandler.initializeAccessibilityActions(); } super.sendAccessibilityEventInternal(eventType); } @Override public void sendAccessibilityEventUnchecked(AccessibilityEvent event) { // Do not send scroll events since first they are not interesting for // accessibility and second such events a generated too frequently. // For details see the implementation of bringTextIntoView(). if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) { return; } super.sendAccessibilityEventUnchecked(event); }
Returns the text that should be exposed to accessibility services.

This approximates what is displayed visually. If the user has specified that accessibility services should speak passwords, this method will bypass any password transformation method and return unobscured text.

Returns:the text that should be exposed to accessibility services, may be null if no text is set
/** * Returns the text that should be exposed to accessibility services. * <p> * This approximates what is displayed visually. If the user has specified * that accessibility services should speak passwords, this method will * bypass any password transformation method and return unobscured text. * * @return the text that should be exposed to accessibility services, may * be {@code null} if no text is set */
@Nullable private CharSequence getTextForAccessibility() { // If the text is empty, we must be showing the hint text. if (TextUtils.isEmpty(mText)) { return mHint; } // Otherwise, return whatever text is being displayed. return TextUtils.trimToParcelableSize(mTransformed); } void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount) { AccessibilityEvent event = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); event.setFromIndex(fromIndex); event.setRemovedCount(removedCount); event.setAddedCount(addedCount); event.setBeforeText(beforeText); sendAccessibilityEventUnchecked(event); }
Returns whether this text view is a current input method target. The default implementation just checks with InputMethodManager.
Returns:True if the TextView is a current input method target; false otherwise.
/** * Returns whether this text view is a current input method target. The * default implementation just checks with {@link InputMethodManager}. * @return True if the TextView is a current input method target; false otherwise. */
public boolean isInputMethodTarget() { InputMethodManager imm = InputMethodManager.peekInstance(); return imm != null && imm.isActive(this); } static final int ID_SELECT_ALL = android.R.id.selectAll; static final int ID_UNDO = android.R.id.undo; static final int ID_REDO = android.R.id.redo; static final int ID_CUT = android.R.id.cut; static final int ID_COPY = android.R.id.copy; static final int ID_PASTE = android.R.id.paste; static final int ID_SHARE = android.R.id.shareText; static final int ID_PASTE_AS_PLAIN_TEXT = android.R.id.pasteAsPlainText; static final int ID_REPLACE = android.R.id.replaceText; static final int ID_ASSIST = android.R.id.textAssist; static final int ID_AUTOFILL = android.R.id.autofill;
Called when a context menu option for the text view is selected. Currently this will be one of selectAll.selectAll, cut.cut, copy.copy, paste.paste or shareText.shareText.
Returns:true if the context menu item action was performed.
/** * Called when a context menu option for the text view is selected. Currently * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, * {@link android.R.id#copy}, {@link android.R.id#paste} or {@link android.R.id#shareText}. * * @return true if the context menu item action was performed. */
public boolean onTextContextMenuItem(int id) { int min = 0; int max = mText.length(); if (isFocused()) { final int selStart = getSelectionStart(); final int selEnd = getSelectionEnd(); min = Math.max(0, Math.min(selStart, selEnd)); max = Math.max(0, Math.max(selStart, selEnd)); } switch (id) { case ID_SELECT_ALL: final boolean hadSelection = hasSelection(); selectAllText(); if (mEditor != null && hadSelection) { mEditor.invalidateActionModeAsync(); } return true; case ID_UNDO: if (mEditor != null) { mEditor.undo(); } return true; // Returns true even if nothing was undone. case ID_REDO: if (mEditor != null) { mEditor.redo(); } return true; // Returns true even if nothing was undone. case ID_PASTE: paste(min, max, true /* withFormatting */); return true; case ID_PASTE_AS_PLAIN_TEXT: paste(min, max, false /* withFormatting */); return true; case ID_CUT: final ClipData cutData = ClipData.newPlainText(null, getTransformedText(min, max)); if (setPrimaryClip(cutData)) { deleteText_internal(min, max); } else { Toast.makeText(getContext(), com.android.internal.R.string.failed_to_copy_to_clipboard, Toast.LENGTH_SHORT).show(); } return true; case ID_COPY: // For link action mode in a non-selectable/non-focusable TextView, // make sure that we set the appropriate min/max. final int selStart = getSelectionStart(); final int selEnd = getSelectionEnd(); min = Math.max(0, Math.min(selStart, selEnd)); max = Math.max(0, Math.max(selStart, selEnd)); final ClipData copyData = ClipData.newPlainText(null, getTransformedText(min, max)); if (setPrimaryClip(copyData)) { stopTextActionMode(); } else { Toast.makeText(getContext(), com.android.internal.R.string.failed_to_copy_to_clipboard, Toast.LENGTH_SHORT).show(); } return true; case ID_REPLACE: if (mEditor != null) { mEditor.replace(); } return true; case ID_SHARE: shareSelectedText(); return true; case ID_AUTOFILL: requestAutofill(); stopTextActionMode(); return true; } return false; } CharSequence getTransformedText(int start, int end) { return removeSuggestionSpans(mTransformed.subSequence(start, end)); } @Override public boolean performLongClick() { boolean handled = false; boolean performedHapticFeedback = false; if (mEditor != null) { mEditor.mIsBeingLongClicked = true; } if (super.performLongClick()) { handled = true; performedHapticFeedback = true; } if (mEditor != null) { handled |= mEditor.performLongClick(handled); mEditor.mIsBeingLongClicked = false; } if (handled) { if (!performedHapticFeedback) { performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } if (mEditor != null) mEditor.mDiscardNextActionUp = true; } else { MetricsLogger.action( mContext, MetricsEvent.TEXT_LONGPRESS, TextViewMetrics.SUBTYPE_LONG_PRESS_OTHER); } return handled; } @Override protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { super.onScrollChanged(horiz, vert, oldHoriz, oldVert); if (mEditor != null) { mEditor.onScrollChanged(); } }
Return whether or not suggestions are enabled on this TextView. The suggestions are generated by the IME or by the spell checker as the user types. This is done by adding SuggestionSpans to the text. When suggestions are enabled (default), this list of suggestions will be displayed when the user asks for them on these parts of the text. This value depends on the inputType of this TextView. The class of the input type must be InputType.TYPE_CLASS_TEXT. In addition, the type variation must be one of InputType.TYPE_TEXT_VARIATION_NORMAL, InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT, InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE, InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE or InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT. And finally, the InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS flag must not be set.
Returns:true if the suggestions popup window is enabled, based on the inputType.
/** * Return whether or not suggestions are enabled on this TextView. The suggestions are generated * by the IME or by the spell checker as the user types. This is done by adding * {@link SuggestionSpan}s to the text. * * When suggestions are enabled (default), this list of suggestions will be displayed when the * user asks for them on these parts of the text. This value depends on the inputType of this * TextView. * * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. * * In addition, the type variation must be one of * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. * * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. * * @return true if the suggestions popup window is enabled, based on the inputType. */
public boolean isSuggestionsEnabled() { if (mEditor == null) return false; if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { return false; } if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT || variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE || variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE || variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); }
If provided, this ActionMode.Callback will be used to create the ActionMode when text selection is initiated in this View.

The standard implementation populates the menu with a subset of Select All, Cut, Copy, Paste, Replace and Share actions, depending on what this View supports.

A custom implementation can add new entries in the default menu in its Callback.onPrepareActionMode(ActionMode, Menu) method. The default actions can also be removed from the menu using Menu.removeItem(int) and passing selectAll.selectAll, cut.cut, copy.copy, paste.paste, replaceText.replaceText or shareText.shareText ids as parameters.

Returning false from Callback.onCreateActionMode(ActionMode, Menu) will prevent the action mode from being started.

Action click events should be handled by the custom implementation of Callback.onActionItemClicked(ActionMode, MenuItem).

Note that text selection mode is not started when a TextView receives focus and the selectAllOnFocus.selectAllOnFocus flag has been set. The content is highlighted in that case, to allow for quick replacement.

/** * If provided, this ActionMode.Callback will be used to create the ActionMode when text * selection is initiated in this View. * * <p>The standard implementation populates the menu with a subset of Select All, Cut, Copy, * Paste, Replace and Share actions, depending on what this View supports. * * <p>A custom implementation can add new entries in the default menu in its * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, android.view.Menu)} * method. The default actions can also be removed from the menu using * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste}, * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters. * * <p>Returning false from * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, android.view.Menu)} * will prevent the action mode from being started. * * <p>Action click events should be handled by the custom implementation of * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, * android.view.MenuItem)}. * * <p>Note that text selection mode is not started when a TextView receives focus and the * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in * that case, to allow for quick replacement. */
public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { createEditorIfNeeded(); mEditor.mCustomSelectionActionModeCallback = actionModeCallback; }
Retrieves the value set in setCustomSelectionActionModeCallback. Default is null.
Returns:The current custom selection callback.
/** * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. * * @return The current custom selection callback. */
public ActionMode.Callback getCustomSelectionActionModeCallback() { return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; }
If provided, this ActionMode.Callback will be used to create the ActionMode when text insertion is initiated in this View. The standard implementation populates the menu with a subset of Select All, Paste and Replace actions, depending on what this View supports.

A custom implementation can add new entries in the default menu in its Callback.onPrepareActionMode(ActionMode, Menu) method. The default actions can also be removed from the menu using Menu.removeItem(int) and passing selectAll.selectAll, paste.paste or replaceText.replaceText ids as parameters.

Returning false from Callback.onCreateActionMode(ActionMode, Menu) will prevent the action mode from being started.

Action click events should be handled by the custom implementation of Callback.onActionItemClicked(ActionMode, MenuItem).

Note that text insertion mode is not started when a TextView receives focus and the selectAllOnFocus.selectAllOnFocus flag has been set.

/** * If provided, this ActionMode.Callback will be used to create the ActionMode when text * insertion is initiated in this View. * The standard implementation populates the menu with a subset of Select All, * Paste and Replace actions, depending on what this View supports. * * <p>A custom implementation can add new entries in the default menu in its * {@link android.view.ActionMode.Callback#onPrepareActionMode(android.view.ActionMode, * android.view.Menu)} method. The default actions can also be removed from the menu using * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll}, * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.</p> * * <p>Returning false from * {@link android.view.ActionMode.Callback#onCreateActionMode(android.view.ActionMode, * android.view.Menu)} will prevent the action mode from being started.</p> * * <p>Action click events should be handled by the custom implementation of * {@link android.view.ActionMode.Callback#onActionItemClicked(android.view.ActionMode, * android.view.MenuItem)}.</p> * * <p>Note that text insertion mode is not started when a TextView receives focus and the * {@link android.R.attr#selectAllOnFocus} flag has been set.</p> */
public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) { createEditorIfNeeded(); mEditor.mCustomInsertionActionModeCallback = actionModeCallback; }
Retrieves the value set in setCustomInsertionActionModeCallback. Default is null.
Returns:The current custom insertion callback.
/** * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null. * * @return The current custom insertion callback. */
public ActionMode.Callback getCustomInsertionActionModeCallback() { return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback; }
Sets the TextClassifier for this TextView.
/** * Sets the {@link TextClassifier} for this TextView. */
public void setTextClassifier(@Nullable TextClassifier textClassifier) { mTextClassifier = textClassifier; }
Returns the TextClassifier used by this TextView. If no TextClassifier has been set, this TextView uses the default set by the TextClassificationManager.
/** * Returns the {@link TextClassifier} used by this TextView. * If no TextClassifier has been set, this TextView uses the default set by the * {@link TextClassificationManager}. */
@NonNull public TextClassifier getTextClassifier() { if (mTextClassifier == null) { final TextClassificationManager tcm = mContext.getSystemService(TextClassificationManager.class); if (tcm != null) { return tcm.getTextClassifier(); } return TextClassifier.NO_OP; } return mTextClassifier; }
Returns a session-aware text classifier. This method creates one if none already exists or the current one is destroyed.
/** * Returns a session-aware text classifier. * This method creates one if none already exists or the current one is destroyed. */
@NonNull TextClassifier getTextClassificationSession() { if (mTextClassificationSession == null || mTextClassificationSession.isDestroyed()) { final TextClassificationManager tcm = mContext.getSystemService(TextClassificationManager.class); if (tcm != null) { final String widgetType; if (isTextEditable()) { widgetType = TextClassifier.WIDGET_TYPE_EDITTEXT; } else if (isTextSelectable()) { widgetType = TextClassifier.WIDGET_TYPE_TEXTVIEW; } else { widgetType = TextClassifier.WIDGET_TYPE_UNSELECTABLE_TEXTVIEW; } final TextClassificationContext textClassificationContext = new TextClassificationContext.Builder( mContext.getPackageName(), widgetType) .build(); if (mTextClassifier != null) { mTextClassificationSession = tcm.createTextClassificationSession( textClassificationContext, mTextClassifier); } else { mTextClassificationSession = tcm.createTextClassificationSession( textClassificationContext); } } else { mTextClassificationSession = TextClassifier.NO_OP; } } return mTextClassificationSession; }
Returns true if this TextView uses a no-op TextClassifier.
/** * Returns true if this TextView uses a no-op TextClassifier. */
boolean usesNoOpTextClassifier() { return getTextClassifier() == TextClassifier.NO_OP; }
Starts an ActionMode for the specified TextLinkSpan.
Returns:Whether or not we're attempting to start the action mode.
@hide
/** * Starts an ActionMode for the specified TextLinkSpan. * * @return Whether or not we're attempting to start the action mode. * @hide */
public boolean requestActionMode(@NonNull TextLinks.TextLinkSpan clickedSpan) { Preconditions.checkNotNull(clickedSpan); if (!(mText instanceof Spanned)) { return false; } final int start = ((Spanned) mText).getSpanStart(clickedSpan); final int end = ((Spanned) mText).getSpanEnd(clickedSpan); if (start < 0 || end > mText.length() || start >= end) { return false; } createEditorIfNeeded(); mEditor.startLinkActionModeAsync(start, end); return true; }
Handles a click on the specified TextLinkSpan.
Returns:Whether or not the click is being handled.
@hide
/** * Handles a click on the specified TextLinkSpan. * * @return Whether or not the click is being handled. * @hide */
public boolean handleClick(@NonNull TextLinks.TextLinkSpan clickedSpan) { Preconditions.checkNotNull(clickedSpan); if (mText instanceof Spanned) { final Spanned spanned = (Spanned) mText; final int start = spanned.getSpanStart(clickedSpan); final int end = spanned.getSpanEnd(clickedSpan); if (start >= 0 && end <= mText.length() && start < end) { final TextClassification.Request request = new TextClassification.Request.Builder( mText, start, end) .setDefaultLocales(getTextLocales()) .build(); final Supplier<TextClassification> supplier = () -> getTextClassifier().classifyText(request); final Consumer<TextClassification> consumer = classification -> { if (classification != null) { if (!classification.getActions().isEmpty()) { try { classification.getActions().get(0).getActionIntent().send(); } catch (PendingIntent.CanceledException e) { Log.e(LOG_TAG, "Error sending PendingIntent", e); } } else { Log.d(LOG_TAG, "No link action to perform"); } } else { // classification == null Log.d(LOG_TAG, "Timeout while classifying text"); } }; CompletableFuture.supplyAsync(supplier) .completeOnTimeout(null, 1, TimeUnit.SECONDS) .thenAccept(consumer); return true; } } return false; }
@hide
/** * @hide */
protected void stopTextActionMode() { if (mEditor != null) { mEditor.stopTextActionMode(); } }
@hide
/** @hide */
public void hideFloatingToolbar(int durationMs) { if (mEditor != null) { mEditor.hideFloatingToolbar(durationMs); } } boolean canUndo() { return mEditor != null && mEditor.canUndo(); } boolean canRedo() { return mEditor != null && mEditor.canRedo(); } boolean canCut() { if (hasPasswordTransformationMethod()) { return false; } if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && mEditor.mKeyListener != null) { return true; } return false; } boolean canCopy() { if (hasPasswordTransformationMethod()) { return false; } if (mText.length() > 0 && hasSelection() && mEditor != null) { return true; } return false; } boolean canShare() { if (!getContext().canStartActivityForResult() || !isDeviceProvisioned()) { return false; } return canCopy(); } boolean isDeviceProvisioned() { if (mDeviceProvisionedState == DEVICE_PROVISIONED_UNKNOWN) { mDeviceProvisionedState = Settings.Global.getInt( mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) != 0 ? DEVICE_PROVISIONED_YES : DEVICE_PROVISIONED_NO; } return mDeviceProvisionedState == DEVICE_PROVISIONED_YES; } boolean canPaste() { return (mText instanceof Editable && mEditor != null && mEditor.mKeyListener != null && getSelectionStart() >= 0 && getSelectionEnd() >= 0 && ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)) .hasPrimaryClip()); } boolean canPasteAsPlainText() { if (!canPaste()) { return false; } final ClipData clipData = ((ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE)) .getPrimaryClip(); final ClipDescription description = clipData.getDescription(); final boolean isPlainType = description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN); final CharSequence text = clipData.getItemAt(0).getText(); if (isPlainType && (text instanceof Spanned)) { Spanned spanned = (Spanned) text; if (TextUtils.hasStyleSpan(spanned)) { return true; } } return description.hasMimeType(ClipDescription.MIMETYPE_TEXT_HTML); } boolean canProcessText() { if (getId() == View.NO_ID) { return false; } return canShare(); } boolean canSelectAllText() { return canSelectText() && !hasPasswordTransformationMethod() && !(getSelectionStart() == 0 && getSelectionEnd() == mText.length()); } boolean selectAllText() { if (mEditor != null) { // Hide the toolbar before changing the selection to avoid flickering. hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY); } final int length = mText.length(); Selection.setSelection(mSpannable, 0, length); return length > 0; } void replaceSelectionWithText(CharSequence text) { ((Editable) mText).replace(getSelectionStart(), getSelectionEnd(), text); }
Paste clipboard content between min and max positions.
/** * Paste clipboard content between min and max positions. */
private void paste(int min, int max, boolean withFormatting) { ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = clipboard.getPrimaryClip(); if (clip != null) { boolean didFirst = false; for (int i = 0; i < clip.getItemCount(); i++) { final CharSequence paste; if (withFormatting) { paste = clip.getItemAt(i).coerceToStyledText(getContext()); } else { // Get an item as text and remove all spans by toString(). final CharSequence text = clip.getItemAt(i).coerceToText(getContext()); paste = (text instanceof Spanned) ? text.toString() : text; } if (paste != null) { if (!didFirst) { Selection.setSelection(mSpannable, max); ((Editable) mText).replace(min, max, paste); didFirst = true; } else { ((Editable) mText).insert(getSelectionEnd(), "\n"); ((Editable) mText).insert(getSelectionEnd(), paste); } } } sLastCutCopyOrTextChangedTime = 0; } } private void shareSelectedText() { String selectedText = getSelectedText(); if (selectedText != null && !selectedText.isEmpty()) { Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND); sharingIntent.setType("text/plain"); sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT); selectedText = TextUtils.trimToParcelableSize(selectedText); sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText); getContext().startActivity(Intent.createChooser(sharingIntent, null)); Selection.setSelection(mSpannable, getSelectionEnd()); } } @CheckResult private boolean setPrimaryClip(ClipData clip) { ClipboardManager clipboard = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); try { clipboard.setPrimaryClip(clip); } catch (Throwable t) { return false; } sLastCutCopyOrTextChangedTime = SystemClock.uptimeMillis(); return true; }
Get the character offset closest to the specified absolute position. A typical use case is to pass the result of MotionEvent.getX() and MotionEvent.getY() to this method.
Params:
  • x – The horizontal absolute position of a point on screen
  • y – The vertical absolute position of a point on screen
Returns:the character offset for the character whose position is closest to the specified position. Returns -1 if there is no layout.
/** * Get the character offset closest to the specified absolute position. A typical use case is to * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. * * @param x The horizontal absolute position of a point on screen * @param y The vertical absolute position of a point on screen * @return the character offset for the character whose position is closest to the specified * position. Returns -1 if there is no layout. */
public int getOffsetForPosition(float x, float y) { if (getLayout() == null) return -1; final int line = getLineAtCoordinate(y); final int offset = getOffsetAtCoordinate(line, x); return offset; } float convertToLocalHorizontalCoordinate(float x) { x -= getTotalPaddingLeft(); // Clamp the position to inside of the view. x = Math.max(0.0f, x); x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); x += getScrollX(); return x; } int getLineAtCoordinate(float y) { y -= getTotalPaddingTop(); // Clamp the position to inside of the view. y = Math.max(0.0f, y); y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); y += getScrollY(); return getLayout().getLineForVertical((int) y); } int getLineAtCoordinateUnclamped(float y) { y -= getTotalPaddingTop(); y += getScrollY(); return getLayout().getLineForVertical((int) y); } int getOffsetAtCoordinate(int line, float x) { x = convertToLocalHorizontalCoordinate(x); return getLayout().getOffsetForHorizontal(line, x); } @Override public boolean onDragEvent(DragEvent event) { switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: return mEditor != null && mEditor.hasInsertionController(); case DragEvent.ACTION_DRAG_ENTERED: TextView.this.requestFocus(); return true; case DragEvent.ACTION_DRAG_LOCATION: if (mText instanceof Spannable) { final int offset = getOffsetForPosition(event.getX(), event.getY()); Selection.setSelection(mSpannable, offset); } return true; case DragEvent.ACTION_DROP: if (mEditor != null) mEditor.onDrop(event); return true; case DragEvent.ACTION_DRAG_ENDED: case DragEvent.ACTION_DRAG_EXITED: default: return true; } } boolean isInBatchEditMode() { if (mEditor == null) return false; final Editor.InputMethodState ims = mEditor.mInputMethodState; if (ims != null) { return ims.mBatchEditNesting > 0; } return mEditor.mInBatchEditControllers; } @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); final TextDirectionHeuristic newTextDir = getTextDirectionHeuristic(); if (mTextDir != newTextDir) { mTextDir = newTextDir; if (mLayout != null) { checkForRelayout(); } } }
Returns the current TextDirectionHeuristic.
Returns:the current TextDirectionHeuristic.
@hide
/** * Returns the current {@link TextDirectionHeuristic}. * * @return the current {@link TextDirectionHeuristic}. * @hide */
protected TextDirectionHeuristic getTextDirectionHeuristic() { if (hasPasswordTransformationMethod()) { // passwords fields should be LTR return TextDirectionHeuristics.LTR; } if (mEditor != null && (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_PHONE) { // Phone numbers must be in the direction of the locale's digits. Most locales have LTR // digits, but some locales, such as those written in the Adlam or N'Ko scripts, have // RTL digits. final DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(getTextLocale()); final String zero = symbols.getDigitStrings()[0]; // In case the zero digit is multi-codepoint, just use the first codepoint to determine // direction. final int firstCodepoint = zero.codePointAt(0); final byte digitDirection = Character.getDirectionality(firstCodepoint); if (digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT || digitDirection == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC) { return TextDirectionHeuristics.RTL; } else { return TextDirectionHeuristics.LTR; } } // Always need to resolve layout direction first final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); // Now, we can select the heuristic switch (getTextDirection()) { default: case TEXT_DIRECTION_FIRST_STRONG: return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : TextDirectionHeuristics.FIRSTSTRONG_LTR); case TEXT_DIRECTION_ANY_RTL: return TextDirectionHeuristics.ANYRTL_LTR; case TEXT_DIRECTION_LTR: return TextDirectionHeuristics.LTR; case TEXT_DIRECTION_RTL: return TextDirectionHeuristics.RTL; case TEXT_DIRECTION_LOCALE: return TextDirectionHeuristics.LOCALE; case TEXT_DIRECTION_FIRST_STRONG_LTR: return TextDirectionHeuristics.FIRSTSTRONG_LTR; case TEXT_DIRECTION_FIRST_STRONG_RTL: return TextDirectionHeuristics.FIRSTSTRONG_RTL; } }
@hide
/** * @hide */
@Override public void onResolveDrawables(int layoutDirection) { // No need to resolve twice if (mLastLayoutDirection == layoutDirection) { return; } mLastLayoutDirection = layoutDirection; // Resolve drawables if (mDrawables != null) { if (mDrawables.resolveWithLayoutDirection(layoutDirection)) { prepareDrawableForDisplay(mDrawables.mShowing[Drawables.LEFT]); prepareDrawableForDisplay(mDrawables.mShowing[Drawables.RIGHT]); applyCompoundDrawableTint(); } } }
Prepares a drawable for display by propagating layout direction and drawable state.
Params:
  • dr – the drawable to prepare
/** * Prepares a drawable for display by propagating layout direction and * drawable state. * * @param dr the drawable to prepare */
private void prepareDrawableForDisplay(@Nullable Drawable dr) { if (dr == null) { return; } dr.setLayoutDirection(getLayoutDirection()); if (dr.isStateful()) { dr.setState(getDrawableState()); dr.jumpToCurrentState(); } }
@hide
/** * @hide */
protected void resetResolvedDrawables() { super.resetResolvedDrawables(); mLastLayoutDirection = -1; }
@hide
/** * @hide */
protected void viewClicked(InputMethodManager imm) { if (imm != null) { imm.viewClicked(this); } }
Deletes the range of text [start, end[.
@hide
/** * Deletes the range of text [start, end[. * @hide */
protected void deleteText_internal(int start, int end) { ((Editable) mText).delete(start, end); }
Replaces the range of text [start, end[ by replacement text
@hide
/** * Replaces the range of text [start, end[ by replacement text * @hide */
protected void replaceText_internal(int start, int end, CharSequence text) { ((Editable) mText).replace(start, end, text); }
Sets a span on the specified range of text
@hide
/** * Sets a span on the specified range of text * @hide */
protected void setSpan_internal(Object span, int start, int end, int flags) { ((Editable) mText).setSpan(span, start, end, flags); }
Moves the cursor to the specified offset position in text
@hide
/** * Moves the cursor to the specified offset position in text * @hide */
protected void setCursorPosition_internal(int start, int end) { Selection.setSelection(((Editable) mText), start, end); }
An Editor should be created as soon as any of the editable-specific fields (grouped inside the Editor object) is assigned to a non-default value. This method will create the Editor if needed. A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will have a null Editor, unlike an EditText. Inconsistent in-between states will have an Editor for backward compatibility, as soon as one of these fields is assigned. Also note that for performance reasons, the mEditor is created when needed, but not reset when no more edit-specific fields are needed.
/** * An Editor should be created as soon as any of the editable-specific fields (grouped * inside the Editor object) is assigned to a non-default value. * This method will create the Editor if needed. * * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will * have a null Editor, unlike an EditText. Inconsistent in-between states will have an * Editor for backward compatibility, as soon as one of these fields is assigned. * * Also note that for performance reasons, the mEditor is created when needed, but not * reset when no more edit-specific fields are needed. */
private void createEditorIfNeeded() { if (mEditor == null) { mEditor = new Editor(this); } }
@hide
/** * @hide */
@Override public CharSequence getIterableTextForAccessibility() { return mText; } private void ensureIterableTextForAccessibilitySelectable() { if (!(mText instanceof Spannable)) { setText(mText, BufferType.SPANNABLE); } }
@hide
/** * @hide */
@Override public TextSegmentIterator getIteratorForGranularity(int granularity) { switch (granularity) { case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { Spannable text = (Spannable) getIterableTextForAccessibility(); if (!TextUtils.isEmpty(text) && getLayout() != null) { AccessibilityIterators.LineTextSegmentIterator iterator = AccessibilityIterators.LineTextSegmentIterator.getInstance(); iterator.initialize(text, getLayout()); return iterator; } } break; case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { Spannable text = (Spannable) getIterableTextForAccessibility(); if (!TextUtils.isEmpty(text) && getLayout() != null) { AccessibilityIterators.PageTextSegmentIterator iterator = AccessibilityIterators.PageTextSegmentIterator.getInstance(); iterator.initialize(this); return iterator; } } break; } return super.getIteratorForGranularity(granularity); }
@hide
/** * @hide */
@Override public int getAccessibilitySelectionStart() { return getSelectionStart(); }
@hide
/** * @hide */
public boolean isAccessibilitySelectionExtendable() { return true; }
@hide
/** * @hide */
@Override public int getAccessibilitySelectionEnd() { return getSelectionEnd(); }
@hide
/** * @hide */
@Override public void setAccessibilitySelection(int start, int end) { if (getAccessibilitySelectionStart() == start && getAccessibilitySelectionEnd() == end) { return; } CharSequence text = getIterableTextForAccessibility(); if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { Selection.setSelection((Spannable) text, start, end); } else { Selection.removeSelection((Spannable) text); } // Hide all selection controllers used for adjusting selection // since we are doing so explicitlty by other means and these // controllers interact with how selection behaves. if (mEditor != null) { mEditor.hideCursorAndSpanControllers(); mEditor.stopTextActionMode(); } }
@hide
/** @hide */
@Override protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { super.encodeProperties(stream); TruncateAt ellipsize = getEllipsize(); stream.addProperty("text:ellipsize", ellipsize == null ? null : ellipsize.name()); stream.addProperty("text:textSize", getTextSize()); stream.addProperty("text:scaledTextSize", getScaledTextSize()); stream.addProperty("text:typefaceStyle", getTypefaceStyle()); stream.addProperty("text:selectionStart", getSelectionStart()); stream.addProperty("text:selectionEnd", getSelectionEnd()); stream.addProperty("text:curTextColor", mCurTextColor); stream.addProperty("text:text", mText == null ? null : mText.toString()); stream.addProperty("text:gravity", mGravity); }
User interface state that is stored by TextView for implementing View.onSaveInstanceState.
/** * User interface state that is stored by TextView for implementing * {@link View#onSaveInstanceState}. */
public static class SavedState extends BaseSavedState { int selStart = -1; int selEnd = -1; CharSequence text; boolean frozenWithFocus; CharSequence error; ParcelableParcel editorState; // Optional state from Editor. SavedState(Parcelable superState) { super(superState); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); out.writeInt(selStart); out.writeInt(selEnd); out.writeInt(frozenWithFocus ? 1 : 0); TextUtils.writeToParcel(text, out, flags); if (error == null) { out.writeInt(0); } else { out.writeInt(1); TextUtils.writeToParcel(error, out, flags); } if (editorState == null) { out.writeInt(0); } else { out.writeInt(1); editorState.writeToParcel(out, flags); } } @Override public String toString() { String str = "TextView.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " start=" + selStart + " end=" + selEnd; if (text != null) { str += " text=" + text; } return str + "}"; } @SuppressWarnings("hiding") public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { return new SavedState(in); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; private SavedState(Parcel in) { super(in); selStart = in.readInt(); selEnd = in.readInt(); frozenWithFocus = (in.readInt() != 0); text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); if (in.readInt() != 0) { error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); } if (in.readInt() != 0) { editorState = ParcelableParcel.CREATOR.createFromParcel(in); } } } private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { private char[] mChars; private int mStart, mLength; public CharWrapper(char[] chars, int start, int len) { mChars = chars; mStart = start; mLength = len; } /* package */ void set(char[] chars, int start, int len) { mChars = chars; mStart = start; mLength = len; } public int length() { return mLength; } public char charAt(int off) { return mChars[off + mStart]; } @Override public String toString() { return new String(mChars, mStart, mLength); } public CharSequence subSequence(int start, int end) { if (start < 0 || end < 0 || start > mLength || end > mLength) { throw new IndexOutOfBoundsException(start + ", " + end); } return new String(mChars, start + mStart, end - start); } public void getChars(int start, int end, char[] buf, int off) { if (start < 0 || end < 0 || start > mLength || end > mLength) { throw new IndexOutOfBoundsException(start + ", " + end); } System.arraycopy(mChars, start + mStart, buf, off, end - start); } @Override public void drawText(BaseCanvas c, int start, int end, float x, float y, Paint p) { c.drawText(mChars, start + mStart, end - start, x, y, p); } @Override public void drawTextRun(BaseCanvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { int count = end - start; int contextCount = contextEnd - contextStart; c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, contextCount, x, y, isRtl, p); } public float measureText(int start, int end, Paint p) { return p.measureText(mChars, start + mStart, end - start); } public int getTextWidths(int start, int end, float[] widths, Paint p) { return p.getTextWidths(mChars, start + mStart, end - start, widths); } public float getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p) { int count = end - start; int contextCount = contextEnd - contextStart; return p.getTextRunAdvances(mChars, start + mStart, count, contextStart + mStart, contextCount, isRtl, advances, advancesIndex); } public int getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p) { int contextCount = contextEnd - contextStart; return p.getTextRunCursor(mChars, contextStart + mStart, contextCount, dir, offset + mStart, cursorOpt); } } private static final class Marquee { // TODO: Add an option to configure this private static final float MARQUEE_DELTA_MAX = 0.07f; private static final int MARQUEE_DELAY = 1200; private static final int MARQUEE_DP_PER_SECOND = 30; private static final byte MARQUEE_STOPPED = 0x0; private static final byte MARQUEE_STARTING = 0x1; private static final byte MARQUEE_RUNNING = 0x2; private final WeakReference<TextView> mView; private final Choreographer mChoreographer; private byte mStatus = MARQUEE_STOPPED; private final float mPixelsPerMs; private float mMaxScroll; private float mMaxFadeScroll; private float mGhostStart; private float mGhostOffset; private float mFadeStop; private int mRepeatLimit; private float mScroll; private long mLastAnimationMs; Marquee(TextView v) { final float density = v.getContext().getResources().getDisplayMetrics().density; mPixelsPerMs = MARQUEE_DP_PER_SECOND * density / 1000f; mView = new WeakReference<TextView>(v); mChoreographer = Choreographer.getInstance(); } private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { tick(); } }; private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { mStatus = MARQUEE_RUNNING; mLastAnimationMs = mChoreographer.getFrameTime(); tick(); } }; private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { if (mStatus == MARQUEE_RUNNING) { if (mRepeatLimit >= 0) { mRepeatLimit--; } start(mRepeatLimit); } } }; void tick() { if (mStatus != MARQUEE_RUNNING) { return; } mChoreographer.removeFrameCallback(mTickCallback); final TextView textView = mView.get(); if (textView != null && (textView.isFocused() || textView.isSelected())) { long currentMs = mChoreographer.getFrameTime(); long deltaMs = currentMs - mLastAnimationMs; mLastAnimationMs = currentMs; float deltaPx = deltaMs * mPixelsPerMs; mScroll += deltaPx; if (mScroll > mMaxScroll) { mScroll = mMaxScroll; mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); } else { mChoreographer.postFrameCallback(mTickCallback); } textView.invalidate(); } } void stop() { mStatus = MARQUEE_STOPPED; mChoreographer.removeFrameCallback(mStartCallback); mChoreographer.removeFrameCallback(mRestartCallback); mChoreographer.removeFrameCallback(mTickCallback); resetScroll(); } private void resetScroll() { mScroll = 0.0f; final TextView textView = mView.get(); if (textView != null) textView.invalidate(); } void start(int repeatLimit) { if (repeatLimit == 0) { stop(); return; } mRepeatLimit = repeatLimit; final TextView textView = mView.get(); if (textView != null && textView.mLayout != null) { mStatus = MARQUEE_STARTING; mScroll = 0.0f; final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight(); final float lineWidth = textView.mLayout.getLineWidth(0); final float gap = textWidth / 3.0f; mGhostStart = lineWidth - textWidth + gap; mMaxScroll = mGhostStart + textWidth; mGhostOffset = lineWidth + gap; mFadeStop = lineWidth + textWidth / 6.0f; mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; textView.invalidate(); mChoreographer.postFrameCallback(mStartCallback); } } float getGhostOffset() { return mGhostOffset; } float getScroll() { return mScroll; } float getMaxFadeScroll() { return mMaxFadeScroll; } boolean shouldDrawLeftFade() { return mScroll <= mFadeStop; } boolean shouldDrawGhost() { return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; } boolean isRunning() { return mStatus == MARQUEE_RUNNING; } boolean isStopped() { return mStatus == MARQUEE_STOPPED; } } private class ChangeWatcher implements TextWatcher, SpanWatcher { private CharSequence mBeforeText; public void beforeTextChanged(CharSequence buffer, int start, int before, int after) { if (DEBUG_EXTRACT) { Log.v(LOG_TAG, "beforeTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); } if (AccessibilityManager.getInstance(mContext).isEnabled() && (mTransformed != null)) { mBeforeText = mTransformed.toString(); } TextView.this.sendBeforeTextChanged(buffer, start, before, after); } public void onTextChanged(CharSequence buffer, int start, int before, int after) { if (DEBUG_EXTRACT) { Log.v(LOG_TAG, "onTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); } TextView.this.handleTextChanged(buffer, start, before, after); if (AccessibilityManager.getInstance(mContext).isEnabled() && (isFocused() || isSelected() && isShown())) { sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); mBeforeText = null; } } public void afterTextChanged(Editable buffer) { if (DEBUG_EXTRACT) { Log.v(LOG_TAG, "afterTextChanged: " + buffer); } TextView.this.sendAfterTextChanged(buffer); if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { MetaKeyKeyListener.stopSelecting(TextView.this, buffer); } } public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { if (DEBUG_EXTRACT) { Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e + " st=" + st + " en=" + en + " what=" + what + ": " + buf); } TextView.this.spanChange(buf, what, s, st, e, en); } public void onSpanAdded(Spannable buf, Object what, int s, int e) { if (DEBUG_EXTRACT) { Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); } TextView.this.spanChange(buf, what, -1, s, -1, e); } public void onSpanRemoved(Spannable buf, Object what, int s, int e) { if (DEBUG_EXTRACT) { Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); } TextView.this.spanChange(buf, what, s, -1, e, -1); } } }