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