1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import android.R; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.ClipData; 23 import android.content.ClipboardManager; 24 import android.content.Context; 25 import android.content.UndoManager; 26 import android.content.res.ColorStateList; 27 import android.content.res.CompatibilityInfo; 28 import android.content.res.Resources; 29 import android.content.res.TypedArray; 30 import android.content.res.XmlResourceParser; 31 import android.graphics.Canvas; 32 import android.graphics.Insets; 33 import android.graphics.Paint; 34 import android.graphics.Path; 35 import android.graphics.Rect; 36 import android.graphics.RectF; 37 import android.graphics.Typeface; 38 import android.graphics.drawable.Drawable; 39 import android.inputmethodservice.ExtractEditText; 40 import android.os.AsyncTask; 41 import android.os.Bundle; 42 import android.os.Parcel; 43 import android.os.Parcelable; 44 import android.os.SystemClock; 45 import android.provider.Settings; 46 import android.text.BoringLayout; 47 import android.text.DynamicLayout; 48 import android.text.Editable; 49 import android.text.GetChars; 50 import android.text.GraphicsOperations; 51 import android.text.InputFilter; 52 import android.text.InputType; 53 import android.text.Layout; 54 import android.text.ParcelableSpan; 55 import android.text.Selection; 56 import android.text.SpanWatcher; 57 import android.text.Spannable; 58 import android.text.SpannableString; 59 import android.text.SpannableStringBuilder; 60 import android.text.Spanned; 61 import android.text.SpannedString; 62 import android.text.StaticLayout; 63 import android.text.TextDirectionHeuristic; 64 import android.text.TextDirectionHeuristics; 65 import android.text.TextPaint; 66 import android.text.TextUtils; 67 import android.text.TextUtils.TruncateAt; 68 import android.text.TextWatcher; 69 import android.text.method.AllCapsTransformationMethod; 70 import android.text.method.ArrowKeyMovementMethod; 71 import android.text.method.DateKeyListener; 72 import android.text.method.DateTimeKeyListener; 73 import android.text.method.DialerKeyListener; 74 import android.text.method.DigitsKeyListener; 75 import android.text.method.KeyListener; 76 import android.text.method.LinkMovementMethod; 77 import android.text.method.MetaKeyKeyListener; 78 import android.text.method.MovementMethod; 79 import android.text.method.PasswordTransformationMethod; 80 import android.text.method.SingleLineTransformationMethod; 81 import android.text.method.TextKeyListener; 82 import android.text.method.TimeKeyListener; 83 import android.text.method.TransformationMethod; 84 import android.text.method.TransformationMethod2; 85 import android.text.method.WordIterator; 86 import android.text.style.CharacterStyle; 87 import android.text.style.ClickableSpan; 88 import android.text.style.ParagraphStyle; 89 import android.text.style.SpellCheckSpan; 90 import android.text.style.SuggestionSpan; 91 import android.text.style.URLSpan; 92 import android.text.style.UpdateAppearance; 93 import android.text.util.Linkify; 94 import android.util.AttributeSet; 95 import android.util.FloatMath; 96 import android.util.Log; 97 import android.util.TypedValue; 98 import android.view.AccessibilityIterators.TextSegmentIterator; 99 import android.view.ActionMode; 100 import android.view.Choreographer; 101 import android.view.DragEvent; 102 import android.view.Gravity; 103 import android.view.HapticFeedbackConstants; 104 import android.view.KeyCharacterMap; 105 import android.view.KeyEvent; 106 import android.view.Menu; 107 import android.view.MenuItem; 108 import android.view.MotionEvent; 109 import android.view.View; 110 import android.view.ViewConfiguration; 111 import android.view.ViewDebug; 112 import android.view.ViewGroup.LayoutParams; 113 import android.view.ViewRootImpl; 114 import android.view.ViewTreeObserver; 115 import android.view.accessibility.AccessibilityEvent; 116 import android.view.accessibility.AccessibilityManager; 117 import android.view.accessibility.AccessibilityNodeInfo; 118 import android.view.animation.AnimationUtils; 119 import android.view.inputmethod.BaseInputConnection; 120 import android.view.inputmethod.CompletionInfo; 121 import android.view.inputmethod.CorrectionInfo; 122 import android.view.inputmethod.EditorInfo; 123 import android.view.inputmethod.ExtractedText; 124 import android.view.inputmethod.ExtractedTextRequest; 125 import android.view.inputmethod.InputConnection; 126 import android.view.inputmethod.InputMethodManager; 127 import android.view.textservice.SpellCheckerSubtype; 128 import android.view.textservice.TextServicesManager; 129 import android.widget.RemoteViews.RemoteView; 130 131 import com.android.internal.util.FastMath; 132 import com.android.internal.widget.EditableInputConnection; 133 134 import org.xmlpull.v1.XmlPullParserException; 135 136 import java.io.IOException; 137 import java.lang.ref.WeakReference; 138 import java.util.ArrayList; 139 import java.util.Locale; 140 141 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 142 143 /** 144 * Displays text to the user and optionally allows them to edit it. A TextView 145 * is a complete text editor, however the basic class is configured to not 146 * allow editing; see {@link EditText} for a subclass that configures the text 147 * view for editing. 148 * 149 * <p> 150 * To allow users to copy some or all of the TextView's value and paste it somewhere else, set the 151 * XML attribute {@link android.R.styleable#TextView_textIsSelectable 152 * android:textIsSelectable} to "true" or call 153 * {@link #setTextIsSelectable setTextIsSelectable(true)}. The {@code textIsSelectable} flag 154 * allows users to make selection gestures in the TextView, which in turn triggers the system's 155 * built-in copy/paste controls. 156 * <p> 157 * <b>XML attributes</b> 158 * <p> 159 * See {@link android.R.styleable#TextView TextView Attributes}, 160 * {@link android.R.styleable#View View Attributes} 161 * 162 * @attr ref android.R.styleable#TextView_text 163 * @attr ref android.R.styleable#TextView_bufferType 164 * @attr ref android.R.styleable#TextView_hint 165 * @attr ref android.R.styleable#TextView_textColor 166 * @attr ref android.R.styleable#TextView_textColorHighlight 167 * @attr ref android.R.styleable#TextView_textColorHint 168 * @attr ref android.R.styleable#TextView_textAppearance 169 * @attr ref android.R.styleable#TextView_textColorLink 170 * @attr ref android.R.styleable#TextView_textSize 171 * @attr ref android.R.styleable#TextView_textScaleX 172 * @attr ref android.R.styleable#TextView_fontFamily 173 * @attr ref android.R.styleable#TextView_typeface 174 * @attr ref android.R.styleable#TextView_textStyle 175 * @attr ref android.R.styleable#TextView_cursorVisible 176 * @attr ref android.R.styleable#TextView_maxLines 177 * @attr ref android.R.styleable#TextView_maxHeight 178 * @attr ref android.R.styleable#TextView_lines 179 * @attr ref android.R.styleable#TextView_height 180 * @attr ref android.R.styleable#TextView_minLines 181 * @attr ref android.R.styleable#TextView_minHeight 182 * @attr ref android.R.styleable#TextView_maxEms 183 * @attr ref android.R.styleable#TextView_maxWidth 184 * @attr ref android.R.styleable#TextView_ems 185 * @attr ref android.R.styleable#TextView_width 186 * @attr ref android.R.styleable#TextView_minEms 187 * @attr ref android.R.styleable#TextView_minWidth 188 * @attr ref android.R.styleable#TextView_gravity 189 * @attr ref android.R.styleable#TextView_scrollHorizontally 190 * @attr ref android.R.styleable#TextView_password 191 * @attr ref android.R.styleable#TextView_singleLine 192 * @attr ref android.R.styleable#TextView_selectAllOnFocus 193 * @attr ref android.R.styleable#TextView_includeFontPadding 194 * @attr ref android.R.styleable#TextView_maxLength 195 * @attr ref android.R.styleable#TextView_shadowColor 196 * @attr ref android.R.styleable#TextView_shadowDx 197 * @attr ref android.R.styleable#TextView_shadowDy 198 * @attr ref android.R.styleable#TextView_shadowRadius 199 * @attr ref android.R.styleable#TextView_autoLink 200 * @attr ref android.R.styleable#TextView_linksClickable 201 * @attr ref android.R.styleable#TextView_numeric 202 * @attr ref android.R.styleable#TextView_digits 203 * @attr ref android.R.styleable#TextView_phoneNumber 204 * @attr ref android.R.styleable#TextView_inputMethod 205 * @attr ref android.R.styleable#TextView_capitalize 206 * @attr ref android.R.styleable#TextView_autoText 207 * @attr ref android.R.styleable#TextView_editable 208 * @attr ref android.R.styleable#TextView_freezesText 209 * @attr ref android.R.styleable#TextView_ellipsize 210 * @attr ref android.R.styleable#TextView_drawableTop 211 * @attr ref android.R.styleable#TextView_drawableBottom 212 * @attr ref android.R.styleable#TextView_drawableRight 213 * @attr ref android.R.styleable#TextView_drawableLeft 214 * @attr ref android.R.styleable#TextView_drawableStart 215 * @attr ref android.R.styleable#TextView_drawableEnd 216 * @attr ref android.R.styleable#TextView_drawablePadding 217 * @attr ref android.R.styleable#TextView_lineSpacingExtra 218 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 219 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 220 * @attr ref android.R.styleable#TextView_inputType 221 * @attr ref android.R.styleable#TextView_imeOptions 222 * @attr ref android.R.styleable#TextView_privateImeOptions 223 * @attr ref android.R.styleable#TextView_imeActionLabel 224 * @attr ref android.R.styleable#TextView_imeActionId 225 * @attr ref android.R.styleable#TextView_editorExtras 226 * @attr ref android.R.styleable#TextView_elegantTextHeight 227 * @attr ref android.R.styleable#TextView_letterSpacing 228 * @attr ref android.R.styleable#TextView_fontFeatureSettings 229 */ 230 @RemoteView 231 public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 232 static final String LOG_TAG = "TextView"; 233 static final boolean DEBUG_EXTRACT = false; 234 235 // Enum for the "typeface" XML parameter. 236 // TODO: How can we get this from the XML instead of hardcoding it here? 237 private static final int SANS = 1; 238 private static final int SERIF = 2; 239 private static final int MONOSPACE = 3; 240 241 // Bitfield for the "numeric" XML parameter. 242 // TODO: How can we get this from the XML instead of hardcoding it here? 243 private static final int SIGNED = 2; 244 private static final int DECIMAL = 4; 245 246 /** 247 * Draw marquee text with fading edges as usual 248 */ 249 private static final int MARQUEE_FADE_NORMAL = 0; 250 251 /** 252 * Draw marquee text as ellipsize end while inactive instead of with the fade. 253 * (Useful for devices where the fade can be expensive if overdone) 254 */ 255 private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1; 256 257 /** 258 * Draw marquee text with fading edges because it is currently active/animating. 259 */ 260 private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2; 261 262 private static final int LINES = 1; 263 private static final int EMS = LINES; 264 private static final int PIXELS = 2; 265 266 private static final RectF TEMP_RECTF = new RectF(); 267 268 // XXX should be much larger 269 private static final int VERY_WIDE = 1024*1024; 270 private static final int ANIMATED_SCROLL_GAP = 250; 271 272 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 273 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 274 275 private static final int CHANGE_WATCHER_PRIORITY = 100; 276 277 // New state used to change background based on whether this TextView is multiline. 278 private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline }; 279 280 // System wide time for last cut or copy action. 281 static long LAST_CUT_OR_COPY_TIME; 282 283 private ColorStateList mTextColor; 284 private ColorStateList mHintTextColor; 285 private ColorStateList mLinkTextColor; 286 @ViewDebug.ExportedProperty(category = "text") 287 private int mCurTextColor; 288 private int mCurHintTextColor; 289 private boolean mFreezesText; 290 private boolean mTemporaryDetach; 291 private boolean mDispatchTemporaryDetach; 292 293 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 294 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 295 296 private float mShadowRadius, mShadowDx, mShadowDy; 297 private int mShadowColor; 298 299 300 private boolean mPreDrawRegistered; 301 private boolean mPreDrawListenerDetached; 302 303 // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is 304 // that if a user is holding down a movement key to traverse text, we shouldn't also traverse 305 // the view hierarchy. On the other hand, if the user is using the movement key to traverse views 306 // (i.e. the first movement was to traverse out of this view, or this view was traversed into by 307 // the user holding the movement key down) then we shouldn't prevent the focus from changing. 308 private boolean mPreventDefaultMovement; 309 310 private TextUtils.TruncateAt mEllipsize; 311 312 static class Drawables { 313 final static int DRAWABLE_NONE = -1; 314 final static int DRAWABLE_RIGHT = 0; 315 final static int DRAWABLE_LEFT = 1; 316 317 final Rect mCompoundRect = new Rect(); 318 319 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight, 320 mDrawableStart, mDrawableEnd, mDrawableError, mDrawableTemp; 321 322 Drawable mDrawableLeftInitial, mDrawableRightInitial; 323 boolean mIsRtlCompatibilityMode; 324 boolean mOverride; 325 326 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight, 327 mDrawableSizeStart, mDrawableSizeEnd, mDrawableSizeError, mDrawableSizeTemp; 328 329 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight, 330 mDrawableHeightStart, mDrawableHeightEnd, mDrawableHeightError, mDrawableHeightTemp; 331 332 int mDrawablePadding; 333 334 int mDrawableSaved = DRAWABLE_NONE; 335 Drawables(Context context)336 public Drawables(Context context) { 337 final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion; 338 mIsRtlCompatibilityMode = (targetSdkVersion < JELLY_BEAN_MR1 || 339 !context.getApplicationInfo().hasRtlSupport()); 340 mOverride = false; 341 } 342 resolveWithLayoutDirection(int layoutDirection)343 public void resolveWithLayoutDirection(int layoutDirection) { 344 // First reset "left" and "right" drawables to their initial values 345 mDrawableLeft = mDrawableLeftInitial; 346 mDrawableRight = mDrawableRightInitial; 347 348 if (mIsRtlCompatibilityMode) { 349 // Use "start" drawable as "left" drawable if the "left" drawable was not defined 350 if (mDrawableStart != null && mDrawableLeft == null) { 351 mDrawableLeft = mDrawableStart; 352 mDrawableSizeLeft = mDrawableSizeStart; 353 mDrawableHeightLeft = mDrawableHeightStart; 354 } 355 // Use "end" drawable as "right" drawable if the "right" drawable was not defined 356 if (mDrawableEnd != null && mDrawableRight == null) { 357 mDrawableRight = mDrawableEnd; 358 mDrawableSizeRight = mDrawableSizeEnd; 359 mDrawableHeightRight = mDrawableHeightEnd; 360 } 361 } else { 362 // JB-MR1+ normal case: "start" / "end" drawables are overriding "left" / "right" 363 // drawable if and only if they have been defined 364 switch(layoutDirection) { 365 case LAYOUT_DIRECTION_RTL: 366 if (mOverride) { 367 mDrawableRight = mDrawableStart; 368 mDrawableSizeRight = mDrawableSizeStart; 369 mDrawableHeightRight = mDrawableHeightStart; 370 371 mDrawableLeft = mDrawableEnd; 372 mDrawableSizeLeft = mDrawableSizeEnd; 373 mDrawableHeightLeft = mDrawableHeightEnd; 374 } 375 break; 376 377 case LAYOUT_DIRECTION_LTR: 378 default: 379 if (mOverride) { 380 mDrawableLeft = mDrawableStart; 381 mDrawableSizeLeft = mDrawableSizeStart; 382 mDrawableHeightLeft = mDrawableHeightStart; 383 384 mDrawableRight = mDrawableEnd; 385 mDrawableSizeRight = mDrawableSizeEnd; 386 mDrawableHeightRight = mDrawableHeightEnd; 387 } 388 break; 389 } 390 } 391 applyErrorDrawableIfNeeded(layoutDirection); 392 updateDrawablesLayoutDirection(layoutDirection); 393 } 394 updateDrawablesLayoutDirection(int layoutDirection)395 private void updateDrawablesLayoutDirection(int layoutDirection) { 396 if (mDrawableLeft != null) { 397 mDrawableLeft.setLayoutDirection(layoutDirection); 398 } 399 if (mDrawableRight != null) { 400 mDrawableRight.setLayoutDirection(layoutDirection); 401 } 402 if (mDrawableTop != null) { 403 mDrawableTop.setLayoutDirection(layoutDirection); 404 } 405 if (mDrawableBottom != null) { 406 mDrawableBottom.setLayoutDirection(layoutDirection); 407 } 408 } 409 setErrorDrawable(Drawable dr, TextView tv)410 public void setErrorDrawable(Drawable dr, TextView tv) { 411 if (mDrawableError != dr && mDrawableError != null) { 412 mDrawableError.setCallback(null); 413 } 414 mDrawableError = dr; 415 416 final Rect compoundRect = mCompoundRect; 417 int[] state = tv.getDrawableState(); 418 419 if (mDrawableError != null) { 420 mDrawableError.setState(state); 421 mDrawableError.copyBounds(compoundRect); 422 mDrawableError.setCallback(tv); 423 mDrawableSizeError = compoundRect.width(); 424 mDrawableHeightError = compoundRect.height(); 425 } else { 426 mDrawableSizeError = mDrawableHeightError = 0; 427 } 428 } 429 applyErrorDrawableIfNeeded(int layoutDirection)430 private void applyErrorDrawableIfNeeded(int layoutDirection) { 431 // first restore the initial state if needed 432 switch (mDrawableSaved) { 433 case DRAWABLE_LEFT: 434 mDrawableLeft = mDrawableTemp; 435 mDrawableSizeLeft = mDrawableSizeTemp; 436 mDrawableHeightLeft = mDrawableHeightTemp; 437 break; 438 case DRAWABLE_RIGHT: 439 mDrawableRight = mDrawableTemp; 440 mDrawableSizeRight = mDrawableSizeTemp; 441 mDrawableHeightRight = mDrawableHeightTemp; 442 break; 443 case DRAWABLE_NONE: 444 default: 445 } 446 // then, if needed, assign the Error drawable to the correct location 447 if (mDrawableError != null) { 448 switch(layoutDirection) { 449 case LAYOUT_DIRECTION_RTL: 450 mDrawableSaved = DRAWABLE_LEFT; 451 452 mDrawableTemp = mDrawableLeft; 453 mDrawableSizeTemp = mDrawableSizeLeft; 454 mDrawableHeightTemp = mDrawableHeightLeft; 455 456 mDrawableLeft = mDrawableError; 457 mDrawableSizeLeft = mDrawableSizeError; 458 mDrawableHeightLeft = mDrawableHeightError; 459 break; 460 case LAYOUT_DIRECTION_LTR: 461 default: 462 mDrawableSaved = DRAWABLE_RIGHT; 463 464 mDrawableTemp = mDrawableRight; 465 mDrawableSizeTemp = mDrawableSizeRight; 466 mDrawableHeightTemp = mDrawableHeightRight; 467 468 mDrawableRight = mDrawableError; 469 mDrawableSizeRight = mDrawableSizeError; 470 mDrawableHeightRight = mDrawableHeightError; 471 break; 472 } 473 } 474 } 475 } 476 477 Drawables mDrawables; 478 479 private CharWrapper mCharWrapper; 480 481 private Marquee mMarquee; 482 private boolean mRestartMarquee; 483 484 private int mMarqueeRepeatLimit = 3; 485 486 private int mLastLayoutDirection = -1; 487 488 /** 489 * On some devices the fading edges add a performance penalty if used 490 * extensively in the same layout. This mode indicates how the marquee 491 * is currently being shown, if applicable. (mEllipsize will == MARQUEE) 492 */ 493 private int mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 494 495 /** 496 * When mMarqueeFadeMode is not MARQUEE_FADE_NORMAL, this stores 497 * the layout that should be used when the mode switches. 498 */ 499 private Layout mSavedMarqueeModeLayout; 500 501 @ViewDebug.ExportedProperty(category = "text") 502 private CharSequence mText; 503 private CharSequence mTransformed; 504 private BufferType mBufferType = BufferType.NORMAL; 505 506 private CharSequence mHint; 507 private Layout mHintLayout; 508 509 private MovementMethod mMovement; 510 511 private TransformationMethod mTransformation; 512 private boolean mAllowTransformationLengthChange; 513 private ChangeWatcher mChangeWatcher; 514 515 private ArrayList<TextWatcher> mListeners; 516 517 // display attributes 518 private final TextPaint mTextPaint; 519 private boolean mUserSetTextScaleX; 520 private Layout mLayout; 521 522 private int mGravity = Gravity.TOP | Gravity.START; 523 private boolean mHorizontallyScrolling; 524 525 private int mAutoLinkMask; 526 private boolean mLinksClickable = true; 527 528 private float mSpacingMult = 1.0f; 529 private float mSpacingAdd = 0.0f; 530 531 private int mMaximum = Integer.MAX_VALUE; 532 private int mMaxMode = LINES; 533 private int mMinimum = 0; 534 private int mMinMode = LINES; 535 536 private int mOldMaximum = mMaximum; 537 private int mOldMaxMode = mMaxMode; 538 539 private int mMaxWidth = Integer.MAX_VALUE; 540 private int mMaxWidthMode = PIXELS; 541 private int mMinWidth = 0; 542 private int mMinWidthMode = PIXELS; 543 544 private boolean mSingleLine; 545 private int mDesiredHeightAtMeasure = -1; 546 private boolean mIncludePad = true; 547 private int mDeferScroll = -1; 548 549 // tmp primitives, so we don't alloc them on each draw 550 private Rect mTempRect; 551 private long mLastScroll; 552 private Scroller mScroller; 553 554 private BoringLayout.Metrics mBoring, mHintBoring; 555 private BoringLayout mSavedLayout, mSavedHintLayout; 556 557 private TextDirectionHeuristic mTextDir; 558 559 private InputFilter[] mFilters = NO_FILTERS; 560 561 private volatile Locale mCurrentSpellCheckerLocaleCache; 562 563 // It is possible to have a selection even when mEditor is null (programmatically set, like when 564 // a link is pressed). These highlight-related fields do not go in mEditor. 565 int mHighlightColor = 0x6633B5E5; 566 private Path mHighlightPath; 567 private final Paint mHighlightPaint; 568 private boolean mHighlightPathBogus = true; 569 570 // Although these fields are specific to editable text, they are not added to Editor because 571 // they are defined by the TextView's style and are theme-dependent. 572 int mCursorDrawableRes; 573 // These four fields, could be moved to Editor, since we know their default values and we 574 // could condition the creation of the Editor to a non standard value. This is however 575 // brittle since the hardcoded values here (such as 576 // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the 577 // default style is modified. 578 int mTextSelectHandleLeftRes; 579 int mTextSelectHandleRightRes; 580 int mTextSelectHandleRes; 581 int mTextEditSuggestionItemLayout; 582 583 /** 584 * EditText specific data, created on demand when one of the Editor fields is used. 585 * See {@link #createEditorIfNeeded()}. 586 */ 587 private Editor mEditor; 588 589 /* 590 * Kick-start the font cache for the zygote process (to pay the cost of 591 * initializing freetype for our default font only once). 592 */ 593 static { 594 Paint p = new Paint(); 595 p.setAntiAlias(true); 596 // We don't care about the result, just the side-effect of measuring. 597 p.measureText("H"); 598 } 599 600 /** 601 * Interface definition for a callback to be invoked when an action is 602 * performed on the editor. 603 */ 604 public interface OnEditorActionListener { 605 /** 606 * Called when an action is being performed. 607 * 608 * @param v The view that was clicked. 609 * @param actionId Identifier of the action. This will be either the 610 * identifier you supplied, or {@link EditorInfo#IME_NULL 611 * EditorInfo.IME_NULL} if being called due to the enter key 612 * being pressed. 613 * @param event If triggered by an enter key, this is the event; 614 * otherwise, this is null. 615 * @return Return true if you have consumed the action, else false. 616 */ onEditorAction(TextView v, int actionId, KeyEvent event)617 boolean onEditorAction(TextView v, int actionId, KeyEvent event); 618 } 619 TextView(Context context)620 public TextView(Context context) { 621 this(context, null); 622 } 623 TextView(Context context, AttributeSet attrs)624 public TextView(Context context, AttributeSet attrs) { 625 this(context, attrs, com.android.internal.R.attr.textViewStyle); 626 } 627 TextView(Context context, AttributeSet attrs, int defStyleAttr)628 public TextView(Context context, AttributeSet attrs, int defStyleAttr) { 629 this(context, attrs, defStyleAttr, 0); 630 } 631 632 @SuppressWarnings("deprecation") TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)633 public TextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 634 super(context, attrs, defStyleAttr, defStyleRes); 635 636 mText = ""; 637 638 final Resources res = getResources(); 639 final CompatibilityInfo compat = res.getCompatibilityInfo(); 640 641 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 642 mTextPaint.density = res.getDisplayMetrics().density; 643 mTextPaint.setCompatibilityScaling(compat.applicationScale); 644 645 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 646 mHighlightPaint.setCompatibilityScaling(compat.applicationScale); 647 648 mMovement = getDefaultMovementMethod(); 649 650 mTransformation = null; 651 652 int textColorHighlight = 0; 653 ColorStateList textColor = null; 654 ColorStateList textColorHint = null; 655 ColorStateList textColorLink = null; 656 int textSize = 15; 657 String fontFamily = null; 658 int typefaceIndex = -1; 659 int styleIndex = -1; 660 boolean allCaps = false; 661 int shadowcolor = 0; 662 float dx = 0, dy = 0, r = 0; 663 boolean elegant = false; 664 float letterSpacing = 0; 665 String fontFeatureSettings = null; 666 667 final Resources.Theme theme = context.getTheme(); 668 669 /* 670 * Look the appearance up without checking first if it exists because 671 * almost every TextView has one and it greatly simplifies the logic 672 * to be able to parse the appearance first and then let specific tags 673 * for this View override it. 674 */ 675 TypedArray a = theme.obtainStyledAttributes(attrs, 676 com.android.internal.R.styleable.TextViewAppearance, defStyleAttr, defStyleRes); 677 TypedArray appearance = null; 678 int ap = a.getResourceId( 679 com.android.internal.R.styleable.TextViewAppearance_textAppearance, -1); 680 a.recycle(); 681 if (ap != -1) { 682 appearance = theme.obtainStyledAttributes( 683 ap, com.android.internal.R.styleable.TextAppearance); 684 } 685 if (appearance != null) { 686 int n = appearance.getIndexCount(); 687 for (int i = 0; i < n; i++) { 688 int attr = appearance.getIndex(i); 689 690 switch (attr) { 691 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 692 textColorHighlight = appearance.getColor(attr, textColorHighlight); 693 break; 694 695 case com.android.internal.R.styleable.TextAppearance_textColor: 696 textColor = appearance.getColorStateList(attr); 697 break; 698 699 case com.android.internal.R.styleable.TextAppearance_textColorHint: 700 textColorHint = appearance.getColorStateList(attr); 701 break; 702 703 case com.android.internal.R.styleable.TextAppearance_textColorLink: 704 textColorLink = appearance.getColorStateList(attr); 705 break; 706 707 case com.android.internal.R.styleable.TextAppearance_textSize: 708 textSize = appearance.getDimensionPixelSize(attr, textSize); 709 break; 710 711 case com.android.internal.R.styleable.TextAppearance_typeface: 712 typefaceIndex = appearance.getInt(attr, -1); 713 break; 714 715 case com.android.internal.R.styleable.TextAppearance_fontFamily: 716 fontFamily = appearance.getString(attr); 717 break; 718 719 case com.android.internal.R.styleable.TextAppearance_textStyle: 720 styleIndex = appearance.getInt(attr, -1); 721 break; 722 723 case com.android.internal.R.styleable.TextAppearance_textAllCaps: 724 allCaps = appearance.getBoolean(attr, false); 725 break; 726 727 case com.android.internal.R.styleable.TextAppearance_shadowColor: 728 shadowcolor = appearance.getInt(attr, 0); 729 break; 730 731 case com.android.internal.R.styleable.TextAppearance_shadowDx: 732 dx = appearance.getFloat(attr, 0); 733 break; 734 735 case com.android.internal.R.styleable.TextAppearance_shadowDy: 736 dy = appearance.getFloat(attr, 0); 737 break; 738 739 case com.android.internal.R.styleable.TextAppearance_shadowRadius: 740 r = appearance.getFloat(attr, 0); 741 break; 742 743 case com.android.internal.R.styleable.TextAppearance_elegantTextHeight: 744 elegant = appearance.getBoolean(attr, false); 745 break; 746 747 case com.android.internal.R.styleable.TextAppearance_letterSpacing: 748 letterSpacing = appearance.getFloat(attr, 0); 749 break; 750 751 case com.android.internal.R.styleable.TextAppearance_fontFeatureSettings: 752 fontFeatureSettings = appearance.getString(attr); 753 break; 754 } 755 } 756 757 appearance.recycle(); 758 } 759 760 boolean editable = getDefaultEditable(); 761 CharSequence inputMethod = null; 762 int numeric = 0; 763 CharSequence digits = null; 764 boolean phone = false; 765 boolean autotext = false; 766 int autocap = -1; 767 int buffertype = 0; 768 boolean selectallonfocus = false; 769 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 770 drawableBottom = null, drawableStart = null, drawableEnd = null; 771 int drawablePadding = 0; 772 int ellipsize = -1; 773 boolean singleLine = false; 774 int maxlength = -1; 775 CharSequence text = ""; 776 CharSequence hint = null; 777 boolean password = false; 778 int inputType = EditorInfo.TYPE_NULL; 779 780 a = theme.obtainStyledAttributes( 781 attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); 782 783 int n = a.getIndexCount(); 784 for (int i = 0; i < n; i++) { 785 int attr = a.getIndex(i); 786 787 switch (attr) { 788 case com.android.internal.R.styleable.TextView_editable: 789 editable = a.getBoolean(attr, editable); 790 break; 791 792 case com.android.internal.R.styleable.TextView_inputMethod: 793 inputMethod = a.getText(attr); 794 break; 795 796 case com.android.internal.R.styleable.TextView_numeric: 797 numeric = a.getInt(attr, numeric); 798 break; 799 800 case com.android.internal.R.styleable.TextView_digits: 801 digits = a.getText(attr); 802 break; 803 804 case com.android.internal.R.styleable.TextView_phoneNumber: 805 phone = a.getBoolean(attr, phone); 806 break; 807 808 case com.android.internal.R.styleable.TextView_autoText: 809 autotext = a.getBoolean(attr, autotext); 810 break; 811 812 case com.android.internal.R.styleable.TextView_capitalize: 813 autocap = a.getInt(attr, autocap); 814 break; 815 816 case com.android.internal.R.styleable.TextView_bufferType: 817 buffertype = a.getInt(attr, buffertype); 818 break; 819 820 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 821 selectallonfocus = a.getBoolean(attr, selectallonfocus); 822 break; 823 824 case com.android.internal.R.styleable.TextView_autoLink: 825 mAutoLinkMask = a.getInt(attr, 0); 826 break; 827 828 case com.android.internal.R.styleable.TextView_linksClickable: 829 mLinksClickable = a.getBoolean(attr, true); 830 break; 831 832 case com.android.internal.R.styleable.TextView_drawableLeft: 833 drawableLeft = a.getDrawable(attr); 834 break; 835 836 case com.android.internal.R.styleable.TextView_drawableTop: 837 drawableTop = a.getDrawable(attr); 838 break; 839 840 case com.android.internal.R.styleable.TextView_drawableRight: 841 drawableRight = a.getDrawable(attr); 842 break; 843 844 case com.android.internal.R.styleable.TextView_drawableBottom: 845 drawableBottom = a.getDrawable(attr); 846 break; 847 848 case com.android.internal.R.styleable.TextView_drawableStart: 849 drawableStart = a.getDrawable(attr); 850 break; 851 852 case com.android.internal.R.styleable.TextView_drawableEnd: 853 drawableEnd = a.getDrawable(attr); 854 break; 855 856 case com.android.internal.R.styleable.TextView_drawablePadding: 857 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 858 break; 859 860 case com.android.internal.R.styleable.TextView_maxLines: 861 setMaxLines(a.getInt(attr, -1)); 862 break; 863 864 case com.android.internal.R.styleable.TextView_maxHeight: 865 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 866 break; 867 868 case com.android.internal.R.styleable.TextView_lines: 869 setLines(a.getInt(attr, -1)); 870 break; 871 872 case com.android.internal.R.styleable.TextView_height: 873 setHeight(a.getDimensionPixelSize(attr, -1)); 874 break; 875 876 case com.android.internal.R.styleable.TextView_minLines: 877 setMinLines(a.getInt(attr, -1)); 878 break; 879 880 case com.android.internal.R.styleable.TextView_minHeight: 881 setMinHeight(a.getDimensionPixelSize(attr, -1)); 882 break; 883 884 case com.android.internal.R.styleable.TextView_maxEms: 885 setMaxEms(a.getInt(attr, -1)); 886 break; 887 888 case com.android.internal.R.styleable.TextView_maxWidth: 889 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 890 break; 891 892 case com.android.internal.R.styleable.TextView_ems: 893 setEms(a.getInt(attr, -1)); 894 break; 895 896 case com.android.internal.R.styleable.TextView_width: 897 setWidth(a.getDimensionPixelSize(attr, -1)); 898 break; 899 900 case com.android.internal.R.styleable.TextView_minEms: 901 setMinEms(a.getInt(attr, -1)); 902 break; 903 904 case com.android.internal.R.styleable.TextView_minWidth: 905 setMinWidth(a.getDimensionPixelSize(attr, -1)); 906 break; 907 908 case com.android.internal.R.styleable.TextView_gravity: 909 setGravity(a.getInt(attr, -1)); 910 break; 911 912 case com.android.internal.R.styleable.TextView_hint: 913 hint = a.getText(attr); 914 break; 915 916 case com.android.internal.R.styleable.TextView_text: 917 text = a.getText(attr); 918 break; 919 920 case com.android.internal.R.styleable.TextView_scrollHorizontally: 921 if (a.getBoolean(attr, false)) { 922 setHorizontallyScrolling(true); 923 } 924 break; 925 926 case com.android.internal.R.styleable.TextView_singleLine: 927 singleLine = a.getBoolean(attr, singleLine); 928 break; 929 930 case com.android.internal.R.styleable.TextView_ellipsize: 931 ellipsize = a.getInt(attr, ellipsize); 932 break; 933 934 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 935 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 936 break; 937 938 case com.android.internal.R.styleable.TextView_includeFontPadding: 939 if (!a.getBoolean(attr, true)) { 940 setIncludeFontPadding(false); 941 } 942 break; 943 944 case com.android.internal.R.styleable.TextView_cursorVisible: 945 if (!a.getBoolean(attr, true)) { 946 setCursorVisible(false); 947 } 948 break; 949 950 case com.android.internal.R.styleable.TextView_maxLength: 951 maxlength = a.getInt(attr, -1); 952 break; 953 954 case com.android.internal.R.styleable.TextView_textScaleX: 955 setTextScaleX(a.getFloat(attr, 1.0f)); 956 break; 957 958 case com.android.internal.R.styleable.TextView_freezesText: 959 mFreezesText = a.getBoolean(attr, false); 960 break; 961 962 case com.android.internal.R.styleable.TextView_shadowColor: 963 shadowcolor = a.getInt(attr, 0); 964 break; 965 966 case com.android.internal.R.styleable.TextView_shadowDx: 967 dx = a.getFloat(attr, 0); 968 break; 969 970 case com.android.internal.R.styleable.TextView_shadowDy: 971 dy = a.getFloat(attr, 0); 972 break; 973 974 case com.android.internal.R.styleable.TextView_shadowRadius: 975 r = a.getFloat(attr, 0); 976 break; 977 978 case com.android.internal.R.styleable.TextView_enabled: 979 setEnabled(a.getBoolean(attr, isEnabled())); 980 break; 981 982 case com.android.internal.R.styleable.TextView_textColorHighlight: 983 textColorHighlight = a.getColor(attr, textColorHighlight); 984 break; 985 986 case com.android.internal.R.styleable.TextView_textColor: 987 textColor = a.getColorStateList(attr); 988 break; 989 990 case com.android.internal.R.styleable.TextView_textColorHint: 991 textColorHint = a.getColorStateList(attr); 992 break; 993 994 case com.android.internal.R.styleable.TextView_textColorLink: 995 textColorLink = a.getColorStateList(attr); 996 break; 997 998 case com.android.internal.R.styleable.TextView_textSize: 999 textSize = a.getDimensionPixelSize(attr, textSize); 1000 break; 1001 1002 case com.android.internal.R.styleable.TextView_typeface: 1003 typefaceIndex = a.getInt(attr, typefaceIndex); 1004 break; 1005 1006 case com.android.internal.R.styleable.TextView_textStyle: 1007 styleIndex = a.getInt(attr, styleIndex); 1008 break; 1009 1010 case com.android.internal.R.styleable.TextView_fontFamily: 1011 fontFamily = a.getString(attr); 1012 break; 1013 1014 case com.android.internal.R.styleable.TextView_password: 1015 password = a.getBoolean(attr, password); 1016 break; 1017 1018 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 1019 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 1020 break; 1021 1022 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 1023 mSpacingMult = a.getFloat(attr, mSpacingMult); 1024 break; 1025 1026 case com.android.internal.R.styleable.TextView_inputType: 1027 inputType = a.getInt(attr, EditorInfo.TYPE_NULL); 1028 break; 1029 1030 case com.android.internal.R.styleable.TextView_imeOptions: 1031 createEditorIfNeeded(); 1032 mEditor.createInputContentTypeIfNeeded(); 1033 mEditor.mInputContentType.imeOptions = a.getInt(attr, 1034 mEditor.mInputContentType.imeOptions); 1035 break; 1036 1037 case com.android.internal.R.styleable.TextView_imeActionLabel: 1038 createEditorIfNeeded(); 1039 mEditor.createInputContentTypeIfNeeded(); 1040 mEditor.mInputContentType.imeActionLabel = a.getText(attr); 1041 break; 1042 1043 case com.android.internal.R.styleable.TextView_imeActionId: 1044 createEditorIfNeeded(); 1045 mEditor.createInputContentTypeIfNeeded(); 1046 mEditor.mInputContentType.imeActionId = a.getInt(attr, 1047 mEditor.mInputContentType.imeActionId); 1048 break; 1049 1050 case com.android.internal.R.styleable.TextView_privateImeOptions: 1051 setPrivateImeOptions(a.getString(attr)); 1052 break; 1053 1054 case com.android.internal.R.styleable.TextView_editorExtras: 1055 try { 1056 setInputExtras(a.getResourceId(attr, 0)); 1057 } catch (XmlPullParserException e) { 1058 Log.w(LOG_TAG, "Failure reading input extras", e); 1059 } catch (IOException e) { 1060 Log.w(LOG_TAG, "Failure reading input extras", e); 1061 } 1062 break; 1063 1064 case com.android.internal.R.styleable.TextView_textCursorDrawable: 1065 mCursorDrawableRes = a.getResourceId(attr, 0); 1066 break; 1067 1068 case com.android.internal.R.styleable.TextView_textSelectHandleLeft: 1069 mTextSelectHandleLeftRes = a.getResourceId(attr, 0); 1070 break; 1071 1072 case com.android.internal.R.styleable.TextView_textSelectHandleRight: 1073 mTextSelectHandleRightRes = a.getResourceId(attr, 0); 1074 break; 1075 1076 case com.android.internal.R.styleable.TextView_textSelectHandle: 1077 mTextSelectHandleRes = a.getResourceId(attr, 0); 1078 break; 1079 1080 case com.android.internal.R.styleable.TextView_textEditSuggestionItemLayout: 1081 mTextEditSuggestionItemLayout = a.getResourceId(attr, 0); 1082 break; 1083 1084 case com.android.internal.R.styleable.TextView_textIsSelectable: 1085 setTextIsSelectable(a.getBoolean(attr, false)); 1086 break; 1087 1088 case com.android.internal.R.styleable.TextView_textAllCaps: 1089 allCaps = a.getBoolean(attr, false); 1090 break; 1091 1092 case com.android.internal.R.styleable.TextView_elegantTextHeight: 1093 elegant = a.getBoolean(attr, false); 1094 break; 1095 1096 case com.android.internal.R.styleable.TextView_letterSpacing: 1097 letterSpacing = a.getFloat(attr, 0); 1098 break; 1099 1100 case com.android.internal.R.styleable.TextView_fontFeatureSettings: 1101 fontFeatureSettings = a.getString(attr); 1102 break; 1103 } 1104 } 1105 a.recycle(); 1106 1107 BufferType bufferType = BufferType.EDITABLE; 1108 1109 final int variation = 1110 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 1111 final boolean passwordInputType = variation 1112 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); 1113 final boolean webPasswordInputType = variation 1114 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD); 1115 final boolean numberPasswordInputType = variation 1116 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 1117 1118 if (inputMethod != null) { 1119 Class<?> c; 1120 1121 try { 1122 c = Class.forName(inputMethod.toString()); 1123 } catch (ClassNotFoundException ex) { 1124 throw new RuntimeException(ex); 1125 } 1126 1127 try { 1128 createEditorIfNeeded(); 1129 mEditor.mKeyListener = (KeyListener) c.newInstance(); 1130 } catch (InstantiationException ex) { 1131 throw new RuntimeException(ex); 1132 } catch (IllegalAccessException ex) { 1133 throw new RuntimeException(ex); 1134 } 1135 try { 1136 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1137 ? inputType 1138 : mEditor.mKeyListener.getInputType(); 1139 } catch (IncompatibleClassChangeError e) { 1140 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1141 } 1142 } else if (digits != null) { 1143 createEditorIfNeeded(); 1144 mEditor.mKeyListener = DigitsKeyListener.getInstance(digits.toString()); 1145 // If no input type was specified, we will default to generic 1146 // text, since we can't tell the IME about the set of digits 1147 // that was selected. 1148 mEditor.mInputType = inputType != EditorInfo.TYPE_NULL 1149 ? inputType : EditorInfo.TYPE_CLASS_TEXT; 1150 } else if (inputType != EditorInfo.TYPE_NULL) { 1151 setInputType(inputType, true); 1152 // If set, the input type overrides what was set using the deprecated singleLine flag. 1153 singleLine = !isMultilineInputType(inputType); 1154 } else if (phone) { 1155 createEditorIfNeeded(); 1156 mEditor.mKeyListener = DialerKeyListener.getInstance(); 1157 mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE; 1158 } else if (numeric != 0) { 1159 createEditorIfNeeded(); 1160 mEditor.mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, 1161 (numeric & DECIMAL) != 0); 1162 inputType = EditorInfo.TYPE_CLASS_NUMBER; 1163 if ((numeric & SIGNED) != 0) { 1164 inputType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED; 1165 } 1166 if ((numeric & DECIMAL) != 0) { 1167 inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; 1168 } 1169 mEditor.mInputType = inputType; 1170 } else if (autotext || autocap != -1) { 1171 TextKeyListener.Capitalize cap; 1172 1173 inputType = EditorInfo.TYPE_CLASS_TEXT; 1174 1175 switch (autocap) { 1176 case 1: 1177 cap = TextKeyListener.Capitalize.SENTENCES; 1178 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 1179 break; 1180 1181 case 2: 1182 cap = TextKeyListener.Capitalize.WORDS; 1183 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 1184 break; 1185 1186 case 3: 1187 cap = TextKeyListener.Capitalize.CHARACTERS; 1188 inputType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 1189 break; 1190 1191 default: 1192 cap = TextKeyListener.Capitalize.NONE; 1193 break; 1194 } 1195 1196 createEditorIfNeeded(); 1197 mEditor.mKeyListener = TextKeyListener.getInstance(autotext, cap); 1198 mEditor.mInputType = inputType; 1199 } else if (isTextSelectable()) { 1200 // Prevent text changes from keyboard. 1201 if (mEditor != null) { 1202 mEditor.mKeyListener = null; 1203 mEditor.mInputType = EditorInfo.TYPE_NULL; 1204 } 1205 bufferType = BufferType.SPANNABLE; 1206 // So that selection can be changed using arrow keys and touch is handled. 1207 setMovementMethod(ArrowKeyMovementMethod.getInstance()); 1208 } else if (editable) { 1209 createEditorIfNeeded(); 1210 mEditor.mKeyListener = TextKeyListener.getInstance(); 1211 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1212 } else { 1213 if (mEditor != null) mEditor.mKeyListener = null; 1214 1215 switch (buffertype) { 1216 case 0: 1217 bufferType = BufferType.NORMAL; 1218 break; 1219 case 1: 1220 bufferType = BufferType.SPANNABLE; 1221 break; 1222 case 2: 1223 bufferType = BufferType.EDITABLE; 1224 break; 1225 } 1226 } 1227 1228 if (mEditor != null) mEditor.adjustInputType(password, passwordInputType, 1229 webPasswordInputType, numberPasswordInputType); 1230 1231 if (selectallonfocus) { 1232 createEditorIfNeeded(); 1233 mEditor.mSelectAllOnFocus = true; 1234 1235 if (bufferType == BufferType.NORMAL) 1236 bufferType = BufferType.SPANNABLE; 1237 } 1238 1239 // This call will save the initial left/right drawables 1240 setCompoundDrawablesWithIntrinsicBounds( 1241 drawableLeft, drawableTop, drawableRight, drawableBottom); 1242 setRelativeDrawablesIfNeeded(drawableStart, drawableEnd); 1243 setCompoundDrawablePadding(drawablePadding); 1244 1245 // Same as setSingleLine(), but make sure the transformation method and the maximum number 1246 // of lines of height are unchanged for multi-line TextViews. 1247 setInputTypeSingleLine(singleLine); 1248 applySingleLine(singleLine, singleLine, singleLine); 1249 1250 if (singleLine && getKeyListener() == null && ellipsize < 0) { 1251 ellipsize = 3; // END 1252 } 1253 1254 switch (ellipsize) { 1255 case 1: 1256 setEllipsize(TextUtils.TruncateAt.START); 1257 break; 1258 case 2: 1259 setEllipsize(TextUtils.TruncateAt.MIDDLE); 1260 break; 1261 case 3: 1262 setEllipsize(TextUtils.TruncateAt.END); 1263 break; 1264 case 4: 1265 if (ViewConfiguration.get(context).isFadingMarqueeEnabled()) { 1266 setHorizontalFadingEdgeEnabled(true); 1267 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 1268 } else { 1269 setHorizontalFadingEdgeEnabled(false); 1270 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 1271 } 1272 setEllipsize(TextUtils.TruncateAt.MARQUEE); 1273 break; 1274 } 1275 1276 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); 1277 setHintTextColor(textColorHint); 1278 setLinkTextColor(textColorLink); 1279 if (textColorHighlight != 0) { 1280 setHighlightColor(textColorHighlight); 1281 } 1282 setRawTextSize(textSize); 1283 setElegantTextHeight(elegant); 1284 setLetterSpacing(letterSpacing); 1285 setFontFeatureSettings(fontFeatureSettings); 1286 1287 if (allCaps) { 1288 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 1289 } 1290 1291 if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) { 1292 setTransformationMethod(PasswordTransformationMethod.getInstance()); 1293 typefaceIndex = MONOSPACE; 1294 } else if (mEditor != null && 1295 (mEditor.mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) 1296 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 1297 typefaceIndex = MONOSPACE; 1298 } 1299 1300 setTypefaceFromAttrs(fontFamily, typefaceIndex, styleIndex); 1301 1302 if (shadowcolor != 0) { 1303 setShadowLayer(r, dx, dy, shadowcolor); 1304 } 1305 1306 if (maxlength >= 0) { 1307 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 1308 } else { 1309 setFilters(NO_FILTERS); 1310 } 1311 1312 setText(text, bufferType); 1313 if (hint != null) setHint(hint); 1314 1315 /* 1316 * Views are not normally focusable unless specified to be. 1317 * However, TextViews that have input or movement methods *are* 1318 * focusable by default. 1319 */ 1320 a = context.obtainStyledAttributes( 1321 attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); 1322 1323 boolean focusable = mMovement != null || getKeyListener() != null; 1324 boolean clickable = focusable || isClickable(); 1325 boolean longClickable = focusable || isLongClickable(); 1326 1327 n = a.getIndexCount(); 1328 for (int i = 0; i < n; i++) { 1329 int attr = a.getIndex(i); 1330 1331 switch (attr) { 1332 case com.android.internal.R.styleable.View_focusable: 1333 focusable = a.getBoolean(attr, focusable); 1334 break; 1335 1336 case com.android.internal.R.styleable.View_clickable: 1337 clickable = a.getBoolean(attr, clickable); 1338 break; 1339 1340 case com.android.internal.R.styleable.View_longClickable: 1341 longClickable = a.getBoolean(attr, longClickable); 1342 break; 1343 } 1344 } 1345 a.recycle(); 1346 1347 setFocusable(focusable); 1348 setClickable(clickable); 1349 setLongClickable(longClickable); 1350 1351 if (mEditor != null) mEditor.prepareCursorControllers(); 1352 1353 // If not explicitly specified this view is important for accessibility. 1354 if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 1355 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); 1356 } 1357 } 1358 setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex)1359 private void setTypefaceFromAttrs(String familyName, int typefaceIndex, int styleIndex) { 1360 Typeface tf = null; 1361 if (familyName != null) { 1362 tf = Typeface.create(familyName, styleIndex); 1363 if (tf != null) { 1364 setTypeface(tf); 1365 return; 1366 } 1367 } 1368 switch (typefaceIndex) { 1369 case SANS: 1370 tf = Typeface.SANS_SERIF; 1371 break; 1372 1373 case SERIF: 1374 tf = Typeface.SERIF; 1375 break; 1376 1377 case MONOSPACE: 1378 tf = Typeface.MONOSPACE; 1379 break; 1380 } 1381 1382 setTypeface(tf, styleIndex); 1383 } 1384 setRelativeDrawablesIfNeeded(Drawable start, Drawable end)1385 private void setRelativeDrawablesIfNeeded(Drawable start, Drawable end) { 1386 boolean hasRelativeDrawables = (start != null) || (end != null); 1387 if (hasRelativeDrawables) { 1388 Drawables dr = mDrawables; 1389 if (dr == null) { 1390 mDrawables = dr = new Drawables(getContext()); 1391 } 1392 mDrawables.mOverride = true; 1393 final Rect compoundRect = dr.mCompoundRect; 1394 int[] state = getDrawableState(); 1395 if (start != null) { 1396 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 1397 start.setState(state); 1398 start.copyBounds(compoundRect); 1399 start.setCallback(this); 1400 1401 dr.mDrawableStart = start; 1402 dr.mDrawableSizeStart = compoundRect.width(); 1403 dr.mDrawableHeightStart = compoundRect.height(); 1404 } else { 1405 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 1406 } 1407 if (end != null) { 1408 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 1409 end.setState(state); 1410 end.copyBounds(compoundRect); 1411 end.setCallback(this); 1412 1413 dr.mDrawableEnd = end; 1414 dr.mDrawableSizeEnd = compoundRect.width(); 1415 dr.mDrawableHeightEnd = compoundRect.height(); 1416 } else { 1417 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 1418 } 1419 resetResolvedDrawables(); 1420 resolveDrawables(); 1421 } 1422 } 1423 1424 @Override setEnabled(boolean enabled)1425 public void setEnabled(boolean enabled) { 1426 if (enabled == isEnabled()) { 1427 return; 1428 } 1429 1430 if (!enabled) { 1431 // Hide the soft input if the currently active TextView is disabled 1432 InputMethodManager imm = InputMethodManager.peekInstance(); 1433 if (imm != null && imm.isActive(this)) { 1434 imm.hideSoftInputFromWindow(getWindowToken(), 0); 1435 } 1436 } 1437 1438 super.setEnabled(enabled); 1439 1440 if (enabled) { 1441 // Make sure IME is updated with current editor info. 1442 InputMethodManager imm = InputMethodManager.peekInstance(); 1443 if (imm != null) imm.restartInput(this); 1444 } 1445 1446 // Will change text color 1447 if (mEditor != null) { 1448 mEditor.invalidateTextDisplayList(); 1449 mEditor.prepareCursorControllers(); 1450 1451 // start or stop the cursor blinking as appropriate 1452 mEditor.makeBlink(); 1453 } 1454 } 1455 1456 /** 1457 * Sets the typeface and style in which the text should be displayed, 1458 * and turns on the fake bold and italic bits in the Paint if the 1459 * Typeface that you provided does not have all the bits in the 1460 * style that you specified. 1461 * 1462 * @attr ref android.R.styleable#TextView_typeface 1463 * @attr ref android.R.styleable#TextView_textStyle 1464 */ setTypeface(Typeface tf, int style)1465 public void setTypeface(Typeface tf, int style) { 1466 if (style > 0) { 1467 if (tf == null) { 1468 tf = Typeface.defaultFromStyle(style); 1469 } else { 1470 tf = Typeface.create(tf, style); 1471 } 1472 1473 setTypeface(tf); 1474 // now compute what (if any) algorithmic styling is needed 1475 int typefaceStyle = tf != null ? tf.getStyle() : 0; 1476 int need = style & ~typefaceStyle; 1477 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 1478 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 1479 } else { 1480 mTextPaint.setFakeBoldText(false); 1481 mTextPaint.setTextSkewX(0); 1482 setTypeface(tf); 1483 } 1484 } 1485 1486 /** 1487 * Subclasses override this to specify that they have a KeyListener 1488 * by default even if not specifically called for in the XML options. 1489 */ getDefaultEditable()1490 protected boolean getDefaultEditable() { 1491 return false; 1492 } 1493 1494 /** 1495 * Subclasses override this to specify a default movement method. 1496 */ getDefaultMovementMethod()1497 protected MovementMethod getDefaultMovementMethod() { 1498 return null; 1499 } 1500 1501 /** 1502 * Return the text the TextView is displaying. If setText() was called with 1503 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast 1504 * the return value from this method to Spannable or Editable, respectively. 1505 * 1506 * Note: The content of the return value should not be modified. If you want 1507 * a modifiable one, you should make your own copy first. 1508 * 1509 * @attr ref android.R.styleable#TextView_text 1510 */ 1511 @ViewDebug.CapturedViewProperty getText()1512 public CharSequence getText() { 1513 return mText; 1514 } 1515 1516 /** 1517 * Returns the length, in characters, of the text managed by this TextView 1518 */ length()1519 public int length() { 1520 return mText.length(); 1521 } 1522 1523 /** 1524 * Return the text the TextView is displaying as an Editable object. If 1525 * the text is not editable, null is returned. 1526 * 1527 * @see #getText 1528 */ getEditableText()1529 public Editable getEditableText() { 1530 return (mText instanceof Editable) ? (Editable)mText : null; 1531 } 1532 1533 /** 1534 * @return the height of one standard line in pixels. Note that markup 1535 * within the text can cause individual lines to be taller or shorter 1536 * than this height, and the layout may contain additional first- 1537 * or last-line padding. 1538 */ getLineHeight()1539 public int getLineHeight() { 1540 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult + mSpacingAdd); 1541 } 1542 1543 /** 1544 * @return the Layout that is currently being used to display the text. 1545 * This can be null if the text or width has recently changes. 1546 */ getLayout()1547 public final Layout getLayout() { 1548 return mLayout; 1549 } 1550 1551 /** 1552 * @return the Layout that is currently being used to display the hint text. 1553 * This can be null. 1554 */ getHintLayout()1555 final Layout getHintLayout() { 1556 return mHintLayout; 1557 } 1558 1559 /** 1560 * Retrieve the {@link android.content.UndoManager} that is currently associated 1561 * with this TextView. By default there is no associated UndoManager, so null 1562 * is returned. One can be associated with the TextView through 1563 * {@link #setUndoManager(android.content.UndoManager, String)} 1564 * 1565 * @hide 1566 */ getUndoManager()1567 public final UndoManager getUndoManager() { 1568 return mEditor == null ? null : mEditor.mUndoManager; 1569 } 1570 1571 /** 1572 * Associate an {@link android.content.UndoManager} with this TextView. Once 1573 * done, all edit operations on the TextView will result in appropriate 1574 * {@link android.content.UndoOperation} objects pushed on the given UndoManager's 1575 * stack. 1576 * 1577 * @param undoManager The {@link android.content.UndoManager} to associate with 1578 * this TextView, or null to clear any existing association. 1579 * @param tag String tag identifying this particular TextView owner in the 1580 * UndoManager. This is used to keep the correct association with the 1581 * {@link android.content.UndoOwner} of any operations inside of the UndoManager. 1582 * 1583 * @hide 1584 */ setUndoManager(UndoManager undoManager, String tag)1585 public final void setUndoManager(UndoManager undoManager, String tag) { 1586 if (undoManager != null) { 1587 createEditorIfNeeded(); 1588 mEditor.mUndoManager = undoManager; 1589 mEditor.mUndoOwner = undoManager.getOwner(tag, this); 1590 mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor); 1591 if (!(mText instanceof Editable)) { 1592 setText(mText, BufferType.EDITABLE); 1593 } 1594 1595 setFilters((Editable) mText, mFilters); 1596 } else if (mEditor != null) { 1597 // XXX need to destroy all associated state. 1598 mEditor.mUndoManager = null; 1599 mEditor.mUndoOwner = null; 1600 mEditor.mUndoInputFilter = null; 1601 } 1602 } 1603 1604 /** 1605 * @return the current key listener for this TextView. 1606 * This will frequently be null for non-EditText TextViews. 1607 * 1608 * @attr ref android.R.styleable#TextView_numeric 1609 * @attr ref android.R.styleable#TextView_digits 1610 * @attr ref android.R.styleable#TextView_phoneNumber 1611 * @attr ref android.R.styleable#TextView_inputMethod 1612 * @attr ref android.R.styleable#TextView_capitalize 1613 * @attr ref android.R.styleable#TextView_autoText 1614 */ getKeyListener()1615 public final KeyListener getKeyListener() { 1616 return mEditor == null ? null : mEditor.mKeyListener; 1617 } 1618 1619 /** 1620 * Sets the key listener to be used with this TextView. This can be null 1621 * to disallow user input. Note that this method has significant and 1622 * subtle interactions with soft keyboards and other input method: 1623 * see {@link KeyListener#getInputType() KeyListener.getContentType()} 1624 * for important details. Calling this method will replace the current 1625 * content type of the text view with the content type returned by the 1626 * key listener. 1627 * <p> 1628 * Be warned that if you want a TextView with a key listener or movement 1629 * method not to be focusable, or if you want a TextView without a 1630 * key listener or movement method to be focusable, you must call 1631 * {@link #setFocusable} again after calling this to get the focusability 1632 * back the way you want it. 1633 * 1634 * @attr ref android.R.styleable#TextView_numeric 1635 * @attr ref android.R.styleable#TextView_digits 1636 * @attr ref android.R.styleable#TextView_phoneNumber 1637 * @attr ref android.R.styleable#TextView_inputMethod 1638 * @attr ref android.R.styleable#TextView_capitalize 1639 * @attr ref android.R.styleable#TextView_autoText 1640 */ setKeyListener(KeyListener input)1641 public void setKeyListener(KeyListener input) { 1642 setKeyListenerOnly(input); 1643 fixFocusableAndClickableSettings(); 1644 1645 if (input != null) { 1646 createEditorIfNeeded(); 1647 try { 1648 mEditor.mInputType = mEditor.mKeyListener.getInputType(); 1649 } catch (IncompatibleClassChangeError e) { 1650 mEditor.mInputType = EditorInfo.TYPE_CLASS_TEXT; 1651 } 1652 // Change inputType, without affecting transformation. 1653 // No need to applySingleLine since mSingleLine is unchanged. 1654 setInputTypeSingleLine(mSingleLine); 1655 } else { 1656 if (mEditor != null) mEditor.mInputType = EditorInfo.TYPE_NULL; 1657 } 1658 1659 InputMethodManager imm = InputMethodManager.peekInstance(); 1660 if (imm != null) imm.restartInput(this); 1661 } 1662 setKeyListenerOnly(KeyListener input)1663 private void setKeyListenerOnly(KeyListener input) { 1664 if (mEditor == null && input == null) return; // null is the default value 1665 1666 createEditorIfNeeded(); 1667 if (mEditor.mKeyListener != input) { 1668 mEditor.mKeyListener = input; 1669 if (input != null && !(mText instanceof Editable)) { 1670 setText(mText); 1671 } 1672 1673 setFilters((Editable) mText, mFilters); 1674 } 1675 } 1676 1677 /** 1678 * @return the movement method being used for this TextView. 1679 * This will frequently be null for non-EditText TextViews. 1680 */ getMovementMethod()1681 public final MovementMethod getMovementMethod() { 1682 return mMovement; 1683 } 1684 1685 /** 1686 * Sets the movement method (arrow key handler) to be used for 1687 * this TextView. This can be null to disallow using the arrow keys 1688 * to move the cursor or scroll the view. 1689 * <p> 1690 * Be warned that if you want a TextView with a key listener or movement 1691 * method not to be focusable, or if you want a TextView without a 1692 * key listener or movement method to be focusable, you must call 1693 * {@link #setFocusable} again after calling this to get the focusability 1694 * back the way you want it. 1695 */ setMovementMethod(MovementMethod movement)1696 public final void setMovementMethod(MovementMethod movement) { 1697 if (mMovement != movement) { 1698 mMovement = movement; 1699 1700 if (movement != null && !(mText instanceof Spannable)) { 1701 setText(mText); 1702 } 1703 1704 fixFocusableAndClickableSettings(); 1705 1706 // SelectionModifierCursorController depends on textCanBeSelected, which depends on 1707 // mMovement 1708 if (mEditor != null) mEditor.prepareCursorControllers(); 1709 } 1710 } 1711 fixFocusableAndClickableSettings()1712 private void fixFocusableAndClickableSettings() { 1713 if (mMovement != null || (mEditor != null && mEditor.mKeyListener != null)) { 1714 setFocusable(true); 1715 setClickable(true); 1716 setLongClickable(true); 1717 } else { 1718 setFocusable(false); 1719 setClickable(false); 1720 setLongClickable(false); 1721 } 1722 } 1723 1724 /** 1725 * @return the current transformation method for this TextView. 1726 * This will frequently be null except for single-line and password 1727 * fields. 1728 * 1729 * @attr ref android.R.styleable#TextView_password 1730 * @attr ref android.R.styleable#TextView_singleLine 1731 */ getTransformationMethod()1732 public final TransformationMethod getTransformationMethod() { 1733 return mTransformation; 1734 } 1735 1736 /** 1737 * Sets the transformation that is applied to the text that this 1738 * TextView is displaying. 1739 * 1740 * @attr ref android.R.styleable#TextView_password 1741 * @attr ref android.R.styleable#TextView_singleLine 1742 */ setTransformationMethod(TransformationMethod method)1743 public final void setTransformationMethod(TransformationMethod method) { 1744 if (method == mTransformation) { 1745 // Avoid the setText() below if the transformation is 1746 // the same. 1747 return; 1748 } 1749 if (mTransformation != null) { 1750 if (mText instanceof Spannable) { 1751 ((Spannable) mText).removeSpan(mTransformation); 1752 } 1753 } 1754 1755 mTransformation = method; 1756 1757 if (method instanceof TransformationMethod2) { 1758 TransformationMethod2 method2 = (TransformationMethod2) method; 1759 mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable); 1760 method2.setLengthChangesAllowed(mAllowTransformationLengthChange); 1761 } else { 1762 mAllowTransformationLengthChange = false; 1763 } 1764 1765 setText(mText); 1766 1767 if (hasPasswordTransformationMethod()) { 1768 notifyViewAccessibilityStateChangedIfNeeded( 1769 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 1770 } 1771 } 1772 1773 /** 1774 * Returns the top padding of the view, plus space for the top 1775 * Drawable if any. 1776 */ getCompoundPaddingTop()1777 public int getCompoundPaddingTop() { 1778 final Drawables dr = mDrawables; 1779 if (dr == null || dr.mDrawableTop == null) { 1780 return mPaddingTop; 1781 } else { 1782 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 1783 } 1784 } 1785 1786 /** 1787 * Returns the bottom padding of the view, plus space for the bottom 1788 * Drawable if any. 1789 */ getCompoundPaddingBottom()1790 public int getCompoundPaddingBottom() { 1791 final Drawables dr = mDrawables; 1792 if (dr == null || dr.mDrawableBottom == null) { 1793 return mPaddingBottom; 1794 } else { 1795 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 1796 } 1797 } 1798 1799 /** 1800 * Returns the left padding of the view, plus space for the left 1801 * Drawable if any. 1802 */ getCompoundPaddingLeft()1803 public int getCompoundPaddingLeft() { 1804 final Drawables dr = mDrawables; 1805 if (dr == null || dr.mDrawableLeft == null) { 1806 return mPaddingLeft; 1807 } else { 1808 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 1809 } 1810 } 1811 1812 /** 1813 * Returns the right padding of the view, plus space for the right 1814 * Drawable if any. 1815 */ getCompoundPaddingRight()1816 public int getCompoundPaddingRight() { 1817 final Drawables dr = mDrawables; 1818 if (dr == null || dr.mDrawableRight == null) { 1819 return mPaddingRight; 1820 } else { 1821 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 1822 } 1823 } 1824 1825 /** 1826 * Returns the start padding of the view, plus space for the start 1827 * Drawable if any. 1828 */ getCompoundPaddingStart()1829 public int getCompoundPaddingStart() { 1830 resolveDrawables(); 1831 switch(getLayoutDirection()) { 1832 default: 1833 case LAYOUT_DIRECTION_LTR: 1834 return getCompoundPaddingLeft(); 1835 case LAYOUT_DIRECTION_RTL: 1836 return getCompoundPaddingRight(); 1837 } 1838 } 1839 1840 /** 1841 * Returns the end padding of the view, plus space for the end 1842 * Drawable if any. 1843 */ getCompoundPaddingEnd()1844 public int getCompoundPaddingEnd() { 1845 resolveDrawables(); 1846 switch(getLayoutDirection()) { 1847 default: 1848 case LAYOUT_DIRECTION_LTR: 1849 return getCompoundPaddingRight(); 1850 case LAYOUT_DIRECTION_RTL: 1851 return getCompoundPaddingLeft(); 1852 } 1853 } 1854 1855 /** 1856 * Returns the extended top padding of the view, including both the 1857 * top Drawable if any and any extra space to keep more than maxLines 1858 * of text from showing. It is only valid to call this after measuring. 1859 */ getExtendedPaddingTop()1860 public int getExtendedPaddingTop() { 1861 if (mMaxMode != LINES) { 1862 return getCompoundPaddingTop(); 1863 } 1864 1865 if (mLayout == null) { 1866 assumeLayout(); 1867 } 1868 1869 if (mLayout.getLineCount() <= mMaximum) { 1870 return getCompoundPaddingTop(); 1871 } 1872 1873 int top = getCompoundPaddingTop(); 1874 int bottom = getCompoundPaddingBottom(); 1875 int viewht = getHeight() - top - bottom; 1876 int layoutht = mLayout.getLineTop(mMaximum); 1877 1878 if (layoutht >= viewht) { 1879 return top; 1880 } 1881 1882 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1883 if (gravity == Gravity.TOP) { 1884 return top; 1885 } else if (gravity == Gravity.BOTTOM) { 1886 return top + viewht - layoutht; 1887 } else { // (gravity == Gravity.CENTER_VERTICAL) 1888 return top + (viewht - layoutht) / 2; 1889 } 1890 } 1891 1892 /** 1893 * Returns the extended bottom padding of the view, including both the 1894 * bottom Drawable if any and any extra space to keep more than maxLines 1895 * of text from showing. It is only valid to call this after measuring. 1896 */ getExtendedPaddingBottom()1897 public int getExtendedPaddingBottom() { 1898 if (mMaxMode != LINES) { 1899 return getCompoundPaddingBottom(); 1900 } 1901 1902 if (mLayout == null) { 1903 assumeLayout(); 1904 } 1905 1906 if (mLayout.getLineCount() <= mMaximum) { 1907 return getCompoundPaddingBottom(); 1908 } 1909 1910 int top = getCompoundPaddingTop(); 1911 int bottom = getCompoundPaddingBottom(); 1912 int viewht = getHeight() - top - bottom; 1913 int layoutht = mLayout.getLineTop(mMaximum); 1914 1915 if (layoutht >= viewht) { 1916 return bottom; 1917 } 1918 1919 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1920 if (gravity == Gravity.TOP) { 1921 return bottom + viewht - layoutht; 1922 } else if (gravity == Gravity.BOTTOM) { 1923 return bottom; 1924 } else { // (gravity == Gravity.CENTER_VERTICAL) 1925 return bottom + (viewht - layoutht) / 2; 1926 } 1927 } 1928 1929 /** 1930 * Returns the total left padding of the view, including the left 1931 * Drawable if any. 1932 */ getTotalPaddingLeft()1933 public int getTotalPaddingLeft() { 1934 return getCompoundPaddingLeft(); 1935 } 1936 1937 /** 1938 * Returns the total right padding of the view, including the right 1939 * Drawable if any. 1940 */ getTotalPaddingRight()1941 public int getTotalPaddingRight() { 1942 return getCompoundPaddingRight(); 1943 } 1944 1945 /** 1946 * Returns the total start padding of the view, including the start 1947 * Drawable if any. 1948 */ getTotalPaddingStart()1949 public int getTotalPaddingStart() { 1950 return getCompoundPaddingStart(); 1951 } 1952 1953 /** 1954 * Returns the total end padding of the view, including the end 1955 * Drawable if any. 1956 */ getTotalPaddingEnd()1957 public int getTotalPaddingEnd() { 1958 return getCompoundPaddingEnd(); 1959 } 1960 1961 /** 1962 * Returns the total top padding of the view, including the top 1963 * Drawable if any, the extra space to keep more than maxLines 1964 * from showing, and the vertical offset for gravity, if any. 1965 */ getTotalPaddingTop()1966 public int getTotalPaddingTop() { 1967 return getExtendedPaddingTop() + getVerticalOffset(true); 1968 } 1969 1970 /** 1971 * Returns the total bottom padding of the view, including the bottom 1972 * Drawable if any, the extra space to keep more than maxLines 1973 * from showing, and the vertical offset for gravity, if any. 1974 */ getTotalPaddingBottom()1975 public int getTotalPaddingBottom() { 1976 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 1977 } 1978 1979 /** 1980 * Sets the Drawables (if any) to appear to the left of, above, to the 1981 * right of, and below the text. Use {@code null} if you do not want a 1982 * Drawable there. The Drawables must already have had 1983 * {@link Drawable#setBounds} called. 1984 * <p> 1985 * Calling this method will overwrite any Drawables previously set using 1986 * {@link #setCompoundDrawablesRelative} or related methods. 1987 * 1988 * @attr ref android.R.styleable#TextView_drawableLeft 1989 * @attr ref android.R.styleable#TextView_drawableTop 1990 * @attr ref android.R.styleable#TextView_drawableRight 1991 * @attr ref android.R.styleable#TextView_drawableBottom 1992 */ setCompoundDrawables(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)1993 public void setCompoundDrawables(@Nullable Drawable left, @Nullable Drawable top, 1994 @Nullable Drawable right, @Nullable Drawable bottom) { 1995 Drawables dr = mDrawables; 1996 1997 // We're switching to absolute, discard relative. 1998 if (dr != null) { 1999 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2000 dr.mDrawableStart = null; 2001 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2002 dr.mDrawableEnd = null; 2003 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2004 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2005 } 2006 2007 final boolean drawables = left != null || top != null || right != null || bottom != null; 2008 if (!drawables) { 2009 // Clearing drawables... can we free the data structure? 2010 if (dr != null) { 2011 if (dr.mDrawablePadding == 0) { 2012 mDrawables = null; 2013 } else { 2014 // We need to retain the last set padding, so just clear 2015 // out all of the fields in the existing structure. 2016 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null); 2017 dr.mDrawableLeft = null; 2018 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); 2019 dr.mDrawableTop = null; 2020 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null); 2021 dr.mDrawableRight = null; 2022 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); 2023 dr.mDrawableBottom = null; 2024 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2025 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2026 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2027 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2028 } 2029 } 2030 } else { 2031 if (dr == null) { 2032 mDrawables = dr = new Drawables(getContext()); 2033 } 2034 2035 mDrawables.mOverride = false; 2036 2037 if (dr.mDrawableLeft != left && dr.mDrawableLeft != null) { 2038 dr.mDrawableLeft.setCallback(null); 2039 } 2040 dr.mDrawableLeft = left; 2041 2042 if (dr.mDrawableTop != top && dr.mDrawableTop != null) { 2043 dr.mDrawableTop.setCallback(null); 2044 } 2045 dr.mDrawableTop = top; 2046 2047 if (dr.mDrawableRight != right && dr.mDrawableRight != null) { 2048 dr.mDrawableRight.setCallback(null); 2049 } 2050 dr.mDrawableRight = right; 2051 2052 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) { 2053 dr.mDrawableBottom.setCallback(null); 2054 } 2055 dr.mDrawableBottom = bottom; 2056 2057 final Rect compoundRect = dr.mCompoundRect; 2058 int[] state; 2059 2060 state = getDrawableState(); 2061 2062 if (left != null) { 2063 left.setState(state); 2064 left.copyBounds(compoundRect); 2065 left.setCallback(this); 2066 dr.mDrawableSizeLeft = compoundRect.width(); 2067 dr.mDrawableHeightLeft = compoundRect.height(); 2068 } else { 2069 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2070 } 2071 2072 if (right != null) { 2073 right.setState(state); 2074 right.copyBounds(compoundRect); 2075 right.setCallback(this); 2076 dr.mDrawableSizeRight = compoundRect.width(); 2077 dr.mDrawableHeightRight = compoundRect.height(); 2078 } else { 2079 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2080 } 2081 2082 if (top != null) { 2083 top.setState(state); 2084 top.copyBounds(compoundRect); 2085 top.setCallback(this); 2086 dr.mDrawableSizeTop = compoundRect.height(); 2087 dr.mDrawableWidthTop = compoundRect.width(); 2088 } else { 2089 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2090 } 2091 2092 if (bottom != null) { 2093 bottom.setState(state); 2094 bottom.copyBounds(compoundRect); 2095 bottom.setCallback(this); 2096 dr.mDrawableSizeBottom = compoundRect.height(); 2097 dr.mDrawableWidthBottom = compoundRect.width(); 2098 } else { 2099 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2100 } 2101 } 2102 2103 // Save initial left/right drawables 2104 if (dr != null) { 2105 dr.mDrawableLeftInitial = left; 2106 dr.mDrawableRightInitial = right; 2107 } 2108 2109 resetResolvedDrawables(); 2110 resolveDrawables(); 2111 invalidate(); 2112 requestLayout(); 2113 } 2114 2115 /** 2116 * Sets the Drawables (if any) to appear to the left of, above, to the 2117 * right of, and below the text. Use 0 if you do not want a Drawable there. 2118 * The Drawables' bounds will be set to their intrinsic bounds. 2119 * <p> 2120 * Calling this method will overwrite any Drawables previously set using 2121 * {@link #setCompoundDrawablesRelative} or related methods. 2122 * 2123 * @param left Resource identifier of the left Drawable. 2124 * @param top Resource identifier of the top Drawable. 2125 * @param right Resource identifier of the right Drawable. 2126 * @param bottom Resource identifier of the bottom Drawable. 2127 * 2128 * @attr ref android.R.styleable#TextView_drawableLeft 2129 * @attr ref android.R.styleable#TextView_drawableTop 2130 * @attr ref android.R.styleable#TextView_drawableRight 2131 * @attr ref android.R.styleable#TextView_drawableBottom 2132 */ 2133 @android.view.RemotableViewMethod setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom)2134 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { 2135 final Context context = getContext(); 2136 setCompoundDrawablesWithIntrinsicBounds(left != 0 ? context.getDrawable(left) : null, 2137 top != 0 ? context.getDrawable(top) : null, 2138 right != 0 ? context.getDrawable(right) : null, 2139 bottom != 0 ? context.getDrawable(bottom) : null); 2140 } 2141 2142 /** 2143 * Sets the Drawables (if any) to appear to the left of, above, to the 2144 * right of, and below the text. Use {@code null} if you do not want a 2145 * Drawable there. The Drawables' bounds will be set to their intrinsic 2146 * bounds. 2147 * <p> 2148 * Calling this method will overwrite any Drawables previously set using 2149 * {@link #setCompoundDrawablesRelative} or related methods. 2150 * 2151 * @attr ref android.R.styleable#TextView_drawableLeft 2152 * @attr ref android.R.styleable#TextView_drawableTop 2153 * @attr ref android.R.styleable#TextView_drawableRight 2154 * @attr ref android.R.styleable#TextView_drawableBottom 2155 */ setCompoundDrawablesWithIntrinsicBounds(@ullable Drawable left, @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom)2156 public void setCompoundDrawablesWithIntrinsicBounds(@Nullable Drawable left, 2157 @Nullable Drawable top, @Nullable Drawable right, @Nullable Drawable bottom) { 2158 2159 if (left != null) { 2160 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 2161 } 2162 if (right != null) { 2163 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 2164 } 2165 if (top != null) { 2166 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 2167 } 2168 if (bottom != null) { 2169 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 2170 } 2171 setCompoundDrawables(left, top, right, bottom); 2172 } 2173 2174 /** 2175 * Sets the Drawables (if any) to appear to the start of, above, to the end 2176 * of, and below the text. Use {@code null} if you do not want a Drawable 2177 * there. The Drawables must already have had {@link Drawable#setBounds} 2178 * called. 2179 * <p> 2180 * Calling this method will overwrite any Drawables previously set using 2181 * {@link #setCompoundDrawables} or related methods. 2182 * 2183 * @attr ref android.R.styleable#TextView_drawableStart 2184 * @attr ref android.R.styleable#TextView_drawableTop 2185 * @attr ref android.R.styleable#TextView_drawableEnd 2186 * @attr ref android.R.styleable#TextView_drawableBottom 2187 */ setCompoundDrawablesRelative(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)2188 public void setCompoundDrawablesRelative(@Nullable Drawable start, @Nullable Drawable top, 2189 @Nullable Drawable end, @Nullable Drawable bottom) { 2190 Drawables dr = mDrawables; 2191 2192 // We're switching to relative, discard absolute. 2193 if (dr != null) { 2194 if (dr.mDrawableLeft != null) dr.mDrawableLeft.setCallback(null); 2195 dr.mDrawableLeft = dr.mDrawableLeftInitial = null; 2196 if (dr.mDrawableRight != null) dr.mDrawableRight.setCallback(null); 2197 dr.mDrawableRight = dr.mDrawableRightInitial = null; 2198 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 2199 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 2200 } 2201 2202 final boolean drawables = start != null || top != null 2203 || end != null || bottom != null; 2204 2205 if (!drawables) { 2206 // Clearing drawables... can we free the data structure? 2207 if (dr != null) { 2208 if (dr.mDrawablePadding == 0) { 2209 mDrawables = null; 2210 } else { 2211 // We need to retain the last set padding, so just clear 2212 // out all of the fields in the existing structure. 2213 if (dr.mDrawableStart != null) dr.mDrawableStart.setCallback(null); 2214 dr.mDrawableStart = null; 2215 if (dr.mDrawableTop != null) dr.mDrawableTop.setCallback(null); 2216 dr.mDrawableTop = null; 2217 if (dr.mDrawableEnd != null) dr.mDrawableEnd.setCallback(null); 2218 dr.mDrawableEnd = null; 2219 if (dr.mDrawableBottom != null) dr.mDrawableBottom.setCallback(null); 2220 dr.mDrawableBottom = null; 2221 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2222 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2223 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2224 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2225 } 2226 } 2227 } else { 2228 if (dr == null) { 2229 mDrawables = dr = new Drawables(getContext()); 2230 } 2231 2232 mDrawables.mOverride = true; 2233 2234 if (dr.mDrawableStart != start && dr.mDrawableStart != null) { 2235 dr.mDrawableStart.setCallback(null); 2236 } 2237 dr.mDrawableStart = start; 2238 2239 if (dr.mDrawableTop != top && dr.mDrawableTop != null) { 2240 dr.mDrawableTop.setCallback(null); 2241 } 2242 dr.mDrawableTop = top; 2243 2244 if (dr.mDrawableEnd != end && dr.mDrawableEnd != null) { 2245 dr.mDrawableEnd.setCallback(null); 2246 } 2247 dr.mDrawableEnd = end; 2248 2249 if (dr.mDrawableBottom != bottom && dr.mDrawableBottom != null) { 2250 dr.mDrawableBottom.setCallback(null); 2251 } 2252 dr.mDrawableBottom = bottom; 2253 2254 final Rect compoundRect = dr.mCompoundRect; 2255 int[] state; 2256 2257 state = getDrawableState(); 2258 2259 if (start != null) { 2260 start.setState(state); 2261 start.copyBounds(compoundRect); 2262 start.setCallback(this); 2263 dr.mDrawableSizeStart = compoundRect.width(); 2264 dr.mDrawableHeightStart = compoundRect.height(); 2265 } else { 2266 dr.mDrawableSizeStart = dr.mDrawableHeightStart = 0; 2267 } 2268 2269 if (end != null) { 2270 end.setState(state); 2271 end.copyBounds(compoundRect); 2272 end.setCallback(this); 2273 dr.mDrawableSizeEnd = compoundRect.width(); 2274 dr.mDrawableHeightEnd = compoundRect.height(); 2275 } else { 2276 dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; 2277 } 2278 2279 if (top != null) { 2280 top.setState(state); 2281 top.copyBounds(compoundRect); 2282 top.setCallback(this); 2283 dr.mDrawableSizeTop = compoundRect.height(); 2284 dr.mDrawableWidthTop = compoundRect.width(); 2285 } else { 2286 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 2287 } 2288 2289 if (bottom != null) { 2290 bottom.setState(state); 2291 bottom.copyBounds(compoundRect); 2292 bottom.setCallback(this); 2293 dr.mDrawableSizeBottom = compoundRect.height(); 2294 dr.mDrawableWidthBottom = compoundRect.width(); 2295 } else { 2296 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 2297 } 2298 } 2299 2300 resetResolvedDrawables(); 2301 resolveDrawables(); 2302 invalidate(); 2303 requestLayout(); 2304 } 2305 2306 /** 2307 * Sets the Drawables (if any) to appear to the start of, above, to the end 2308 * of, and below the text. Use 0 if you do not want a Drawable there. The 2309 * Drawables' bounds will be set to their intrinsic bounds. 2310 * <p> 2311 * Calling this method will overwrite any Drawables previously set using 2312 * {@link #setCompoundDrawables} or related methods. 2313 * 2314 * @param start Resource identifier of the start Drawable. 2315 * @param top Resource identifier of the top Drawable. 2316 * @param end Resource identifier of the end Drawable. 2317 * @param bottom Resource identifier of the bottom Drawable. 2318 * 2319 * @attr ref android.R.styleable#TextView_drawableStart 2320 * @attr ref android.R.styleable#TextView_drawableTop 2321 * @attr ref android.R.styleable#TextView_drawableEnd 2322 * @attr ref android.R.styleable#TextView_drawableBottom 2323 */ 2324 @android.view.RemotableViewMethod setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, int bottom)2325 public void setCompoundDrawablesRelativeWithIntrinsicBounds(int start, int top, int end, 2326 int bottom) { 2327 final Context context = getContext(); 2328 setCompoundDrawablesRelativeWithIntrinsicBounds( 2329 start != 0 ? context.getDrawable(start) : null, 2330 top != 0 ? context.getDrawable(top) : null, 2331 end != 0 ? context.getDrawable(end) : null, 2332 bottom != 0 ? context.getDrawable(bottom) : null); 2333 } 2334 2335 /** 2336 * Sets the Drawables (if any) to appear to the start of, above, to the end 2337 * of, and below the text. Use {@code null} if you do not want a Drawable 2338 * there. The Drawables' bounds will be set to their intrinsic bounds. 2339 * <p> 2340 * Calling this method will overwrite any Drawables previously set using 2341 * {@link #setCompoundDrawables} or related methods. 2342 * 2343 * @attr ref android.R.styleable#TextView_drawableStart 2344 * @attr ref android.R.styleable#TextView_drawableTop 2345 * @attr ref android.R.styleable#TextView_drawableEnd 2346 * @attr ref android.R.styleable#TextView_drawableBottom 2347 */ setCompoundDrawablesRelativeWithIntrinsicBounds(@ullable Drawable start, @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom)2348 public void setCompoundDrawablesRelativeWithIntrinsicBounds(@Nullable Drawable start, 2349 @Nullable Drawable top, @Nullable Drawable end, @Nullable Drawable bottom) { 2350 2351 if (start != null) { 2352 start.setBounds(0, 0, start.getIntrinsicWidth(), start.getIntrinsicHeight()); 2353 } 2354 if (end != null) { 2355 end.setBounds(0, 0, end.getIntrinsicWidth(), end.getIntrinsicHeight()); 2356 } 2357 if (top != null) { 2358 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 2359 } 2360 if (bottom != null) { 2361 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 2362 } 2363 setCompoundDrawablesRelative(start, top, end, bottom); 2364 } 2365 2366 /** 2367 * Returns drawables for the left, top, right, and bottom borders. 2368 * 2369 * @attr ref android.R.styleable#TextView_drawableLeft 2370 * @attr ref android.R.styleable#TextView_drawableTop 2371 * @attr ref android.R.styleable#TextView_drawableRight 2372 * @attr ref android.R.styleable#TextView_drawableBottom 2373 */ 2374 @NonNull getCompoundDrawables()2375 public Drawable[] getCompoundDrawables() { 2376 final Drawables dr = mDrawables; 2377 if (dr != null) { 2378 return new Drawable[] { 2379 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom 2380 }; 2381 } else { 2382 return new Drawable[] { null, null, null, null }; 2383 } 2384 } 2385 2386 /** 2387 * Returns drawables for the start, top, end, and bottom borders. 2388 * 2389 * @attr ref android.R.styleable#TextView_drawableStart 2390 * @attr ref android.R.styleable#TextView_drawableTop 2391 * @attr ref android.R.styleable#TextView_drawableEnd 2392 * @attr ref android.R.styleable#TextView_drawableBottom 2393 */ 2394 @NonNull getCompoundDrawablesRelative()2395 public Drawable[] getCompoundDrawablesRelative() { 2396 final Drawables dr = mDrawables; 2397 if (dr != null) { 2398 return new Drawable[] { 2399 dr.mDrawableStart, dr.mDrawableTop, dr.mDrawableEnd, dr.mDrawableBottom 2400 }; 2401 } else { 2402 return new Drawable[] { null, null, null, null }; 2403 } 2404 } 2405 2406 /** 2407 * Sets the size of the padding between the compound drawables and 2408 * the text. 2409 * 2410 * @attr ref android.R.styleable#TextView_drawablePadding 2411 */ 2412 @android.view.RemotableViewMethod setCompoundDrawablePadding(int pad)2413 public void setCompoundDrawablePadding(int pad) { 2414 Drawables dr = mDrawables; 2415 if (pad == 0) { 2416 if (dr != null) { 2417 dr.mDrawablePadding = pad; 2418 } 2419 } else { 2420 if (dr == null) { 2421 mDrawables = dr = new Drawables(getContext()); 2422 } 2423 dr.mDrawablePadding = pad; 2424 } 2425 2426 invalidate(); 2427 requestLayout(); 2428 } 2429 2430 /** 2431 * Returns the padding between the compound drawables and the text. 2432 * 2433 * @attr ref android.R.styleable#TextView_drawablePadding 2434 */ getCompoundDrawablePadding()2435 public int getCompoundDrawablePadding() { 2436 final Drawables dr = mDrawables; 2437 return dr != null ? dr.mDrawablePadding : 0; 2438 } 2439 2440 @Override setPadding(int left, int top, int right, int bottom)2441 public void setPadding(int left, int top, int right, int bottom) { 2442 if (left != mPaddingLeft || 2443 right != mPaddingRight || 2444 top != mPaddingTop || 2445 bottom != mPaddingBottom) { 2446 nullLayouts(); 2447 } 2448 2449 // the super call will requestLayout() 2450 super.setPadding(left, top, right, bottom); 2451 invalidate(); 2452 } 2453 2454 @Override setPaddingRelative(int start, int top, int end, int bottom)2455 public void setPaddingRelative(int start, int top, int end, int bottom) { 2456 if (start != getPaddingStart() || 2457 end != getPaddingEnd() || 2458 top != mPaddingTop || 2459 bottom != mPaddingBottom) { 2460 nullLayouts(); 2461 } 2462 2463 // the super call will requestLayout() 2464 super.setPaddingRelative(start, top, end, bottom); 2465 invalidate(); 2466 } 2467 2468 /** 2469 * Gets the autolink mask of the text. See {@link 2470 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 2471 * possible values. 2472 * 2473 * @attr ref android.R.styleable#TextView_autoLink 2474 */ getAutoLinkMask()2475 public final int getAutoLinkMask() { 2476 return mAutoLinkMask; 2477 } 2478 2479 /** 2480 * Sets the text color, size, style, hint color, and highlight color 2481 * from the specified TextAppearance resource. 2482 */ setTextAppearance(Context context, int resid)2483 public void setTextAppearance(Context context, int resid) { 2484 TypedArray appearance = 2485 context.obtainStyledAttributes(resid, 2486 com.android.internal.R.styleable.TextAppearance); 2487 2488 int color; 2489 ColorStateList colors; 2490 int ts; 2491 2492 color = appearance.getColor( 2493 com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0); 2494 if (color != 0) { 2495 setHighlightColor(color); 2496 } 2497 2498 colors = appearance.getColorStateList(com.android.internal.R.styleable. 2499 TextAppearance_textColor); 2500 if (colors != null) { 2501 setTextColor(colors); 2502 } 2503 2504 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. 2505 TextAppearance_textSize, 0); 2506 if (ts != 0) { 2507 setRawTextSize(ts); 2508 } 2509 2510 colors = appearance.getColorStateList(com.android.internal.R.styleable. 2511 TextAppearance_textColorHint); 2512 if (colors != null) { 2513 setHintTextColor(colors); 2514 } 2515 2516 colors = appearance.getColorStateList(com.android.internal.R.styleable. 2517 TextAppearance_textColorLink); 2518 if (colors != null) { 2519 setLinkTextColor(colors); 2520 } 2521 2522 String familyName; 2523 int typefaceIndex, styleIndex; 2524 2525 familyName = appearance.getString(com.android.internal.R.styleable. 2526 TextAppearance_fontFamily); 2527 typefaceIndex = appearance.getInt(com.android.internal.R.styleable. 2528 TextAppearance_typeface, -1); 2529 styleIndex = appearance.getInt(com.android.internal.R.styleable. 2530 TextAppearance_textStyle, -1); 2531 2532 setTypefaceFromAttrs(familyName, typefaceIndex, styleIndex); 2533 2534 final int shadowcolor = appearance.getInt( 2535 com.android.internal.R.styleable.TextAppearance_shadowColor, 0); 2536 if (shadowcolor != 0) { 2537 final float dx = appearance.getFloat( 2538 com.android.internal.R.styleable.TextAppearance_shadowDx, 0); 2539 final float dy = appearance.getFloat( 2540 com.android.internal.R.styleable.TextAppearance_shadowDy, 0); 2541 final float r = appearance.getFloat( 2542 com.android.internal.R.styleable.TextAppearance_shadowRadius, 0); 2543 2544 setShadowLayer(r, dx, dy, shadowcolor); 2545 } 2546 2547 if (appearance.getBoolean(com.android.internal.R.styleable.TextAppearance_textAllCaps, 2548 false)) { 2549 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 2550 } 2551 2552 if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_elegantTextHeight)) { 2553 setElegantTextHeight(appearance.getBoolean( 2554 com.android.internal.R.styleable.TextAppearance_elegantTextHeight, false)); 2555 } 2556 2557 if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_letterSpacing)) { 2558 setLetterSpacing(appearance.getFloat( 2559 com.android.internal.R.styleable.TextAppearance_letterSpacing, 0)); 2560 } 2561 2562 if (appearance.hasValue(com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)) { 2563 setFontFeatureSettings(appearance.getString( 2564 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings)); 2565 } 2566 2567 appearance.recycle(); 2568 } 2569 2570 /** 2571 * Get the default {@link Locale} of the text in this TextView. 2572 * @return the default {@link Locale} of the text in this TextView. 2573 */ getTextLocale()2574 public Locale getTextLocale() { 2575 return mTextPaint.getTextLocale(); 2576 } 2577 2578 /** 2579 * Set the default {@link Locale} of the text in this TextView to the given value. This value 2580 * is used to choose appropriate typefaces for ambiguous characters. Typically used for CJK 2581 * locales to disambiguate Hanzi/Kanji/Hanja characters. 2582 * 2583 * @param locale the {@link Locale} for drawing text, must not be null. 2584 * 2585 * @see Paint#setTextLocale 2586 */ setTextLocale(Locale locale)2587 public void setTextLocale(Locale locale) { 2588 mTextPaint.setTextLocale(locale); 2589 } 2590 2591 /** 2592 * @return the size (in pixels) of the default text size in this TextView. 2593 */ 2594 @ViewDebug.ExportedProperty(category = "text") getTextSize()2595 public float getTextSize() { 2596 return mTextPaint.getTextSize(); 2597 } 2598 2599 /** 2600 * @return the size (in scaled pixels) of thee default text size in this TextView. 2601 * @hide 2602 */ 2603 @ViewDebug.ExportedProperty(category = "text") getScaledTextSize()2604 public float getScaledTextSize() { 2605 return mTextPaint.getTextSize() / mTextPaint.density; 2606 } 2607 2608 /** @hide */ 2609 @ViewDebug.ExportedProperty(category = "text", mapping = { 2610 @ViewDebug.IntToString(from = Typeface.NORMAL, to = "NORMAL"), 2611 @ViewDebug.IntToString(from = Typeface.BOLD, to = "BOLD"), 2612 @ViewDebug.IntToString(from = Typeface.ITALIC, to = "ITALIC"), 2613 @ViewDebug.IntToString(from = Typeface.BOLD_ITALIC, to = "BOLD_ITALIC") 2614 }) getTypefaceStyle()2615 public int getTypefaceStyle() { 2616 return mTextPaint.getTypeface().getStyle(); 2617 } 2618 2619 /** 2620 * Set the default text size to the given value, interpreted as "scaled 2621 * pixel" units. This size is adjusted based on the current density and 2622 * user font size preference. 2623 * 2624 * @param size The scaled pixel size. 2625 * 2626 * @attr ref android.R.styleable#TextView_textSize 2627 */ 2628 @android.view.RemotableViewMethod setTextSize(float size)2629 public void setTextSize(float size) { 2630 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 2631 } 2632 2633 /** 2634 * Set the default text size to a given unit and value. See {@link 2635 * TypedValue} for the possible dimension units. 2636 * 2637 * @param unit The desired dimension unit. 2638 * @param size The desired size in the given units. 2639 * 2640 * @attr ref android.R.styleable#TextView_textSize 2641 */ setTextSize(int unit, float size)2642 public void setTextSize(int unit, float size) { 2643 Context c = getContext(); 2644 Resources r; 2645 2646 if (c == null) 2647 r = Resources.getSystem(); 2648 else 2649 r = c.getResources(); 2650 2651 setRawTextSize(TypedValue.applyDimension( 2652 unit, size, r.getDisplayMetrics())); 2653 } 2654 setRawTextSize(float size)2655 private void setRawTextSize(float size) { 2656 if (size != mTextPaint.getTextSize()) { 2657 mTextPaint.setTextSize(size); 2658 2659 if (mLayout != null) { 2660 nullLayouts(); 2661 requestLayout(); 2662 invalidate(); 2663 } 2664 } 2665 } 2666 2667 /** 2668 * @return the extent by which text is currently being stretched 2669 * horizontally. This will usually be 1. 2670 */ getTextScaleX()2671 public float getTextScaleX() { 2672 return mTextPaint.getTextScaleX(); 2673 } 2674 2675 /** 2676 * Sets the extent by which text should be stretched horizontally. 2677 * 2678 * @attr ref android.R.styleable#TextView_textScaleX 2679 */ 2680 @android.view.RemotableViewMethod setTextScaleX(float size)2681 public void setTextScaleX(float size) { 2682 if (size != mTextPaint.getTextScaleX()) { 2683 mUserSetTextScaleX = true; 2684 mTextPaint.setTextScaleX(size); 2685 2686 if (mLayout != null) { 2687 nullLayouts(); 2688 requestLayout(); 2689 invalidate(); 2690 } 2691 } 2692 } 2693 2694 /** 2695 * Sets the typeface and style in which the text should be displayed. 2696 * Note that not all Typeface families actually have bold and italic 2697 * variants, so you may need to use 2698 * {@link #setTypeface(Typeface, int)} to get the appearance 2699 * that you actually want. 2700 * 2701 * @see #getTypeface() 2702 * 2703 * @attr ref android.R.styleable#TextView_fontFamily 2704 * @attr ref android.R.styleable#TextView_typeface 2705 * @attr ref android.R.styleable#TextView_textStyle 2706 */ setTypeface(Typeface tf)2707 public void setTypeface(Typeface tf) { 2708 if (mTextPaint.getTypeface() != tf) { 2709 mTextPaint.setTypeface(tf); 2710 2711 if (mLayout != null) { 2712 nullLayouts(); 2713 requestLayout(); 2714 invalidate(); 2715 } 2716 } 2717 } 2718 2719 /** 2720 * @return the current typeface and style in which the text is being 2721 * displayed. 2722 * 2723 * @see #setTypeface(Typeface) 2724 * 2725 * @attr ref android.R.styleable#TextView_fontFamily 2726 * @attr ref android.R.styleable#TextView_typeface 2727 * @attr ref android.R.styleable#TextView_textStyle 2728 */ getTypeface()2729 public Typeface getTypeface() { 2730 return mTextPaint.getTypeface(); 2731 } 2732 2733 /** 2734 * Set the TextView's elegant height metrics flag. This setting selects font 2735 * variants that have not been compacted to fit Latin-based vertical 2736 * metrics, and also increases top and bottom bounds to provide more space. 2737 * 2738 * @param elegant set the paint's elegant metrics flag. 2739 * 2740 * @attr ref android.R.styleable#TextView_elegantTextHeight 2741 */ setElegantTextHeight(boolean elegant)2742 public void setElegantTextHeight(boolean elegant) { 2743 mTextPaint.setElegantTextHeight(elegant); 2744 } 2745 2746 /** 2747 * @return the extent by which text is currently being letter-spaced. 2748 * This will normally be 0. 2749 * 2750 * @see #setLetterSpacing(float) 2751 * @see Paint#setLetterSpacing 2752 */ getLetterSpacing()2753 public float getLetterSpacing() { 2754 return mTextPaint.getLetterSpacing(); 2755 } 2756 2757 /** 2758 * Sets text letter-spacing. The value is in 'EM' units. Typical values 2759 * for slight expansion will be around 0.05. Negative values tighten text. 2760 * 2761 * @see #getLetterSpacing() 2762 * @see Paint#getLetterSpacing 2763 * 2764 * @attr ref android.R.styleable#TextView_letterSpacing 2765 */ 2766 @android.view.RemotableViewMethod setLetterSpacing(float letterSpacing)2767 public void setLetterSpacing(float letterSpacing) { 2768 if (letterSpacing != mTextPaint.getLetterSpacing()) { 2769 mTextPaint.setLetterSpacing(letterSpacing); 2770 2771 if (mLayout != null) { 2772 nullLayouts(); 2773 requestLayout(); 2774 invalidate(); 2775 } 2776 } 2777 } 2778 2779 /** 2780 * @return the currently set font feature settings. Default is null. 2781 * 2782 * @see #setFontFeatureSettings(String) 2783 * @see Paint#setFontFeatureSettings 2784 */ 2785 @Nullable getFontFeatureSettings()2786 public String getFontFeatureSettings() { 2787 return mTextPaint.getFontFeatureSettings(); 2788 } 2789 2790 /** 2791 * Sets font feature settings. The format is the same as the CSS 2792 * font-feature-settings attribute: 2793 * http://dev.w3.org/csswg/css-fonts/#propdef-font-feature-settings 2794 * 2795 * @param fontFeatureSettings font feature settings represented as CSS compatible string 2796 * @see #getFontFeatureSettings() 2797 * @see Paint#getFontFeatureSettings 2798 * 2799 * @attr ref android.R.styleable#TextView_fontFeatureSettings 2800 */ 2801 @android.view.RemotableViewMethod setFontFeatureSettings(@ullable String fontFeatureSettings)2802 public void setFontFeatureSettings(@Nullable String fontFeatureSettings) { 2803 if (fontFeatureSettings != mTextPaint.getFontFeatureSettings()) { 2804 mTextPaint.setFontFeatureSettings(fontFeatureSettings); 2805 2806 if (mLayout != null) { 2807 nullLayouts(); 2808 requestLayout(); 2809 invalidate(); 2810 } 2811 } 2812 } 2813 2814 2815 /** 2816 * Sets the text color for all the states (normal, selected, 2817 * focused) to be this color. 2818 * 2819 * @see #setTextColor(ColorStateList) 2820 * @see #getTextColors() 2821 * 2822 * @attr ref android.R.styleable#TextView_textColor 2823 */ 2824 @android.view.RemotableViewMethod setTextColor(int color)2825 public void setTextColor(int color) { 2826 mTextColor = ColorStateList.valueOf(color); 2827 updateTextColors(); 2828 } 2829 2830 /** 2831 * Sets the text color. 2832 * 2833 * @see #setTextColor(int) 2834 * @see #getTextColors() 2835 * @see #setHintTextColor(ColorStateList) 2836 * @see #setLinkTextColor(ColorStateList) 2837 * 2838 * @attr ref android.R.styleable#TextView_textColor 2839 */ setTextColor(ColorStateList colors)2840 public void setTextColor(ColorStateList colors) { 2841 if (colors == null) { 2842 throw new NullPointerException(); 2843 } 2844 2845 mTextColor = colors; 2846 updateTextColors(); 2847 } 2848 2849 /** 2850 * Gets the text colors for the different states (normal, selected, focused) of the TextView. 2851 * 2852 * @see #setTextColor(ColorStateList) 2853 * @see #setTextColor(int) 2854 * 2855 * @attr ref android.R.styleable#TextView_textColor 2856 */ getTextColors()2857 public final ColorStateList getTextColors() { 2858 return mTextColor; 2859 } 2860 2861 /** 2862 * <p>Return the current color selected for normal text.</p> 2863 * 2864 * @return Returns the current text color. 2865 */ getCurrentTextColor()2866 public final int getCurrentTextColor() { 2867 return mCurTextColor; 2868 } 2869 2870 /** 2871 * Sets the color used to display the selection highlight. 2872 * 2873 * @attr ref android.R.styleable#TextView_textColorHighlight 2874 */ 2875 @android.view.RemotableViewMethod setHighlightColor(int color)2876 public void setHighlightColor(int color) { 2877 if (mHighlightColor != color) { 2878 mHighlightColor = color; 2879 invalidate(); 2880 } 2881 } 2882 2883 /** 2884 * @return the color used to display the selection highlight 2885 * 2886 * @see #setHighlightColor(int) 2887 * 2888 * @attr ref android.R.styleable#TextView_textColorHighlight 2889 */ getHighlightColor()2890 public int getHighlightColor() { 2891 return mHighlightColor; 2892 } 2893 2894 /** 2895 * Sets whether the soft input method will be made visible when this 2896 * TextView gets focused. The default is true. 2897 */ 2898 @android.view.RemotableViewMethod setShowSoftInputOnFocus(boolean show)2899 public final void setShowSoftInputOnFocus(boolean show) { 2900 createEditorIfNeeded(); 2901 mEditor.mShowSoftInputOnFocus = show; 2902 } 2903 2904 /** 2905 * Returns whether the soft input method will be made visible when this 2906 * TextView gets focused. The default is true. 2907 */ getShowSoftInputOnFocus()2908 public final boolean getShowSoftInputOnFocus() { 2909 // When there is no Editor, return default true value 2910 return mEditor == null || mEditor.mShowSoftInputOnFocus; 2911 } 2912 2913 /** 2914 * Gives the text a shadow of the specified blur radius and color, the specified 2915 * distance from its drawn position. 2916 * <p> 2917 * The text shadow produced does not interact with the properties on view 2918 * that are responsible for real time shadows, 2919 * {@link View#getElevation() elevation} and 2920 * {@link View#getTranslationZ() translationZ}. 2921 * 2922 * @see Paint#setShadowLayer(float, float, float, int) 2923 * 2924 * @attr ref android.R.styleable#TextView_shadowColor 2925 * @attr ref android.R.styleable#TextView_shadowDx 2926 * @attr ref android.R.styleable#TextView_shadowDy 2927 * @attr ref android.R.styleable#TextView_shadowRadius 2928 */ setShadowLayer(float radius, float dx, float dy, int color)2929 public void setShadowLayer(float radius, float dx, float dy, int color) { 2930 mTextPaint.setShadowLayer(radius, dx, dy, color); 2931 2932 mShadowRadius = radius; 2933 mShadowDx = dx; 2934 mShadowDy = dy; 2935 mShadowColor = color; 2936 2937 // Will change text clip region 2938 if (mEditor != null) mEditor.invalidateTextDisplayList(); 2939 invalidate(); 2940 } 2941 2942 /** 2943 * Gets the radius of the shadow layer. 2944 * 2945 * @return the radius of the shadow layer. If 0, the shadow layer is not visible 2946 * 2947 * @see #setShadowLayer(float, float, float, int) 2948 * 2949 * @attr ref android.R.styleable#TextView_shadowRadius 2950 */ getShadowRadius()2951 public float getShadowRadius() { 2952 return mShadowRadius; 2953 } 2954 2955 /** 2956 * @return the horizontal offset of the shadow layer 2957 * 2958 * @see #setShadowLayer(float, float, float, int) 2959 * 2960 * @attr ref android.R.styleable#TextView_shadowDx 2961 */ getShadowDx()2962 public float getShadowDx() { 2963 return mShadowDx; 2964 } 2965 2966 /** 2967 * @return the vertical offset of the shadow layer 2968 * 2969 * @see #setShadowLayer(float, float, float, int) 2970 * 2971 * @attr ref android.R.styleable#TextView_shadowDy 2972 */ getShadowDy()2973 public float getShadowDy() { 2974 return mShadowDy; 2975 } 2976 2977 /** 2978 * @return the color of the shadow layer 2979 * 2980 * @see #setShadowLayer(float, float, float, int) 2981 * 2982 * @attr ref android.R.styleable#TextView_shadowColor 2983 */ getShadowColor()2984 public int getShadowColor() { 2985 return mShadowColor; 2986 } 2987 2988 /** 2989 * @return the base paint used for the text. Please use this only to 2990 * consult the Paint's properties and not to change them. 2991 */ getPaint()2992 public TextPaint getPaint() { 2993 return mTextPaint; 2994 } 2995 2996 /** 2997 * Sets the autolink mask of the text. See {@link 2998 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 2999 * possible values. 3000 * 3001 * @attr ref android.R.styleable#TextView_autoLink 3002 */ 3003 @android.view.RemotableViewMethod setAutoLinkMask(int mask)3004 public final void setAutoLinkMask(int mask) { 3005 mAutoLinkMask = mask; 3006 } 3007 3008 /** 3009 * Sets whether the movement method will automatically be set to 3010 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 3011 * set to nonzero and links are detected in {@link #setText}. 3012 * The default is true. 3013 * 3014 * @attr ref android.R.styleable#TextView_linksClickable 3015 */ 3016 @android.view.RemotableViewMethod setLinksClickable(boolean whether)3017 public final void setLinksClickable(boolean whether) { 3018 mLinksClickable = whether; 3019 } 3020 3021 /** 3022 * Returns whether the movement method will automatically be set to 3023 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 3024 * set to nonzero and links are detected in {@link #setText}. 3025 * The default is true. 3026 * 3027 * @attr ref android.R.styleable#TextView_linksClickable 3028 */ getLinksClickable()3029 public final boolean getLinksClickable() { 3030 return mLinksClickable; 3031 } 3032 3033 /** 3034 * Returns the list of URLSpans attached to the text 3035 * (by {@link Linkify} or otherwise) if any. You can call 3036 * {@link URLSpan#getURL} on them to find where they link to 3037 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 3038 * to find the region of the text they are attached to. 3039 */ getUrls()3040 public URLSpan[] getUrls() { 3041 if (mText instanceof Spanned) { 3042 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 3043 } else { 3044 return new URLSpan[0]; 3045 } 3046 } 3047 3048 /** 3049 * Sets the color of the hint text for all the states (disabled, focussed, selected...) of this 3050 * TextView. 3051 * 3052 * @see #setHintTextColor(ColorStateList) 3053 * @see #getHintTextColors() 3054 * @see #setTextColor(int) 3055 * 3056 * @attr ref android.R.styleable#TextView_textColorHint 3057 */ 3058 @android.view.RemotableViewMethod setHintTextColor(int color)3059 public final void setHintTextColor(int color) { 3060 mHintTextColor = ColorStateList.valueOf(color); 3061 updateTextColors(); 3062 } 3063 3064 /** 3065 * Sets the color of the hint text. 3066 * 3067 * @see #getHintTextColors() 3068 * @see #setHintTextColor(int) 3069 * @see #setTextColor(ColorStateList) 3070 * @see #setLinkTextColor(ColorStateList) 3071 * 3072 * @attr ref android.R.styleable#TextView_textColorHint 3073 */ setHintTextColor(ColorStateList colors)3074 public final void setHintTextColor(ColorStateList colors) { 3075 mHintTextColor = colors; 3076 updateTextColors(); 3077 } 3078 3079 /** 3080 * @return the color of the hint text, for the different states of this TextView. 3081 * 3082 * @see #setHintTextColor(ColorStateList) 3083 * @see #setHintTextColor(int) 3084 * @see #setTextColor(ColorStateList) 3085 * @see #setLinkTextColor(ColorStateList) 3086 * 3087 * @attr ref android.R.styleable#TextView_textColorHint 3088 */ getHintTextColors()3089 public final ColorStateList getHintTextColors() { 3090 return mHintTextColor; 3091 } 3092 3093 /** 3094 * <p>Return the current color selected to paint the hint text.</p> 3095 * 3096 * @return Returns the current hint text color. 3097 */ getCurrentHintTextColor()3098 public final int getCurrentHintTextColor() { 3099 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 3100 } 3101 3102 /** 3103 * Sets the color of links in the text. 3104 * 3105 * @see #setLinkTextColor(ColorStateList) 3106 * @see #getLinkTextColors() 3107 * 3108 * @attr ref android.R.styleable#TextView_textColorLink 3109 */ 3110 @android.view.RemotableViewMethod setLinkTextColor(int color)3111 public final void setLinkTextColor(int color) { 3112 mLinkTextColor = ColorStateList.valueOf(color); 3113 updateTextColors(); 3114 } 3115 3116 /** 3117 * Sets the color of links in the text. 3118 * 3119 * @see #setLinkTextColor(int) 3120 * @see #getLinkTextColors() 3121 * @see #setTextColor(ColorStateList) 3122 * @see #setHintTextColor(ColorStateList) 3123 * 3124 * @attr ref android.R.styleable#TextView_textColorLink 3125 */ setLinkTextColor(ColorStateList colors)3126 public final void setLinkTextColor(ColorStateList colors) { 3127 mLinkTextColor = colors; 3128 updateTextColors(); 3129 } 3130 3131 /** 3132 * @return the list of colors used to paint the links in the text, for the different states of 3133 * this TextView 3134 * 3135 * @see #setLinkTextColor(ColorStateList) 3136 * @see #setLinkTextColor(int) 3137 * 3138 * @attr ref android.R.styleable#TextView_textColorLink 3139 */ getLinkTextColors()3140 public final ColorStateList getLinkTextColors() { 3141 return mLinkTextColor; 3142 } 3143 3144 /** 3145 * Sets the horizontal alignment of the text and the 3146 * vertical gravity that will be used when there is extra space 3147 * in the TextView beyond what is required for the text itself. 3148 * 3149 * @see android.view.Gravity 3150 * @attr ref android.R.styleable#TextView_gravity 3151 */ setGravity(int gravity)3152 public void setGravity(int gravity) { 3153 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 3154 gravity |= Gravity.START; 3155 } 3156 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 3157 gravity |= Gravity.TOP; 3158 } 3159 3160 boolean newLayout = false; 3161 3162 if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) != 3163 (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK)) { 3164 newLayout = true; 3165 } 3166 3167 if (gravity != mGravity) { 3168 invalidate(); 3169 } 3170 3171 mGravity = gravity; 3172 3173 if (mLayout != null && newLayout) { 3174 // XXX this is heavy-handed because no actual content changes. 3175 int want = mLayout.getWidth(); 3176 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 3177 3178 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 3179 mRight - mLeft - getCompoundPaddingLeft() - 3180 getCompoundPaddingRight(), true); 3181 } 3182 } 3183 3184 /** 3185 * Returns the horizontal and vertical alignment of this TextView. 3186 * 3187 * @see android.view.Gravity 3188 * @attr ref android.R.styleable#TextView_gravity 3189 */ getGravity()3190 public int getGravity() { 3191 return mGravity; 3192 } 3193 3194 /** 3195 * @return the flags on the Paint being used to display the text. 3196 * @see Paint#getFlags 3197 */ getPaintFlags()3198 public int getPaintFlags() { 3199 return mTextPaint.getFlags(); 3200 } 3201 3202 /** 3203 * Sets flags on the Paint being used to display the text and 3204 * reflows the text if they are different from the old flags. 3205 * @see Paint#setFlags 3206 */ 3207 @android.view.RemotableViewMethod setPaintFlags(int flags)3208 public void setPaintFlags(int flags) { 3209 if (mTextPaint.getFlags() != flags) { 3210 mTextPaint.setFlags(flags); 3211 3212 if (mLayout != null) { 3213 nullLayouts(); 3214 requestLayout(); 3215 invalidate(); 3216 } 3217 } 3218 } 3219 3220 /** 3221 * Sets whether the text should be allowed to be wider than the 3222 * View is. If false, it will be wrapped to the width of the View. 3223 * 3224 * @attr ref android.R.styleable#TextView_scrollHorizontally 3225 */ setHorizontallyScrolling(boolean whether)3226 public void setHorizontallyScrolling(boolean whether) { 3227 if (mHorizontallyScrolling != whether) { 3228 mHorizontallyScrolling = whether; 3229 3230 if (mLayout != null) { 3231 nullLayouts(); 3232 requestLayout(); 3233 invalidate(); 3234 } 3235 } 3236 } 3237 3238 /** 3239 * Returns whether the text is allowed to be wider than the View is. 3240 * If false, the text will be wrapped to the width of the View. 3241 * 3242 * @attr ref android.R.styleable#TextView_scrollHorizontally 3243 * @hide 3244 */ getHorizontallyScrolling()3245 public boolean getHorizontallyScrolling() { 3246 return mHorizontallyScrolling; 3247 } 3248 3249 /** 3250 * Makes the TextView at least this many lines tall. 3251 * 3252 * Setting this value overrides any other (minimum) height setting. A single line TextView will 3253 * set this value to 1. 3254 * 3255 * @see #getMinLines() 3256 * 3257 * @attr ref android.R.styleable#TextView_minLines 3258 */ 3259 @android.view.RemotableViewMethod setMinLines(int minlines)3260 public void setMinLines(int minlines) { 3261 mMinimum = minlines; 3262 mMinMode = LINES; 3263 3264 requestLayout(); 3265 invalidate(); 3266 } 3267 3268 /** 3269 * @return the minimum number of lines displayed in this TextView, or -1 if the minimum 3270 * height was set in pixels instead using {@link #setMinHeight(int) or #setHeight(int)}. 3271 * 3272 * @see #setMinLines(int) 3273 * 3274 * @attr ref android.R.styleable#TextView_minLines 3275 */ getMinLines()3276 public int getMinLines() { 3277 return mMinMode == LINES ? mMinimum : -1; 3278 } 3279 3280 /** 3281 * Makes the TextView at least this many pixels tall. 3282 * 3283 * Setting this value overrides any other (minimum) number of lines setting. 3284 * 3285 * @attr ref android.R.styleable#TextView_minHeight 3286 */ 3287 @android.view.RemotableViewMethod setMinHeight(int minHeight)3288 public void setMinHeight(int minHeight) { 3289 mMinimum = minHeight; 3290 mMinMode = PIXELS; 3291 3292 requestLayout(); 3293 invalidate(); 3294 } 3295 3296 /** 3297 * @return the minimum height of this TextView expressed in pixels, or -1 if the minimum 3298 * height was set in number of lines instead using {@link #setMinLines(int) or #setLines(int)}. 3299 * 3300 * @see #setMinHeight(int) 3301 * 3302 * @attr ref android.R.styleable#TextView_minHeight 3303 */ getMinHeight()3304 public int getMinHeight() { 3305 return mMinMode == PIXELS ? mMinimum : -1; 3306 } 3307 3308 /** 3309 * Makes the TextView at most this many lines tall. 3310 * 3311 * Setting this value overrides any other (maximum) height setting. 3312 * 3313 * @attr ref android.R.styleable#TextView_maxLines 3314 */ 3315 @android.view.RemotableViewMethod setMaxLines(int maxlines)3316 public void setMaxLines(int maxlines) { 3317 mMaximum = maxlines; 3318 mMaxMode = LINES; 3319 3320 requestLayout(); 3321 invalidate(); 3322 } 3323 3324 /** 3325 * @return the maximum number of lines displayed in this TextView, or -1 if the maximum 3326 * height was set in pixels instead using {@link #setMaxHeight(int) or #setHeight(int)}. 3327 * 3328 * @see #setMaxLines(int) 3329 * 3330 * @attr ref android.R.styleable#TextView_maxLines 3331 */ getMaxLines()3332 public int getMaxLines() { 3333 return mMaxMode == LINES ? mMaximum : -1; 3334 } 3335 3336 /** 3337 * Makes the TextView at most this many pixels tall. This option is mutually exclusive with the 3338 * {@link #setMaxLines(int)} method. 3339 * 3340 * Setting this value overrides any other (maximum) number of lines setting. 3341 * 3342 * @attr ref android.R.styleable#TextView_maxHeight 3343 */ 3344 @android.view.RemotableViewMethod setMaxHeight(int maxHeight)3345 public void setMaxHeight(int maxHeight) { 3346 mMaximum = maxHeight; 3347 mMaxMode = PIXELS; 3348 3349 requestLayout(); 3350 invalidate(); 3351 } 3352 3353 /** 3354 * @return the maximum height of this TextView expressed in pixels, or -1 if the maximum 3355 * height was set in number of lines instead using {@link #setMaxLines(int) or #setLines(int)}. 3356 * 3357 * @see #setMaxHeight(int) 3358 * 3359 * @attr ref android.R.styleable#TextView_maxHeight 3360 */ getMaxHeight()3361 public int getMaxHeight() { 3362 return mMaxMode == PIXELS ? mMaximum : -1; 3363 } 3364 3365 /** 3366 * Makes the TextView exactly this many lines tall. 3367 * 3368 * Note that setting this value overrides any other (minimum / maximum) number of lines or 3369 * height setting. A single line TextView will set this value to 1. 3370 * 3371 * @attr ref android.R.styleable#TextView_lines 3372 */ 3373 @android.view.RemotableViewMethod setLines(int lines)3374 public void setLines(int lines) { 3375 mMaximum = mMinimum = lines; 3376 mMaxMode = mMinMode = LINES; 3377 3378 requestLayout(); 3379 invalidate(); 3380 } 3381 3382 /** 3383 * Makes the TextView exactly this many pixels tall. 3384 * You could do the same thing by specifying this number in the 3385 * LayoutParams. 3386 * 3387 * Note that setting this value overrides any other (minimum / maximum) number of lines or 3388 * height setting. 3389 * 3390 * @attr ref android.R.styleable#TextView_height 3391 */ 3392 @android.view.RemotableViewMethod setHeight(int pixels)3393 public void setHeight(int pixels) { 3394 mMaximum = mMinimum = pixels; 3395 mMaxMode = mMinMode = PIXELS; 3396 3397 requestLayout(); 3398 invalidate(); 3399 } 3400 3401 /** 3402 * Makes the TextView at least this many ems wide 3403 * 3404 * @attr ref android.R.styleable#TextView_minEms 3405 */ 3406 @android.view.RemotableViewMethod setMinEms(int minems)3407 public void setMinEms(int minems) { 3408 mMinWidth = minems; 3409 mMinWidthMode = EMS; 3410 3411 requestLayout(); 3412 invalidate(); 3413 } 3414 3415 /** 3416 * @return the minimum width of the TextView, expressed in ems or -1 if the minimum width 3417 * was set in pixels instead (using {@link #setMinWidth(int)} or {@link #setWidth(int)}). 3418 * 3419 * @see #setMinEms(int) 3420 * @see #setEms(int) 3421 * 3422 * @attr ref android.R.styleable#TextView_minEms 3423 */ getMinEms()3424 public int getMinEms() { 3425 return mMinWidthMode == EMS ? mMinWidth : -1; 3426 } 3427 3428 /** 3429 * Makes the TextView at least this many pixels wide 3430 * 3431 * @attr ref android.R.styleable#TextView_minWidth 3432 */ 3433 @android.view.RemotableViewMethod setMinWidth(int minpixels)3434 public void setMinWidth(int minpixels) { 3435 mMinWidth = minpixels; 3436 mMinWidthMode = PIXELS; 3437 3438 requestLayout(); 3439 invalidate(); 3440 } 3441 3442 /** 3443 * @return the minimum width of the TextView, in pixels or -1 if the minimum width 3444 * was set in ems instead (using {@link #setMinEms(int)} or {@link #setEms(int)}). 3445 * 3446 * @see #setMinWidth(int) 3447 * @see #setWidth(int) 3448 * 3449 * @attr ref android.R.styleable#TextView_minWidth 3450 */ getMinWidth()3451 public int getMinWidth() { 3452 return mMinWidthMode == PIXELS ? mMinWidth : -1; 3453 } 3454 3455 /** 3456 * Makes the TextView at most this many ems wide 3457 * 3458 * @attr ref android.R.styleable#TextView_maxEms 3459 */ 3460 @android.view.RemotableViewMethod setMaxEms(int maxems)3461 public void setMaxEms(int maxems) { 3462 mMaxWidth = maxems; 3463 mMaxWidthMode = EMS; 3464 3465 requestLayout(); 3466 invalidate(); 3467 } 3468 3469 /** 3470 * @return the maximum width of the TextView, expressed in ems or -1 if the maximum width 3471 * was set in pixels instead (using {@link #setMaxWidth(int)} or {@link #setWidth(int)}). 3472 * 3473 * @see #setMaxEms(int) 3474 * @see #setEms(int) 3475 * 3476 * @attr ref android.R.styleable#TextView_maxEms 3477 */ getMaxEms()3478 public int getMaxEms() { 3479 return mMaxWidthMode == EMS ? mMaxWidth : -1; 3480 } 3481 3482 /** 3483 * Makes the TextView at most this many pixels wide 3484 * 3485 * @attr ref android.R.styleable#TextView_maxWidth 3486 */ 3487 @android.view.RemotableViewMethod setMaxWidth(int maxpixels)3488 public void setMaxWidth(int maxpixels) { 3489 mMaxWidth = maxpixels; 3490 mMaxWidthMode = PIXELS; 3491 3492 requestLayout(); 3493 invalidate(); 3494 } 3495 3496 /** 3497 * @return the maximum width of the TextView, in pixels or -1 if the maximum width 3498 * was set in ems instead (using {@link #setMaxEms(int)} or {@link #setEms(int)}). 3499 * 3500 * @see #setMaxWidth(int) 3501 * @see #setWidth(int) 3502 * 3503 * @attr ref android.R.styleable#TextView_maxWidth 3504 */ getMaxWidth()3505 public int getMaxWidth() { 3506 return mMaxWidthMode == PIXELS ? mMaxWidth : -1; 3507 } 3508 3509 /** 3510 * Makes the TextView exactly this many ems wide 3511 * 3512 * @see #setMaxEms(int) 3513 * @see #setMinEms(int) 3514 * @see #getMinEms() 3515 * @see #getMaxEms() 3516 * 3517 * @attr ref android.R.styleable#TextView_ems 3518 */ 3519 @android.view.RemotableViewMethod setEms(int ems)3520 public void setEms(int ems) { 3521 mMaxWidth = mMinWidth = ems; 3522 mMaxWidthMode = mMinWidthMode = EMS; 3523 3524 requestLayout(); 3525 invalidate(); 3526 } 3527 3528 /** 3529 * Makes the TextView exactly this many pixels wide. 3530 * You could do the same thing by specifying this number in the 3531 * LayoutParams. 3532 * 3533 * @see #setMaxWidth(int) 3534 * @see #setMinWidth(int) 3535 * @see #getMinWidth() 3536 * @see #getMaxWidth() 3537 * 3538 * @attr ref android.R.styleable#TextView_width 3539 */ 3540 @android.view.RemotableViewMethod setWidth(int pixels)3541 public void setWidth(int pixels) { 3542 mMaxWidth = mMinWidth = pixels; 3543 mMaxWidthMode = mMinWidthMode = PIXELS; 3544 3545 requestLayout(); 3546 invalidate(); 3547 } 3548 3549 /** 3550 * Sets line spacing for this TextView. Each line will have its height 3551 * multiplied by <code>mult</code> and have <code>add</code> added to it. 3552 * 3553 * @attr ref android.R.styleable#TextView_lineSpacingExtra 3554 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 3555 */ setLineSpacing(float add, float mult)3556 public void setLineSpacing(float add, float mult) { 3557 if (mSpacingAdd != add || mSpacingMult != mult) { 3558 mSpacingAdd = add; 3559 mSpacingMult = mult; 3560 3561 if (mLayout != null) { 3562 nullLayouts(); 3563 requestLayout(); 3564 invalidate(); 3565 } 3566 } 3567 } 3568 3569 /** 3570 * Gets the line spacing multiplier 3571 * 3572 * @return the value by which each line's height is multiplied to get its actual height. 3573 * 3574 * @see #setLineSpacing(float, float) 3575 * @see #getLineSpacingExtra() 3576 * 3577 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 3578 */ getLineSpacingMultiplier()3579 public float getLineSpacingMultiplier() { 3580 return mSpacingMult; 3581 } 3582 3583 /** 3584 * Gets the line spacing extra space 3585 * 3586 * @return the extra space that is added to the height of each lines of this TextView. 3587 * 3588 * @see #setLineSpacing(float, float) 3589 * @see #getLineSpacingMultiplier() 3590 * 3591 * @attr ref android.R.styleable#TextView_lineSpacingExtra 3592 */ getLineSpacingExtra()3593 public float getLineSpacingExtra() { 3594 return mSpacingAdd; 3595 } 3596 3597 /** 3598 * Convenience method: Append the specified text to the TextView's 3599 * display buffer, upgrading it to BufferType.EDITABLE if it was 3600 * not already editable. 3601 */ append(CharSequence text)3602 public final void append(CharSequence text) { 3603 append(text, 0, text.length()); 3604 } 3605 3606 /** 3607 * Convenience method: Append the specified text slice to the TextView's 3608 * display buffer, upgrading it to BufferType.EDITABLE if it was 3609 * not already editable. 3610 */ append(CharSequence text, int start, int end)3611 public void append(CharSequence text, int start, int end) { 3612 if (!(mText instanceof Editable)) { 3613 setText(mText, BufferType.EDITABLE); 3614 } 3615 3616 ((Editable) mText).append(text, start, end); 3617 } 3618 updateTextColors()3619 private void updateTextColors() { 3620 boolean inval = false; 3621 int color = mTextColor.getColorForState(getDrawableState(), 0); 3622 if (color != mCurTextColor) { 3623 mCurTextColor = color; 3624 inval = true; 3625 } 3626 if (mLinkTextColor != null) { 3627 color = mLinkTextColor.getColorForState(getDrawableState(), 0); 3628 if (color != mTextPaint.linkColor) { 3629 mTextPaint.linkColor = color; 3630 inval = true; 3631 } 3632 } 3633 if (mHintTextColor != null) { 3634 color = mHintTextColor.getColorForState(getDrawableState(), 0); 3635 if (color != mCurHintTextColor && mText.length() == 0) { 3636 mCurHintTextColor = color; 3637 inval = true; 3638 } 3639 } 3640 if (inval) { 3641 // Text needs to be redrawn with the new color 3642 if (mEditor != null) mEditor.invalidateTextDisplayList(); 3643 invalidate(); 3644 } 3645 } 3646 3647 @Override drawableStateChanged()3648 protected void drawableStateChanged() { 3649 super.drawableStateChanged(); 3650 if (mTextColor != null && mTextColor.isStateful() 3651 || (mHintTextColor != null && mHintTextColor.isStateful()) 3652 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 3653 updateTextColors(); 3654 } 3655 3656 final Drawables dr = mDrawables; 3657 if (dr != null) { 3658 int[] state = getDrawableState(); 3659 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { 3660 dr.mDrawableTop.setState(state); 3661 } 3662 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { 3663 dr.mDrawableBottom.setState(state); 3664 } 3665 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { 3666 dr.mDrawableLeft.setState(state); 3667 } 3668 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { 3669 dr.mDrawableRight.setState(state); 3670 } 3671 if (dr.mDrawableStart != null && dr.mDrawableStart.isStateful()) { 3672 dr.mDrawableStart.setState(state); 3673 } 3674 if (dr.mDrawableEnd != null && dr.mDrawableEnd.isStateful()) { 3675 dr.mDrawableEnd.setState(state); 3676 } 3677 } 3678 } 3679 3680 @Override drawableHotspotChanged(float x, float y)3681 public void drawableHotspotChanged(float x, float y) { 3682 super.drawableHotspotChanged(x, y); 3683 3684 final Drawables dr = mDrawables; 3685 if (dr != null) { 3686 if (dr.mDrawableTop != null) { 3687 dr.mDrawableTop.setHotspot(x, y); 3688 } 3689 if (dr.mDrawableBottom != null) { 3690 dr.mDrawableBottom.setHotspot(x, y); 3691 } 3692 if (dr.mDrawableLeft != null) { 3693 dr.mDrawableLeft.setHotspot(x, y); 3694 } 3695 if (dr.mDrawableRight != null) { 3696 dr.mDrawableRight.setHotspot(x, y); 3697 } 3698 if (dr.mDrawableStart != null) { 3699 dr.mDrawableStart.setHotspot(x, y); 3700 } 3701 if (dr.mDrawableEnd != null) { 3702 dr.mDrawableEnd.setHotspot(x, y); 3703 } 3704 } 3705 } 3706 3707 @Override onSaveInstanceState()3708 public Parcelable onSaveInstanceState() { 3709 Parcelable superState = super.onSaveInstanceState(); 3710 3711 // Save state if we are forced to 3712 boolean save = mFreezesText; 3713 int start = 0; 3714 int end = 0; 3715 3716 if (mText != null) { 3717 start = getSelectionStart(); 3718 end = getSelectionEnd(); 3719 if (start >= 0 || end >= 0) { 3720 // Or save state if there is a selection 3721 save = true; 3722 } 3723 } 3724 3725 if (save) { 3726 SavedState ss = new SavedState(superState); 3727 // XXX Should also save the current scroll position! 3728 ss.selStart = start; 3729 ss.selEnd = end; 3730 3731 if (mText instanceof Spanned) { 3732 Spannable sp = new SpannableStringBuilder(mText); 3733 3734 if (mEditor != null) { 3735 removeMisspelledSpans(sp); 3736 sp.removeSpan(mEditor.mSuggestionRangeSpan); 3737 } 3738 3739 ss.text = sp; 3740 } else { 3741 ss.text = mText.toString(); 3742 } 3743 3744 if (isFocused() && start >= 0 && end >= 0) { 3745 ss.frozenWithFocus = true; 3746 } 3747 3748 ss.error = getError(); 3749 3750 return ss; 3751 } 3752 3753 return superState; 3754 } 3755 removeMisspelledSpans(Spannable spannable)3756 void removeMisspelledSpans(Spannable spannable) { 3757 SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), 3758 SuggestionSpan.class); 3759 for (int i = 0; i < suggestionSpans.length; i++) { 3760 int flags = suggestionSpans[i].getFlags(); 3761 if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 3762 && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { 3763 spannable.removeSpan(suggestionSpans[i]); 3764 } 3765 } 3766 } 3767 3768 @Override onRestoreInstanceState(Parcelable state)3769 public void onRestoreInstanceState(Parcelable state) { 3770 if (!(state instanceof SavedState)) { 3771 super.onRestoreInstanceState(state); 3772 return; 3773 } 3774 3775 SavedState ss = (SavedState)state; 3776 super.onRestoreInstanceState(ss.getSuperState()); 3777 3778 // XXX restore buffer type too, as well as lots of other stuff 3779 if (ss.text != null) { 3780 setText(ss.text); 3781 } 3782 3783 if (ss.selStart >= 0 && ss.selEnd >= 0) { 3784 if (mText instanceof Spannable) { 3785 int len = mText.length(); 3786 3787 if (ss.selStart > len || ss.selEnd > len) { 3788 String restored = ""; 3789 3790 if (ss.text != null) { 3791 restored = "(restored) "; 3792 } 3793 3794 Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + 3795 "/" + ss.selEnd + " out of range for " + restored + 3796 "text " + mText); 3797 } else { 3798 Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd); 3799 3800 if (ss.frozenWithFocus) { 3801 createEditorIfNeeded(); 3802 mEditor.mFrozenWithFocus = true; 3803 } 3804 } 3805 } 3806 } 3807 3808 if (ss.error != null) { 3809 final CharSequence error = ss.error; 3810 // Display the error later, after the first layout pass 3811 post(new Runnable() { 3812 public void run() { 3813 setError(error); 3814 } 3815 }); 3816 } 3817 } 3818 3819 /** 3820 * Control whether this text view saves its entire text contents when 3821 * freezing to an icicle, in addition to dynamic state such as cursor 3822 * position. By default this is false, not saving the text. Set to true 3823 * if the text in the text view is not being saved somewhere else in 3824 * persistent storage (such as in a content provider) so that if the 3825 * view is later thawed the user will not lose their data. 3826 * 3827 * @param freezesText Controls whether a frozen icicle should include the 3828 * entire text data: true to include it, false to not. 3829 * 3830 * @attr ref android.R.styleable#TextView_freezesText 3831 */ 3832 @android.view.RemotableViewMethod setFreezesText(boolean freezesText)3833 public void setFreezesText(boolean freezesText) { 3834 mFreezesText = freezesText; 3835 } 3836 3837 /** 3838 * Return whether this text view is including its entire text contents 3839 * in frozen icicles. 3840 * 3841 * @return Returns true if text is included, false if it isn't. 3842 * 3843 * @see #setFreezesText 3844 */ getFreezesText()3845 public boolean getFreezesText() { 3846 return mFreezesText; 3847 } 3848 3849 /////////////////////////////////////////////////////////////////////////// 3850 3851 /** 3852 * Sets the Factory used to create new Editables. 3853 */ setEditableFactory(Editable.Factory factory)3854 public final void setEditableFactory(Editable.Factory factory) { 3855 mEditableFactory = factory; 3856 setText(mText); 3857 } 3858 3859 /** 3860 * Sets the Factory used to create new Spannables. 3861 */ setSpannableFactory(Spannable.Factory factory)3862 public final void setSpannableFactory(Spannable.Factory factory) { 3863 mSpannableFactory = factory; 3864 setText(mText); 3865 } 3866 3867 /** 3868 * Sets the string value of the TextView. TextView <em>does not</em> accept 3869 * HTML-like formatting, which you can do with text strings in XML resource files. 3870 * To style your strings, attach android.text.style.* objects to a 3871 * {@link android.text.SpannableString SpannableString}, or see the 3872 * <a href="{@docRoot}guide/topics/resources/available-resources.html#stringresources"> 3873 * Available Resource Types</a> documentation for an example of setting 3874 * formatted text in the XML resource file. 3875 * 3876 * @attr ref android.R.styleable#TextView_text 3877 */ 3878 @android.view.RemotableViewMethod setText(CharSequence text)3879 public final void setText(CharSequence text) { 3880 setText(text, mBufferType); 3881 } 3882 3883 /** 3884 * Like {@link #setText(CharSequence)}, 3885 * except that the cursor position (if any) is retained in the new text. 3886 * 3887 * @param text The new text to place in the text view. 3888 * 3889 * @see #setText(CharSequence) 3890 */ 3891 @android.view.RemotableViewMethod setTextKeepState(CharSequence text)3892 public final void setTextKeepState(CharSequence text) { 3893 setTextKeepState(text, mBufferType); 3894 } 3895 3896 /** 3897 * Sets the text that this TextView is to display (see 3898 * {@link #setText(CharSequence)}) and also sets whether it is stored 3899 * in a styleable/spannable buffer and whether it is editable. 3900 * 3901 * @attr ref android.R.styleable#TextView_text 3902 * @attr ref android.R.styleable#TextView_bufferType 3903 */ setText(CharSequence text, BufferType type)3904 public void setText(CharSequence text, BufferType type) { 3905 setText(text, type, true, 0); 3906 3907 if (mCharWrapper != null) { 3908 mCharWrapper.mChars = null; 3909 } 3910 } 3911 setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen)3912 private void setText(CharSequence text, BufferType type, 3913 boolean notifyBefore, int oldlen) { 3914 if (text == null) { 3915 text = ""; 3916 } 3917 3918 // If suggestions are not enabled, remove the suggestion spans from the text 3919 if (!isSuggestionsEnabled()) { 3920 text = removeSuggestionSpans(text); 3921 } 3922 3923 if (!mUserSetTextScaleX) mTextPaint.setTextScaleX(1.0f); 3924 3925 if (text instanceof Spanned && 3926 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 3927 if (ViewConfiguration.get(mContext).isFadingMarqueeEnabled()) { 3928 setHorizontalFadingEdgeEnabled(true); 3929 mMarqueeFadeMode = MARQUEE_FADE_NORMAL; 3930 } else { 3931 setHorizontalFadingEdgeEnabled(false); 3932 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 3933 } 3934 setEllipsize(TextUtils.TruncateAt.MARQUEE); 3935 } 3936 3937 int n = mFilters.length; 3938 for (int i = 0; i < n; i++) { 3939 CharSequence out = mFilters[i].filter(text, 0, text.length(), EMPTY_SPANNED, 0, 0); 3940 if (out != null) { 3941 text = out; 3942 } 3943 } 3944 3945 if (notifyBefore) { 3946 if (mText != null) { 3947 oldlen = mText.length(); 3948 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 3949 } else { 3950 sendBeforeTextChanged("", 0, 0, text.length()); 3951 } 3952 } 3953 3954 boolean needEditableForNotification = false; 3955 3956 if (mListeners != null && mListeners.size() != 0) { 3957 needEditableForNotification = true; 3958 } 3959 3960 if (type == BufferType.EDITABLE || getKeyListener() != null || 3961 needEditableForNotification) { 3962 createEditorIfNeeded(); 3963 Editable t = mEditableFactory.newEditable(text); 3964 text = t; 3965 setFilters(t, mFilters); 3966 InputMethodManager imm = InputMethodManager.peekInstance(); 3967 if (imm != null) imm.restartInput(this); 3968 } else if (type == BufferType.SPANNABLE || mMovement != null) { 3969 text = mSpannableFactory.newSpannable(text); 3970 } else if (!(text instanceof CharWrapper)) { 3971 text = TextUtils.stringOrSpannedString(text); 3972 } 3973 3974 if (mAutoLinkMask != 0) { 3975 Spannable s2; 3976 3977 if (type == BufferType.EDITABLE || text instanceof Spannable) { 3978 s2 = (Spannable) text; 3979 } else { 3980 s2 = mSpannableFactory.newSpannable(text); 3981 } 3982 3983 if (Linkify.addLinks(s2, mAutoLinkMask)) { 3984 text = s2; 3985 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 3986 3987 /* 3988 * We must go ahead and set the text before changing the 3989 * movement method, because setMovementMethod() may call 3990 * setText() again to try to upgrade the buffer type. 3991 */ 3992 mText = text; 3993 3994 // Do not change the movement method for text that support text selection as it 3995 // would prevent an arbitrary cursor displacement. 3996 if (mLinksClickable && !textCanBeSelected()) { 3997 setMovementMethod(LinkMovementMethod.getInstance()); 3998 } 3999 } 4000 } 4001 4002 mBufferType = type; 4003 mText = text; 4004 4005 if (mTransformation == null) { 4006 mTransformed = text; 4007 } else { 4008 mTransformed = mTransformation.getTransformation(text, this); 4009 } 4010 4011 final int textLength = text.length(); 4012 4013 if (text instanceof Spannable && !mAllowTransformationLengthChange) { 4014 Spannable sp = (Spannable) text; 4015 4016 // Remove any ChangeWatchers that might have come from other TextViews. 4017 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 4018 final int count = watchers.length; 4019 for (int i = 0; i < count; i++) { 4020 sp.removeSpan(watchers[i]); 4021 } 4022 4023 if (mChangeWatcher == null) mChangeWatcher = new ChangeWatcher(); 4024 4025 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | 4026 (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 4027 4028 if (mEditor != null) mEditor.addSpanWatchers(sp); 4029 4030 if (mTransformation != null) { 4031 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 4032 } 4033 4034 if (mMovement != null) { 4035 mMovement.initialize(this, (Spannable) text); 4036 4037 /* 4038 * Initializing the movement method will have set the 4039 * selection, so reset mSelectionMoved to keep that from 4040 * interfering with the normal on-focus selection-setting. 4041 */ 4042 if (mEditor != null) mEditor.mSelectionMoved = false; 4043 } 4044 } 4045 4046 if (mLayout != null) { 4047 checkForRelayout(); 4048 } 4049 4050 sendOnTextChanged(text, 0, oldlen, textLength); 4051 onTextChanged(text, 0, oldlen, textLength); 4052 4053 notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); 4054 4055 if (needEditableForNotification) { 4056 sendAfterTextChanged((Editable) text); 4057 } 4058 4059 // SelectionModifierCursorController depends on textCanBeSelected, which depends on text 4060 if (mEditor != null) mEditor.prepareCursorControllers(); 4061 } 4062 4063 /** 4064 * Sets the TextView to display the specified slice of the specified 4065 * char array. You must promise that you will not change the contents 4066 * of the array except for right before another call to setText(), 4067 * since the TextView has no way to know that the text 4068 * has changed and that it needs to invalidate and re-layout. 4069 */ setText(char[] text, int start, int len)4070 public final void setText(char[] text, int start, int len) { 4071 int oldlen = 0; 4072 4073 if (start < 0 || len < 0 || start + len > text.length) { 4074 throw new IndexOutOfBoundsException(start + ", " + len); 4075 } 4076 4077 /* 4078 * We must do the before-notification here ourselves because if 4079 * the old text is a CharWrapper we destroy it before calling 4080 * into the normal path. 4081 */ 4082 if (mText != null) { 4083 oldlen = mText.length(); 4084 sendBeforeTextChanged(mText, 0, oldlen, len); 4085 } else { 4086 sendBeforeTextChanged("", 0, 0, len); 4087 } 4088 4089 if (mCharWrapper == null) { 4090 mCharWrapper = new CharWrapper(text, start, len); 4091 } else { 4092 mCharWrapper.set(text, start, len); 4093 } 4094 4095 setText(mCharWrapper, mBufferType, false, oldlen); 4096 } 4097 4098 /** 4099 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, 4100 * except that the cursor position (if any) is retained in the new text. 4101 * 4102 * @see #setText(CharSequence, android.widget.TextView.BufferType) 4103 */ setTextKeepState(CharSequence text, BufferType type)4104 public final void setTextKeepState(CharSequence text, BufferType type) { 4105 int start = getSelectionStart(); 4106 int end = getSelectionEnd(); 4107 int len = text.length(); 4108 4109 setText(text, type); 4110 4111 if (start >= 0 || end >= 0) { 4112 if (mText instanceof Spannable) { 4113 Selection.setSelection((Spannable) mText, 4114 Math.max(0, Math.min(start, len)), 4115 Math.max(0, Math.min(end, len))); 4116 } 4117 } 4118 } 4119 4120 @android.view.RemotableViewMethod setText(int resid)4121 public final void setText(int resid) { 4122 setText(getContext().getResources().getText(resid)); 4123 } 4124 setText(int resid, BufferType type)4125 public final void setText(int resid, BufferType type) { 4126 setText(getContext().getResources().getText(resid), type); 4127 } 4128 4129 /** 4130 * Sets the text to be displayed when the text of the TextView is empty. 4131 * Null means to use the normal empty text. The hint does not currently 4132 * participate in determining the size of the view. 4133 * 4134 * @attr ref android.R.styleable#TextView_hint 4135 */ 4136 @android.view.RemotableViewMethod setHint(CharSequence hint)4137 public final void setHint(CharSequence hint) { 4138 mHint = TextUtils.stringOrSpannedString(hint); 4139 4140 if (mLayout != null) { 4141 checkForRelayout(); 4142 } 4143 4144 if (mText.length() == 0) { 4145 invalidate(); 4146 } 4147 4148 // Invalidate display list if hint is currently used 4149 if (mEditor != null && mText.length() == 0 && mHint != null) { 4150 mEditor.invalidateTextDisplayList(); 4151 } 4152 } 4153 4154 /** 4155 * Sets the text to be displayed when the text of the TextView is empty, 4156 * from a resource. 4157 * 4158 * @attr ref android.R.styleable#TextView_hint 4159 */ 4160 @android.view.RemotableViewMethod setHint(int resid)4161 public final void setHint(int resid) { 4162 setHint(getContext().getResources().getText(resid)); 4163 } 4164 4165 /** 4166 * Returns the hint that is displayed when the text of the TextView 4167 * is empty. 4168 * 4169 * @attr ref android.R.styleable#TextView_hint 4170 */ 4171 @ViewDebug.CapturedViewProperty getHint()4172 public CharSequence getHint() { 4173 return mHint; 4174 } 4175 isSingleLine()4176 boolean isSingleLine() { 4177 return mSingleLine; 4178 } 4179 isMultilineInputType(int type)4180 private static boolean isMultilineInputType(int type) { 4181 return (type & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == 4182 (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 4183 } 4184 4185 /** 4186 * Removes the suggestion spans. 4187 */ removeSuggestionSpans(CharSequence text)4188 CharSequence removeSuggestionSpans(CharSequence text) { 4189 if (text instanceof Spanned) { 4190 Spannable spannable; 4191 if (text instanceof Spannable) { 4192 spannable = (Spannable) text; 4193 } else { 4194 spannable = new SpannableString(text); 4195 text = spannable; 4196 } 4197 4198 SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class); 4199 for (int i = 0; i < spans.length; i++) { 4200 spannable.removeSpan(spans[i]); 4201 } 4202 } 4203 return text; 4204 } 4205 4206 /** 4207 * Set the type of the content with a constant as defined for {@link EditorInfo#inputType}. This 4208 * will take care of changing the key listener, by calling {@link #setKeyListener(KeyListener)}, 4209 * to match the given content type. If the given content type is {@link EditorInfo#TYPE_NULL} 4210 * then a soft keyboard will not be displayed for this text view. 4211 * 4212 * Note that the maximum number of displayed lines (see {@link #setMaxLines(int)}) will be 4213 * modified if you change the {@link EditorInfo#TYPE_TEXT_FLAG_MULTI_LINE} flag of the input 4214 * type. 4215 * 4216 * @see #getInputType() 4217 * @see #setRawInputType(int) 4218 * @see android.text.InputType 4219 * @attr ref android.R.styleable#TextView_inputType 4220 */ setInputType(int type)4221 public void setInputType(int type) { 4222 final boolean wasPassword = isPasswordInputType(getInputType()); 4223 final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType()); 4224 setInputType(type, false); 4225 final boolean isPassword = isPasswordInputType(type); 4226 final boolean isVisiblePassword = isVisiblePasswordInputType(type); 4227 boolean forceUpdate = false; 4228 if (isPassword) { 4229 setTransformationMethod(PasswordTransformationMethod.getInstance()); 4230 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 4231 } else if (isVisiblePassword) { 4232 if (mTransformation == PasswordTransformationMethod.getInstance()) { 4233 forceUpdate = true; 4234 } 4235 setTypefaceFromAttrs(null /* fontFamily */, MONOSPACE, 0); 4236 } else if (wasPassword || wasVisiblePassword) { 4237 // not in password mode, clean up typeface and transformation 4238 setTypefaceFromAttrs(null /* fontFamily */, -1, -1); 4239 if (mTransformation == PasswordTransformationMethod.getInstance()) { 4240 forceUpdate = true; 4241 } 4242 } 4243 4244 boolean singleLine = !isMultilineInputType(type); 4245 4246 // We need to update the single line mode if it has changed or we 4247 // were previously in password mode. 4248 if (mSingleLine != singleLine || forceUpdate) { 4249 // Change single line mode, but only change the transformation if 4250 // we are not in password mode. 4251 applySingleLine(singleLine, !isPassword, true); 4252 } 4253 4254 if (!isSuggestionsEnabled()) { 4255 mText = removeSuggestionSpans(mText); 4256 } 4257 4258 InputMethodManager imm = InputMethodManager.peekInstance(); 4259 if (imm != null) imm.restartInput(this); 4260 } 4261 4262 /** 4263 * It would be better to rely on the input type for everything. A password inputType should have 4264 * a password transformation. We should hence use isPasswordInputType instead of this method. 4265 * 4266 * We should: 4267 * - Call setInputType in setKeyListener instead of changing the input type directly (which 4268 * would install the correct transformation). 4269 * - Refuse the installation of a non-password transformation in setTransformation if the input 4270 * type is password. 4271 * 4272 * However, this is like this for legacy reasons and we cannot break existing apps. This method 4273 * is useful since it matches what the user can see (obfuscated text or not). 4274 * 4275 * @return true if the current transformation method is of the password type. 4276 */ hasPasswordTransformationMethod()4277 private boolean hasPasswordTransformationMethod() { 4278 return mTransformation instanceof PasswordTransformationMethod; 4279 } 4280 isPasswordInputType(int inputType)4281 private static boolean isPasswordInputType(int inputType) { 4282 final int variation = 4283 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 4284 return variation 4285 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD) 4286 || variation 4287 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD) 4288 || variation 4289 == (EditorInfo.TYPE_CLASS_NUMBER | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD); 4290 } 4291 isVisiblePasswordInputType(int inputType)4292 private static boolean isVisiblePasswordInputType(int inputType) { 4293 final int variation = 4294 inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION); 4295 return variation 4296 == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); 4297 } 4298 4299 /** 4300 * Directly change the content type integer of the text view, without 4301 * modifying any other state. 4302 * @see #setInputType(int) 4303 * @see android.text.InputType 4304 * @attr ref android.R.styleable#TextView_inputType 4305 */ setRawInputType(int type)4306 public void setRawInputType(int type) { 4307 if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value 4308 createEditorIfNeeded(); 4309 mEditor.mInputType = type; 4310 } 4311 setInputType(int type, boolean direct)4312 private void setInputType(int type, boolean direct) { 4313 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 4314 KeyListener input; 4315 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 4316 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; 4317 TextKeyListener.Capitalize cap; 4318 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 4319 cap = TextKeyListener.Capitalize.CHARACTERS; 4320 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 4321 cap = TextKeyListener.Capitalize.WORDS; 4322 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 4323 cap = TextKeyListener.Capitalize.SENTENCES; 4324 } else { 4325 cap = TextKeyListener.Capitalize.NONE; 4326 } 4327 input = TextKeyListener.getInstance(autotext, cap); 4328 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 4329 input = DigitsKeyListener.getInstance( 4330 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 4331 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 4332 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 4333 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 4334 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 4335 input = DateKeyListener.getInstance(); 4336 break; 4337 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 4338 input = TimeKeyListener.getInstance(); 4339 break; 4340 default: 4341 input = DateTimeKeyListener.getInstance(); 4342 break; 4343 } 4344 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 4345 input = DialerKeyListener.getInstance(); 4346 } else { 4347 input = TextKeyListener.getInstance(); 4348 } 4349 setRawInputType(type); 4350 if (direct) { 4351 createEditorIfNeeded(); 4352 mEditor.mKeyListener = input; 4353 } else { 4354 setKeyListenerOnly(input); 4355 } 4356 } 4357 4358 /** 4359 * Get the type of the editable content. 4360 * 4361 * @see #setInputType(int) 4362 * @see android.text.InputType 4363 */ getInputType()4364 public int getInputType() { 4365 return mEditor == null ? EditorInfo.TYPE_NULL : mEditor.mInputType; 4366 } 4367 4368 /** 4369 * Change the editor type integer associated with the text view, which 4370 * will be reported to an IME with {@link EditorInfo#imeOptions} when it 4371 * has focus. 4372 * @see #getImeOptions 4373 * @see android.view.inputmethod.EditorInfo 4374 * @attr ref android.R.styleable#TextView_imeOptions 4375 */ setImeOptions(int imeOptions)4376 public void setImeOptions(int imeOptions) { 4377 createEditorIfNeeded(); 4378 mEditor.createInputContentTypeIfNeeded(); 4379 mEditor.mInputContentType.imeOptions = imeOptions; 4380 } 4381 4382 /** 4383 * Get the type of the IME editor. 4384 * 4385 * @see #setImeOptions(int) 4386 * @see android.view.inputmethod.EditorInfo 4387 */ getImeOptions()4388 public int getImeOptions() { 4389 return mEditor != null && mEditor.mInputContentType != null 4390 ? mEditor.mInputContentType.imeOptions : EditorInfo.IME_NULL; 4391 } 4392 4393 /** 4394 * Change the custom IME action associated with the text view, which 4395 * will be reported to an IME with {@link EditorInfo#actionLabel} 4396 * and {@link EditorInfo#actionId} when it has focus. 4397 * @see #getImeActionLabel 4398 * @see #getImeActionId 4399 * @see android.view.inputmethod.EditorInfo 4400 * @attr ref android.R.styleable#TextView_imeActionLabel 4401 * @attr ref android.R.styleable#TextView_imeActionId 4402 */ setImeActionLabel(CharSequence label, int actionId)4403 public void setImeActionLabel(CharSequence label, int actionId) { 4404 createEditorIfNeeded(); 4405 mEditor.createInputContentTypeIfNeeded(); 4406 mEditor.mInputContentType.imeActionLabel = label; 4407 mEditor.mInputContentType.imeActionId = actionId; 4408 } 4409 4410 /** 4411 * Get the IME action label previous set with {@link #setImeActionLabel}. 4412 * 4413 * @see #setImeActionLabel 4414 * @see android.view.inputmethod.EditorInfo 4415 */ getImeActionLabel()4416 public CharSequence getImeActionLabel() { 4417 return mEditor != null && mEditor.mInputContentType != null 4418 ? mEditor.mInputContentType.imeActionLabel : null; 4419 } 4420 4421 /** 4422 * Get the IME action ID previous set with {@link #setImeActionLabel}. 4423 * 4424 * @see #setImeActionLabel 4425 * @see android.view.inputmethod.EditorInfo 4426 */ getImeActionId()4427 public int getImeActionId() { 4428 return mEditor != null && mEditor.mInputContentType != null 4429 ? mEditor.mInputContentType.imeActionId : 0; 4430 } 4431 4432 /** 4433 * Set a special listener to be called when an action is performed 4434 * on the text view. This will be called when the enter key is pressed, 4435 * or when an action supplied to the IME is selected by the user. Setting 4436 * this means that the normal hard key event will not insert a newline 4437 * into the text view, even if it is multi-line; holding down the ALT 4438 * modifier will, however, allow the user to insert a newline character. 4439 */ setOnEditorActionListener(OnEditorActionListener l)4440 public void setOnEditorActionListener(OnEditorActionListener l) { 4441 createEditorIfNeeded(); 4442 mEditor.createInputContentTypeIfNeeded(); 4443 mEditor.mInputContentType.onEditorActionListener = l; 4444 } 4445 4446 /** 4447 * Called when an attached input method calls 4448 * {@link InputConnection#performEditorAction(int) 4449 * InputConnection.performEditorAction()} 4450 * for this text view. The default implementation will call your action 4451 * listener supplied to {@link #setOnEditorActionListener}, or perform 4452 * a standard operation for {@link EditorInfo#IME_ACTION_NEXT 4453 * EditorInfo.IME_ACTION_NEXT}, {@link EditorInfo#IME_ACTION_PREVIOUS 4454 * EditorInfo.IME_ACTION_PREVIOUS}, or {@link EditorInfo#IME_ACTION_DONE 4455 * EditorInfo.IME_ACTION_DONE}. 4456 * 4457 * <p>For backwards compatibility, if no IME options have been set and the 4458 * text view would not normally advance focus on enter, then 4459 * the NEXT and DONE actions received here will be turned into an enter 4460 * key down/up pair to go through the normal key handling. 4461 * 4462 * @param actionCode The code of the action being performed. 4463 * 4464 * @see #setOnEditorActionListener 4465 */ onEditorAction(int actionCode)4466 public void onEditorAction(int actionCode) { 4467 final Editor.InputContentType ict = mEditor == null ? null : mEditor.mInputContentType; 4468 if (ict != null) { 4469 if (ict.onEditorActionListener != null) { 4470 if (ict.onEditorActionListener.onEditorAction(this, 4471 actionCode, null)) { 4472 return; 4473 } 4474 } 4475 4476 // This is the handling for some default action. 4477 // Note that for backwards compatibility we don't do this 4478 // default handling if explicit ime options have not been given, 4479 // instead turning this into the normal enter key codes that an 4480 // app may be expecting. 4481 if (actionCode == EditorInfo.IME_ACTION_NEXT) { 4482 View v = focusSearch(FOCUS_FORWARD); 4483 if (v != null) { 4484 if (!v.requestFocus(FOCUS_FORWARD)) { 4485 throw new IllegalStateException("focus search returned a view " + 4486 "that wasn't able to take focus!"); 4487 } 4488 } 4489 return; 4490 4491 } else if (actionCode == EditorInfo.IME_ACTION_PREVIOUS) { 4492 View v = focusSearch(FOCUS_BACKWARD); 4493 if (v != null) { 4494 if (!v.requestFocus(FOCUS_BACKWARD)) { 4495 throw new IllegalStateException("focus search returned a view " + 4496 "that wasn't able to take focus!"); 4497 } 4498 } 4499 return; 4500 4501 } else if (actionCode == EditorInfo.IME_ACTION_DONE) { 4502 InputMethodManager imm = InputMethodManager.peekInstance(); 4503 if (imm != null && imm.isActive(this)) { 4504 imm.hideSoftInputFromWindow(getWindowToken(), 0); 4505 } 4506 return; 4507 } 4508 } 4509 4510 ViewRootImpl viewRootImpl = getViewRootImpl(); 4511 if (viewRootImpl != null) { 4512 long eventTime = SystemClock.uptimeMillis(); 4513 viewRootImpl.dispatchKeyFromIme( 4514 new KeyEvent(eventTime, eventTime, 4515 KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER, 0, 0, 4516 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4517 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4518 | KeyEvent.FLAG_EDITOR_ACTION)); 4519 viewRootImpl.dispatchKeyFromIme( 4520 new KeyEvent(SystemClock.uptimeMillis(), eventTime, 4521 KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER, 0, 0, 4522 KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 4523 KeyEvent.FLAG_SOFT_KEYBOARD | KeyEvent.FLAG_KEEP_TOUCH_MODE 4524 | KeyEvent.FLAG_EDITOR_ACTION)); 4525 } 4526 } 4527 4528 /** 4529 * Set the private content type of the text, which is the 4530 * {@link EditorInfo#privateImeOptions EditorInfo.privateImeOptions} 4531 * field that will be filled in when creating an input connection. 4532 * 4533 * @see #getPrivateImeOptions() 4534 * @see EditorInfo#privateImeOptions 4535 * @attr ref android.R.styleable#TextView_privateImeOptions 4536 */ setPrivateImeOptions(String type)4537 public void setPrivateImeOptions(String type) { 4538 createEditorIfNeeded(); 4539 mEditor.createInputContentTypeIfNeeded(); 4540 mEditor.mInputContentType.privateImeOptions = type; 4541 } 4542 4543 /** 4544 * Get the private type of the content. 4545 * 4546 * @see #setPrivateImeOptions(String) 4547 * @see EditorInfo#privateImeOptions 4548 */ getPrivateImeOptions()4549 public String getPrivateImeOptions() { 4550 return mEditor != null && mEditor.mInputContentType != null 4551 ? mEditor.mInputContentType.privateImeOptions : null; 4552 } 4553 4554 /** 4555 * Set the extra input data of the text, which is the 4556 * {@link EditorInfo#extras TextBoxAttribute.extras} 4557 * Bundle that will be filled in when creating an input connection. The 4558 * given integer is the resource ID of an XML resource holding an 4559 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 4560 * 4561 * @see #getInputExtras(boolean) 4562 * @see EditorInfo#extras 4563 * @attr ref android.R.styleable#TextView_editorExtras 4564 */ setInputExtras(int xmlResId)4565 public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException { 4566 createEditorIfNeeded(); 4567 XmlResourceParser parser = getResources().getXml(xmlResId); 4568 mEditor.createInputContentTypeIfNeeded(); 4569 mEditor.mInputContentType.extras = new Bundle(); 4570 getResources().parseBundleExtras(parser, mEditor.mInputContentType.extras); 4571 } 4572 4573 /** 4574 * Retrieve the input extras currently associated with the text view, which 4575 * can be viewed as well as modified. 4576 * 4577 * @param create If true, the extras will be created if they don't already 4578 * exist. Otherwise, null will be returned if none have been created. 4579 * @see #setInputExtras(int) 4580 * @see EditorInfo#extras 4581 * @attr ref android.R.styleable#TextView_editorExtras 4582 */ getInputExtras(boolean create)4583 public Bundle getInputExtras(boolean create) { 4584 if (mEditor == null && !create) return null; 4585 createEditorIfNeeded(); 4586 if (mEditor.mInputContentType == null) { 4587 if (!create) return null; 4588 mEditor.createInputContentTypeIfNeeded(); 4589 } 4590 if (mEditor.mInputContentType.extras == null) { 4591 if (!create) return null; 4592 mEditor.mInputContentType.extras = new Bundle(); 4593 } 4594 return mEditor.mInputContentType.extras; 4595 } 4596 4597 /** 4598 * Returns the error message that was set to be displayed with 4599 * {@link #setError}, or <code>null</code> if no error was set 4600 * or if it the error was cleared by the widget after user input. 4601 */ getError()4602 public CharSequence getError() { 4603 return mEditor == null ? null : mEditor.mError; 4604 } 4605 4606 /** 4607 * Sets the right-hand compound drawable of the TextView to the "error" 4608 * icon and sets an error message that will be displayed in a popup when 4609 * the TextView has focus. The icon and error message will be reset to 4610 * null when any key events cause changes to the TextView's text. If the 4611 * <code>error</code> is <code>null</code>, the error message and icon 4612 * will be cleared. 4613 */ 4614 @android.view.RemotableViewMethod setError(CharSequence error)4615 public void setError(CharSequence error) { 4616 if (error == null) { 4617 setError(null, null); 4618 } else { 4619 Drawable dr = getContext().getDrawable( 4620 com.android.internal.R.drawable.indicator_input_error); 4621 4622 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 4623 setError(error, dr); 4624 } 4625 } 4626 4627 /** 4628 * Sets the right-hand compound drawable of the TextView to the specified 4629 * icon and sets an error message that will be displayed in a popup when 4630 * the TextView has focus. The icon and error message will be reset to 4631 * null when any key events cause changes to the TextView's text. The 4632 * drawable must already have had {@link Drawable#setBounds} set on it. 4633 * If the <code>error</code> is <code>null</code>, the error message will 4634 * be cleared (and you should provide a <code>null</code> icon as well). 4635 */ setError(CharSequence error, Drawable icon)4636 public void setError(CharSequence error, Drawable icon) { 4637 createEditorIfNeeded(); 4638 mEditor.setError(error, icon); 4639 notifyViewAccessibilityStateChangedIfNeeded( 4640 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 4641 } 4642 4643 @Override setFrame(int l, int t, int r, int b)4644 protected boolean setFrame(int l, int t, int r, int b) { 4645 boolean result = super.setFrame(l, t, r, b); 4646 4647 if (mEditor != null) mEditor.setFrame(); 4648 4649 restartMarqueeIfNeeded(); 4650 4651 return result; 4652 } 4653 restartMarqueeIfNeeded()4654 private void restartMarqueeIfNeeded() { 4655 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4656 mRestartMarquee = false; 4657 startMarquee(); 4658 } 4659 } 4660 4661 /** 4662 * Sets the list of input filters that will be used if the buffer is 4663 * Editable. Has no effect otherwise. 4664 * 4665 * @attr ref android.R.styleable#TextView_maxLength 4666 */ setFilters(InputFilter[] filters)4667 public void setFilters(InputFilter[] filters) { 4668 if (filters == null) { 4669 throw new IllegalArgumentException(); 4670 } 4671 4672 mFilters = filters; 4673 4674 if (mText instanceof Editable) { 4675 setFilters((Editable) mText, filters); 4676 } 4677 } 4678 4679 /** 4680 * Sets the list of input filters on the specified Editable, 4681 * and includes mInput in the list if it is an InputFilter. 4682 */ setFilters(Editable e, InputFilter[] filters)4683 private void setFilters(Editable e, InputFilter[] filters) { 4684 if (mEditor != null) { 4685 final boolean undoFilter = mEditor.mUndoInputFilter != null; 4686 final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; 4687 int num = 0; 4688 if (undoFilter) num++; 4689 if (keyFilter) num++; 4690 if (num > 0) { 4691 InputFilter[] nf = new InputFilter[filters.length + num]; 4692 4693 System.arraycopy(filters, 0, nf, 0, filters.length); 4694 num = 0; 4695 if (undoFilter) { 4696 nf[filters.length] = mEditor.mUndoInputFilter; 4697 num++; 4698 } 4699 if (keyFilter) { 4700 nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; 4701 } 4702 4703 e.setFilters(nf); 4704 return; 4705 } 4706 } 4707 e.setFilters(filters); 4708 } 4709 4710 /** 4711 * Returns the current list of input filters. 4712 * 4713 * @attr ref android.R.styleable#TextView_maxLength 4714 */ getFilters()4715 public InputFilter[] getFilters() { 4716 return mFilters; 4717 } 4718 4719 ///////////////////////////////////////////////////////////////////////// 4720 getBoxHeight(Layout l)4721 private int getBoxHeight(Layout l) { 4722 Insets opticalInsets = isLayoutModeOptical(mParent) ? getOpticalInsets() : Insets.NONE; 4723 int padding = (l == mHintLayout) ? 4724 getCompoundPaddingTop() + getCompoundPaddingBottom() : 4725 getExtendedPaddingTop() + getExtendedPaddingBottom(); 4726 return getMeasuredHeight() - padding + opticalInsets.top + opticalInsets.bottom; 4727 } 4728 getVerticalOffset(boolean forceNormal)4729 int getVerticalOffset(boolean forceNormal) { 4730 int voffset = 0; 4731 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 4732 4733 Layout l = mLayout; 4734 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 4735 l = mHintLayout; 4736 } 4737 4738 if (gravity != Gravity.TOP) { 4739 int boxht = getBoxHeight(l); 4740 int textht = l.getHeight(); 4741 4742 if (textht < boxht) { 4743 if (gravity == Gravity.BOTTOM) 4744 voffset = boxht - textht; 4745 else // (gravity == Gravity.CENTER_VERTICAL) 4746 voffset = (boxht - textht) >> 1; 4747 } 4748 } 4749 return voffset; 4750 } 4751 getBottomVerticalOffset(boolean forceNormal)4752 private int getBottomVerticalOffset(boolean forceNormal) { 4753 int voffset = 0; 4754 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 4755 4756 Layout l = mLayout; 4757 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 4758 l = mHintLayout; 4759 } 4760 4761 if (gravity != Gravity.BOTTOM) { 4762 int boxht = getBoxHeight(l); 4763 int textht = l.getHeight(); 4764 4765 if (textht < boxht) { 4766 if (gravity == Gravity.TOP) 4767 voffset = boxht - textht; 4768 else // (gravity == Gravity.CENTER_VERTICAL) 4769 voffset = (boxht - textht) >> 1; 4770 } 4771 } 4772 return voffset; 4773 } 4774 invalidateCursorPath()4775 void invalidateCursorPath() { 4776 if (mHighlightPathBogus) { 4777 invalidateCursor(); 4778 } else { 4779 final int horizontalPadding = getCompoundPaddingLeft(); 4780 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 4781 4782 if (mEditor.mCursorCount == 0) { 4783 synchronized (TEMP_RECTF) { 4784 /* 4785 * The reason for this concern about the thickness of the 4786 * cursor and doing the floor/ceil on the coordinates is that 4787 * some EditTexts (notably textfields in the Browser) have 4788 * anti-aliased text where not all the characters are 4789 * necessarily at integer-multiple locations. This should 4790 * make sure the entire cursor gets invalidated instead of 4791 * sometimes missing half a pixel. 4792 */ 4793 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth()); 4794 if (thick < 1.0f) { 4795 thick = 1.0f; 4796 } 4797 4798 thick /= 2.0f; 4799 4800 // mHighlightPath is guaranteed to be non null at that point. 4801 mHighlightPath.computeBounds(TEMP_RECTF, false); 4802 4803 invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick), 4804 (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick), 4805 (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick), 4806 (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick)); 4807 } 4808 } else { 4809 for (int i = 0; i < mEditor.mCursorCount; i++) { 4810 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 4811 invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding, 4812 bounds.right + horizontalPadding, bounds.bottom + verticalPadding); 4813 } 4814 } 4815 } 4816 } 4817 invalidateCursor()4818 void invalidateCursor() { 4819 int where = getSelectionEnd(); 4820 4821 invalidateCursor(where, where, where); 4822 } 4823 invalidateCursor(int a, int b, int c)4824 private void invalidateCursor(int a, int b, int c) { 4825 if (a >= 0 || b >= 0 || c >= 0) { 4826 int start = Math.min(Math.min(a, b), c); 4827 int end = Math.max(Math.max(a, b), c); 4828 invalidateRegion(start, end, true /* Also invalidates blinking cursor */); 4829 } 4830 } 4831 4832 /** 4833 * Invalidates the region of text enclosed between the start and end text offsets. 4834 */ invalidateRegion(int start, int end, boolean invalidateCursor)4835 void invalidateRegion(int start, int end, boolean invalidateCursor) { 4836 if (mLayout == null) { 4837 invalidate(); 4838 } else { 4839 int lineStart = mLayout.getLineForOffset(start); 4840 int top = mLayout.getLineTop(lineStart); 4841 4842 // This is ridiculous, but the descent from the line above 4843 // can hang down into the line we really want to redraw, 4844 // so we have to invalidate part of the line above to make 4845 // sure everything that needs to be redrawn really is. 4846 // (But not the whole line above, because that would cause 4847 // the same problem with the descenders on the line above it!) 4848 if (lineStart > 0) { 4849 top -= mLayout.getLineDescent(lineStart - 1); 4850 } 4851 4852 int lineEnd; 4853 4854 if (start == end) 4855 lineEnd = lineStart; 4856 else 4857 lineEnd = mLayout.getLineForOffset(end); 4858 4859 int bottom = mLayout.getLineBottom(lineEnd); 4860 4861 // mEditor can be null in case selection is set programmatically. 4862 if (invalidateCursor && mEditor != null) { 4863 for (int i = 0; i < mEditor.mCursorCount; i++) { 4864 Rect bounds = mEditor.mCursorDrawable[i].getBounds(); 4865 top = Math.min(top, bounds.top); 4866 bottom = Math.max(bottom, bounds.bottom); 4867 } 4868 } 4869 4870 final int compoundPaddingLeft = getCompoundPaddingLeft(); 4871 final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true); 4872 4873 int left, right; 4874 if (lineStart == lineEnd && !invalidateCursor) { 4875 left = (int) mLayout.getPrimaryHorizontal(start); 4876 right = (int) (mLayout.getPrimaryHorizontal(end) + 1.0); 4877 left += compoundPaddingLeft; 4878 right += compoundPaddingLeft; 4879 } else { 4880 // Rectangle bounding box when the region spans several lines 4881 left = compoundPaddingLeft; 4882 right = getWidth() - getCompoundPaddingRight(); 4883 } 4884 4885 invalidate(mScrollX + left, verticalPadding + top, 4886 mScrollX + right, verticalPadding + bottom); 4887 } 4888 } 4889 registerForPreDraw()4890 private void registerForPreDraw() { 4891 if (!mPreDrawRegistered) { 4892 getViewTreeObserver().addOnPreDrawListener(this); 4893 mPreDrawRegistered = true; 4894 } 4895 } 4896 unregisterForPreDraw()4897 private void unregisterForPreDraw() { 4898 getViewTreeObserver().removeOnPreDrawListener(this); 4899 mPreDrawRegistered = false; 4900 mPreDrawListenerDetached = false; 4901 } 4902 4903 /** 4904 * {@inheritDoc} 4905 */ onPreDraw()4906 public boolean onPreDraw() { 4907 if (mLayout == null) { 4908 assumeLayout(); 4909 } 4910 4911 if (mMovement != null) { 4912 /* This code also provides auto-scrolling when a cursor is moved using a 4913 * CursorController (insertion point or selection limits). 4914 * For selection, ensure start or end is visible depending on controller's state. 4915 */ 4916 int curs = getSelectionEnd(); 4917 // Do not create the controller if it is not already created. 4918 if (mEditor != null && mEditor.mSelectionModifierCursorController != null && 4919 mEditor.mSelectionModifierCursorController.isSelectionStartDragged()) { 4920 curs = getSelectionStart(); 4921 } 4922 4923 /* 4924 * TODO: This should really only keep the end in view if 4925 * it already was before the text changed. I'm not sure 4926 * of a good way to tell from here if it was. 4927 */ 4928 if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 4929 curs = mText.length(); 4930 } 4931 4932 if (curs >= 0) { 4933 bringPointIntoView(curs); 4934 } 4935 } else { 4936 bringTextIntoView(); 4937 } 4938 4939 // This has to be checked here since: 4940 // - onFocusChanged cannot start it when focus is given to a view with selected text (after 4941 // a screen rotation) since layout is not yet initialized at that point. 4942 if (mEditor != null && mEditor.mCreatedWithASelection) { 4943 mEditor.startSelectionActionMode(); 4944 mEditor.mCreatedWithASelection = false; 4945 } 4946 4947 // Phone specific code (there is no ExtractEditText on tablets). 4948 // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can 4949 // not be set. Do the test here instead. 4950 if (this instanceof ExtractEditText && hasSelection() && mEditor != null) { 4951 mEditor.startSelectionActionMode(); 4952 } 4953 4954 unregisterForPreDraw(); 4955 4956 return true; 4957 } 4958 4959 @Override onAttachedToWindow()4960 protected void onAttachedToWindow() { 4961 super.onAttachedToWindow(); 4962 4963 mTemporaryDetach = false; 4964 4965 if (mEditor != null) mEditor.onAttachedToWindow(); 4966 4967 if (mPreDrawListenerDetached) { 4968 getViewTreeObserver().addOnPreDrawListener(this); 4969 mPreDrawListenerDetached = false; 4970 } 4971 } 4972 4973 /** @hide */ 4974 @Override onDetachedFromWindowInternal()4975 protected void onDetachedFromWindowInternal() { 4976 if (mPreDrawRegistered) { 4977 getViewTreeObserver().removeOnPreDrawListener(this); 4978 mPreDrawListenerDetached = true; 4979 } 4980 4981 resetResolvedDrawables(); 4982 4983 if (mEditor != null) mEditor.onDetachedFromWindow(); 4984 4985 super.onDetachedFromWindowInternal(); 4986 } 4987 4988 @Override onScreenStateChanged(int screenState)4989 public void onScreenStateChanged(int screenState) { 4990 super.onScreenStateChanged(screenState); 4991 if (mEditor != null) mEditor.onScreenStateChanged(screenState); 4992 } 4993 4994 @Override isPaddingOffsetRequired()4995 protected boolean isPaddingOffsetRequired() { 4996 return mShadowRadius != 0 || mDrawables != null; 4997 } 4998 4999 @Override getLeftPaddingOffset()5000 protected int getLeftPaddingOffset() { 5001 return getCompoundPaddingLeft() - mPaddingLeft + 5002 (int) Math.min(0, mShadowDx - mShadowRadius); 5003 } 5004 5005 @Override getTopPaddingOffset()5006 protected int getTopPaddingOffset() { 5007 return (int) Math.min(0, mShadowDy - mShadowRadius); 5008 } 5009 5010 @Override getBottomPaddingOffset()5011 protected int getBottomPaddingOffset() { 5012 return (int) Math.max(0, mShadowDy + mShadowRadius); 5013 } 5014 5015 @Override getRightPaddingOffset()5016 protected int getRightPaddingOffset() { 5017 return -(getCompoundPaddingRight() - mPaddingRight) + 5018 (int) Math.max(0, mShadowDx + mShadowRadius); 5019 } 5020 5021 @Override verifyDrawable(Drawable who)5022 protected boolean verifyDrawable(Drawable who) { 5023 final boolean verified = super.verifyDrawable(who); 5024 if (!verified && mDrawables != null) { 5025 return who == mDrawables.mDrawableLeft || who == mDrawables.mDrawableTop || 5026 who == mDrawables.mDrawableRight || who == mDrawables.mDrawableBottom || 5027 who == mDrawables.mDrawableStart || who == mDrawables.mDrawableEnd; 5028 } 5029 return verified; 5030 } 5031 5032 @Override jumpDrawablesToCurrentState()5033 public void jumpDrawablesToCurrentState() { 5034 super.jumpDrawablesToCurrentState(); 5035 if (mDrawables != null) { 5036 if (mDrawables.mDrawableLeft != null) { 5037 mDrawables.mDrawableLeft.jumpToCurrentState(); 5038 } 5039 if (mDrawables.mDrawableTop != null) { 5040 mDrawables.mDrawableTop.jumpToCurrentState(); 5041 } 5042 if (mDrawables.mDrawableRight != null) { 5043 mDrawables.mDrawableRight.jumpToCurrentState(); 5044 } 5045 if (mDrawables.mDrawableBottom != null) { 5046 mDrawables.mDrawableBottom.jumpToCurrentState(); 5047 } 5048 if (mDrawables.mDrawableStart != null) { 5049 mDrawables.mDrawableStart.jumpToCurrentState(); 5050 } 5051 if (mDrawables.mDrawableEnd != null) { 5052 mDrawables.mDrawableEnd.jumpToCurrentState(); 5053 } 5054 } 5055 } 5056 5057 @Override invalidateDrawable(Drawable drawable)5058 public void invalidateDrawable(Drawable drawable) { 5059 boolean handled = false; 5060 5061 if (verifyDrawable(drawable)) { 5062 final Rect dirty = drawable.getBounds(); 5063 int scrollX = mScrollX; 5064 int scrollY = mScrollY; 5065 5066 // IMPORTANT: The coordinates below are based on the coordinates computed 5067 // for each compound drawable in onDraw(). Make sure to update each section 5068 // accordingly. 5069 final TextView.Drawables drawables = mDrawables; 5070 if (drawables != null) { 5071 if (drawable == drawables.mDrawableLeft) { 5072 final int compoundPaddingTop = getCompoundPaddingTop(); 5073 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5074 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5075 5076 scrollX += mPaddingLeft; 5077 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightLeft) / 2; 5078 handled = true; 5079 } else if (drawable == drawables.mDrawableRight) { 5080 final int compoundPaddingTop = getCompoundPaddingTop(); 5081 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5082 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5083 5084 scrollX += (mRight - mLeft - mPaddingRight - drawables.mDrawableSizeRight); 5085 scrollY += compoundPaddingTop + (vspace - drawables.mDrawableHeightRight) / 2; 5086 handled = true; 5087 } else if (drawable == drawables.mDrawableTop) { 5088 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5089 final int compoundPaddingRight = getCompoundPaddingRight(); 5090 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 5091 5092 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthTop) / 2; 5093 scrollY += mPaddingTop; 5094 handled = true; 5095 } else if (drawable == drawables.mDrawableBottom) { 5096 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5097 final int compoundPaddingRight = getCompoundPaddingRight(); 5098 final int hspace = mRight - mLeft - compoundPaddingRight - compoundPaddingLeft; 5099 5100 scrollX += compoundPaddingLeft + (hspace - drawables.mDrawableWidthBottom) / 2; 5101 scrollY += (mBottom - mTop - mPaddingBottom - drawables.mDrawableSizeBottom); 5102 handled = true; 5103 } 5104 } 5105 5106 if (handled) { 5107 invalidate(dirty.left + scrollX, dirty.top + scrollY, 5108 dirty.right + scrollX, dirty.bottom + scrollY); 5109 } 5110 } 5111 5112 if (!handled) { 5113 super.invalidateDrawable(drawable); 5114 } 5115 } 5116 5117 @Override hasOverlappingRendering()5118 public boolean hasOverlappingRendering() { 5119 // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation 5120 return ((getBackground() != null && getBackground().getCurrent() != null) 5121 || mText instanceof Spannable || hasSelection() 5122 || isHorizontalFadingEdgeEnabled()); 5123 } 5124 5125 /** 5126 * 5127 * Returns the state of the {@code textIsSelectable} flag (See 5128 * {@link #setTextIsSelectable setTextIsSelectable()}). Although you have to set this flag 5129 * to allow users to select and copy text in a non-editable TextView, the content of an 5130 * {@link EditText} can always be selected, independently of the value of this flag. 5131 * <p> 5132 * 5133 * @return True if the text displayed in this TextView can be selected by the user. 5134 * 5135 * @attr ref android.R.styleable#TextView_textIsSelectable 5136 */ isTextSelectable()5137 public boolean isTextSelectable() { 5138 return mEditor == null ? false : mEditor.mTextIsSelectable; 5139 } 5140 5141 /** 5142 * Sets whether the content of this view is selectable by the user. The default is 5143 * {@code false}, meaning that the content is not selectable. 5144 * <p> 5145 * When you use a TextView to display a useful piece of information to the user (such as a 5146 * contact's address), make it selectable, so that the user can select and copy its 5147 * content. You can also use set the XML attribute 5148 * {@link android.R.styleable#TextView_textIsSelectable} to "true". 5149 * <p> 5150 * When you call this method to set the value of {@code textIsSelectable}, it sets 5151 * the flags {@code focusable}, {@code focusableInTouchMode}, {@code clickable}, 5152 * and {@code longClickable} to the same value. These flags correspond to the attributes 5153 * {@link android.R.styleable#View_focusable android:focusable}, 5154 * {@link android.R.styleable#View_focusableInTouchMode android:focusableInTouchMode}, 5155 * {@link android.R.styleable#View_clickable android:clickable}, and 5156 * {@link android.R.styleable#View_longClickable android:longClickable}. To restore any of these 5157 * flags to a state you had set previously, call one or more of the following methods: 5158 * {@link #setFocusable(boolean) setFocusable()}, 5159 * {@link #setFocusableInTouchMode(boolean) setFocusableInTouchMode()}, 5160 * {@link #setClickable(boolean) setClickable()} or 5161 * {@link #setLongClickable(boolean) setLongClickable()}. 5162 * 5163 * @param selectable Whether the content of this TextView should be selectable. 5164 */ setTextIsSelectable(boolean selectable)5165 public void setTextIsSelectable(boolean selectable) { 5166 if (!selectable && mEditor == null) return; // false is default value with no edit data 5167 5168 createEditorIfNeeded(); 5169 if (mEditor.mTextIsSelectable == selectable) return; 5170 5171 mEditor.mTextIsSelectable = selectable; 5172 setFocusableInTouchMode(selectable); 5173 setFocusable(selectable); 5174 setClickable(selectable); 5175 setLongClickable(selectable); 5176 5177 // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null 5178 5179 setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null); 5180 setText(mText, selectable ? BufferType.SPANNABLE : BufferType.NORMAL); 5181 5182 // Called by setText above, but safer in case of future code changes 5183 mEditor.prepareCursorControllers(); 5184 } 5185 5186 @Override onCreateDrawableState(int extraSpace)5187 protected int[] onCreateDrawableState(int extraSpace) { 5188 final int[] drawableState; 5189 5190 if (mSingleLine) { 5191 drawableState = super.onCreateDrawableState(extraSpace); 5192 } else { 5193 drawableState = super.onCreateDrawableState(extraSpace + 1); 5194 mergeDrawableStates(drawableState, MULTILINE_STATE_SET); 5195 } 5196 5197 if (isTextSelectable()) { 5198 // Disable pressed state, which was introduced when TextView was made clickable. 5199 // Prevents text color change. 5200 // setClickable(false) would have a similar effect, but it also disables focus changes 5201 // and long press actions, which are both needed by text selection. 5202 final int length = drawableState.length; 5203 for (int i = 0; i < length; i++) { 5204 if (drawableState[i] == R.attr.state_pressed) { 5205 final int[] nonPressedState = new int[length - 1]; 5206 System.arraycopy(drawableState, 0, nonPressedState, 0, i); 5207 System.arraycopy(drawableState, i + 1, nonPressedState, i, length - i - 1); 5208 return nonPressedState; 5209 } 5210 } 5211 } 5212 5213 return drawableState; 5214 } 5215 getUpdatedHighlightPath()5216 private Path getUpdatedHighlightPath() { 5217 Path highlight = null; 5218 Paint highlightPaint = mHighlightPaint; 5219 5220 final int selStart = getSelectionStart(); 5221 final int selEnd = getSelectionEnd(); 5222 if (mMovement != null && (isFocused() || isPressed()) && selStart >= 0) { 5223 if (selStart == selEnd) { 5224 if (mEditor != null && mEditor.isCursorVisible() && 5225 (SystemClock.uptimeMillis() - mEditor.mShowCursor) % 5226 (2 * Editor.BLINK) < Editor.BLINK) { 5227 if (mHighlightPathBogus) { 5228 if (mHighlightPath == null) mHighlightPath = new Path(); 5229 mHighlightPath.reset(); 5230 mLayout.getCursorPath(selStart, mHighlightPath, mText); 5231 mEditor.updateCursorsPositions(); 5232 mHighlightPathBogus = false; 5233 } 5234 5235 // XXX should pass to skin instead of drawing directly 5236 highlightPaint.setColor(mCurTextColor); 5237 highlightPaint.setStyle(Paint.Style.STROKE); 5238 highlight = mHighlightPath; 5239 } 5240 } else { 5241 if (mHighlightPathBogus) { 5242 if (mHighlightPath == null) mHighlightPath = new Path(); 5243 mHighlightPath.reset(); 5244 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 5245 mHighlightPathBogus = false; 5246 } 5247 5248 // XXX should pass to skin instead of drawing directly 5249 highlightPaint.setColor(mHighlightColor); 5250 highlightPaint.setStyle(Paint.Style.FILL); 5251 5252 highlight = mHighlightPath; 5253 } 5254 } 5255 return highlight; 5256 } 5257 5258 /** 5259 * @hide 5260 */ getHorizontalOffsetForDrawables()5261 public int getHorizontalOffsetForDrawables() { 5262 return 0; 5263 } 5264 5265 @Override onDraw(Canvas canvas)5266 protected void onDraw(Canvas canvas) { 5267 restartMarqueeIfNeeded(); 5268 5269 // Draw the background for this view 5270 super.onDraw(canvas); 5271 5272 final int compoundPaddingLeft = getCompoundPaddingLeft(); 5273 final int compoundPaddingTop = getCompoundPaddingTop(); 5274 final int compoundPaddingRight = getCompoundPaddingRight(); 5275 final int compoundPaddingBottom = getCompoundPaddingBottom(); 5276 final int scrollX = mScrollX; 5277 final int scrollY = mScrollY; 5278 final int right = mRight; 5279 final int left = mLeft; 5280 final int bottom = mBottom; 5281 final int top = mTop; 5282 final boolean isLayoutRtl = isLayoutRtl(); 5283 final int offset = getHorizontalOffsetForDrawables(); 5284 final int leftOffset = isLayoutRtl ? 0 : offset; 5285 final int rightOffset = isLayoutRtl ? offset : 0 ; 5286 5287 final Drawables dr = mDrawables; 5288 if (dr != null) { 5289 /* 5290 * Compound, not extended, because the icon is not clipped 5291 * if the text height is smaller. 5292 */ 5293 5294 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 5295 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 5296 5297 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5298 // Make sure to update invalidateDrawable() when changing this code. 5299 if (dr.mDrawableLeft != null) { 5300 canvas.save(); 5301 canvas.translate(scrollX + mPaddingLeft + leftOffset, 5302 scrollY + compoundPaddingTop + 5303 (vspace - dr.mDrawableHeightLeft) / 2); 5304 dr.mDrawableLeft.draw(canvas); 5305 canvas.restore(); 5306 } 5307 5308 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5309 // Make sure to update invalidateDrawable() when changing this code. 5310 if (dr.mDrawableRight != null) { 5311 canvas.save(); 5312 canvas.translate(scrollX + right - left - mPaddingRight 5313 - dr.mDrawableSizeRight - rightOffset, 5314 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 5315 dr.mDrawableRight.draw(canvas); 5316 canvas.restore(); 5317 } 5318 5319 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5320 // Make sure to update invalidateDrawable() when changing this code. 5321 if (dr.mDrawableTop != null) { 5322 canvas.save(); 5323 canvas.translate(scrollX + compoundPaddingLeft + 5324 (hspace - dr.mDrawableWidthTop) / 2, scrollY + mPaddingTop); 5325 dr.mDrawableTop.draw(canvas); 5326 canvas.restore(); 5327 } 5328 5329 // IMPORTANT: The coordinates computed are also used in invalidateDrawable() 5330 // Make sure to update invalidateDrawable() when changing this code. 5331 if (dr.mDrawableBottom != null) { 5332 canvas.save(); 5333 canvas.translate(scrollX + compoundPaddingLeft + 5334 (hspace - dr.mDrawableWidthBottom) / 2, 5335 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 5336 dr.mDrawableBottom.draw(canvas); 5337 canvas.restore(); 5338 } 5339 } 5340 5341 int color = mCurTextColor; 5342 5343 if (mLayout == null) { 5344 assumeLayout(); 5345 } 5346 5347 Layout layout = mLayout; 5348 5349 if (mHint != null && mText.length() == 0) { 5350 if (mHintTextColor != null) { 5351 color = mCurHintTextColor; 5352 } 5353 5354 layout = mHintLayout; 5355 } 5356 5357 mTextPaint.setColor(color); 5358 mTextPaint.drawableState = getDrawableState(); 5359 5360 canvas.save(); 5361 /* Would be faster if we didn't have to do this. Can we chop the 5362 (displayable) text so that we don't need to do this ever? 5363 */ 5364 5365 int extendedPaddingTop = getExtendedPaddingTop(); 5366 int extendedPaddingBottom = getExtendedPaddingBottom(); 5367 5368 final int vspace = mBottom - mTop - compoundPaddingBottom - compoundPaddingTop; 5369 final int maxScrollY = mLayout.getHeight() - vspace; 5370 5371 float clipLeft = compoundPaddingLeft + scrollX; 5372 float clipTop = (scrollY == 0) ? 0 : extendedPaddingTop + scrollY; 5373 float clipRight = right - left - compoundPaddingRight + scrollX; 5374 float clipBottom = bottom - top + scrollY - 5375 ((scrollY == maxScrollY) ? 0 : extendedPaddingBottom); 5376 5377 if (mShadowRadius != 0) { 5378 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 5379 clipRight += Math.max(0, mShadowDx + mShadowRadius); 5380 5381 clipTop += Math.min(0, mShadowDy - mShadowRadius); 5382 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 5383 } 5384 5385 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 5386 5387 int voffsetText = 0; 5388 int voffsetCursor = 0; 5389 5390 // translate in by our padding 5391 /* shortcircuit calling getVerticaOffset() */ 5392 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5393 voffsetText = getVerticalOffset(false); 5394 voffsetCursor = getVerticalOffset(true); 5395 } 5396 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 5397 5398 final int layoutDirection = getLayoutDirection(); 5399 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 5400 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 5401 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 5402 if (!mSingleLine && getLineCount() == 1 && canMarquee() && 5403 (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 5404 final int width = mRight - mLeft; 5405 final int padding = getCompoundPaddingLeft() + getCompoundPaddingRight(); 5406 final float dx = mLayout.getLineRight(0) - (width - padding); 5407 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5408 } 5409 5410 if (mMarquee != null && mMarquee.isRunning()) { 5411 final float dx = -mMarquee.getScroll(); 5412 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5413 } 5414 } 5415 5416 final int cursorOffsetVertical = voffsetCursor - voffsetText; 5417 5418 Path highlight = getUpdatedHighlightPath(); 5419 if (mEditor != null) { 5420 mEditor.onDraw(canvas, layout, highlight, mHighlightPaint, cursorOffsetVertical); 5421 } else { 5422 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 5423 } 5424 5425 if (mMarquee != null && mMarquee.shouldDrawGhost()) { 5426 final float dx = mMarquee.getGhostOffset(); 5427 canvas.translate(layout.getParagraphDirection(0) * dx, 0.0f); 5428 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical); 5429 } 5430 5431 canvas.restore(); 5432 } 5433 5434 @Override getFocusedRect(Rect r)5435 public void getFocusedRect(Rect r) { 5436 if (mLayout == null) { 5437 super.getFocusedRect(r); 5438 return; 5439 } 5440 5441 int selEnd = getSelectionEnd(); 5442 if (selEnd < 0) { 5443 super.getFocusedRect(r); 5444 return; 5445 } 5446 5447 int selStart = getSelectionStart(); 5448 if (selStart < 0 || selStart >= selEnd) { 5449 int line = mLayout.getLineForOffset(selEnd); 5450 r.top = mLayout.getLineTop(line); 5451 r.bottom = mLayout.getLineBottom(line); 5452 r.left = (int) mLayout.getPrimaryHorizontal(selEnd) - 2; 5453 r.right = r.left + 4; 5454 } else { 5455 int lineStart = mLayout.getLineForOffset(selStart); 5456 int lineEnd = mLayout.getLineForOffset(selEnd); 5457 r.top = mLayout.getLineTop(lineStart); 5458 r.bottom = mLayout.getLineBottom(lineEnd); 5459 if (lineStart == lineEnd) { 5460 r.left = (int) mLayout.getPrimaryHorizontal(selStart); 5461 r.right = (int) mLayout.getPrimaryHorizontal(selEnd); 5462 } else { 5463 // Selection extends across multiple lines -- make the focused 5464 // rect cover the entire width. 5465 if (mHighlightPathBogus) { 5466 if (mHighlightPath == null) mHighlightPath = new Path(); 5467 mHighlightPath.reset(); 5468 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 5469 mHighlightPathBogus = false; 5470 } 5471 synchronized (TEMP_RECTF) { 5472 mHighlightPath.computeBounds(TEMP_RECTF, true); 5473 r.left = (int)TEMP_RECTF.left-1; 5474 r.right = (int)TEMP_RECTF.right+1; 5475 } 5476 } 5477 } 5478 5479 // Adjust for padding and gravity. 5480 int paddingLeft = getCompoundPaddingLeft(); 5481 int paddingTop = getExtendedPaddingTop(); 5482 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5483 paddingTop += getVerticalOffset(false); 5484 } 5485 r.offset(paddingLeft, paddingTop); 5486 int paddingBottom = getExtendedPaddingBottom(); 5487 r.bottom += paddingBottom; 5488 } 5489 5490 /** 5491 * Return the number of lines of text, or 0 if the internal Layout has not 5492 * been built. 5493 */ getLineCount()5494 public int getLineCount() { 5495 return mLayout != null ? mLayout.getLineCount() : 0; 5496 } 5497 5498 /** 5499 * Return the baseline for the specified line (0...getLineCount() - 1) 5500 * If bounds is not null, return the top, left, right, bottom extents 5501 * of the specified line in it. If the internal Layout has not been built, 5502 * return 0 and set bounds to (0, 0, 0, 0) 5503 * @param line which line to examine (0..getLineCount() - 1) 5504 * @param bounds Optional. If not null, it returns the extent of the line 5505 * @return the Y-coordinate of the baseline 5506 */ getLineBounds(int line, Rect bounds)5507 public int getLineBounds(int line, Rect bounds) { 5508 if (mLayout == null) { 5509 if (bounds != null) { 5510 bounds.set(0, 0, 0, 0); 5511 } 5512 return 0; 5513 } 5514 else { 5515 int baseline = mLayout.getLineBounds(line, bounds); 5516 5517 int voffset = getExtendedPaddingTop(); 5518 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5519 voffset += getVerticalOffset(true); 5520 } 5521 if (bounds != null) { 5522 bounds.offset(getCompoundPaddingLeft(), voffset); 5523 } 5524 return baseline + voffset; 5525 } 5526 } 5527 5528 @Override getBaseline()5529 public int getBaseline() { 5530 if (mLayout == null) { 5531 return super.getBaseline(); 5532 } 5533 5534 int voffset = 0; 5535 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5536 voffset = getVerticalOffset(true); 5537 } 5538 5539 if (isLayoutModeOptical(mParent)) { 5540 voffset -= getOpticalInsets().top; 5541 } 5542 5543 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); 5544 } 5545 5546 /** 5547 * @hide 5548 */ 5549 @Override getFadeTop(boolean offsetRequired)5550 protected int getFadeTop(boolean offsetRequired) { 5551 if (mLayout == null) return 0; 5552 5553 int voffset = 0; 5554 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 5555 voffset = getVerticalOffset(true); 5556 } 5557 5558 if (offsetRequired) voffset += getTopPaddingOffset(); 5559 5560 return getExtendedPaddingTop() + voffset; 5561 } 5562 5563 /** 5564 * @hide 5565 */ 5566 @Override getFadeHeight(boolean offsetRequired)5567 protected int getFadeHeight(boolean offsetRequired) { 5568 return mLayout != null ? mLayout.getHeight() : 0; 5569 } 5570 5571 @Override onKeyPreIme(int keyCode, KeyEvent event)5572 public boolean onKeyPreIme(int keyCode, KeyEvent event) { 5573 if (keyCode == KeyEvent.KEYCODE_BACK) { 5574 boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null; 5575 5576 if (isInSelectionMode) { 5577 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) { 5578 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5579 if (state != null) { 5580 state.startTracking(event, this); 5581 } 5582 return true; 5583 } else if (event.getAction() == KeyEvent.ACTION_UP) { 5584 KeyEvent.DispatcherState state = getKeyDispatcherState(); 5585 if (state != null) { 5586 state.handleUpEvent(event); 5587 } 5588 if (event.isTracking() && !event.isCanceled()) { 5589 stopSelectionActionMode(); 5590 return true; 5591 } 5592 } 5593 } 5594 } 5595 return super.onKeyPreIme(keyCode, event); 5596 } 5597 5598 @Override onKeyDown(int keyCode, KeyEvent event)5599 public boolean onKeyDown(int keyCode, KeyEvent event) { 5600 int which = doKeyDown(keyCode, event, null); 5601 if (which == 0) { 5602 return super.onKeyDown(keyCode, event); 5603 } 5604 5605 return true; 5606 } 5607 5608 @Override onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)5609 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 5610 KeyEvent down = KeyEvent.changeAction(event, KeyEvent.ACTION_DOWN); 5611 5612 int which = doKeyDown(keyCode, down, event); 5613 if (which == 0) { 5614 // Go through default dispatching. 5615 return super.onKeyMultiple(keyCode, repeatCount, event); 5616 } 5617 if (which == -1) { 5618 // Consumed the whole thing. 5619 return true; 5620 } 5621 5622 repeatCount--; 5623 5624 // We are going to dispatch the remaining events to either the input 5625 // or movement method. To do this, we will just send a repeated stream 5626 // of down and up events until we have done the complete repeatCount. 5627 // It would be nice if those interfaces had an onKeyMultiple() method, 5628 // but adding that is a more complicated change. 5629 KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP); 5630 if (which == 1) { 5631 // mEditor and mEditor.mInput are not null from doKeyDown 5632 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 5633 while (--repeatCount > 0) { 5634 mEditor.mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down); 5635 mEditor.mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up); 5636 } 5637 hideErrorIfUnchanged(); 5638 5639 } else if (which == 2) { 5640 // mMovement is not null from doKeyDown 5641 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 5642 while (--repeatCount > 0) { 5643 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); 5644 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 5645 } 5646 } 5647 5648 return true; 5649 } 5650 5651 /** 5652 * Returns true if pressing ENTER in this field advances focus instead 5653 * of inserting the character. This is true mostly in single-line fields, 5654 * but also in mail addresses and subjects which will display on multiple 5655 * lines but where it doesn't make sense to insert newlines. 5656 */ shouldAdvanceFocusOnEnter()5657 private boolean shouldAdvanceFocusOnEnter() { 5658 if (getKeyListener() == null) { 5659 return false; 5660 } 5661 5662 if (mSingleLine) { 5663 return true; 5664 } 5665 5666 if (mEditor != null && 5667 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 5668 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 5669 if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS 5670 || variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) { 5671 return true; 5672 } 5673 } 5674 5675 return false; 5676 } 5677 5678 /** 5679 * Returns true if pressing TAB in this field advances focus instead 5680 * of inserting the character. Insert tabs only in multi-line editors. 5681 */ shouldAdvanceFocusOnTab()5682 private boolean shouldAdvanceFocusOnTab() { 5683 if (getKeyListener() != null && !mSingleLine && mEditor != null && 5684 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 5685 int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 5686 if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE 5687 || variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) { 5688 return false; 5689 } 5690 } 5691 return true; 5692 } 5693 doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent)5694 private int doKeyDown(int keyCode, KeyEvent event, KeyEvent otherEvent) { 5695 if (!isEnabled()) { 5696 return 0; 5697 } 5698 5699 // If this is the initial keydown, we don't want to prevent a movement away from this view. 5700 // While this shouldn't be necessary because any time we're preventing default movement we 5701 // should be restricting the focus to remain within this view, thus we'll also receive 5702 // the key up event, occasionally key up events will get dropped and we don't want to 5703 // prevent the user from traversing out of this on the next key down. 5704 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 5705 mPreventDefaultMovement = false; 5706 } 5707 5708 switch (keyCode) { 5709 case KeyEvent.KEYCODE_ENTER: 5710 if (event.hasNoModifiers()) { 5711 // When mInputContentType is set, we know that we are 5712 // running in a "modern" cupcake environment, so don't need 5713 // to worry about the application trying to capture 5714 // enter key events. 5715 if (mEditor != null && mEditor.mInputContentType != null) { 5716 // If there is an action listener, given them a 5717 // chance to consume the event. 5718 if (mEditor.mInputContentType.onEditorActionListener != null && 5719 mEditor.mInputContentType.onEditorActionListener.onEditorAction( 5720 this, EditorInfo.IME_NULL, event)) { 5721 mEditor.mInputContentType.enterDown = true; 5722 // We are consuming the enter key for them. 5723 return -1; 5724 } 5725 } 5726 5727 // If our editor should move focus when enter is pressed, or 5728 // this is a generated event from an IME action button, then 5729 // don't let it be inserted into the text. 5730 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 5731 || shouldAdvanceFocusOnEnter()) { 5732 if (hasOnClickListeners()) { 5733 return 0; 5734 } 5735 return -1; 5736 } 5737 } 5738 break; 5739 5740 case KeyEvent.KEYCODE_DPAD_CENTER: 5741 if (event.hasNoModifiers()) { 5742 if (shouldAdvanceFocusOnEnter()) { 5743 return 0; 5744 } 5745 } 5746 break; 5747 5748 case KeyEvent.KEYCODE_TAB: 5749 if (event.hasNoModifiers() || event.hasModifiers(KeyEvent.META_SHIFT_ON)) { 5750 if (shouldAdvanceFocusOnTab()) { 5751 return 0; 5752 } 5753 } 5754 break; 5755 5756 // Has to be done on key down (and not on key up) to correctly be intercepted. 5757 case KeyEvent.KEYCODE_BACK: 5758 if (mEditor != null && mEditor.mSelectionActionMode != null) { 5759 stopSelectionActionMode(); 5760 return -1; 5761 } 5762 break; 5763 } 5764 5765 if (mEditor != null && mEditor.mKeyListener != null) { 5766 boolean doDown = true; 5767 if (otherEvent != null) { 5768 try { 5769 beginBatchEdit(); 5770 final boolean handled = mEditor.mKeyListener.onKeyOther(this, (Editable) mText, 5771 otherEvent); 5772 hideErrorIfUnchanged(); 5773 doDown = false; 5774 if (handled) { 5775 return -1; 5776 } 5777 } catch (AbstractMethodError e) { 5778 // onKeyOther was added after 1.0, so if it isn't 5779 // implemented we need to try to dispatch as a regular down. 5780 } finally { 5781 endBatchEdit(); 5782 } 5783 } 5784 5785 if (doDown) { 5786 beginBatchEdit(); 5787 final boolean handled = mEditor.mKeyListener.onKeyDown(this, (Editable) mText, 5788 keyCode, event); 5789 endBatchEdit(); 5790 hideErrorIfUnchanged(); 5791 if (handled) return 1; 5792 } 5793 } 5794 5795 // bug 650865: sometimes we get a key event before a layout. 5796 // don't try to move around if we don't know the layout. 5797 5798 if (mMovement != null && mLayout != null) { 5799 boolean doDown = true; 5800 if (otherEvent != null) { 5801 try { 5802 boolean handled = mMovement.onKeyOther(this, (Spannable) mText, 5803 otherEvent); 5804 doDown = false; 5805 if (handled) { 5806 return -1; 5807 } 5808 } catch (AbstractMethodError e) { 5809 // onKeyOther was added after 1.0, so if it isn't 5810 // implemented we need to try to dispatch as a regular down. 5811 } 5812 } 5813 if (doDown) { 5814 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) { 5815 if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { 5816 mPreventDefaultMovement = true; 5817 } 5818 return 2; 5819 } 5820 } 5821 } 5822 5823 return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0; 5824 } 5825 5826 /** 5827 * Resets the mErrorWasChanged flag, so that future calls to {@link #setError(CharSequence)} 5828 * can be recorded. 5829 * @hide 5830 */ resetErrorChangedFlag()5831 public void resetErrorChangedFlag() { 5832 /* 5833 * Keep track of what the error was before doing the input 5834 * so that if an input filter changed the error, we leave 5835 * that error showing. Otherwise, we take down whatever 5836 * error was showing when the user types something. 5837 */ 5838 if (mEditor != null) mEditor.mErrorWasChanged = false; 5839 } 5840 5841 /** 5842 * @hide 5843 */ hideErrorIfUnchanged()5844 public void hideErrorIfUnchanged() { 5845 if (mEditor != null && mEditor.mError != null && !mEditor.mErrorWasChanged) { 5846 setError(null, null); 5847 } 5848 } 5849 5850 @Override onKeyUp(int keyCode, KeyEvent event)5851 public boolean onKeyUp(int keyCode, KeyEvent event) { 5852 if (!isEnabled()) { 5853 return super.onKeyUp(keyCode, event); 5854 } 5855 5856 if (!KeyEvent.isModifierKey(keyCode)) { 5857 mPreventDefaultMovement = false; 5858 } 5859 5860 switch (keyCode) { 5861 case KeyEvent.KEYCODE_DPAD_CENTER: 5862 if (event.hasNoModifiers()) { 5863 /* 5864 * If there is a click listener, just call through to 5865 * super, which will invoke it. 5866 * 5867 * If there isn't a click listener, try to show the soft 5868 * input method. (It will also 5869 * call performClick(), but that won't do anything in 5870 * this case.) 5871 */ 5872 if (!hasOnClickListeners()) { 5873 if (mMovement != null && mText instanceof Editable 5874 && mLayout != null && onCheckIsTextEditor()) { 5875 InputMethodManager imm = InputMethodManager.peekInstance(); 5876 viewClicked(imm); 5877 if (imm != null && getShowSoftInputOnFocus()) { 5878 imm.showSoftInput(this, 0); 5879 } 5880 } 5881 } 5882 } 5883 return super.onKeyUp(keyCode, event); 5884 5885 case KeyEvent.KEYCODE_ENTER: 5886 if (event.hasNoModifiers()) { 5887 if (mEditor != null && mEditor.mInputContentType != null 5888 && mEditor.mInputContentType.onEditorActionListener != null 5889 && mEditor.mInputContentType.enterDown) { 5890 mEditor.mInputContentType.enterDown = false; 5891 if (mEditor.mInputContentType.onEditorActionListener.onEditorAction( 5892 this, EditorInfo.IME_NULL, event)) { 5893 return true; 5894 } 5895 } 5896 5897 if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 5898 || shouldAdvanceFocusOnEnter()) { 5899 /* 5900 * If there is a click listener, just call through to 5901 * super, which will invoke it. 5902 * 5903 * If there isn't a click listener, try to advance focus, 5904 * but still call through to super, which will reset the 5905 * pressed state and longpress state. (It will also 5906 * call performClick(), but that won't do anything in 5907 * this case.) 5908 */ 5909 if (!hasOnClickListeners()) { 5910 View v = focusSearch(FOCUS_DOWN); 5911 5912 if (v != null) { 5913 if (!v.requestFocus(FOCUS_DOWN)) { 5914 throw new IllegalStateException( 5915 "focus search returned a view " + 5916 "that wasn't able to take focus!"); 5917 } 5918 5919 /* 5920 * Return true because we handled the key; super 5921 * will return false because there was no click 5922 * listener. 5923 */ 5924 super.onKeyUp(keyCode, event); 5925 return true; 5926 } else if ((event.getFlags() 5927 & KeyEvent.FLAG_EDITOR_ACTION) != 0) { 5928 // No target for next focus, but make sure the IME 5929 // if this came from it. 5930 InputMethodManager imm = InputMethodManager.peekInstance(); 5931 if (imm != null && imm.isActive(this)) { 5932 imm.hideSoftInputFromWindow(getWindowToken(), 0); 5933 } 5934 } 5935 } 5936 } 5937 return super.onKeyUp(keyCode, event); 5938 } 5939 break; 5940 } 5941 5942 if (mEditor != null && mEditor.mKeyListener != null) 5943 if (mEditor.mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event)) 5944 return true; 5945 5946 if (mMovement != null && mLayout != null) 5947 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) 5948 return true; 5949 5950 return super.onKeyUp(keyCode, event); 5951 } 5952 5953 @Override onCheckIsTextEditor()5954 public boolean onCheckIsTextEditor() { 5955 return mEditor != null && mEditor.mInputType != EditorInfo.TYPE_NULL; 5956 } 5957 5958 @Override onCreateInputConnection(EditorInfo outAttrs)5959 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 5960 if (onCheckIsTextEditor() && isEnabled()) { 5961 mEditor.createInputMethodStateIfNeeded(); 5962 outAttrs.inputType = getInputType(); 5963 if (mEditor.mInputContentType != null) { 5964 outAttrs.imeOptions = mEditor.mInputContentType.imeOptions; 5965 outAttrs.privateImeOptions = mEditor.mInputContentType.privateImeOptions; 5966 outAttrs.actionLabel = mEditor.mInputContentType.imeActionLabel; 5967 outAttrs.actionId = mEditor.mInputContentType.imeActionId; 5968 outAttrs.extras = mEditor.mInputContentType.extras; 5969 } else { 5970 outAttrs.imeOptions = EditorInfo.IME_NULL; 5971 } 5972 if (focusSearch(FOCUS_DOWN) != null) { 5973 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; 5974 } 5975 if (focusSearch(FOCUS_UP) != null) { 5976 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS; 5977 } 5978 if ((outAttrs.imeOptions&EditorInfo.IME_MASK_ACTION) 5979 == EditorInfo.IME_ACTION_UNSPECIFIED) { 5980 if ((outAttrs.imeOptions&EditorInfo.IME_FLAG_NAVIGATE_NEXT) != 0) { 5981 // An action has not been set, but the enter key will move to 5982 // the next focus, so set the action to that. 5983 outAttrs.imeOptions |= EditorInfo.IME_ACTION_NEXT; 5984 } else { 5985 // An action has not been set, and there is no focus to move 5986 // to, so let's just supply a "done" action. 5987 outAttrs.imeOptions |= EditorInfo.IME_ACTION_DONE; 5988 } 5989 if (!shouldAdvanceFocusOnEnter()) { 5990 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 5991 } 5992 } 5993 if (isMultilineInputType(outAttrs.inputType)) { 5994 // Multi-line text editors should always show an enter key. 5995 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_ENTER_ACTION; 5996 } 5997 outAttrs.hintText = mHint; 5998 if (mText instanceof Editable) { 5999 InputConnection ic = new EditableInputConnection(this); 6000 outAttrs.initialSelStart = getSelectionStart(); 6001 outAttrs.initialSelEnd = getSelectionEnd(); 6002 outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType()); 6003 return ic; 6004 } 6005 } 6006 return null; 6007 } 6008 6009 /** 6010 * If this TextView contains editable content, extract a portion of it 6011 * based on the information in <var>request</var> in to <var>outText</var>. 6012 * @return Returns true if the text was successfully extracted, else false. 6013 */ extractText(ExtractedTextRequest request, ExtractedText outText)6014 public boolean extractText(ExtractedTextRequest request, ExtractedText outText) { 6015 createEditorIfNeeded(); 6016 return mEditor.extractText(request, outText); 6017 } 6018 6019 /** 6020 * This is used to remove all style-impacting spans from text before new 6021 * extracted text is being replaced into it, so that we don't have any 6022 * lingering spans applied during the replace. 6023 */ removeParcelableSpans(Spannable spannable, int start, int end)6024 static void removeParcelableSpans(Spannable spannable, int start, int end) { 6025 Object[] spans = spannable.getSpans(start, end, ParcelableSpan.class); 6026 int i = spans.length; 6027 while (i > 0) { 6028 i--; 6029 spannable.removeSpan(spans[i]); 6030 } 6031 } 6032 6033 /** 6034 * Apply to this text view the given extracted text, as previously 6035 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 6036 */ setExtractedText(ExtractedText text)6037 public void setExtractedText(ExtractedText text) { 6038 Editable content = getEditableText(); 6039 if (text.text != null) { 6040 if (content == null) { 6041 setText(text.text, TextView.BufferType.EDITABLE); 6042 } else if (text.partialStartOffset < 0) { 6043 removeParcelableSpans(content, 0, content.length()); 6044 content.replace(0, content.length(), text.text); 6045 } else { 6046 final int N = content.length(); 6047 int start = text.partialStartOffset; 6048 if (start > N) start = N; 6049 int end = text.partialEndOffset; 6050 if (end > N) end = N; 6051 removeParcelableSpans(content, start, end); 6052 content.replace(start, end, text.text); 6053 } 6054 } 6055 6056 // Now set the selection position... make sure it is in range, to 6057 // avoid crashes. If this is a partial update, it is possible that 6058 // the underlying text may have changed, causing us problems here. 6059 // Also we just don't want to trust clients to do the right thing. 6060 Spannable sp = (Spannable)getText(); 6061 final int N = sp.length(); 6062 int start = text.selectionStart; 6063 if (start < 0) start = 0; 6064 else if (start > N) start = N; 6065 int end = text.selectionEnd; 6066 if (end < 0) end = 0; 6067 else if (end > N) end = N; 6068 Selection.setSelection(sp, start, end); 6069 6070 // Finally, update the selection mode. 6071 if ((text.flags&ExtractedText.FLAG_SELECTING) != 0) { 6072 MetaKeyKeyListener.startSelecting(this, sp); 6073 } else { 6074 MetaKeyKeyListener.stopSelecting(this, sp); 6075 } 6076 } 6077 6078 /** 6079 * @hide 6080 */ setExtracting(ExtractedTextRequest req)6081 public void setExtracting(ExtractedTextRequest req) { 6082 if (mEditor.mInputMethodState != null) { 6083 mEditor.mInputMethodState.mExtractedTextRequest = req; 6084 } 6085 // This would stop a possible selection mode, but no such mode is started in case 6086 // extracted mode will start. Some text is selected though, and will trigger an action mode 6087 // in the extracted view. 6088 mEditor.hideControllers(); 6089 } 6090 6091 /** 6092 * Called by the framework in response to a text completion from 6093 * the current input method, provided by it calling 6094 * {@link InputConnection#commitCompletion 6095 * InputConnection.commitCompletion()}. The default implementation does 6096 * nothing; text views that are supporting auto-completion should override 6097 * this to do their desired behavior. 6098 * 6099 * @param text The auto complete text the user has selected. 6100 */ onCommitCompletion(CompletionInfo text)6101 public void onCommitCompletion(CompletionInfo text) { 6102 // intentionally empty 6103 } 6104 6105 /** 6106 * Called by the framework in response to a text auto-correction (such as fixing a typo using a 6107 * a dictionnary) from the current input method, provided by it calling 6108 * {@link InputConnection#commitCorrection} InputConnection.commitCorrection()}. The default 6109 * implementation flashes the background of the corrected word to provide feedback to the user. 6110 * 6111 * @param info The auto correct info about the text that was corrected. 6112 */ onCommitCorrection(CorrectionInfo info)6113 public void onCommitCorrection(CorrectionInfo info) { 6114 if (mEditor != null) mEditor.onCommitCorrection(info); 6115 } 6116 beginBatchEdit()6117 public void beginBatchEdit() { 6118 if (mEditor != null) mEditor.beginBatchEdit(); 6119 } 6120 endBatchEdit()6121 public void endBatchEdit() { 6122 if (mEditor != null) mEditor.endBatchEdit(); 6123 } 6124 6125 /** 6126 * Called by the framework in response to a request to begin a batch 6127 * of edit operations through a call to link {@link #beginBatchEdit()}. 6128 */ onBeginBatchEdit()6129 public void onBeginBatchEdit() { 6130 // intentionally empty 6131 } 6132 6133 /** 6134 * Called by the framework in response to a request to end a batch 6135 * of edit operations through a call to link {@link #endBatchEdit}. 6136 */ onEndBatchEdit()6137 public void onEndBatchEdit() { 6138 // intentionally empty 6139 } 6140 6141 /** 6142 * Called by the framework in response to a private command from the 6143 * current method, provided by it calling 6144 * {@link InputConnection#performPrivateCommand 6145 * InputConnection.performPrivateCommand()}. 6146 * 6147 * @param action The action name of the command. 6148 * @param data Any additional data for the command. This may be null. 6149 * @return Return true if you handled the command, else false. 6150 */ onPrivateIMECommand(String action, Bundle data)6151 public boolean onPrivateIMECommand(String action, Bundle data) { 6152 return false; 6153 } 6154 nullLayouts()6155 private void nullLayouts() { 6156 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 6157 mSavedLayout = (BoringLayout) mLayout; 6158 } 6159 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 6160 mSavedHintLayout = (BoringLayout) mHintLayout; 6161 } 6162 6163 mSavedMarqueeModeLayout = mLayout = mHintLayout = null; 6164 6165 mBoring = mHintBoring = null; 6166 6167 // Since it depends on the value of mLayout 6168 if (mEditor != null) mEditor.prepareCursorControllers(); 6169 } 6170 6171 /** 6172 * Make a new Layout based on the already-measured size of the view, 6173 * on the assumption that it was measured correctly at some point. 6174 */ assumeLayout()6175 private void assumeLayout() { 6176 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6177 6178 if (width < 1) { 6179 width = 0; 6180 } 6181 6182 int physicalWidth = width; 6183 6184 if (mHorizontallyScrolling) { 6185 width = VERY_WIDE; 6186 } 6187 6188 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 6189 physicalWidth, false); 6190 } 6191 getLayoutAlignment()6192 private Layout.Alignment getLayoutAlignment() { 6193 Layout.Alignment alignment; 6194 switch (getTextAlignment()) { 6195 case TEXT_ALIGNMENT_GRAVITY: 6196 switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { 6197 case Gravity.START: 6198 alignment = Layout.Alignment.ALIGN_NORMAL; 6199 break; 6200 case Gravity.END: 6201 alignment = Layout.Alignment.ALIGN_OPPOSITE; 6202 break; 6203 case Gravity.LEFT: 6204 alignment = Layout.Alignment.ALIGN_LEFT; 6205 break; 6206 case Gravity.RIGHT: 6207 alignment = Layout.Alignment.ALIGN_RIGHT; 6208 break; 6209 case Gravity.CENTER_HORIZONTAL: 6210 alignment = Layout.Alignment.ALIGN_CENTER; 6211 break; 6212 default: 6213 alignment = Layout.Alignment.ALIGN_NORMAL; 6214 break; 6215 } 6216 break; 6217 case TEXT_ALIGNMENT_TEXT_START: 6218 alignment = Layout.Alignment.ALIGN_NORMAL; 6219 break; 6220 case TEXT_ALIGNMENT_TEXT_END: 6221 alignment = Layout.Alignment.ALIGN_OPPOSITE; 6222 break; 6223 case TEXT_ALIGNMENT_CENTER: 6224 alignment = Layout.Alignment.ALIGN_CENTER; 6225 break; 6226 case TEXT_ALIGNMENT_VIEW_START: 6227 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 6228 Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; 6229 break; 6230 case TEXT_ALIGNMENT_VIEW_END: 6231 alignment = (getLayoutDirection() == LAYOUT_DIRECTION_RTL) ? 6232 Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; 6233 break; 6234 case TEXT_ALIGNMENT_INHERIT: 6235 // This should never happen as we have already resolved the text alignment 6236 // but better safe than sorry so we just fall through 6237 default: 6238 alignment = Layout.Alignment.ALIGN_NORMAL; 6239 break; 6240 } 6241 return alignment; 6242 } 6243 6244 /** 6245 * The width passed in is now the desired layout width, 6246 * not the full view width with padding. 6247 * {@hide} 6248 */ makeNewLayout(int wantWidth, int hintWidth, BoringLayout.Metrics boring, BoringLayout.Metrics hintBoring, int ellipsisWidth, boolean bringIntoView)6249 protected void makeNewLayout(int wantWidth, int hintWidth, 6250 BoringLayout.Metrics boring, 6251 BoringLayout.Metrics hintBoring, 6252 int ellipsisWidth, boolean bringIntoView) { 6253 stopMarquee(); 6254 6255 // Update "old" cached values 6256 mOldMaximum = mMaximum; 6257 mOldMaxMode = mMaxMode; 6258 6259 mHighlightPathBogus = true; 6260 6261 if (wantWidth < 0) { 6262 wantWidth = 0; 6263 } 6264 if (hintWidth < 0) { 6265 hintWidth = 0; 6266 } 6267 6268 Layout.Alignment alignment = getLayoutAlignment(); 6269 final boolean testDirChange = mSingleLine && mLayout != null && 6270 (alignment == Layout.Alignment.ALIGN_NORMAL || 6271 alignment == Layout.Alignment.ALIGN_OPPOSITE); 6272 int oldDir = 0; 6273 if (testDirChange) oldDir = mLayout.getParagraphDirection(0); 6274 boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null; 6275 final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE && 6276 mMarqueeFadeMode != MARQUEE_FADE_NORMAL; 6277 TruncateAt effectiveEllipsize = mEllipsize; 6278 if (mEllipsize == TruncateAt.MARQUEE && 6279 mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 6280 effectiveEllipsize = TruncateAt.END_SMALL; 6281 } 6282 6283 if (mTextDir == null) { 6284 mTextDir = getTextDirectionHeuristic(); 6285 } 6286 6287 mLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, shouldEllipsize, 6288 effectiveEllipsize, effectiveEllipsize == mEllipsize); 6289 if (switchEllipsize) { 6290 TruncateAt oppositeEllipsize = effectiveEllipsize == TruncateAt.MARQUEE ? 6291 TruncateAt.END : TruncateAt.MARQUEE; 6292 mSavedMarqueeModeLayout = makeSingleLayout(wantWidth, boring, ellipsisWidth, alignment, 6293 shouldEllipsize, oppositeEllipsize, effectiveEllipsize != mEllipsize); 6294 } 6295 6296 shouldEllipsize = mEllipsize != null; 6297 mHintLayout = null; 6298 6299 if (mHint != null) { 6300 if (shouldEllipsize) hintWidth = wantWidth; 6301 6302 if (hintBoring == UNKNOWN_BORING) { 6303 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, 6304 mHintBoring); 6305 if (hintBoring != null) { 6306 mHintBoring = hintBoring; 6307 } 6308 } 6309 6310 if (hintBoring != null) { 6311 if (hintBoring.width <= hintWidth && 6312 (!shouldEllipsize || hintBoring.width <= ellipsisWidth)) { 6313 if (mSavedHintLayout != null) { 6314 mHintLayout = mSavedHintLayout. 6315 replaceOrMake(mHint, mTextPaint, 6316 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6317 hintBoring, mIncludePad); 6318 } else { 6319 mHintLayout = BoringLayout.make(mHint, mTextPaint, 6320 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6321 hintBoring, mIncludePad); 6322 } 6323 6324 mSavedHintLayout = (BoringLayout) mHintLayout; 6325 } else if (shouldEllipsize && hintBoring.width <= hintWidth) { 6326 if (mSavedHintLayout != null) { 6327 mHintLayout = mSavedHintLayout. 6328 replaceOrMake(mHint, mTextPaint, 6329 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6330 hintBoring, mIncludePad, mEllipsize, 6331 ellipsisWidth); 6332 } else { 6333 mHintLayout = BoringLayout.make(mHint, mTextPaint, 6334 hintWidth, alignment, mSpacingMult, mSpacingAdd, 6335 hintBoring, mIncludePad, mEllipsize, 6336 ellipsisWidth); 6337 } 6338 } else if (shouldEllipsize) { 6339 mHintLayout = new StaticLayout(mHint, 6340 0, mHint.length(), 6341 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, 6342 mSpacingAdd, mIncludePad, mEllipsize, 6343 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6344 } else { 6345 mHintLayout = new StaticLayout(mHint, mTextPaint, 6346 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6347 mIncludePad); 6348 } 6349 } else if (shouldEllipsize) { 6350 mHintLayout = new StaticLayout(mHint, 6351 0, mHint.length(), 6352 mTextPaint, hintWidth, alignment, mTextDir, mSpacingMult, 6353 mSpacingAdd, mIncludePad, mEllipsize, 6354 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6355 } else { 6356 mHintLayout = new StaticLayout(mHint, mTextPaint, 6357 hintWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6358 mIncludePad); 6359 } 6360 } 6361 6362 if (bringIntoView || (testDirChange && oldDir != mLayout.getParagraphDirection(0))) { 6363 registerForPreDraw(); 6364 } 6365 6366 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 6367 if (!compressText(ellipsisWidth)) { 6368 final int height = mLayoutParams.height; 6369 // If the size of the view does not depend on the size of the text, try to 6370 // start the marquee immediately 6371 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.MATCH_PARENT) { 6372 startMarquee(); 6373 } else { 6374 // Defer the start of the marquee until we know our width (see setFrame()) 6375 mRestartMarquee = true; 6376 } 6377 } 6378 } 6379 6380 // CursorControllers need a non-null mLayout 6381 if (mEditor != null) mEditor.prepareCursorControllers(); 6382 } 6383 makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, boolean useSaved)6384 private Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth, 6385 Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize, 6386 boolean useSaved) { 6387 Layout result = null; 6388 if (mText instanceof Spannable) { 6389 result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth, 6390 alignment, mTextDir, mSpacingMult, 6391 mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null, 6392 ellipsisWidth); 6393 } else { 6394 if (boring == UNKNOWN_BORING) { 6395 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 6396 if (boring != null) { 6397 mBoring = boring; 6398 } 6399 } 6400 6401 if (boring != null) { 6402 if (boring.width <= wantWidth && 6403 (effectiveEllipsize == null || boring.width <= ellipsisWidth)) { 6404 if (useSaved && mSavedLayout != null) { 6405 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 6406 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6407 boring, mIncludePad); 6408 } else { 6409 result = BoringLayout.make(mTransformed, mTextPaint, 6410 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6411 boring, mIncludePad); 6412 } 6413 6414 if (useSaved) { 6415 mSavedLayout = (BoringLayout) result; 6416 } 6417 } else if (shouldEllipsize && boring.width <= wantWidth) { 6418 if (useSaved && mSavedLayout != null) { 6419 result = mSavedLayout.replaceOrMake(mTransformed, mTextPaint, 6420 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6421 boring, mIncludePad, effectiveEllipsize, 6422 ellipsisWidth); 6423 } else { 6424 result = BoringLayout.make(mTransformed, mTextPaint, 6425 wantWidth, alignment, mSpacingMult, mSpacingAdd, 6426 boring, mIncludePad, effectiveEllipsize, 6427 ellipsisWidth); 6428 } 6429 } else if (shouldEllipsize) { 6430 result = new StaticLayout(mTransformed, 6431 0, mTransformed.length(), 6432 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, 6433 mSpacingAdd, mIncludePad, effectiveEllipsize, 6434 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6435 } else { 6436 result = new StaticLayout(mTransformed, mTextPaint, 6437 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6438 mIncludePad); 6439 } 6440 } else if (shouldEllipsize) { 6441 result = new StaticLayout(mTransformed, 6442 0, mTransformed.length(), 6443 mTextPaint, wantWidth, alignment, mTextDir, mSpacingMult, 6444 mSpacingAdd, mIncludePad, effectiveEllipsize, 6445 ellipsisWidth, mMaxMode == LINES ? mMaximum : Integer.MAX_VALUE); 6446 } else { 6447 result = new StaticLayout(mTransformed, mTextPaint, 6448 wantWidth, alignment, mTextDir, mSpacingMult, mSpacingAdd, 6449 mIncludePad); 6450 } 6451 } 6452 return result; 6453 } 6454 compressText(float width)6455 private boolean compressText(float width) { 6456 if (isHardwareAccelerated()) return false; 6457 6458 // Only compress the text if it hasn't been compressed by the previous pass 6459 if (width > 0.0f && mLayout != null && getLineCount() == 1 && !mUserSetTextScaleX && 6460 mTextPaint.getTextScaleX() == 1.0f) { 6461 final float textWidth = mLayout.getLineWidth(0); 6462 final float overflow = (textWidth + 1.0f - width) / width; 6463 if (overflow > 0.0f && overflow <= Marquee.MARQUEE_DELTA_MAX) { 6464 mTextPaint.setTextScaleX(1.0f - overflow - 0.005f); 6465 post(new Runnable() { 6466 public void run() { 6467 requestLayout(); 6468 } 6469 }); 6470 return true; 6471 } 6472 } 6473 6474 return false; 6475 } 6476 desired(Layout layout)6477 private static int desired(Layout layout) { 6478 int n = layout.getLineCount(); 6479 CharSequence text = layout.getText(); 6480 float max = 0; 6481 6482 // if any line was wrapped, we can't use it. 6483 // but it's ok for the last line not to have a newline 6484 6485 for (int i = 0; i < n - 1; i++) { 6486 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') 6487 return -1; 6488 } 6489 6490 for (int i = 0; i < n; i++) { 6491 max = Math.max(max, layout.getLineWidth(i)); 6492 } 6493 6494 return (int) FloatMath.ceil(max); 6495 } 6496 6497 /** 6498 * Set whether the TextView includes extra top and bottom padding to make 6499 * room for accents that go above the normal ascent and descent. 6500 * The default is true. 6501 * 6502 * @see #getIncludeFontPadding() 6503 * 6504 * @attr ref android.R.styleable#TextView_includeFontPadding 6505 */ setIncludeFontPadding(boolean includepad)6506 public void setIncludeFontPadding(boolean includepad) { 6507 if (mIncludePad != includepad) { 6508 mIncludePad = includepad; 6509 6510 if (mLayout != null) { 6511 nullLayouts(); 6512 requestLayout(); 6513 invalidate(); 6514 } 6515 } 6516 } 6517 6518 /** 6519 * Gets whether the TextView includes extra top and bottom padding to make 6520 * room for accents that go above the normal ascent and descent. 6521 * 6522 * @see #setIncludeFontPadding(boolean) 6523 * 6524 * @attr ref android.R.styleable#TextView_includeFontPadding 6525 */ getIncludeFontPadding()6526 public boolean getIncludeFontPadding() { 6527 return mIncludePad; 6528 } 6529 6530 private static final BoringLayout.Metrics UNKNOWN_BORING = new BoringLayout.Metrics(); 6531 6532 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)6533 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 6534 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 6535 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 6536 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 6537 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 6538 6539 int width; 6540 int height; 6541 6542 BoringLayout.Metrics boring = UNKNOWN_BORING; 6543 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 6544 6545 if (mTextDir == null) { 6546 mTextDir = getTextDirectionHeuristic(); 6547 } 6548 6549 int des = -1; 6550 boolean fromexisting = false; 6551 6552 if (widthMode == MeasureSpec.EXACTLY) { 6553 // Parent has told us how big to be. So be it. 6554 width = widthSize; 6555 } else { 6556 if (mLayout != null && mEllipsize == null) { 6557 des = desired(mLayout); 6558 } 6559 6560 if (des < 0) { 6561 boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring); 6562 if (boring != null) { 6563 mBoring = boring; 6564 } 6565 } else { 6566 fromexisting = true; 6567 } 6568 6569 if (boring == null || boring == UNKNOWN_BORING) { 6570 if (des < 0) { 6571 des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint)); 6572 } 6573 width = des; 6574 } else { 6575 width = boring.width; 6576 } 6577 6578 final Drawables dr = mDrawables; 6579 if (dr != null) { 6580 width = Math.max(width, dr.mDrawableWidthTop); 6581 width = Math.max(width, dr.mDrawableWidthBottom); 6582 } 6583 6584 if (mHint != null) { 6585 int hintDes = -1; 6586 int hintWidth; 6587 6588 if (mHintLayout != null && mEllipsize == null) { 6589 hintDes = desired(mHintLayout); 6590 } 6591 6592 if (hintDes < 0) { 6593 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring); 6594 if (hintBoring != null) { 6595 mHintBoring = hintBoring; 6596 } 6597 } 6598 6599 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 6600 if (hintDes < 0) { 6601 hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint)); 6602 } 6603 hintWidth = hintDes; 6604 } else { 6605 hintWidth = hintBoring.width; 6606 } 6607 6608 if (hintWidth > width) { 6609 width = hintWidth; 6610 } 6611 } 6612 6613 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 6614 6615 if (mMaxWidthMode == EMS) { 6616 width = Math.min(width, mMaxWidth * getLineHeight()); 6617 } else { 6618 width = Math.min(width, mMaxWidth); 6619 } 6620 6621 if (mMinWidthMode == EMS) { 6622 width = Math.max(width, mMinWidth * getLineHeight()); 6623 } else { 6624 width = Math.max(width, mMinWidth); 6625 } 6626 6627 // Check against our minimum width 6628 width = Math.max(width, getSuggestedMinimumWidth()); 6629 6630 if (widthMode == MeasureSpec.AT_MOST) { 6631 width = Math.min(widthSize, width); 6632 } 6633 } 6634 6635 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6636 int unpaddedWidth = want; 6637 6638 if (mHorizontallyScrolling) want = VERY_WIDE; 6639 6640 int hintWant = want; 6641 int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth(); 6642 6643 if (mLayout == null) { 6644 makeNewLayout(want, hintWant, boring, hintBoring, 6645 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 6646 } else { 6647 final boolean layoutChanged = (mLayout.getWidth() != want) || 6648 (hintWidth != hintWant) || 6649 (mLayout.getEllipsizedWidth() != 6650 width - getCompoundPaddingLeft() - getCompoundPaddingRight()); 6651 6652 final boolean widthChanged = (mHint == null) && 6653 (mEllipsize == null) && 6654 (want > mLayout.getWidth()) && 6655 (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want)); 6656 6657 final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum); 6658 6659 if (layoutChanged || maximumChanged) { 6660 if (!maximumChanged && widthChanged) { 6661 mLayout.increaseWidthTo(want); 6662 } else { 6663 makeNewLayout(want, hintWant, boring, hintBoring, 6664 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 6665 } 6666 } else { 6667 // Nothing has changed 6668 } 6669 } 6670 6671 if (heightMode == MeasureSpec.EXACTLY) { 6672 // Parent has told us how big to be. So be it. 6673 height = heightSize; 6674 mDesiredHeightAtMeasure = -1; 6675 } else { 6676 int desired = getDesiredHeight(); 6677 6678 height = desired; 6679 mDesiredHeightAtMeasure = desired; 6680 6681 if (heightMode == MeasureSpec.AT_MOST) { 6682 height = Math.min(desired, heightSize); 6683 } 6684 } 6685 6686 int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom(); 6687 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 6688 unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum)); 6689 } 6690 6691 /* 6692 * We didn't let makeNewLayout() register to bring the cursor into view, 6693 * so do it here if there is any possibility that it is needed. 6694 */ 6695 if (mMovement != null || 6696 mLayout.getWidth() > unpaddedWidth || 6697 mLayout.getHeight() > unpaddedHeight) { 6698 registerForPreDraw(); 6699 } else { 6700 scrollTo(0, 0); 6701 } 6702 6703 setMeasuredDimension(width, height); 6704 } 6705 getDesiredHeight()6706 private int getDesiredHeight() { 6707 return Math.max( 6708 getDesiredHeight(mLayout, true), 6709 getDesiredHeight(mHintLayout, mEllipsize != null)); 6710 } 6711 getDesiredHeight(Layout layout, boolean cap)6712 private int getDesiredHeight(Layout layout, boolean cap) { 6713 if (layout == null) { 6714 return 0; 6715 } 6716 6717 int linecount = layout.getLineCount(); 6718 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); 6719 int desired = layout.getLineTop(linecount); 6720 6721 final Drawables dr = mDrawables; 6722 if (dr != null) { 6723 desired = Math.max(desired, dr.mDrawableHeightLeft); 6724 desired = Math.max(desired, dr.mDrawableHeightRight); 6725 } 6726 6727 desired += pad; 6728 6729 if (mMaxMode == LINES) { 6730 /* 6731 * Don't cap the hint to a certain number of lines. 6732 * (Do cap it, though, if we have a maximum pixel height.) 6733 */ 6734 if (cap) { 6735 if (linecount > mMaximum) { 6736 desired = layout.getLineTop(mMaximum); 6737 6738 if (dr != null) { 6739 desired = Math.max(desired, dr.mDrawableHeightLeft); 6740 desired = Math.max(desired, dr.mDrawableHeightRight); 6741 } 6742 6743 desired += pad; 6744 linecount = mMaximum; 6745 } 6746 } 6747 } else { 6748 desired = Math.min(desired, mMaximum); 6749 } 6750 6751 if (mMinMode == LINES) { 6752 if (linecount < mMinimum) { 6753 desired += getLineHeight() * (mMinimum - linecount); 6754 } 6755 } else { 6756 desired = Math.max(desired, mMinimum); 6757 } 6758 6759 // Check against our minimum height 6760 desired = Math.max(desired, getSuggestedMinimumHeight()); 6761 6762 return desired; 6763 } 6764 6765 /** 6766 * Check whether a change to the existing text layout requires a 6767 * new view layout. 6768 */ checkForResize()6769 private void checkForResize() { 6770 boolean sizeChanged = false; 6771 6772 if (mLayout != null) { 6773 // Check if our width changed 6774 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 6775 sizeChanged = true; 6776 invalidate(); 6777 } 6778 6779 // Check if our height changed 6780 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 6781 int desiredHeight = getDesiredHeight(); 6782 6783 if (desiredHeight != this.getHeight()) { 6784 sizeChanged = true; 6785 } 6786 } else if (mLayoutParams.height == LayoutParams.MATCH_PARENT) { 6787 if (mDesiredHeightAtMeasure >= 0) { 6788 int desiredHeight = getDesiredHeight(); 6789 6790 if (desiredHeight != mDesiredHeightAtMeasure) { 6791 sizeChanged = true; 6792 } 6793 } 6794 } 6795 } 6796 6797 if (sizeChanged) { 6798 requestLayout(); 6799 // caller will have already invalidated 6800 } 6801 } 6802 6803 /** 6804 * Check whether entirely new text requires a new view layout 6805 * or merely a new text layout. 6806 */ checkForRelayout()6807 private void checkForRelayout() { 6808 // If we have a fixed width, we can just swap in a new text layout 6809 // if the text height stays the same or if the view height is fixed. 6810 6811 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || 6812 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && 6813 (mHint == null || mHintLayout != null) && 6814 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 6815 // Static width, so try making a new text layout. 6816 6817 int oldht = mLayout.getHeight(); 6818 int want = mLayout.getWidth(); 6819 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 6820 6821 /* 6822 * No need to bring the text into view, since the size is not 6823 * changing (unless we do the requestLayout(), in which case it 6824 * will happen at measure). 6825 */ 6826 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 6827 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), 6828 false); 6829 6830 if (mEllipsize != TextUtils.TruncateAt.MARQUEE) { 6831 // In a fixed-height view, so use our new text layout. 6832 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && 6833 mLayoutParams.height != LayoutParams.MATCH_PARENT) { 6834 invalidate(); 6835 return; 6836 } 6837 6838 // Dynamic height, but height has stayed the same, 6839 // so use our new text layout. 6840 if (mLayout.getHeight() == oldht && 6841 (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 6842 invalidate(); 6843 return; 6844 } 6845 } 6846 6847 // We lose: the height has changed and we have a dynamic height. 6848 // Request a new view layout using our new text layout. 6849 requestLayout(); 6850 invalidate(); 6851 } else { 6852 // Dynamic width, so we have no choice but to request a new 6853 // view layout with a new text layout. 6854 nullLayouts(); 6855 requestLayout(); 6856 invalidate(); 6857 } 6858 } 6859 6860 @Override onLayout(boolean changed, int left, int top, int right, int bottom)6861 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 6862 super.onLayout(changed, left, top, right, bottom); 6863 if (mDeferScroll >= 0) { 6864 int curs = mDeferScroll; 6865 mDeferScroll = -1; 6866 bringPointIntoView(Math.min(curs, mText.length())); 6867 } 6868 } 6869 isShowingHint()6870 private boolean isShowingHint() { 6871 return TextUtils.isEmpty(mText) && !TextUtils.isEmpty(mHint); 6872 } 6873 6874 /** 6875 * Returns true if anything changed. 6876 */ bringTextIntoView()6877 private boolean bringTextIntoView() { 6878 Layout layout = isShowingHint() ? mHintLayout : mLayout; 6879 int line = 0; 6880 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6881 line = layout.getLineCount() - 1; 6882 } 6883 6884 Layout.Alignment a = layout.getParagraphAlignment(line); 6885 int dir = layout.getParagraphDirection(line); 6886 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 6887 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 6888 int ht = layout.getHeight(); 6889 6890 int scrollx, scrolly; 6891 6892 // Convert to left, center, or right alignment. 6893 if (a == Layout.Alignment.ALIGN_NORMAL) { 6894 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_LEFT : 6895 Layout.Alignment.ALIGN_RIGHT; 6896 } else if (a == Layout.Alignment.ALIGN_OPPOSITE){ 6897 a = dir == Layout.DIR_LEFT_TO_RIGHT ? Layout.Alignment.ALIGN_RIGHT : 6898 Layout.Alignment.ALIGN_LEFT; 6899 } 6900 6901 if (a == Layout.Alignment.ALIGN_CENTER) { 6902 /* 6903 * Keep centered if possible, or, if it is too wide to fit, 6904 * keep leading edge in view. 6905 */ 6906 6907 int left = (int) FloatMath.floor(layout.getLineLeft(line)); 6908 int right = (int) FloatMath.ceil(layout.getLineRight(line)); 6909 6910 if (right - left < hspace) { 6911 scrollx = (right + left) / 2 - hspace / 2; 6912 } else { 6913 if (dir < 0) { 6914 scrollx = right - hspace; 6915 } else { 6916 scrollx = left; 6917 } 6918 } 6919 } else if (a == Layout.Alignment.ALIGN_RIGHT) { 6920 int right = (int) FloatMath.ceil(layout.getLineRight(line)); 6921 scrollx = right - hspace; 6922 } else { // a == Layout.Alignment.ALIGN_LEFT (will also be the default) 6923 scrollx = (int) FloatMath.floor(layout.getLineLeft(line)); 6924 } 6925 6926 if (ht < vspace) { 6927 scrolly = 0; 6928 } else { 6929 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 6930 scrolly = ht - vspace; 6931 } else { 6932 scrolly = 0; 6933 } 6934 } 6935 6936 if (scrollx != mScrollX || scrolly != mScrollY) { 6937 scrollTo(scrollx, scrolly); 6938 return true; 6939 } else { 6940 return false; 6941 } 6942 } 6943 6944 /** 6945 * Move the point, specified by the offset, into the view if it is needed. 6946 * This has to be called after layout. Returns true if anything changed. 6947 */ bringPointIntoView(int offset)6948 public boolean bringPointIntoView(int offset) { 6949 if (isLayoutRequested()) { 6950 mDeferScroll = offset; 6951 return false; 6952 } 6953 boolean changed = false; 6954 6955 Layout layout = isShowingHint() ? mHintLayout: mLayout; 6956 6957 if (layout == null) return changed; 6958 6959 int line = layout.getLineForOffset(offset); 6960 6961 int grav; 6962 6963 switch (layout.getParagraphAlignment(line)) { 6964 case ALIGN_LEFT: 6965 grav = 1; 6966 break; 6967 case ALIGN_RIGHT: 6968 grav = -1; 6969 break; 6970 case ALIGN_NORMAL: 6971 grav = layout.getParagraphDirection(line); 6972 break; 6973 case ALIGN_OPPOSITE: 6974 grav = -layout.getParagraphDirection(line); 6975 break; 6976 case ALIGN_CENTER: 6977 default: 6978 grav = 0; 6979 break; 6980 } 6981 6982 // We only want to clamp the cursor to fit within the layout width 6983 // in left-to-right modes, because in a right to left alignment, 6984 // we want to scroll to keep the line-right on the screen, as other 6985 // lines are likely to have text flush with the right margin, which 6986 // we want to keep visible. 6987 // A better long-term solution would probably be to measure both 6988 // the full line and a blank-trimmed version, and, for example, use 6989 // the latter measurement for centering and right alignment, but for 6990 // the time being we only implement the cursor clamping in left to 6991 // right where it is most likely to be annoying. 6992 final boolean clamped = grav > 0; 6993 // FIXME: Is it okay to truncate this, or should we round? 6994 final int x = (int)layout.getPrimaryHorizontal(offset, clamped); 6995 final int top = layout.getLineTop(line); 6996 final int bottom = layout.getLineTop(line + 1); 6997 6998 int left = (int) FloatMath.floor(layout.getLineLeft(line)); 6999 int right = (int) FloatMath.ceil(layout.getLineRight(line)); 7000 int ht = layout.getHeight(); 7001 7002 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7003 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 7004 if (!mHorizontallyScrolling && right - left > hspace && right > x) { 7005 // If cursor has been clamped, make sure we don't scroll. 7006 right = Math.max(x, left + hspace); 7007 } 7008 7009 int hslack = (bottom - top) / 2; 7010 int vslack = hslack; 7011 7012 if (vslack > vspace / 4) 7013 vslack = vspace / 4; 7014 if (hslack > hspace / 4) 7015 hslack = hspace / 4; 7016 7017 int hs = mScrollX; 7018 int vs = mScrollY; 7019 7020 if (top - vs < vslack) 7021 vs = top - vslack; 7022 if (bottom - vs > vspace - vslack) 7023 vs = bottom - (vspace - vslack); 7024 if (ht - vs < vspace) 7025 vs = ht - vspace; 7026 if (0 - vs > 0) 7027 vs = 0; 7028 7029 if (grav != 0) { 7030 if (x - hs < hslack) { 7031 hs = x - hslack; 7032 } 7033 if (x - hs > hspace - hslack) { 7034 hs = x - (hspace - hslack); 7035 } 7036 } 7037 7038 if (grav < 0) { 7039 if (left - hs > 0) 7040 hs = left; 7041 if (right - hs < hspace) 7042 hs = right - hspace; 7043 } else if (grav > 0) { 7044 if (right - hs < hspace) 7045 hs = right - hspace; 7046 if (left - hs > 0) 7047 hs = left; 7048 } else /* grav == 0 */ { 7049 if (right - left <= hspace) { 7050 /* 7051 * If the entire text fits, center it exactly. 7052 */ 7053 hs = left - (hspace - (right - left)) / 2; 7054 } else if (x > right - hslack) { 7055 /* 7056 * If we are near the right edge, keep the right edge 7057 * at the edge of the view. 7058 */ 7059 hs = right - hspace; 7060 } else if (x < left + hslack) { 7061 /* 7062 * If we are near the left edge, keep the left edge 7063 * at the edge of the view. 7064 */ 7065 hs = left; 7066 } else if (left > hs) { 7067 /* 7068 * Is there whitespace visible at the left? Fix it if so. 7069 */ 7070 hs = left; 7071 } else if (right < hs + hspace) { 7072 /* 7073 * Is there whitespace visible at the right? Fix it if so. 7074 */ 7075 hs = right - hspace; 7076 } else { 7077 /* 7078 * Otherwise, float as needed. 7079 */ 7080 if (x - hs < hslack) { 7081 hs = x - hslack; 7082 } 7083 if (x - hs > hspace - hslack) { 7084 hs = x - (hspace - hslack); 7085 } 7086 } 7087 } 7088 7089 if (hs != mScrollX || vs != mScrollY) { 7090 if (mScroller == null) { 7091 scrollTo(hs, vs); 7092 } else { 7093 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 7094 int dx = hs - mScrollX; 7095 int dy = vs - mScrollY; 7096 7097 if (duration > ANIMATED_SCROLL_GAP) { 7098 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 7099 awakenScrollBars(mScroller.getDuration()); 7100 invalidate(); 7101 } else { 7102 if (!mScroller.isFinished()) { 7103 mScroller.abortAnimation(); 7104 } 7105 7106 scrollBy(dx, dy); 7107 } 7108 7109 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 7110 } 7111 7112 changed = true; 7113 } 7114 7115 if (isFocused()) { 7116 // This offsets because getInterestingRect() is in terms of viewport coordinates, but 7117 // requestRectangleOnScreen() is in terms of content coordinates. 7118 7119 // The offsets here are to ensure the rectangle we are using is 7120 // within our view bounds, in case the cursor is on the far left 7121 // or right. If it isn't withing the bounds, then this request 7122 // will be ignored. 7123 if (mTempRect == null) mTempRect = new Rect(); 7124 mTempRect.set(x - 2, top, x + 2, bottom); 7125 getInterestingRect(mTempRect, line); 7126 mTempRect.offset(mScrollX, mScrollY); 7127 7128 if (requestRectangleOnScreen(mTempRect)) { 7129 changed = true; 7130 } 7131 } 7132 7133 return changed; 7134 } 7135 7136 /** 7137 * Move the cursor, if needed, so that it is at an offset that is visible 7138 * to the user. This will not move the cursor if it represents more than 7139 * one character (a selection range). This will only work if the 7140 * TextView contains spannable text; otherwise it will do nothing. 7141 * 7142 * @return True if the cursor was actually moved, false otherwise. 7143 */ moveCursorToVisibleOffset()7144 public boolean moveCursorToVisibleOffset() { 7145 if (!(mText instanceof Spannable)) { 7146 return false; 7147 } 7148 int start = getSelectionStart(); 7149 int end = getSelectionEnd(); 7150 if (start != end) { 7151 return false; 7152 } 7153 7154 // First: make sure the line is visible on screen: 7155 7156 int line = mLayout.getLineForOffset(start); 7157 7158 final int top = mLayout.getLineTop(line); 7159 final int bottom = mLayout.getLineTop(line + 1); 7160 final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 7161 int vslack = (bottom - top) / 2; 7162 if (vslack > vspace / 4) 7163 vslack = vspace / 4; 7164 final int vs = mScrollY; 7165 7166 if (top < (vs+vslack)) { 7167 line = mLayout.getLineForVertical(vs+vslack+(bottom-top)); 7168 } else if (bottom > (vspace+vs-vslack)) { 7169 line = mLayout.getLineForVertical(vspace+vs-vslack-(bottom-top)); 7170 } 7171 7172 // Next: make sure the character is visible on screen: 7173 7174 final int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 7175 final int hs = mScrollX; 7176 final int leftChar = mLayout.getOffsetForHorizontal(line, hs); 7177 final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs); 7178 7179 // line might contain bidirectional text 7180 final int lowChar = leftChar < rightChar ? leftChar : rightChar; 7181 final int highChar = leftChar > rightChar ? leftChar : rightChar; 7182 7183 int newStart = start; 7184 if (newStart < lowChar) { 7185 newStart = lowChar; 7186 } else if (newStart > highChar) { 7187 newStart = highChar; 7188 } 7189 7190 if (newStart != start) { 7191 Selection.setSelection((Spannable)mText, newStart); 7192 return true; 7193 } 7194 7195 return false; 7196 } 7197 7198 @Override computeScroll()7199 public void computeScroll() { 7200 if (mScroller != null) { 7201 if (mScroller.computeScrollOffset()) { 7202 mScrollX = mScroller.getCurrX(); 7203 mScrollY = mScroller.getCurrY(); 7204 invalidateParentCaches(); 7205 postInvalidate(); // So we draw again 7206 } 7207 } 7208 } 7209 getInterestingRect(Rect r, int line)7210 private void getInterestingRect(Rect r, int line) { 7211 convertFromViewportToContentCoordinates(r); 7212 7213 // Rectangle can can be expanded on first and last line to take 7214 // padding into account. 7215 // TODO Take left/right padding into account too? 7216 if (line == 0) r.top -= getExtendedPaddingTop(); 7217 if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); 7218 } 7219 convertFromViewportToContentCoordinates(Rect r)7220 private void convertFromViewportToContentCoordinates(Rect r) { 7221 final int horizontalOffset = viewportToContentHorizontalOffset(); 7222 r.left += horizontalOffset; 7223 r.right += horizontalOffset; 7224 7225 final int verticalOffset = viewportToContentVerticalOffset(); 7226 r.top += verticalOffset; 7227 r.bottom += verticalOffset; 7228 } 7229 viewportToContentHorizontalOffset()7230 int viewportToContentHorizontalOffset() { 7231 return getCompoundPaddingLeft() - mScrollX; 7232 } 7233 viewportToContentVerticalOffset()7234 int viewportToContentVerticalOffset() { 7235 int offset = getExtendedPaddingTop() - mScrollY; 7236 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 7237 offset += getVerticalOffset(false); 7238 } 7239 return offset; 7240 } 7241 7242 @Override debug(int depth)7243 public void debug(int depth) { 7244 super.debug(depth); 7245 7246 String output = debugIndent(depth); 7247 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 7248 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 7249 + "} "; 7250 7251 if (mText != null) { 7252 7253 output += "mText=\"" + mText + "\" "; 7254 if (mLayout != null) { 7255 output += "mLayout width=" + mLayout.getWidth() 7256 + " height=" + mLayout.getHeight(); 7257 } 7258 } else { 7259 output += "mText=NULL"; 7260 } 7261 Log.d(VIEW_LOG_TAG, output); 7262 } 7263 7264 /** 7265 * Convenience for {@link Selection#getSelectionStart}. 7266 */ 7267 @ViewDebug.ExportedProperty(category = "text") getSelectionStart()7268 public int getSelectionStart() { 7269 return Selection.getSelectionStart(getText()); 7270 } 7271 7272 /** 7273 * Convenience for {@link Selection#getSelectionEnd}. 7274 */ 7275 @ViewDebug.ExportedProperty(category = "text") getSelectionEnd()7276 public int getSelectionEnd() { 7277 return Selection.getSelectionEnd(getText()); 7278 } 7279 7280 /** 7281 * Return true iff there is a selection inside this text view. 7282 */ hasSelection()7283 public boolean hasSelection() { 7284 final int selectionStart = getSelectionStart(); 7285 final int selectionEnd = getSelectionEnd(); 7286 7287 return selectionStart >= 0 && selectionStart != selectionEnd; 7288 } 7289 7290 /** 7291 * Sets the properties of this field (lines, horizontally scrolling, 7292 * transformation method) to be for a single-line input. 7293 * 7294 * @attr ref android.R.styleable#TextView_singleLine 7295 */ setSingleLine()7296 public void setSingleLine() { 7297 setSingleLine(true); 7298 } 7299 7300 /** 7301 * Sets the properties of this field to transform input to ALL CAPS 7302 * display. This may use a "small caps" formatting if available. 7303 * This setting will be ignored if this field is editable or selectable. 7304 * 7305 * This call replaces the current transformation method. Disabling this 7306 * will not necessarily restore the previous behavior from before this 7307 * was enabled. 7308 * 7309 * @see #setTransformationMethod(TransformationMethod) 7310 * @attr ref android.R.styleable#TextView_textAllCaps 7311 */ setAllCaps(boolean allCaps)7312 public void setAllCaps(boolean allCaps) { 7313 if (allCaps) { 7314 setTransformationMethod(new AllCapsTransformationMethod(getContext())); 7315 } else { 7316 setTransformationMethod(null); 7317 } 7318 } 7319 7320 /** 7321 * If true, sets the properties of this field (number of lines, horizontally scrolling, 7322 * transformation method) to be for a single-line input; if false, restores these to the default 7323 * conditions. 7324 * 7325 * Note that the default conditions are not necessarily those that were in effect prior this 7326 * method, and you may want to reset these properties to your custom values. 7327 * 7328 * @attr ref android.R.styleable#TextView_singleLine 7329 */ 7330 @android.view.RemotableViewMethod setSingleLine(boolean singleLine)7331 public void setSingleLine(boolean singleLine) { 7332 // Could be used, but may break backward compatibility. 7333 // if (mSingleLine == singleLine) return; 7334 setInputTypeSingleLine(singleLine); 7335 applySingleLine(singleLine, true, true); 7336 } 7337 7338 /** 7339 * Adds or remove the EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE on the mInputType. 7340 * @param singleLine 7341 */ setInputTypeSingleLine(boolean singleLine)7342 private void setInputTypeSingleLine(boolean singleLine) { 7343 if (mEditor != null && 7344 (mEditor.mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) { 7345 if (singleLine) { 7346 mEditor.mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 7347 } else { 7348 mEditor.mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 7349 } 7350 } 7351 } 7352 applySingleLine(boolean singleLine, boolean applyTransformation, boolean changeMaxLines)7353 private void applySingleLine(boolean singleLine, boolean applyTransformation, 7354 boolean changeMaxLines) { 7355 mSingleLine = singleLine; 7356 if (singleLine) { 7357 setLines(1); 7358 setHorizontallyScrolling(true); 7359 if (applyTransformation) { 7360 setTransformationMethod(SingleLineTransformationMethod.getInstance()); 7361 } 7362 } else { 7363 if (changeMaxLines) { 7364 setMaxLines(Integer.MAX_VALUE); 7365 } 7366 setHorizontallyScrolling(false); 7367 if (applyTransformation) { 7368 setTransformationMethod(null); 7369 } 7370 } 7371 } 7372 7373 /** 7374 * Causes words in the text that are longer than the view is wide 7375 * to be ellipsized instead of broken in the middle. You may also 7376 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 7377 * to constrain the text to a single line. Use <code>null</code> 7378 * to turn off ellipsizing. 7379 * 7380 * If {@link #setMaxLines} has been used to set two or more lines, 7381 * only {@link android.text.TextUtils.TruncateAt#END} and 7382 * {@link android.text.TextUtils.TruncateAt#MARQUEE} are supported 7383 * (other ellipsizing types will not do anything). 7384 * 7385 * @attr ref android.R.styleable#TextView_ellipsize 7386 */ setEllipsize(TextUtils.TruncateAt where)7387 public void setEllipsize(TextUtils.TruncateAt where) { 7388 // TruncateAt is an enum. != comparison is ok between these singleton objects. 7389 if (mEllipsize != where) { 7390 mEllipsize = where; 7391 7392 if (mLayout != null) { 7393 nullLayouts(); 7394 requestLayout(); 7395 invalidate(); 7396 } 7397 } 7398 } 7399 7400 /** 7401 * Sets how many times to repeat the marquee animation. Only applied if the 7402 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 7403 * 7404 * @see #getMarqueeRepeatLimit() 7405 * 7406 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 7407 */ setMarqueeRepeatLimit(int marqueeLimit)7408 public void setMarqueeRepeatLimit(int marqueeLimit) { 7409 mMarqueeRepeatLimit = marqueeLimit; 7410 } 7411 7412 /** 7413 * Gets the number of times the marquee animation is repeated. Only meaningful if the 7414 * TextView has marquee enabled. 7415 * 7416 * @return the number of times the marquee animation is repeated. -1 if the animation 7417 * repeats indefinitely 7418 * 7419 * @see #setMarqueeRepeatLimit(int) 7420 * 7421 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 7422 */ getMarqueeRepeatLimit()7423 public int getMarqueeRepeatLimit() { 7424 return mMarqueeRepeatLimit; 7425 } 7426 7427 /** 7428 * Returns where, if anywhere, words that are longer than the view 7429 * is wide should be ellipsized. 7430 */ 7431 @ViewDebug.ExportedProperty getEllipsize()7432 public TextUtils.TruncateAt getEllipsize() { 7433 return mEllipsize; 7434 } 7435 7436 /** 7437 * Set the TextView so that when it takes focus, all the text is 7438 * selected. 7439 * 7440 * @attr ref android.R.styleable#TextView_selectAllOnFocus 7441 */ 7442 @android.view.RemotableViewMethod setSelectAllOnFocus(boolean selectAllOnFocus)7443 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 7444 createEditorIfNeeded(); 7445 mEditor.mSelectAllOnFocus = selectAllOnFocus; 7446 7447 if (selectAllOnFocus && !(mText instanceof Spannable)) { 7448 setText(mText, BufferType.SPANNABLE); 7449 } 7450 } 7451 7452 /** 7453 * Set whether the cursor is visible. The default is true. Note that this property only 7454 * makes sense for editable TextView. 7455 * 7456 * @see #isCursorVisible() 7457 * 7458 * @attr ref android.R.styleable#TextView_cursorVisible 7459 */ 7460 @android.view.RemotableViewMethod setCursorVisible(boolean visible)7461 public void setCursorVisible(boolean visible) { 7462 if (visible && mEditor == null) return; // visible is the default value with no edit data 7463 createEditorIfNeeded(); 7464 if (mEditor.mCursorVisible != visible) { 7465 mEditor.mCursorVisible = visible; 7466 invalidate(); 7467 7468 mEditor.makeBlink(); 7469 7470 // InsertionPointCursorController depends on mCursorVisible 7471 mEditor.prepareCursorControllers(); 7472 } 7473 } 7474 7475 /** 7476 * @return whether or not the cursor is visible (assuming this TextView is editable) 7477 * 7478 * @see #setCursorVisible(boolean) 7479 * 7480 * @attr ref android.R.styleable#TextView_cursorVisible 7481 */ isCursorVisible()7482 public boolean isCursorVisible() { 7483 // true is the default value 7484 return mEditor == null ? true : mEditor.mCursorVisible; 7485 } 7486 canMarquee()7487 private boolean canMarquee() { 7488 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); 7489 return width > 0 && (mLayout.getLineWidth(0) > width || 7490 (mMarqueeFadeMode != MARQUEE_FADE_NORMAL && mSavedMarqueeModeLayout != null && 7491 mSavedMarqueeModeLayout.getLineWidth(0) > width)); 7492 } 7493 startMarquee()7494 private void startMarquee() { 7495 // Do not ellipsize EditText 7496 if (getKeyListener() != null) return; 7497 7498 if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 7499 return; 7500 } 7501 7502 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && 7503 getLineCount() == 1 && canMarquee()) { 7504 7505 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 7506 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_FADE; 7507 final Layout tmp = mLayout; 7508 mLayout = mSavedMarqueeModeLayout; 7509 mSavedMarqueeModeLayout = tmp; 7510 setHorizontalFadingEdgeEnabled(true); 7511 requestLayout(); 7512 invalidate(); 7513 } 7514 7515 if (mMarquee == null) mMarquee = new Marquee(this); 7516 mMarquee.start(mMarqueeRepeatLimit); 7517 } 7518 } 7519 stopMarquee()7520 private void stopMarquee() { 7521 if (mMarquee != null && !mMarquee.isStopped()) { 7522 mMarquee.stop(); 7523 } 7524 7525 if (mMarqueeFadeMode == MARQUEE_FADE_SWITCH_SHOW_FADE) { 7526 mMarqueeFadeMode = MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS; 7527 final Layout tmp = mSavedMarqueeModeLayout; 7528 mSavedMarqueeModeLayout = mLayout; 7529 mLayout = tmp; 7530 setHorizontalFadingEdgeEnabled(false); 7531 requestLayout(); 7532 invalidate(); 7533 } 7534 } 7535 startStopMarquee(boolean start)7536 private void startStopMarquee(boolean start) { 7537 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7538 if (start) { 7539 startMarquee(); 7540 } else { 7541 stopMarquee(); 7542 } 7543 } 7544 } 7545 7546 /** 7547 * This method is called when the text is changed, in case any subclasses 7548 * would like to know. 7549 * 7550 * Within <code>text</code>, the <code>lengthAfter</code> characters 7551 * beginning at <code>start</code> have just replaced old text that had 7552 * length <code>lengthBefore</code>. It is an error to attempt to make 7553 * changes to <code>text</code> from this callback. 7554 * 7555 * @param text The text the TextView is displaying 7556 * @param start The offset of the start of the range of the text that was 7557 * modified 7558 * @param lengthBefore The length of the former text that has been replaced 7559 * @param lengthAfter The length of the replacement modified text 7560 */ onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter)7561 protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { 7562 // intentionally empty, template pattern method can be overridden by subclasses 7563 } 7564 7565 /** 7566 * This method is called when the selection has changed, in case any 7567 * subclasses would like to know. 7568 * 7569 * @param selStart The new selection start location. 7570 * @param selEnd The new selection end location. 7571 */ onSelectionChanged(int selStart, int selEnd)7572 protected void onSelectionChanged(int selStart, int selEnd) { 7573 sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); 7574 } 7575 7576 /** 7577 * Adds a TextWatcher to the list of those whose methods are called 7578 * whenever this TextView's text changes. 7579 * <p> 7580 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 7581 * not called after {@link #setText} calls. Now, doing {@link #setText} 7582 * if there are any text changed listeners forces the buffer type to 7583 * Editable if it would not otherwise be and does call this method. 7584 */ addTextChangedListener(TextWatcher watcher)7585 public void addTextChangedListener(TextWatcher watcher) { 7586 if (mListeners == null) { 7587 mListeners = new ArrayList<TextWatcher>(); 7588 } 7589 7590 mListeners.add(watcher); 7591 } 7592 7593 /** 7594 * Removes the specified TextWatcher from the list of those whose 7595 * methods are called 7596 * whenever this TextView's text changes. 7597 */ removeTextChangedListener(TextWatcher watcher)7598 public void removeTextChangedListener(TextWatcher watcher) { 7599 if (mListeners != null) { 7600 int i = mListeners.indexOf(watcher); 7601 7602 if (i >= 0) { 7603 mListeners.remove(i); 7604 } 7605 } 7606 } 7607 sendBeforeTextChanged(CharSequence text, int start, int before, int after)7608 private void sendBeforeTextChanged(CharSequence text, int start, int before, int after) { 7609 if (mListeners != null) { 7610 final ArrayList<TextWatcher> list = mListeners; 7611 final int count = list.size(); 7612 for (int i = 0; i < count; i++) { 7613 list.get(i).beforeTextChanged(text, start, before, after); 7614 } 7615 } 7616 7617 // The spans that are inside or intersect the modified region no longer make sense 7618 removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); 7619 removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); 7620 } 7621 7622 // Removes all spans that are inside or actually overlap the start..end range removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type)7623 private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { 7624 if (!(mText instanceof Editable)) return; 7625 Editable text = (Editable) mText; 7626 7627 T[] spans = text.getSpans(start, end, type); 7628 final int length = spans.length; 7629 for (int i = 0; i < length; i++) { 7630 final int spanStart = text.getSpanStart(spans[i]); 7631 final int spanEnd = text.getSpanEnd(spans[i]); 7632 if (spanEnd == start || spanStart == end) break; 7633 text.removeSpan(spans[i]); 7634 } 7635 } 7636 removeAdjacentSuggestionSpans(final int pos)7637 void removeAdjacentSuggestionSpans(final int pos) { 7638 if (!(mText instanceof Editable)) return; 7639 final Editable text = (Editable) mText; 7640 7641 final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); 7642 final int length = spans.length; 7643 for (int i = 0; i < length; i++) { 7644 final int spanStart = text.getSpanStart(spans[i]); 7645 final int spanEnd = text.getSpanEnd(spans[i]); 7646 if (spanEnd == pos || spanStart == pos) { 7647 if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { 7648 text.removeSpan(spans[i]); 7649 } 7650 } 7651 } 7652 } 7653 7654 /** 7655 * Not private so it can be called from an inner class without going 7656 * through a thunk. 7657 */ sendOnTextChanged(CharSequence text, int start, int before, int after)7658 void sendOnTextChanged(CharSequence text, int start, int before, int after) { 7659 if (mListeners != null) { 7660 final ArrayList<TextWatcher> list = mListeners; 7661 final int count = list.size(); 7662 for (int i = 0; i < count; i++) { 7663 list.get(i).onTextChanged(text, start, before, after); 7664 } 7665 } 7666 7667 if (mEditor != null) mEditor.sendOnTextChanged(start, after); 7668 } 7669 7670 /** 7671 * Not private so it can be called from an inner class without going 7672 * through a thunk. 7673 */ sendAfterTextChanged(Editable text)7674 void sendAfterTextChanged(Editable text) { 7675 if (mListeners != null) { 7676 final ArrayList<TextWatcher> list = mListeners; 7677 final int count = list.size(); 7678 for (int i = 0; i < count; i++) { 7679 list.get(i).afterTextChanged(text); 7680 } 7681 } 7682 hideErrorIfUnchanged(); 7683 } 7684 updateAfterEdit()7685 void updateAfterEdit() { 7686 invalidate(); 7687 int curs = getSelectionStart(); 7688 7689 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 7690 registerForPreDraw(); 7691 } 7692 7693 checkForResize(); 7694 7695 if (curs >= 0) { 7696 mHighlightPathBogus = true; 7697 if (mEditor != null) mEditor.makeBlink(); 7698 bringPointIntoView(curs); 7699 } 7700 } 7701 7702 /** 7703 * Not private so it can be called from an inner class without going 7704 * through a thunk. 7705 */ handleTextChanged(CharSequence buffer, int start, int before, int after)7706 void handleTextChanged(CharSequence buffer, int start, int before, int after) { 7707 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 7708 if (ims == null || ims.mBatchEditNesting == 0) { 7709 updateAfterEdit(); 7710 } 7711 if (ims != null) { 7712 ims.mContentChanged = true; 7713 if (ims.mChangedStart < 0) { 7714 ims.mChangedStart = start; 7715 ims.mChangedEnd = start+before; 7716 } else { 7717 ims.mChangedStart = Math.min(ims.mChangedStart, start); 7718 ims.mChangedEnd = Math.max(ims.mChangedEnd, start + before - ims.mChangedDelta); 7719 } 7720 ims.mChangedDelta += after-before; 7721 } 7722 resetErrorChangedFlag(); 7723 sendOnTextChanged(buffer, start, before, after); 7724 onTextChanged(buffer, start, before, after); 7725 } 7726 7727 /** 7728 * Not private so it can be called from an inner class without going 7729 * through a thunk. 7730 */ spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd)7731 void spanChange(Spanned buf, Object what, int oldStart, int newStart, int oldEnd, int newEnd) { 7732 // XXX Make the start and end move together if this ends up 7733 // spending too much time invalidating. 7734 7735 boolean selChanged = false; 7736 int newSelStart=-1, newSelEnd=-1; 7737 7738 final Editor.InputMethodState ims = mEditor == null ? null : mEditor.mInputMethodState; 7739 7740 if (what == Selection.SELECTION_END) { 7741 selChanged = true; 7742 newSelEnd = newStart; 7743 7744 if (oldStart >= 0 || newStart >= 0) { 7745 invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart); 7746 checkForResize(); 7747 registerForPreDraw(); 7748 if (mEditor != null) mEditor.makeBlink(); 7749 } 7750 } 7751 7752 if (what == Selection.SELECTION_START) { 7753 selChanged = true; 7754 newSelStart = newStart; 7755 7756 if (oldStart >= 0 || newStart >= 0) { 7757 int end = Selection.getSelectionEnd(buf); 7758 invalidateCursor(end, oldStart, newStart); 7759 } 7760 } 7761 7762 if (selChanged) { 7763 mHighlightPathBogus = true; 7764 if (mEditor != null && !isFocused()) mEditor.mSelectionMoved = true; 7765 7766 if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) { 7767 if (newSelStart < 0) { 7768 newSelStart = Selection.getSelectionStart(buf); 7769 } 7770 if (newSelEnd < 0) { 7771 newSelEnd = Selection.getSelectionEnd(buf); 7772 } 7773 onSelectionChanged(newSelStart, newSelEnd); 7774 } 7775 } 7776 7777 if (what instanceof UpdateAppearance || what instanceof ParagraphStyle || 7778 what instanceof CharacterStyle) { 7779 if (ims == null || ims.mBatchEditNesting == 0) { 7780 invalidate(); 7781 mHighlightPathBogus = true; 7782 checkForResize(); 7783 } else { 7784 ims.mContentChanged = true; 7785 } 7786 if (mEditor != null) { 7787 if (oldStart >= 0) mEditor.invalidateTextDisplayList(mLayout, oldStart, oldEnd); 7788 if (newStart >= 0) mEditor.invalidateTextDisplayList(mLayout, newStart, newEnd); 7789 } 7790 } 7791 7792 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 7793 mHighlightPathBogus = true; 7794 if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) { 7795 ims.mSelectionModeChanged = true; 7796 } 7797 7798 if (Selection.getSelectionStart(buf) >= 0) { 7799 if (ims == null || ims.mBatchEditNesting == 0) { 7800 invalidateCursor(); 7801 } else { 7802 ims.mCursorChanged = true; 7803 } 7804 } 7805 } 7806 7807 if (what instanceof ParcelableSpan) { 7808 // If this is a span that can be sent to a remote process, 7809 // the current extract editor would be interested in it. 7810 if (ims != null && ims.mExtractedTextRequest != null) { 7811 if (ims.mBatchEditNesting != 0) { 7812 if (oldStart >= 0) { 7813 if (ims.mChangedStart > oldStart) { 7814 ims.mChangedStart = oldStart; 7815 } 7816 if (ims.mChangedStart > oldEnd) { 7817 ims.mChangedStart = oldEnd; 7818 } 7819 } 7820 if (newStart >= 0) { 7821 if (ims.mChangedStart > newStart) { 7822 ims.mChangedStart = newStart; 7823 } 7824 if (ims.mChangedStart > newEnd) { 7825 ims.mChangedStart = newEnd; 7826 } 7827 } 7828 } else { 7829 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: " 7830 + oldStart + "-" + oldEnd + "," 7831 + newStart + "-" + newEnd + " " + what); 7832 ims.mContentChanged = true; 7833 } 7834 } 7835 } 7836 7837 if (mEditor != null && mEditor.mSpellChecker != null && newStart < 0 && 7838 what instanceof SpellCheckSpan) { 7839 mEditor.mSpellChecker.onSpellCheckSpanRemoved((SpellCheckSpan) what); 7840 } 7841 } 7842 7843 /** 7844 * @hide 7845 */ 7846 @Override dispatchFinishTemporaryDetach()7847 public void dispatchFinishTemporaryDetach() { 7848 mDispatchTemporaryDetach = true; 7849 super.dispatchFinishTemporaryDetach(); 7850 mDispatchTemporaryDetach = false; 7851 } 7852 7853 @Override onStartTemporaryDetach()7854 public void onStartTemporaryDetach() { 7855 super.onStartTemporaryDetach(); 7856 // Only track when onStartTemporaryDetach() is called directly, 7857 // usually because this instance is an editable field in a list 7858 if (!mDispatchTemporaryDetach) mTemporaryDetach = true; 7859 7860 // Tell the editor that we are temporarily detached. It can use this to preserve 7861 // selection state as needed. 7862 if (mEditor != null) mEditor.mTemporaryDetach = true; 7863 } 7864 7865 @Override onFinishTemporaryDetach()7866 public void onFinishTemporaryDetach() { 7867 super.onFinishTemporaryDetach(); 7868 // Only track when onStartTemporaryDetach() is called directly, 7869 // usually because this instance is an editable field in a list 7870 if (!mDispatchTemporaryDetach) mTemporaryDetach = false; 7871 if (mEditor != null) mEditor.mTemporaryDetach = false; 7872 } 7873 7874 @Override onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)7875 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 7876 if (mTemporaryDetach) { 7877 // If we are temporarily in the detach state, then do nothing. 7878 super.onFocusChanged(focused, direction, previouslyFocusedRect); 7879 return; 7880 } 7881 7882 if (mEditor != null) mEditor.onFocusChanged(focused, direction); 7883 7884 if (focused) { 7885 if (mText instanceof Spannable) { 7886 Spannable sp = (Spannable) mText; 7887 MetaKeyKeyListener.resetMetaState(sp); 7888 } 7889 } 7890 7891 startStopMarquee(focused); 7892 7893 if (mTransformation != null) { 7894 mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); 7895 } 7896 7897 super.onFocusChanged(focused, direction, previouslyFocusedRect); 7898 } 7899 7900 @Override onWindowFocusChanged(boolean hasWindowFocus)7901 public void onWindowFocusChanged(boolean hasWindowFocus) { 7902 super.onWindowFocusChanged(hasWindowFocus); 7903 7904 if (mEditor != null) mEditor.onWindowFocusChanged(hasWindowFocus); 7905 7906 startStopMarquee(hasWindowFocus); 7907 } 7908 7909 @Override onVisibilityChanged(View changedView, int visibility)7910 protected void onVisibilityChanged(View changedView, int visibility) { 7911 super.onVisibilityChanged(changedView, visibility); 7912 if (mEditor != null && visibility != VISIBLE) { 7913 mEditor.hideControllers(); 7914 } 7915 } 7916 7917 /** 7918 * Use {@link BaseInputConnection#removeComposingSpans 7919 * BaseInputConnection.removeComposingSpans()} to remove any IME composing 7920 * state from this text view. 7921 */ clearComposingText()7922 public void clearComposingText() { 7923 if (mText instanceof Spannable) { 7924 BaseInputConnection.removeComposingSpans((Spannable)mText); 7925 } 7926 } 7927 7928 @Override setSelected(boolean selected)7929 public void setSelected(boolean selected) { 7930 boolean wasSelected = isSelected(); 7931 7932 super.setSelected(selected); 7933 7934 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 7935 if (selected) { 7936 startMarquee(); 7937 } else { 7938 stopMarquee(); 7939 } 7940 } 7941 } 7942 7943 @Override onTouchEvent(MotionEvent event)7944 public boolean onTouchEvent(MotionEvent event) { 7945 final int action = event.getActionMasked(); 7946 7947 if (mEditor != null) mEditor.onTouchEvent(event); 7948 7949 final boolean superResult = super.onTouchEvent(event); 7950 7951 /* 7952 * Don't handle the release after a long press, because it will 7953 * move the selection away from whatever the menu action was 7954 * trying to affect. 7955 */ 7956 if (mEditor != null && mEditor.mDiscardNextActionUp && action == MotionEvent.ACTION_UP) { 7957 mEditor.mDiscardNextActionUp = false; 7958 return superResult; 7959 } 7960 7961 final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) && 7962 (mEditor == null || !mEditor.mIgnoreActionUpEvent) && isFocused(); 7963 7964 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 7965 && mText instanceof Spannable && mLayout != null) { 7966 boolean handled = false; 7967 7968 if (mMovement != null) { 7969 handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); 7970 } 7971 7972 final boolean textIsSelectable = isTextSelectable(); 7973 if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) { 7974 // The LinkMovementMethod which should handle taps on links has not been installed 7975 // on non editable text that support text selection. 7976 // We reproduce its behavior here to open links for these. 7977 ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(), 7978 getSelectionEnd(), ClickableSpan.class); 7979 7980 if (links.length > 0) { 7981 links[0].onClick(this); 7982 handled = true; 7983 } 7984 } 7985 7986 if (touchIsFinished && (isTextEditable() || textIsSelectable)) { 7987 // Show the IME, except when selecting in read-only text. 7988 final InputMethodManager imm = InputMethodManager.peekInstance(); 7989 viewClicked(imm); 7990 if (!textIsSelectable && mEditor.mShowSoftInputOnFocus) { 7991 handled |= imm != null && imm.showSoftInput(this, 0); 7992 } 7993 7994 // The above condition ensures that the mEditor is not null 7995 mEditor.onTouchUpEvent(event); 7996 7997 handled = true; 7998 } 7999 8000 if (handled) { 8001 return true; 8002 } 8003 } 8004 8005 return superResult; 8006 } 8007 8008 @Override onGenericMotionEvent(MotionEvent event)8009 public boolean onGenericMotionEvent(MotionEvent event) { 8010 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 8011 try { 8012 if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) { 8013 return true; 8014 } 8015 } catch (AbstractMethodError ex) { 8016 // onGenericMotionEvent was added to the MovementMethod interface in API 12. 8017 // Ignore its absence in case third party applications implemented the 8018 // interface directly. 8019 } 8020 } 8021 return super.onGenericMotionEvent(event); 8022 } 8023 8024 /** 8025 * @return True iff this TextView contains a text that can be edited, or if this is 8026 * a selectable TextView. 8027 */ isTextEditable()8028 boolean isTextEditable() { 8029 return mText instanceof Editable && onCheckIsTextEditor() && isEnabled(); 8030 } 8031 8032 /** 8033 * Returns true, only while processing a touch gesture, if the initial 8034 * touch down event caused focus to move to the text view and as a result 8035 * its selection changed. Only valid while processing the touch gesture 8036 * of interest, in an editable text view. 8037 */ didTouchFocusSelect()8038 public boolean didTouchFocusSelect() { 8039 return mEditor != null && mEditor.mTouchFocusSelected; 8040 } 8041 8042 @Override cancelLongPress()8043 public void cancelLongPress() { 8044 super.cancelLongPress(); 8045 if (mEditor != null) mEditor.mIgnoreActionUpEvent = true; 8046 } 8047 8048 @Override onTrackballEvent(MotionEvent event)8049 public boolean onTrackballEvent(MotionEvent event) { 8050 if (mMovement != null && mText instanceof Spannable && mLayout != null) { 8051 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 8052 return true; 8053 } 8054 } 8055 8056 return super.onTrackballEvent(event); 8057 } 8058 setScroller(Scroller s)8059 public void setScroller(Scroller s) { 8060 mScroller = s; 8061 } 8062 8063 @Override getLeftFadingEdgeStrength()8064 protected float getLeftFadingEdgeStrength() { 8065 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 8066 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8067 if (mMarquee != null && !mMarquee.isStopped()) { 8068 final Marquee marquee = mMarquee; 8069 if (marquee.shouldDrawLeftFade()) { 8070 final float scroll = marquee.getScroll(); 8071 return scroll / getHorizontalFadingEdgeLength(); 8072 } else { 8073 return 0.0f; 8074 } 8075 } else if (getLineCount() == 1) { 8076 final int layoutDirection = getLayoutDirection(); 8077 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8078 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 8079 case Gravity.LEFT: 8080 return 0.0f; 8081 case Gravity.RIGHT: 8082 return (mLayout.getLineRight(0) - (mRight - mLeft) - 8083 getCompoundPaddingLeft() - getCompoundPaddingRight() - 8084 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 8085 case Gravity.CENTER_HORIZONTAL: 8086 case Gravity.FILL_HORIZONTAL: 8087 final int textDirection = mLayout.getParagraphDirection(0); 8088 if (textDirection == Layout.DIR_LEFT_TO_RIGHT) { 8089 return 0.0f; 8090 } else { 8091 return (mLayout.getLineRight(0) - (mRight - mLeft) - 8092 getCompoundPaddingLeft() - getCompoundPaddingRight() - 8093 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 8094 } 8095 } 8096 } 8097 } 8098 return super.getLeftFadingEdgeStrength(); 8099 } 8100 8101 @Override getRightFadingEdgeStrength()8102 protected float getRightFadingEdgeStrength() { 8103 if (mEllipsize == TextUtils.TruncateAt.MARQUEE && 8104 mMarqueeFadeMode != MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS) { 8105 if (mMarquee != null && !mMarquee.isStopped()) { 8106 final Marquee marquee = mMarquee; 8107 final float maxFadeScroll = marquee.getMaxFadeScroll(); 8108 final float scroll = marquee.getScroll(); 8109 return (maxFadeScroll - scroll) / getHorizontalFadingEdgeLength(); 8110 } else if (getLineCount() == 1) { 8111 final int layoutDirection = getLayoutDirection(); 8112 final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection); 8113 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 8114 case Gravity.LEFT: 8115 final int textWidth = (mRight - mLeft) - getCompoundPaddingLeft() - 8116 getCompoundPaddingRight(); 8117 final float lineWidth = mLayout.getLineWidth(0); 8118 return (lineWidth - textWidth) / getHorizontalFadingEdgeLength(); 8119 case Gravity.RIGHT: 8120 return 0.0f; 8121 case Gravity.CENTER_HORIZONTAL: 8122 case Gravity.FILL_HORIZONTAL: 8123 final int textDirection = mLayout.getParagraphDirection(0); 8124 if (textDirection == Layout.DIR_RIGHT_TO_LEFT) { 8125 return 0.0f; 8126 } else { 8127 return (mLayout.getLineWidth(0) - ((mRight - mLeft) - 8128 getCompoundPaddingLeft() - getCompoundPaddingRight())) / 8129 getHorizontalFadingEdgeLength(); 8130 } 8131 } 8132 } 8133 } 8134 return super.getRightFadingEdgeStrength(); 8135 } 8136 8137 @Override computeHorizontalScrollRange()8138 protected int computeHorizontalScrollRange() { 8139 if (mLayout != null) { 8140 return mSingleLine && (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT ? 8141 (int) mLayout.getLineWidth(0) : mLayout.getWidth(); 8142 } 8143 8144 return super.computeHorizontalScrollRange(); 8145 } 8146 8147 @Override computeVerticalScrollRange()8148 protected int computeVerticalScrollRange() { 8149 if (mLayout != null) 8150 return mLayout.getHeight(); 8151 8152 return super.computeVerticalScrollRange(); 8153 } 8154 8155 @Override computeVerticalScrollExtent()8156 protected int computeVerticalScrollExtent() { 8157 return getHeight() - getCompoundPaddingTop() - getCompoundPaddingBottom(); 8158 } 8159 8160 @Override findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags)8161 public void findViewsWithText(ArrayList<View> outViews, CharSequence searched, int flags) { 8162 super.findViewsWithText(outViews, searched, flags); 8163 if (!outViews.contains(this) && (flags & FIND_VIEWS_WITH_TEXT) != 0 8164 && !TextUtils.isEmpty(searched) && !TextUtils.isEmpty(mText)) { 8165 String searchedLowerCase = searched.toString().toLowerCase(); 8166 String textLowerCase = mText.toString().toLowerCase(); 8167 if (textLowerCase.contains(searchedLowerCase)) { 8168 outViews.add(this); 8169 } 8170 } 8171 } 8172 8173 public enum BufferType { 8174 NORMAL, SPANNABLE, EDITABLE, 8175 } 8176 8177 /** 8178 * Returns the TextView_textColor attribute from the TypedArray, if set, or 8179 * the TextAppearance_textColor from the TextView_textAppearance attribute, 8180 * if TextView_textColor was not set directly. 8181 * 8182 * @removed 8183 */ getTextColors(Context context, TypedArray attrs)8184 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 8185 if (attrs == null) { 8186 // Preserve behavior prior to removal of this API. 8187 throw new NullPointerException(); 8188 } 8189 8190 // It's not safe to use this method from apps. The parameter 'attrs' 8191 // must have been obtained using the TextView filter array which is not 8192 // available to the SDK. As such, we grab a default TypedArray with the 8193 // right filter instead here. 8194 final TypedArray a = context.obtainStyledAttributes(R.styleable.TextView); 8195 ColorStateList colors = a.getColorStateList(R.styleable.TextView_textColor); 8196 if (colors == null) { 8197 final int ap = a.getResourceId(R.styleable.TextView_textAppearance, 0); 8198 if (ap != 0) { 8199 final TypedArray appearance = context.obtainStyledAttributes( 8200 ap, R.styleable.TextAppearance); 8201 colors = appearance.getColorStateList(R.styleable.TextAppearance_textColor); 8202 appearance.recycle(); 8203 } 8204 } 8205 a.recycle(); 8206 8207 return colors; 8208 } 8209 8210 /** 8211 * Returns the default color from the TextView_textColor attribute from the 8212 * AttributeSet, if set, or the default color from the 8213 * TextAppearance_textColor from the TextView_textAppearance attribute, if 8214 * TextView_textColor was not set directly. 8215 * 8216 * @removed 8217 */ getTextColor(Context context, TypedArray attrs, int def)8218 public static int getTextColor(Context context, TypedArray attrs, int def) { 8219 final ColorStateList colors = getTextColors(context, attrs); 8220 if (colors == null) { 8221 return def; 8222 } else { 8223 return colors.getDefaultColor(); 8224 } 8225 } 8226 8227 @Override onKeyShortcut(int keyCode, KeyEvent event)8228 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 8229 final int filteredMetaState = event.getMetaState() & ~KeyEvent.META_CTRL_MASK; 8230 if (KeyEvent.metaStateHasNoModifiers(filteredMetaState)) { 8231 switch (keyCode) { 8232 case KeyEvent.KEYCODE_A: 8233 if (canSelectText()) { 8234 return onTextContextMenuItem(ID_SELECT_ALL); 8235 } 8236 break; 8237 case KeyEvent.KEYCODE_X: 8238 if (canCut()) { 8239 return onTextContextMenuItem(ID_CUT); 8240 } 8241 break; 8242 case KeyEvent.KEYCODE_C: 8243 if (canCopy()) { 8244 return onTextContextMenuItem(ID_COPY); 8245 } 8246 break; 8247 case KeyEvent.KEYCODE_V: 8248 if (canPaste()) { 8249 return onTextContextMenuItem(ID_PASTE); 8250 } 8251 break; 8252 } 8253 } 8254 return super.onKeyShortcut(keyCode, event); 8255 } 8256 8257 /** 8258 * Unlike {@link #textCanBeSelected()}, this method is based on the <i>current</i> state of the 8259 * TextView. {@link #textCanBeSelected()} has to be true (this is one of the conditions to have 8260 * a selection controller (see {@link Editor#prepareCursorControllers()}), but this is not 8261 * sufficient. 8262 */ canSelectText()8263 private boolean canSelectText() { 8264 return mText.length() != 0 && mEditor != null && mEditor.hasSelectionController(); 8265 } 8266 8267 /** 8268 * Test based on the <i>intrinsic</i> charateristics of the TextView. 8269 * The text must be spannable and the movement method must allow for arbitary selection. 8270 * 8271 * See also {@link #canSelectText()}. 8272 */ textCanBeSelected()8273 boolean textCanBeSelected() { 8274 // prepareCursorController() relies on this method. 8275 // If you change this condition, make sure prepareCursorController is called anywhere 8276 // the value of this condition might be changed. 8277 if (mMovement == null || !mMovement.canSelectArbitrarily()) return false; 8278 return isTextEditable() || 8279 (isTextSelectable() && mText instanceof Spannable && isEnabled()); 8280 } 8281 getTextServicesLocale(boolean allowNullLocale)8282 private Locale getTextServicesLocale(boolean allowNullLocale) { 8283 // Start fetching the text services locale asynchronously. 8284 updateTextServicesLocaleAsync(); 8285 // If !allowNullLocale and there is no cached text services locale, just return the default 8286 // locale. 8287 return (mCurrentSpellCheckerLocaleCache == null && !allowNullLocale) ? Locale.getDefault() 8288 : mCurrentSpellCheckerLocaleCache; 8289 } 8290 8291 /** 8292 * This is a temporary method. Future versions may support multi-locale text. 8293 * Caveat: This method may not return the latest text services locale, but this should be 8294 * acceptable and it's more important to make this method asynchronous. 8295 * 8296 * @return The locale that should be used for a word iterator 8297 * in this TextView, based on the current spell checker settings, 8298 * the current IME's locale, or the system default locale. 8299 * Please note that a word iterator in this TextView is different from another word iterator 8300 * used by SpellChecker.java of TextView. This method should be used for the former. 8301 * @hide 8302 */ 8303 // TODO: Support multi-locale 8304 // TODO: Update the text services locale immediately after the keyboard locale is switched 8305 // by catching intent of keyboard switch event getTextServicesLocale()8306 public Locale getTextServicesLocale() { 8307 return getTextServicesLocale(false /* allowNullLocale */); 8308 } 8309 8310 /** 8311 * This is a temporary method. Future versions may support multi-locale text. 8312 * Caveat: This method may not return the latest spell checker locale, but this should be 8313 * acceptable and it's more important to make this method asynchronous. 8314 * 8315 * @return The locale that should be used for a spell checker in this TextView, 8316 * based on the current spell checker settings, the current IME's locale, or the system default 8317 * locale. 8318 * @hide 8319 */ getSpellCheckerLocale()8320 public Locale getSpellCheckerLocale() { 8321 return getTextServicesLocale(true /* allowNullLocale */); 8322 } 8323 updateTextServicesLocaleAsync()8324 private void updateTextServicesLocaleAsync() { 8325 // AsyncTask.execute() uses a serial executor which means we don't have 8326 // to lock around updateTextServicesLocaleLocked() to prevent it from 8327 // being executed n times in parallel. 8328 AsyncTask.execute(new Runnable() { 8329 @Override 8330 public void run() { 8331 updateTextServicesLocaleLocked(); 8332 } 8333 }); 8334 } 8335 updateTextServicesLocaleLocked()8336 private void updateTextServicesLocaleLocked() { 8337 final TextServicesManager textServicesManager = (TextServicesManager) 8338 mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); 8339 final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); 8340 final Locale locale; 8341 if (subtype != null) { 8342 locale = SpellCheckerSubtype.constructLocaleFromString(subtype.getLocale()); 8343 } else { 8344 locale = null; 8345 } 8346 mCurrentSpellCheckerLocaleCache = locale; 8347 } 8348 onLocaleChanged()8349 void onLocaleChanged() { 8350 // Will be re-created on demand in getWordIterator with the proper new locale 8351 mEditor.mWordIterator = null; 8352 } 8353 8354 /** 8355 * This method is used by the ArrowKeyMovementMethod to jump from one word to the other. 8356 * Made available to achieve a consistent behavior. 8357 * @hide 8358 */ getWordIterator()8359 public WordIterator getWordIterator() { 8360 if (mEditor != null) { 8361 return mEditor.getWordIterator(); 8362 } else { 8363 return null; 8364 } 8365 } 8366 8367 @Override onPopulateAccessibilityEvent(AccessibilityEvent event)8368 public void onPopulateAccessibilityEvent(AccessibilityEvent event) { 8369 super.onPopulateAccessibilityEvent(event); 8370 8371 final boolean isPassword = hasPasswordTransformationMethod(); 8372 if (!isPassword || shouldSpeakPasswordsForAccessibility()) { 8373 final CharSequence text = getTextForAccessibility(); 8374 if (!TextUtils.isEmpty(text)) { 8375 event.getText().add(text); 8376 } 8377 } 8378 } 8379 8380 /** 8381 * @return true if the user has explicitly allowed accessibility services 8382 * to speak passwords. 8383 */ shouldSpeakPasswordsForAccessibility()8384 private boolean shouldSpeakPasswordsForAccessibility() { 8385 return (Settings.Secure.getInt(mContext.getContentResolver(), 8386 Settings.Secure.ACCESSIBILITY_SPEAK_PASSWORD, 0) == 1); 8387 } 8388 8389 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)8390 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 8391 super.onInitializeAccessibilityEvent(event); 8392 8393 event.setClassName(TextView.class.getName()); 8394 final boolean isPassword = hasPasswordTransformationMethod(); 8395 event.setPassword(isPassword); 8396 8397 if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED) { 8398 event.setFromIndex(Selection.getSelectionStart(mText)); 8399 event.setToIndex(Selection.getSelectionEnd(mText)); 8400 event.setItemCount(mText.length()); 8401 } 8402 } 8403 8404 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)8405 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 8406 super.onInitializeAccessibilityNodeInfo(info); 8407 8408 info.setClassName(TextView.class.getName()); 8409 final boolean isPassword = hasPasswordTransformationMethod(); 8410 info.setPassword(isPassword); 8411 8412 if (!isPassword || shouldSpeakPasswordsForAccessibility()) { 8413 info.setText(getTextForAccessibility()); 8414 } 8415 8416 if (mBufferType == BufferType.EDITABLE) { 8417 info.setEditable(true); 8418 } 8419 8420 if (mEditor != null) { 8421 info.setInputType(mEditor.mInputType); 8422 8423 if (mEditor.mError != null) { 8424 info.setContentInvalid(true); 8425 info.setError(mEditor.mError); 8426 } 8427 } 8428 8429 if (!TextUtils.isEmpty(mText)) { 8430 info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); 8431 info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); 8432 info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER 8433 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD 8434 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE 8435 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PARAGRAPH 8436 | AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE); 8437 } 8438 8439 if (isFocused()) { 8440 if (canSelectText()) { 8441 info.addAction(AccessibilityNodeInfo.ACTION_SET_SELECTION); 8442 } 8443 if (canCopy()) { 8444 info.addAction(AccessibilityNodeInfo.ACTION_COPY); 8445 } 8446 if (canPaste()) { 8447 info.addAction(AccessibilityNodeInfo.ACTION_PASTE); 8448 } 8449 if (canCut()) { 8450 info.addAction(AccessibilityNodeInfo.ACTION_CUT); 8451 } 8452 } 8453 8454 // Check for known input filter types. 8455 final int numFilters = mFilters.length; 8456 for (int i = 0; i < numFilters; i++) { 8457 final InputFilter filter = mFilters[i]; 8458 if (filter instanceof InputFilter.LengthFilter) { 8459 info.setMaxTextLength(((InputFilter.LengthFilter) filter).getMax()); 8460 } 8461 } 8462 8463 if (!isSingleLine()) { 8464 info.setMultiLine(true); 8465 } 8466 } 8467 8468 @Override performAccessibilityAction(int action, Bundle arguments)8469 public boolean performAccessibilityAction(int action, Bundle arguments) { 8470 switch (action) { 8471 case AccessibilityNodeInfo.ACTION_CLICK: { 8472 boolean handled = false; 8473 8474 // Simulate View.onTouchEvent for an ACTION_UP event. 8475 if (isClickable() || isLongClickable()) { 8476 if (isFocusable() && !isFocused()) { 8477 requestFocus(); 8478 } 8479 8480 performClick(); 8481 handled = true; 8482 } 8483 8484 // Simulate TextView.onTouchEvent for an ACTION_UP event. 8485 if ((mMovement != null || onCheckIsTextEditor()) && isEnabled() 8486 && mText instanceof Spannable && mLayout != null 8487 && (isTextEditable() || isTextSelectable()) && isFocused()) { 8488 // Show the IME, except when selecting in read-only text. 8489 final InputMethodManager imm = InputMethodManager.peekInstance(); 8490 viewClicked(imm); 8491 if (!isTextSelectable() && mEditor.mShowSoftInputOnFocus && imm != null) { 8492 handled |= imm.showSoftInput(this, 0); 8493 } 8494 } 8495 8496 return handled; 8497 } 8498 case AccessibilityNodeInfo.ACTION_COPY: { 8499 if (isFocused() && canCopy()) { 8500 if (onTextContextMenuItem(ID_COPY)) { 8501 return true; 8502 } 8503 } 8504 } return false; 8505 case AccessibilityNodeInfo.ACTION_PASTE: { 8506 if (isFocused() && canPaste()) { 8507 if (onTextContextMenuItem(ID_PASTE)) { 8508 return true; 8509 } 8510 } 8511 } return false; 8512 case AccessibilityNodeInfo.ACTION_CUT: { 8513 if (isFocused() && canCut()) { 8514 if (onTextContextMenuItem(ID_CUT)) { 8515 return true; 8516 } 8517 } 8518 } return false; 8519 case AccessibilityNodeInfo.ACTION_SET_SELECTION: { 8520 if (isFocused() && canSelectText()) { 8521 CharSequence text = getIterableTextForAccessibility(); 8522 if (text == null) { 8523 return false; 8524 } 8525 final int start = (arguments != null) ? arguments.getInt( 8526 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, -1) : -1; 8527 final int end = (arguments != null) ? arguments.getInt( 8528 AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, -1) : -1; 8529 if ((getSelectionStart() != start || getSelectionEnd() != end)) { 8530 // No arguments clears the selection. 8531 if (start == end && end == -1) { 8532 Selection.removeSelection((Spannable) text); 8533 return true; 8534 } 8535 if (start >= 0 && start <= end && end <= text.length()) { 8536 Selection.setSelection((Spannable) text, start, end); 8537 // Make sure selection mode is engaged. 8538 if (mEditor != null) { 8539 mEditor.startSelectionActionMode(); 8540 } 8541 return true; 8542 } 8543 } 8544 } 8545 } return false; 8546 default: { 8547 return super.performAccessibilityAction(action, arguments); 8548 } 8549 } 8550 } 8551 8552 @Override sendAccessibilityEvent(int eventType)8553 public void sendAccessibilityEvent(int eventType) { 8554 // Do not send scroll events since first they are not interesting for 8555 // accessibility and second such events a generated too frequently. 8556 // For details see the implementation of bringTextIntoView(). 8557 if (eventType == AccessibilityEvent.TYPE_VIEW_SCROLLED) { 8558 return; 8559 } 8560 super.sendAccessibilityEvent(eventType); 8561 } 8562 8563 /** 8564 * Gets the text reported for accessibility purposes. 8565 * 8566 * @return The accessibility text. 8567 * 8568 * @hide 8569 */ getTextForAccessibility()8570 public CharSequence getTextForAccessibility() { 8571 CharSequence text = getText(); 8572 if (TextUtils.isEmpty(text)) { 8573 text = getHint(); 8574 } 8575 return text; 8576 } 8577 sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, int fromIndex, int removedCount, int addedCount)8578 void sendAccessibilityEventTypeViewTextChanged(CharSequence beforeText, 8579 int fromIndex, int removedCount, int addedCount) { 8580 AccessibilityEvent event = 8581 AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); 8582 event.setFromIndex(fromIndex); 8583 event.setRemovedCount(removedCount); 8584 event.setAddedCount(addedCount); 8585 event.setBeforeText(beforeText); 8586 sendAccessibilityEventUnchecked(event); 8587 } 8588 8589 /** 8590 * Returns whether this text view is a current input method target. The 8591 * default implementation just checks with {@link InputMethodManager}. 8592 */ isInputMethodTarget()8593 public boolean isInputMethodTarget() { 8594 InputMethodManager imm = InputMethodManager.peekInstance(); 8595 return imm != null && imm.isActive(this); 8596 } 8597 8598 static final int ID_SELECT_ALL = android.R.id.selectAll; 8599 static final int ID_CUT = android.R.id.cut; 8600 static final int ID_COPY = android.R.id.copy; 8601 static final int ID_PASTE = android.R.id.paste; 8602 8603 /** 8604 * Called when a context menu option for the text view is selected. Currently 8605 * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, 8606 * {@link android.R.id#copy} or {@link android.R.id#paste}. 8607 * 8608 * @return true if the context menu item action was performed. 8609 */ onTextContextMenuItem(int id)8610 public boolean onTextContextMenuItem(int id) { 8611 int min = 0; 8612 int max = mText.length(); 8613 8614 if (isFocused()) { 8615 final int selStart = getSelectionStart(); 8616 final int selEnd = getSelectionEnd(); 8617 8618 min = Math.max(0, Math.min(selStart, selEnd)); 8619 max = Math.max(0, Math.max(selStart, selEnd)); 8620 } 8621 8622 switch (id) { 8623 case ID_SELECT_ALL: 8624 // This does not enter text selection mode. Text is highlighted, so that it can be 8625 // bulk edited, like selectAllOnFocus does. Returns true even if text is empty. 8626 selectAllText(); 8627 return true; 8628 8629 case ID_PASTE: 8630 paste(min, max); 8631 return true; 8632 8633 case ID_CUT: 8634 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 8635 deleteText_internal(min, max); 8636 stopSelectionActionMode(); 8637 return true; 8638 8639 case ID_COPY: 8640 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max))); 8641 stopSelectionActionMode(); 8642 return true; 8643 } 8644 return false; 8645 } 8646 getTransformedText(int start, int end)8647 CharSequence getTransformedText(int start, int end) { 8648 return removeSuggestionSpans(mTransformed.subSequence(start, end)); 8649 } 8650 8651 @Override performLongClick()8652 public boolean performLongClick() { 8653 boolean handled = false; 8654 8655 if (super.performLongClick()) { 8656 handled = true; 8657 } 8658 8659 if (mEditor != null) { 8660 handled |= mEditor.performLongClick(handled); 8661 } 8662 8663 if (handled) { 8664 performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 8665 if (mEditor != null) mEditor.mDiscardNextActionUp = true; 8666 } 8667 8668 return handled; 8669 } 8670 8671 @Override onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert)8672 protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) { 8673 super.onScrollChanged(horiz, vert, oldHoriz, oldVert); 8674 if (mEditor != null) { 8675 mEditor.onScrollChanged(); 8676 } 8677 } 8678 8679 /** 8680 * Return whether or not suggestions are enabled on this TextView. The suggestions are generated 8681 * by the IME or by the spell checker as the user types. This is done by adding 8682 * {@link SuggestionSpan}s to the text. 8683 * 8684 * When suggestions are enabled (default), this list of suggestions will be displayed when the 8685 * user asks for them on these parts of the text. This value depends on the inputType of this 8686 * TextView. 8687 * 8688 * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}. 8689 * 8690 * In addition, the type variation must be one of 8691 * {@link InputType#TYPE_TEXT_VARIATION_NORMAL}, 8692 * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT}, 8693 * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE}, 8694 * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or 8695 * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}. 8696 * 8697 * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set. 8698 * 8699 * @return true if the suggestions popup window is enabled, based on the inputType. 8700 */ isSuggestionsEnabled()8701 public boolean isSuggestionsEnabled() { 8702 if (mEditor == null) return false; 8703 if ((mEditor.mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) { 8704 return false; 8705 } 8706 if ((mEditor.mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false; 8707 8708 final int variation = mEditor.mInputType & EditorInfo.TYPE_MASK_VARIATION; 8709 return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL || 8710 variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT || 8711 variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE || 8712 variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE || 8713 variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT); 8714 } 8715 8716 /** 8717 * If provided, this ActionMode.Callback will be used to create the ActionMode when text 8718 * selection is initiated in this View. 8719 * 8720 * The standard implementation populates the menu with a subset of Select All, Cut, Copy and 8721 * Paste actions, depending on what this View supports. 8722 * 8723 * A custom implementation can add new entries in the default menu in its 8724 * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The 8725 * default actions can also be removed from the menu using {@link Menu#removeItem(int)} and 8726 * passing {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} 8727 * or {@link android.R.id#paste} ids as parameters. 8728 * 8729 * Returning false from 8730 * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent 8731 * the action mode from being started. 8732 * 8733 * Action click events should be handled by the custom implementation of 8734 * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}. 8735 * 8736 * Note that text selection mode is not started when a TextView receives focus and the 8737 * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in 8738 * that case, to allow for quick replacement. 8739 */ setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback)8740 public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { 8741 createEditorIfNeeded(); 8742 mEditor.mCustomSelectionActionModeCallback = actionModeCallback; 8743 } 8744 8745 /** 8746 * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null. 8747 * 8748 * @return The current custom selection callback. 8749 */ getCustomSelectionActionModeCallback()8750 public ActionMode.Callback getCustomSelectionActionModeCallback() { 8751 return mEditor == null ? null : mEditor.mCustomSelectionActionModeCallback; 8752 } 8753 8754 /** 8755 * @hide 8756 */ stopSelectionActionMode()8757 protected void stopSelectionActionMode() { 8758 mEditor.stopSelectionActionMode(); 8759 } 8760 canCut()8761 boolean canCut() { 8762 if (hasPasswordTransformationMethod()) { 8763 return false; 8764 } 8765 8766 if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && 8767 mEditor.mKeyListener != null) { 8768 return true; 8769 } 8770 8771 return false; 8772 } 8773 canCopy()8774 boolean canCopy() { 8775 if (hasPasswordTransformationMethod()) { 8776 return false; 8777 } 8778 8779 if (mText.length() > 0 && hasSelection() && mEditor != null) { 8780 return true; 8781 } 8782 8783 return false; 8784 } 8785 canPaste()8786 boolean canPaste() { 8787 return (mText instanceof Editable && 8788 mEditor != null && mEditor.mKeyListener != null && 8789 getSelectionStart() >= 0 && 8790 getSelectionEnd() >= 0 && 8791 ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). 8792 hasPrimaryClip()); 8793 } 8794 selectAllText()8795 boolean selectAllText() { 8796 final int length = mText.length(); 8797 Selection.setSelection((Spannable) mText, 0, length); 8798 return length > 0; 8799 } 8800 8801 /** 8802 * Paste clipboard content between min and max positions. 8803 */ paste(int min, int max)8804 private void paste(int min, int max) { 8805 ClipboardManager clipboard = 8806 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); 8807 ClipData clip = clipboard.getPrimaryClip(); 8808 if (clip != null) { 8809 boolean didFirst = false; 8810 for (int i=0; i<clip.getItemCount(); i++) { 8811 CharSequence paste = clip.getItemAt(i).coerceToStyledText(getContext()); 8812 if (paste != null) { 8813 if (!didFirst) { 8814 Selection.setSelection((Spannable) mText, max); 8815 ((Editable) mText).replace(min, max, paste); 8816 didFirst = true; 8817 } else { 8818 ((Editable) mText).insert(getSelectionEnd(), "\n"); 8819 ((Editable) mText).insert(getSelectionEnd(), paste); 8820 } 8821 } 8822 } 8823 stopSelectionActionMode(); 8824 LAST_CUT_OR_COPY_TIME = 0; 8825 } 8826 } 8827 setPrimaryClip(ClipData clip)8828 private void setPrimaryClip(ClipData clip) { 8829 ClipboardManager clipboard = (ClipboardManager) getContext(). 8830 getSystemService(Context.CLIPBOARD_SERVICE); 8831 clipboard.setPrimaryClip(clip); 8832 LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis(); 8833 } 8834 8835 /** 8836 * Get the character offset closest to the specified absolute position. A typical use case is to 8837 * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method. 8838 * 8839 * @param x The horizontal absolute position of a point on screen 8840 * @param y The vertical absolute position of a point on screen 8841 * @return the character offset for the character whose position is closest to the specified 8842 * position. Returns -1 if there is no layout. 8843 */ getOffsetForPosition(float x, float y)8844 public int getOffsetForPosition(float x, float y) { 8845 if (getLayout() == null) return -1; 8846 final int line = getLineAtCoordinate(y); 8847 final int offset = getOffsetAtCoordinate(line, x); 8848 return offset; 8849 } 8850 convertToLocalHorizontalCoordinate(float x)8851 float convertToLocalHorizontalCoordinate(float x) { 8852 x -= getTotalPaddingLeft(); 8853 // Clamp the position to inside of the view. 8854 x = Math.max(0.0f, x); 8855 x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); 8856 x += getScrollX(); 8857 return x; 8858 } 8859 getLineAtCoordinate(float y)8860 int getLineAtCoordinate(float y) { 8861 y -= getTotalPaddingTop(); 8862 // Clamp the position to inside of the view. 8863 y = Math.max(0.0f, y); 8864 y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); 8865 y += getScrollY(); 8866 return getLayout().getLineForVertical((int) y); 8867 } 8868 getOffsetAtCoordinate(int line, float x)8869 private int getOffsetAtCoordinate(int line, float x) { 8870 x = convertToLocalHorizontalCoordinate(x); 8871 return getLayout().getOffsetForHorizontal(line, x); 8872 } 8873 8874 @Override onDragEvent(DragEvent event)8875 public boolean onDragEvent(DragEvent event) { 8876 switch (event.getAction()) { 8877 case DragEvent.ACTION_DRAG_STARTED: 8878 return mEditor != null && mEditor.hasInsertionController(); 8879 8880 case DragEvent.ACTION_DRAG_ENTERED: 8881 TextView.this.requestFocus(); 8882 return true; 8883 8884 case DragEvent.ACTION_DRAG_LOCATION: 8885 final int offset = getOffsetForPosition(event.getX(), event.getY()); 8886 Selection.setSelection((Spannable)mText, offset); 8887 return true; 8888 8889 case DragEvent.ACTION_DROP: 8890 if (mEditor != null) mEditor.onDrop(event); 8891 return true; 8892 8893 case DragEvent.ACTION_DRAG_ENDED: 8894 case DragEvent.ACTION_DRAG_EXITED: 8895 default: 8896 return true; 8897 } 8898 } 8899 isInBatchEditMode()8900 boolean isInBatchEditMode() { 8901 if (mEditor == null) return false; 8902 final Editor.InputMethodState ims = mEditor.mInputMethodState; 8903 if (ims != null) { 8904 return ims.mBatchEditNesting > 0; 8905 } 8906 return mEditor.mInBatchEditControllers; 8907 } 8908 8909 @Override onRtlPropertiesChanged(int layoutDirection)8910 public void onRtlPropertiesChanged(int layoutDirection) { 8911 super.onRtlPropertiesChanged(layoutDirection); 8912 8913 mTextDir = getTextDirectionHeuristic(); 8914 8915 if (mLayout != null) { 8916 checkForRelayout(); 8917 } 8918 } 8919 getTextDirectionHeuristic()8920 TextDirectionHeuristic getTextDirectionHeuristic() { 8921 if (hasPasswordTransformationMethod()) { 8922 // passwords fields should be LTR 8923 return TextDirectionHeuristics.LTR; 8924 } 8925 8926 // Always need to resolve layout direction first 8927 final boolean defaultIsRtl = (getLayoutDirection() == LAYOUT_DIRECTION_RTL); 8928 8929 // Now, we can select the heuristic 8930 switch (getTextDirection()) { 8931 default: 8932 case TEXT_DIRECTION_FIRST_STRONG: 8933 return (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL : 8934 TextDirectionHeuristics.FIRSTSTRONG_LTR); 8935 case TEXT_DIRECTION_ANY_RTL: 8936 return TextDirectionHeuristics.ANYRTL_LTR; 8937 case TEXT_DIRECTION_LTR: 8938 return TextDirectionHeuristics.LTR; 8939 case TEXT_DIRECTION_RTL: 8940 return TextDirectionHeuristics.RTL; 8941 case TEXT_DIRECTION_LOCALE: 8942 return TextDirectionHeuristics.LOCALE; 8943 } 8944 } 8945 8946 /** 8947 * @hide 8948 */ 8949 @Override onResolveDrawables(int layoutDirection)8950 public void onResolveDrawables(int layoutDirection) { 8951 // No need to resolve twice 8952 if (mLastLayoutDirection == layoutDirection) { 8953 return; 8954 } 8955 mLastLayoutDirection = layoutDirection; 8956 8957 // Resolve drawables 8958 if (mDrawables != null) { 8959 mDrawables.resolveWithLayoutDirection(layoutDirection); 8960 } 8961 } 8962 8963 /** 8964 * @hide 8965 */ resetResolvedDrawables()8966 protected void resetResolvedDrawables() { 8967 super.resetResolvedDrawables(); 8968 mLastLayoutDirection = -1; 8969 } 8970 8971 /** 8972 * @hide 8973 */ viewClicked(InputMethodManager imm)8974 protected void viewClicked(InputMethodManager imm) { 8975 if (imm != null) { 8976 imm.viewClicked(this); 8977 } 8978 } 8979 8980 /** 8981 * Deletes the range of text [start, end[. 8982 * @hide 8983 */ deleteText_internal(int start, int end)8984 protected void deleteText_internal(int start, int end) { 8985 ((Editable) mText).delete(start, end); 8986 } 8987 8988 /** 8989 * Replaces the range of text [start, end[ by replacement text 8990 * @hide 8991 */ replaceText_internal(int start, int end, CharSequence text)8992 protected void replaceText_internal(int start, int end, CharSequence text) { 8993 ((Editable) mText).replace(start, end, text); 8994 } 8995 8996 /** 8997 * Sets a span on the specified range of text 8998 * @hide 8999 */ setSpan_internal(Object span, int start, int end, int flags)9000 protected void setSpan_internal(Object span, int start, int end, int flags) { 9001 ((Editable) mText).setSpan(span, start, end, flags); 9002 } 9003 9004 /** 9005 * Moves the cursor to the specified offset position in text 9006 * @hide 9007 */ setCursorPosition_internal(int start, int end)9008 protected void setCursorPosition_internal(int start, int end) { 9009 Selection.setSelection(((Editable) mText), start, end); 9010 } 9011 9012 /** 9013 * An Editor should be created as soon as any of the editable-specific fields (grouped 9014 * inside the Editor object) is assigned to a non-default value. 9015 * This method will create the Editor if needed. 9016 * 9017 * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will 9018 * have a null Editor, unlike an EditText. Inconsistent in-between states will have an 9019 * Editor for backward compatibility, as soon as one of these fields is assigned. 9020 * 9021 * Also note that for performance reasons, the mEditor is created when needed, but not 9022 * reset when no more edit-specific fields are needed. 9023 */ createEditorIfNeeded()9024 private void createEditorIfNeeded() { 9025 if (mEditor == null) { 9026 mEditor = new Editor(this); 9027 } 9028 } 9029 9030 /** 9031 * @hide 9032 */ 9033 @Override getIterableTextForAccessibility()9034 public CharSequence getIterableTextForAccessibility() { 9035 if (!(mText instanceof Spannable)) { 9036 setText(mText, BufferType.SPANNABLE); 9037 } 9038 return mText; 9039 } 9040 9041 /** 9042 * @hide 9043 */ 9044 @Override getIteratorForGranularity(int granularity)9045 public TextSegmentIterator getIteratorForGranularity(int granularity) { 9046 switch (granularity) { 9047 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE: { 9048 Spannable text = (Spannable) getIterableTextForAccessibility(); 9049 if (!TextUtils.isEmpty(text) && getLayout() != null) { 9050 AccessibilityIterators.LineTextSegmentIterator iterator = 9051 AccessibilityIterators.LineTextSegmentIterator.getInstance(); 9052 iterator.initialize(text, getLayout()); 9053 return iterator; 9054 } 9055 } break; 9056 case AccessibilityNodeInfo.MOVEMENT_GRANULARITY_PAGE: { 9057 Spannable text = (Spannable) getIterableTextForAccessibility(); 9058 if (!TextUtils.isEmpty(text) && getLayout() != null) { 9059 AccessibilityIterators.PageTextSegmentIterator iterator = 9060 AccessibilityIterators.PageTextSegmentIterator.getInstance(); 9061 iterator.initialize(this); 9062 return iterator; 9063 } 9064 } break; 9065 } 9066 return super.getIteratorForGranularity(granularity); 9067 } 9068 9069 /** 9070 * @hide 9071 */ 9072 @Override getAccessibilitySelectionStart()9073 public int getAccessibilitySelectionStart() { 9074 return getSelectionStart(); 9075 } 9076 9077 /** 9078 * @hide 9079 */ isAccessibilitySelectionExtendable()9080 public boolean isAccessibilitySelectionExtendable() { 9081 return true; 9082 } 9083 9084 /** 9085 * @hide 9086 */ 9087 @Override getAccessibilitySelectionEnd()9088 public int getAccessibilitySelectionEnd() { 9089 return getSelectionEnd(); 9090 } 9091 9092 /** 9093 * @hide 9094 */ 9095 @Override setAccessibilitySelection(int start, int end)9096 public void setAccessibilitySelection(int start, int end) { 9097 if (getAccessibilitySelectionStart() == start 9098 && getAccessibilitySelectionEnd() == end) { 9099 return; 9100 } 9101 // Hide all selection controllers used for adjusting selection 9102 // since we are doing so explicitlty by other means and these 9103 // controllers interact with how selection behaves. 9104 if (mEditor != null) { 9105 mEditor.hideControllers(); 9106 } 9107 CharSequence text = getIterableTextForAccessibility(); 9108 if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) { 9109 Selection.setSelection((Spannable) text, start, end); 9110 } else { 9111 Selection.removeSelection((Spannable) text); 9112 } 9113 } 9114 9115 /** 9116 * User interface state that is stored by TextView for implementing 9117 * {@link View#onSaveInstanceState}. 9118 */ 9119 public static class SavedState extends BaseSavedState { 9120 int selStart; 9121 int selEnd; 9122 CharSequence text; 9123 boolean frozenWithFocus; 9124 CharSequence error; 9125 SavedState(Parcelable superState)9126 SavedState(Parcelable superState) { 9127 super(superState); 9128 } 9129 9130 @Override writeToParcel(Parcel out, int flags)9131 public void writeToParcel(Parcel out, int flags) { 9132 super.writeToParcel(out, flags); 9133 out.writeInt(selStart); 9134 out.writeInt(selEnd); 9135 out.writeInt(frozenWithFocus ? 1 : 0); 9136 TextUtils.writeToParcel(text, out, flags); 9137 9138 if (error == null) { 9139 out.writeInt(0); 9140 } else { 9141 out.writeInt(1); 9142 TextUtils.writeToParcel(error, out, flags); 9143 } 9144 } 9145 9146 @Override toString()9147 public String toString() { 9148 String str = "TextView.SavedState{" 9149 + Integer.toHexString(System.identityHashCode(this)) 9150 + " start=" + selStart + " end=" + selEnd; 9151 if (text != null) { 9152 str += " text=" + text; 9153 } 9154 return str + "}"; 9155 } 9156 9157 @SuppressWarnings("hiding") 9158 public static final Parcelable.Creator<SavedState> CREATOR 9159 = new Parcelable.Creator<SavedState>() { 9160 public SavedState createFromParcel(Parcel in) { 9161 return new SavedState(in); 9162 } 9163 9164 public SavedState[] newArray(int size) { 9165 return new SavedState[size]; 9166 } 9167 }; 9168 SavedState(Parcel in)9169 private SavedState(Parcel in) { 9170 super(in); 9171 selStart = in.readInt(); 9172 selEnd = in.readInt(); 9173 frozenWithFocus = (in.readInt() != 0); 9174 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 9175 9176 if (in.readInt() != 0) { 9177 error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 9178 } 9179 } 9180 } 9181 9182 private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations { 9183 private char[] mChars; 9184 private int mStart, mLength; 9185 CharWrapper(char[] chars, int start, int len)9186 public CharWrapper(char[] chars, int start, int len) { 9187 mChars = chars; 9188 mStart = start; 9189 mLength = len; 9190 } 9191 set(char[] chars, int start, int len)9192 /* package */ void set(char[] chars, int start, int len) { 9193 mChars = chars; 9194 mStart = start; 9195 mLength = len; 9196 } 9197 length()9198 public int length() { 9199 return mLength; 9200 } 9201 charAt(int off)9202 public char charAt(int off) { 9203 return mChars[off + mStart]; 9204 } 9205 9206 @Override toString()9207 public String toString() { 9208 return new String(mChars, mStart, mLength); 9209 } 9210 subSequence(int start, int end)9211 public CharSequence subSequence(int start, int end) { 9212 if (start < 0 || end < 0 || start > mLength || end > mLength) { 9213 throw new IndexOutOfBoundsException(start + ", " + end); 9214 } 9215 9216 return new String(mChars, start + mStart, end - start); 9217 } 9218 getChars(int start, int end, char[] buf, int off)9219 public void getChars(int start, int end, char[] buf, int off) { 9220 if (start < 0 || end < 0 || start > mLength || end > mLength) { 9221 throw new IndexOutOfBoundsException(start + ", " + end); 9222 } 9223 9224 System.arraycopy(mChars, start + mStart, buf, off, end - start); 9225 } 9226 drawText(Canvas c, int start, int end, float x, float y, Paint p)9227 public void drawText(Canvas c, int start, int end, 9228 float x, float y, Paint p) { 9229 c.drawText(mChars, start + mStart, end - start, x, y, p); 9230 } 9231 drawTextRun(Canvas c, int start, int end, int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p)9232 public void drawTextRun(Canvas c, int start, int end, 9233 int contextStart, int contextEnd, float x, float y, boolean isRtl, Paint p) { 9234 int count = end - start; 9235 int contextCount = contextEnd - contextStart; 9236 c.drawTextRun(mChars, start + mStart, count, contextStart + mStart, 9237 contextCount, x, y, isRtl, p); 9238 } 9239 measureText(int start, int end, Paint p)9240 public float measureText(int start, int end, Paint p) { 9241 return p.measureText(mChars, start + mStart, end - start); 9242 } 9243 getTextWidths(int start, int end, float[] widths, Paint p)9244 public int getTextWidths(int start, int end, float[] widths, Paint p) { 9245 return p.getTextWidths(mChars, start + mStart, end - start, widths); 9246 } 9247 getTextRunAdvances(int start, int end, int contextStart, int contextEnd, boolean isRtl, float[] advances, int advancesIndex, Paint p)9248 public float getTextRunAdvances(int start, int end, int contextStart, 9249 int contextEnd, boolean isRtl, float[] advances, int advancesIndex, 9250 Paint p) { 9251 int count = end - start; 9252 int contextCount = contextEnd - contextStart; 9253 return p.getTextRunAdvances(mChars, start + mStart, count, 9254 contextStart + mStart, contextCount, isRtl, advances, 9255 advancesIndex); 9256 } 9257 getTextRunCursor(int contextStart, int contextEnd, int dir, int offset, int cursorOpt, Paint p)9258 public int getTextRunCursor(int contextStart, int contextEnd, int dir, 9259 int offset, int cursorOpt, Paint p) { 9260 int contextCount = contextEnd - contextStart; 9261 return p.getTextRunCursor(mChars, contextStart + mStart, 9262 contextCount, dir, offset + mStart, cursorOpt); 9263 } 9264 } 9265 9266 private static final class Marquee { 9267 // TODO: Add an option to configure this 9268 private static final float MARQUEE_DELTA_MAX = 0.07f; 9269 private static final int MARQUEE_DELAY = 1200; 9270 private static final int MARQUEE_RESTART_DELAY = 1200; 9271 private static final int MARQUEE_DP_PER_SECOND = 30; 9272 9273 private static final byte MARQUEE_STOPPED = 0x0; 9274 private static final byte MARQUEE_STARTING = 0x1; 9275 private static final byte MARQUEE_RUNNING = 0x2; 9276 9277 private final WeakReference<TextView> mView; 9278 private final Choreographer mChoreographer; 9279 9280 private byte mStatus = MARQUEE_STOPPED; 9281 private final float mPixelsPerSecond; 9282 private float mMaxScroll; 9283 private float mMaxFadeScroll; 9284 private float mGhostStart; 9285 private float mGhostOffset; 9286 private float mFadeStop; 9287 private int mRepeatLimit; 9288 9289 private float mScroll; 9290 private long mLastAnimationMs; 9291 Marquee(TextView v)9292 Marquee(TextView v) { 9293 final float density = v.getContext().getResources().getDisplayMetrics().density; 9294 mPixelsPerSecond = MARQUEE_DP_PER_SECOND * density; 9295 mView = new WeakReference<TextView>(v); 9296 mChoreographer = Choreographer.getInstance(); 9297 } 9298 9299 private Choreographer.FrameCallback mTickCallback = new Choreographer.FrameCallback() { 9300 @Override 9301 public void doFrame(long frameTimeNanos) { 9302 tick(); 9303 } 9304 }; 9305 9306 private Choreographer.FrameCallback mStartCallback = new Choreographer.FrameCallback() { 9307 @Override 9308 public void doFrame(long frameTimeNanos) { 9309 mStatus = MARQUEE_RUNNING; 9310 mLastAnimationMs = mChoreographer.getFrameTime(); 9311 tick(); 9312 } 9313 }; 9314 9315 private Choreographer.FrameCallback mRestartCallback = new Choreographer.FrameCallback() { 9316 @Override 9317 public void doFrame(long frameTimeNanos) { 9318 if (mStatus == MARQUEE_RUNNING) { 9319 if (mRepeatLimit >= 0) { 9320 mRepeatLimit--; 9321 } 9322 start(mRepeatLimit); 9323 } 9324 } 9325 }; 9326 tick()9327 void tick() { 9328 if (mStatus != MARQUEE_RUNNING) { 9329 return; 9330 } 9331 9332 mChoreographer.removeFrameCallback(mTickCallback); 9333 9334 final TextView textView = mView.get(); 9335 if (textView != null && (textView.isFocused() || textView.isSelected())) { 9336 long currentMs = mChoreographer.getFrameTime(); 9337 long deltaMs = currentMs - mLastAnimationMs; 9338 mLastAnimationMs = currentMs; 9339 float deltaPx = deltaMs / 1000f * mPixelsPerSecond; 9340 mScroll += deltaPx; 9341 if (mScroll > mMaxScroll) { 9342 mScroll = mMaxScroll; 9343 mChoreographer.postFrameCallbackDelayed(mRestartCallback, MARQUEE_DELAY); 9344 } else { 9345 mChoreographer.postFrameCallback(mTickCallback); 9346 } 9347 textView.invalidate(); 9348 } 9349 } 9350 stop()9351 void stop() { 9352 mStatus = MARQUEE_STOPPED; 9353 mChoreographer.removeFrameCallback(mStartCallback); 9354 mChoreographer.removeFrameCallback(mRestartCallback); 9355 mChoreographer.removeFrameCallback(mTickCallback); 9356 resetScroll(); 9357 } 9358 resetScroll()9359 private void resetScroll() { 9360 mScroll = 0.0f; 9361 final TextView textView = mView.get(); 9362 if (textView != null) textView.invalidate(); 9363 } 9364 start(int repeatLimit)9365 void start(int repeatLimit) { 9366 if (repeatLimit == 0) { 9367 stop(); 9368 return; 9369 } 9370 mRepeatLimit = repeatLimit; 9371 final TextView textView = mView.get(); 9372 if (textView != null && textView.mLayout != null) { 9373 mStatus = MARQUEE_STARTING; 9374 mScroll = 0.0f; 9375 final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() - 9376 textView.getCompoundPaddingRight(); 9377 final float lineWidth = textView.mLayout.getLineWidth(0); 9378 final float gap = textWidth / 3.0f; 9379 mGhostStart = lineWidth - textWidth + gap; 9380 mMaxScroll = mGhostStart + textWidth; 9381 mGhostOffset = lineWidth + gap; 9382 mFadeStop = lineWidth + textWidth / 6.0f; 9383 mMaxFadeScroll = mGhostStart + lineWidth + lineWidth; 9384 9385 textView.invalidate(); 9386 mChoreographer.postFrameCallback(mStartCallback); 9387 } 9388 } 9389 getGhostOffset()9390 float getGhostOffset() { 9391 return mGhostOffset; 9392 } 9393 getScroll()9394 float getScroll() { 9395 return mScroll; 9396 } 9397 getMaxFadeScroll()9398 float getMaxFadeScroll() { 9399 return mMaxFadeScroll; 9400 } 9401 shouldDrawLeftFade()9402 boolean shouldDrawLeftFade() { 9403 return mScroll <= mFadeStop; 9404 } 9405 shouldDrawGhost()9406 boolean shouldDrawGhost() { 9407 return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart; 9408 } 9409 isRunning()9410 boolean isRunning() { 9411 return mStatus == MARQUEE_RUNNING; 9412 } 9413 isStopped()9414 boolean isStopped() { 9415 return mStatus == MARQUEE_STOPPED; 9416 } 9417 } 9418 9419 private class ChangeWatcher implements TextWatcher, SpanWatcher { 9420 9421 private CharSequence mBeforeText; 9422 beforeTextChanged(CharSequence buffer, int start, int before, int after)9423 public void beforeTextChanged(CharSequence buffer, int start, 9424 int before, int after) { 9425 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start 9426 + " before=" + before + " after=" + after + ": " + buffer); 9427 9428 if (AccessibilityManager.getInstance(mContext).isEnabled() 9429 && ((!isPasswordInputType(getInputType()) && !hasPasswordTransformationMethod()) 9430 || shouldSpeakPasswordsForAccessibility())) { 9431 mBeforeText = buffer.toString(); 9432 } 9433 9434 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 9435 } 9436 onTextChanged(CharSequence buffer, int start, int before, int after)9437 public void onTextChanged(CharSequence buffer, int start, int before, int after) { 9438 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start 9439 + " before=" + before + " after=" + after + ": " + buffer); 9440 TextView.this.handleTextChanged(buffer, start, before, after); 9441 9442 if (AccessibilityManager.getInstance(mContext).isEnabled() && 9443 (isFocused() || isSelected() && isShown())) { 9444 sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after); 9445 mBeforeText = null; 9446 } 9447 } 9448 afterTextChanged(Editable buffer)9449 public void afterTextChanged(Editable buffer) { 9450 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer); 9451 TextView.this.sendAfterTextChanged(buffer); 9452 9453 if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { 9454 MetaKeyKeyListener.stopSelecting(TextView.this, buffer); 9455 } 9456 } 9457 onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en)9458 public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { 9459 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e 9460 + " st=" + st + " en=" + en + " what=" + what + ": " + buf); 9461 TextView.this.spanChange(buf, what, s, st, e, en); 9462 } 9463 onSpanAdded(Spannable buf, Object what, int s, int e)9464 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 9465 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e 9466 + " what=" + what + ": " + buf); 9467 TextView.this.spanChange(buf, what, -1, s, -1, e); 9468 } 9469 onSpanRemoved(Spannable buf, Object what, int s, int e)9470 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 9471 if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e 9472 + " what=" + what + ": " + buf); 9473 TextView.this.spanChange(buf, what, s, -1, e, -1); 9474 } 9475 } 9476 } 9477