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