TextView.java revision f013e1afd1e68af5e3b868c26a653bbfb39538f8
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.content.Context; 20import android.content.res.ColorStateList; 21import android.content.res.Resources; 22import android.content.res.TypedArray; 23import android.content.res.XmlResourceParser; 24import android.graphics.Canvas; 25import android.graphics.Paint; 26import android.graphics.Path; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.graphics.Typeface; 30import android.graphics.drawable.Drawable; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.Parcel; 34import android.os.Parcelable; 35import android.os.SystemClock; 36import android.os.Message; 37import android.text.BoringLayout; 38import android.text.DynamicLayout; 39import android.text.Editable; 40import android.text.GetChars; 41import android.text.GraphicsOperations; 42import android.text.ClipboardManager; 43import android.text.InputFilter; 44import android.text.Layout; 45import android.text.Selection; 46import android.text.SpanWatcher; 47import android.text.Spannable; 48import android.text.Spanned; 49import android.text.SpannedString; 50import android.text.SpannableString; 51import android.text.StaticLayout; 52import android.text.TextPaint; 53import android.text.TextUtils; 54import android.text.TextWatcher; 55import android.text.method.DateKeyListener; 56import android.text.method.DateTimeKeyListener; 57import android.text.method.DialerKeyListener; 58import android.text.method.DigitsKeyListener; 59import android.text.method.KeyListener; 60import android.text.method.LinkMovementMethod; 61import android.text.method.MetaKeyKeyListener; 62import android.text.method.MovementMethod; 63import android.text.method.TimeKeyListener; 64 65import android.text.method.PasswordTransformationMethod; 66import android.text.method.SingleLineTransformationMethod; 67import android.text.method.TextKeyListener; 68import android.text.method.TransformationMethod; 69import android.text.style.ParagraphStyle; 70import android.text.style.URLSpan; 71import android.text.style.UpdateLayout; 72import android.text.util.Linkify; 73import android.util.AttributeSet; 74import android.util.Log; 75import android.util.FloatMath; 76import android.util.TypedValue; 77import android.view.ContextMenu; 78import android.view.Gravity; 79import android.view.KeyEvent; 80import android.view.LayoutInflater; 81import android.view.MenuItem; 82import android.view.MotionEvent; 83import android.view.View; 84import android.view.ViewDebug; 85import android.view.ViewTreeObserver; 86import android.view.ViewGroup.LayoutParams; 87import android.view.animation.AnimationUtils; 88import android.view.inputmethod.CompletionInfo; 89import android.view.inputmethod.ExtractedText; 90import android.view.inputmethod.ExtractedTextRequest; 91import android.view.inputmethod.InputConnection; 92import android.view.inputmethod.InputMethodManager; 93import android.view.inputmethod.EditorInfo; 94import android.widget.RemoteViews.RemoteView; 95 96import java.io.IOException; 97import java.lang.ref.WeakReference; 98import java.util.ArrayList; 99 100import com.android.internal.util.FastMath; 101import com.android.internal.widget.EditableInputConnection; 102 103import org.xmlpull.v1.XmlPullParserException; 104 105/** 106 * Displays text to the user and optionally allows them to edit it. A TextView 107 * is a complete text editor, however the basic class is configured to not 108 * allow editing; see {@link EditText} for a subclass that configures the text 109 * view for editing. 110 * 111 * <p> 112 * <b>XML attributes</b> 113 * <p> 114 * See {@link android.R.styleable#TextView TextView Attributes}, 115 * {@link android.R.styleable#View View Attributes} 116 * 117 * @attr ref android.R.styleable#TextView_text 118 * @attr ref android.R.styleable#TextView_bufferType 119 * @attr ref android.R.styleable#TextView_hint 120 * @attr ref android.R.styleable#TextView_textColor 121 * @attr ref android.R.styleable#TextView_textColorHighlight 122 * @attr ref android.R.styleable#TextView_textColorHint 123 * @attr ref android.R.styleable#TextView_textSize 124 * @attr ref android.R.styleable#TextView_textScaleX 125 * @attr ref android.R.styleable#TextView_typeface 126 * @attr ref android.R.styleable#TextView_textStyle 127 * @attr ref android.R.styleable#TextView_cursorVisible 128 * @attr ref android.R.styleable#TextView_maxLines 129 * @attr ref android.R.styleable#TextView_maxHeight 130 * @attr ref android.R.styleable#TextView_lines 131 * @attr ref android.R.styleable#TextView_height 132 * @attr ref android.R.styleable#TextView_minLines 133 * @attr ref android.R.styleable#TextView_minHeight 134 * @attr ref android.R.styleable#TextView_maxEms 135 * @attr ref android.R.styleable#TextView_maxWidth 136 * @attr ref android.R.styleable#TextView_ems 137 * @attr ref android.R.styleable#TextView_width 138 * @attr ref android.R.styleable#TextView_minEms 139 * @attr ref android.R.styleable#TextView_minWidth 140 * @attr ref android.R.styleable#TextView_gravity 141 * @attr ref android.R.styleable#TextView_scrollHorizontally 142 * @attr ref android.R.styleable#TextView_password 143 * @attr ref android.R.styleable#TextView_singleLine 144 * @attr ref android.R.styleable#TextView_selectAllOnFocus 145 * @attr ref android.R.styleable#TextView_includeFontPadding 146 * @attr ref android.R.styleable#TextView_maxLength 147 * @attr ref android.R.styleable#TextView_shadowColor 148 * @attr ref android.R.styleable#TextView_shadowDx 149 * @attr ref android.R.styleable#TextView_shadowDy 150 * @attr ref android.R.styleable#TextView_shadowRadius 151 * @attr ref android.R.styleable#TextView_autoLink 152 * @attr ref android.R.styleable#TextView_linksClickable 153 * @attr ref android.R.styleable#TextView_numeric 154 * @attr ref android.R.styleable#TextView_digits 155 * @attr ref android.R.styleable#TextView_phoneNumber 156 * @attr ref android.R.styleable#TextView_inputMethod 157 * @attr ref android.R.styleable#TextView_capitalize 158 * @attr ref android.R.styleable#TextView_autoText 159 * @attr ref android.R.styleable#TextView_editable 160 * @attr ref android.R.styleable#TextView_drawableTop 161 * @attr ref android.R.styleable#TextView_drawableBottom 162 * @attr ref android.R.styleable#TextView_drawableRight 163 * @attr ref android.R.styleable#TextView_drawableLeft 164 * @attr ref android.R.styleable#TextView_lineSpacingExtra 165 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 166 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 167 */ 168@RemoteView 169public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { 170 private static int PRIORITY = 100; 171 172 private ColorStateList mTextColor; 173 private int mCurTextColor; 174 private ColorStateList mHintTextColor; 175 private ColorStateList mLinkTextColor; 176 private int mCurHintTextColor; 177 private boolean mFreezesText; 178 private boolean mFrozenWithFocus; 179 180 private Editable.Factory mEditableFactory = Editable.Factory.getInstance(); 181 private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance(); 182 183 private float mShadowRadius, mShadowDx, mShadowDy; 184 185 private static final int PREDRAW_NOT_REGISTERED = 0; 186 private static final int PREDRAW_PENDING = 1; 187 private static final int PREDRAW_DONE = 2; 188 private int mPreDrawState = PREDRAW_NOT_REGISTERED; 189 190 private TextUtils.TruncateAt mEllipsize = null; 191 192 // Enum for the "typeface" XML parameter. 193 // TODO: How can we get this from the XML instead of hardcoding it here? 194 private static final int SANS = 1; 195 private static final int SERIF = 2; 196 private static final int MONOSPACE = 3; 197 198 // Bitfield for the "numeric" XML parameter. 199 // TODO: How can we get this from the XML instead of hardcoding it here? 200 private static final int SIGNED = 2; 201 private static final int DECIMAL = 4; 202 203 class Drawables { 204 final Rect mCompoundRect = new Rect(); 205 Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight; 206 int mDrawableSizeTop, mDrawableSizeBottom, mDrawableSizeLeft, mDrawableSizeRight; 207 int mDrawableWidthTop, mDrawableWidthBottom, mDrawableHeightLeft, mDrawableHeightRight; 208 int mDrawablePadding; 209 }; 210 private Drawables mDrawables; 211 212 private CharSequence mError; 213 private boolean mErrorWasChanged; 214 private PopupWindow mPopup; 215 216 private CharWrapper mCharWrapper = null; 217 218 private boolean mSelectionMoved = false; 219 220 private Marquee mMarquee; 221 private boolean mRestartMarquee; 222 223 private int mMarqueeRepeatLimit = 3; 224 225 class InputContentType { 226 String privateContentType; 227 Bundle extras; 228 } 229 InputContentType mInputContentType; 230 231 class InputMethodState { 232 Rect mCursorRectInWindow = new Rect(); 233 RectF mTmpRectF = new RectF(); 234 float[] mTmpOffset = new float[2]; 235 ExtractedTextRequest mExtracting; 236 final ExtractedText mTmpExtracted = new ExtractedText(); 237 } 238 InputMethodState mInputMethodState; 239 240 /* 241 * Kick-start the font cache for the zygote process (to pay the cost of 242 * initializing freetype for our default font only once). 243 */ 244 static { 245 Paint p = new Paint(); 246 p.setAntiAlias(true); 247 // We don't care about the result, just the side-effect of measuring. 248 p.measureText("H"); 249 } 250 251 public TextView(Context context) { 252 this(context, null); 253 } 254 255 public TextView(Context context, 256 AttributeSet attrs) { 257 this(context, attrs, com.android.internal.R.attr.textViewStyle); 258 } 259 260 public TextView(Context context, 261 AttributeSet attrs, 262 int defStyle) { 263 super(context, attrs, defStyle); 264 mText = ""; 265 266 mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); 267 // If we get the paint from the skin, we should set it to left, since 268 // the layout always wants it to be left. 269 // mTextPaint.setTextAlign(Paint.Align.LEFT); 270 271 mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 272 273 mMovement = getDefaultMovementMethod(); 274 mTransformation = null; 275 276 TypedArray a = 277 context.obtainStyledAttributes( 278 attrs, com.android.internal.R.styleable.TextView, defStyle, 0); 279 280 int textColorHighlight = 0; 281 ColorStateList textColor = null; 282 ColorStateList textColorHint = null; 283 ColorStateList textColorLink = null; 284 int textSize = 15; 285 int typefaceIndex = -1; 286 int styleIndex = -1; 287 288 /* 289 * Look the appearance up without checking first if it exists because 290 * almost every TextView has one and it greatly simplifies the logic 291 * to be able to parse the appearance first and then let specific tags 292 * for this View override it. 293 */ 294 TypedArray appearance = null; 295 int ap = a.getResourceId(com.android.internal.R.styleable.TextView_textAppearance, -1); 296 if (ap != -1) { 297 appearance = context.obtainStyledAttributes(ap, 298 com.android.internal.R.styleable. 299 TextAppearance); 300 } 301 if (appearance != null) { 302 int n = appearance.getIndexCount(); 303 for (int i = 0; i < n; i++) { 304 int attr = appearance.getIndex(i); 305 306 switch (attr) { 307 case com.android.internal.R.styleable.TextAppearance_textColorHighlight: 308 textColorHighlight = appearance.getColor(attr, textColorHighlight); 309 break; 310 311 case com.android.internal.R.styleable.TextAppearance_textColor: 312 textColor = appearance.getColorStateList(attr); 313 break; 314 315 case com.android.internal.R.styleable.TextAppearance_textColorHint: 316 textColorHint = appearance.getColorStateList(attr); 317 break; 318 319 case com.android.internal.R.styleable.TextAppearance_textColorLink: 320 textColorLink = appearance.getColorStateList(attr); 321 break; 322 323 case com.android.internal.R.styleable.TextAppearance_textSize: 324 textSize = appearance.getDimensionPixelSize(attr, textSize); 325 break; 326 327 case com.android.internal.R.styleable.TextAppearance_typeface: 328 typefaceIndex = appearance.getInt(attr, -1); 329 break; 330 331 case com.android.internal.R.styleable.TextAppearance_textStyle: 332 styleIndex = appearance.getInt(attr, -1); 333 break; 334 } 335 } 336 337 appearance.recycle(); 338 } 339 340 boolean editable = getDefaultEditable(); 341 CharSequence inputMethod = null; 342 int numeric = 0; 343 CharSequence digits = null; 344 boolean phone = false; 345 boolean autotext = false; 346 int autocap = -1; 347 int buffertype = 0; 348 boolean selectallonfocus = false; 349 Drawable drawableLeft = null, drawableTop = null, drawableRight = null, 350 drawableBottom = null; 351 int drawablePadding = 0; 352 int ellipsize = -1; 353 boolean singleLine = false; 354 int maxlength = -1; 355 CharSequence text = ""; 356 int shadowcolor = 0; 357 float dx = 0, dy = 0, r = 0; 358 boolean password = false; 359 int contentType = EditorInfo.TYPE_NULL; 360 361 int n = a.getIndexCount(); 362 for (int i = 0; i < n; i++) { 363 int attr = a.getIndex(i); 364 365 switch (attr) { 366 case com.android.internal.R.styleable.TextView_editable: 367 editable = a.getBoolean(attr, editable); 368 break; 369 370 case com.android.internal.R.styleable.TextView_inputMethod: 371 inputMethod = a.getText(attr); 372 break; 373 374 case com.android.internal.R.styleable.TextView_numeric: 375 numeric = a.getInt(attr, numeric); 376 break; 377 378 case com.android.internal.R.styleable.TextView_digits: 379 digits = a.getText(attr); 380 break; 381 382 case com.android.internal.R.styleable.TextView_phoneNumber: 383 phone = a.getBoolean(attr, phone); 384 break; 385 386 case com.android.internal.R.styleable.TextView_autoText: 387 autotext = a.getBoolean(attr, autotext); 388 break; 389 390 case com.android.internal.R.styleable.TextView_capitalize: 391 autocap = a.getInt(attr, autocap); 392 break; 393 394 case com.android.internal.R.styleable.TextView_bufferType: 395 buffertype = a.getInt(attr, buffertype); 396 break; 397 398 case com.android.internal.R.styleable.TextView_selectAllOnFocus: 399 selectallonfocus = a.getBoolean(attr, selectallonfocus); 400 break; 401 402 case com.android.internal.R.styleable.TextView_autoLink: 403 mAutoLinkMask = a.getInt(attr, 0); 404 break; 405 406 case com.android.internal.R.styleable.TextView_linksClickable: 407 mLinksClickable = a.getBoolean(attr, true); 408 break; 409 410 case com.android.internal.R.styleable.TextView_drawableLeft: 411 drawableLeft = a.getDrawable(attr); 412 break; 413 414 case com.android.internal.R.styleable.TextView_drawableTop: 415 drawableTop = a.getDrawable(attr); 416 break; 417 418 case com.android.internal.R.styleable.TextView_drawableRight: 419 drawableRight = a.getDrawable(attr); 420 break; 421 422 case com.android.internal.R.styleable.TextView_drawableBottom: 423 drawableBottom = a.getDrawable(attr); 424 break; 425 426 case com.android.internal.R.styleable.TextView_drawablePadding: 427 drawablePadding = a.getDimensionPixelSize(attr, drawablePadding); 428 break; 429 430 case com.android.internal.R.styleable.TextView_maxLines: 431 setMaxLines(a.getInt(attr, -1)); 432 break; 433 434 case com.android.internal.R.styleable.TextView_maxHeight: 435 setMaxHeight(a.getDimensionPixelSize(attr, -1)); 436 break; 437 438 case com.android.internal.R.styleable.TextView_lines: 439 setLines(a.getInt(attr, -1)); 440 break; 441 442 case com.android.internal.R.styleable.TextView_height: 443 setHeight(a.getDimensionPixelSize(attr, -1)); 444 break; 445 446 case com.android.internal.R.styleable.TextView_minLines: 447 setMinLines(a.getInt(attr, -1)); 448 break; 449 450 case com.android.internal.R.styleable.TextView_minHeight: 451 setMinHeight(a.getDimensionPixelSize(attr, -1)); 452 break; 453 454 case com.android.internal.R.styleable.TextView_maxEms: 455 setMaxEms(a.getInt(attr, -1)); 456 break; 457 458 case com.android.internal.R.styleable.TextView_maxWidth: 459 setMaxWidth(a.getDimensionPixelSize(attr, -1)); 460 break; 461 462 case com.android.internal.R.styleable.TextView_ems: 463 setEms(a.getInt(attr, -1)); 464 break; 465 466 case com.android.internal.R.styleable.TextView_width: 467 setWidth(a.getDimensionPixelSize(attr, -1)); 468 break; 469 470 case com.android.internal.R.styleable.TextView_minEms: 471 setMinEms(a.getInt(attr, -1)); 472 break; 473 474 case com.android.internal.R.styleable.TextView_minWidth: 475 setMinWidth(a.getDimensionPixelSize(attr, -1)); 476 break; 477 478 case com.android.internal.R.styleable.TextView_gravity: 479 setGravity(a.getInt(attr, -1)); 480 break; 481 482 case com.android.internal.R.styleable.TextView_hint: 483 setHint(a.getText(attr)); 484 break; 485 486 case com.android.internal.R.styleable.TextView_text: 487 text = a.getText(attr); 488 break; 489 490 case com.android.internal.R.styleable.TextView_scrollHorizontally: 491 if (a.getBoolean(attr, false)) { 492 setHorizontallyScrolling(true); 493 } 494 break; 495 496 case com.android.internal.R.styleable.TextView_singleLine: 497 singleLine = a.getBoolean(attr, singleLine); 498 break; 499 500 case com.android.internal.R.styleable.TextView_ellipsize: 501 ellipsize = a.getInt(attr, ellipsize); 502 break; 503 504 case com.android.internal.R.styleable.TextView_marqueeRepeatLimit: 505 setMarqueeRepeatLimit(a.getInt(attr, mMarqueeRepeatLimit)); 506 break; 507 508 case com.android.internal.R.styleable.TextView_includeFontPadding: 509 if (!a.getBoolean(attr, true)) { 510 setIncludeFontPadding(false); 511 } 512 break; 513 514 case com.android.internal.R.styleable.TextView_cursorVisible: 515 if (!a.getBoolean(attr, true)) { 516 setCursorVisible(false); 517 } 518 break; 519 520 case com.android.internal.R.styleable.TextView_maxLength: 521 maxlength = a.getInt(attr, -1); 522 break; 523 524 case com.android.internal.R.styleable.TextView_textScaleX: 525 setTextScaleX(a.getFloat(attr, 1.0f)); 526 break; 527 528 case com.android.internal.R.styleable.TextView_freezesText: 529 mFreezesText = a.getBoolean(attr, false); 530 break; 531 532 case com.android.internal.R.styleable.TextView_shadowColor: 533 shadowcolor = a.getInt(attr, 0); 534 break; 535 536 case com.android.internal.R.styleable.TextView_shadowDx: 537 dx = a.getFloat(attr, 0); 538 break; 539 540 case com.android.internal.R.styleable.TextView_shadowDy: 541 dy = a.getFloat(attr, 0); 542 break; 543 544 case com.android.internal.R.styleable.TextView_shadowRadius: 545 r = a.getFloat(attr, 0); 546 break; 547 548 case com.android.internal.R.styleable.TextView_enabled: 549 setEnabled(a.getBoolean(attr, isEnabled())); 550 break; 551 552 case com.android.internal.R.styleable.TextView_textColorHighlight: 553 textColorHighlight = a.getColor(attr, textColorHighlight); 554 break; 555 556 case com.android.internal.R.styleable.TextView_textColor: 557 textColor = a.getColorStateList(attr); 558 break; 559 560 case com.android.internal.R.styleable.TextView_textColorHint: 561 textColorHint = a.getColorStateList(attr); 562 break; 563 564 case com.android.internal.R.styleable.TextView_textColorLink: 565 textColorLink = a.getColorStateList(attr); 566 break; 567 568 case com.android.internal.R.styleable.TextView_textSize: 569 textSize = a.getDimensionPixelSize(attr, textSize); 570 break; 571 572 case com.android.internal.R.styleable.TextView_typeface: 573 typefaceIndex = a.getInt(attr, typefaceIndex); 574 break; 575 576 case com.android.internal.R.styleable.TextView_textStyle: 577 styleIndex = a.getInt(attr, styleIndex); 578 break; 579 580 case com.android.internal.R.styleable.TextView_password: 581 password = a.getBoolean(attr, password); 582 break; 583 584 case com.android.internal.R.styleable.TextView_lineSpacingExtra: 585 mSpacingAdd = a.getDimensionPixelSize(attr, (int) mSpacingAdd); 586 break; 587 588 case com.android.internal.R.styleable.TextView_lineSpacingMultiplier: 589 mSpacingMult = a.getFloat(attr, mSpacingMult); 590 break; 591 592 case com.android.internal.R.styleable.TextView_inputType: 593 contentType = a.getInt(attr, mInputType); 594 break; 595 596 case com.android.internal.R.styleable.TextView_editorPrivateContentType: 597 setPrivateContentType(a.getString(attr)); 598 break; 599 600 case com.android.internal.R.styleable.TextView_editorExtras: 601 try { 602 setInputExtras(a.getResourceId(attr, 0)); 603 } catch (XmlPullParserException e) { 604 Log.w("TextView", "Failure reading input extras", e); 605 } catch (IOException e) { 606 Log.w("TextView", "Failure reading input extras", e); 607 } 608 break; 609 } 610 } 611 a.recycle(); 612 613 BufferType bufferType = BufferType.EDITABLE; 614 615 if ((contentType&(EditorInfo.TYPE_MASK_CLASS 616 |EditorInfo.TYPE_MASK_VARIATION)) 617 == (EditorInfo.TYPE_CLASS_TEXT 618 |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 619 password = true; 620 } 621 622 if (inputMethod != null) { 623 Class c; 624 625 try { 626 c = Class.forName(inputMethod.toString()); 627 } catch (ClassNotFoundException ex) { 628 throw new RuntimeException(ex); 629 } 630 631 try { 632 mInput = (KeyListener) c.newInstance(); 633 } catch (InstantiationException ex) { 634 throw new RuntimeException(ex); 635 } catch (IllegalAccessException ex) { 636 throw new RuntimeException(ex); 637 } 638 try { 639 mInputType = contentType != EditorInfo.TYPE_NULL 640 ? contentType 641 : mInput.getInputType(); 642 } catch (IncompatibleClassChangeError e) { 643 mInputType = EditorInfo.TYPE_CLASS_TEXT; 644 } 645 } else if (digits != null) { 646 mInput = DigitsKeyListener.getInstance(digits.toString()); 647 mInputType = contentType; 648 } else if (contentType != EditorInfo.TYPE_NULL) { 649 setInputType(contentType, true); 650 singleLine = (contentType&(EditorInfo.TYPE_MASK_CLASS 651 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) != 652 (EditorInfo.TYPE_CLASS_TEXT 653 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 654 } else if (phone) { 655 mInput = DialerKeyListener.getInstance(); 656 contentType = EditorInfo.TYPE_CLASS_PHONE; 657 } else if (numeric != 0) { 658 mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0, 659 (numeric & DECIMAL) != 0); 660 contentType = EditorInfo.TYPE_CLASS_NUMBER; 661 if ((numeric & SIGNED) != 0) { 662 contentType |= EditorInfo.TYPE_NUMBER_FLAG_SIGNED; 663 } 664 if ((numeric & DECIMAL) != 0) { 665 contentType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL; 666 } 667 mInputType = contentType; 668 } else if (autotext || autocap != -1) { 669 TextKeyListener.Capitalize cap; 670 671 contentType = EditorInfo.TYPE_CLASS_TEXT; 672 if (!singleLine) { 673 contentType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 674 } 675 676 switch (autocap) { 677 case 1: 678 cap = TextKeyListener.Capitalize.SENTENCES; 679 contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES; 680 break; 681 682 case 2: 683 cap = TextKeyListener.Capitalize.WORDS; 684 contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS; 685 break; 686 687 case 3: 688 cap = TextKeyListener.Capitalize.CHARACTERS; 689 contentType |= EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS; 690 break; 691 692 default: 693 cap = TextKeyListener.Capitalize.NONE; 694 break; 695 } 696 697 mInput = TextKeyListener.getInstance(autotext, cap); 698 mInputType = contentType; 699 } else if (editable) { 700 mInput = TextKeyListener.getInstance(); 701 mInputType = EditorInfo.TYPE_CLASS_TEXT; 702 } else { 703 mInput = null; 704 705 switch (buffertype) { 706 case 0: 707 bufferType = BufferType.NORMAL; 708 break; 709 case 1: 710 bufferType = BufferType.SPANNABLE; 711 break; 712 case 2: 713 bufferType = BufferType.EDITABLE; 714 break; 715 } 716 mInputType = EditorInfo.TYPE_CLASS_TEXT; 717 } 718 719 if (password) { 720 mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION)) 721 | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD; 722 } 723 724 if (selectallonfocus) { 725 mSelectAllOnFocus = true; 726 727 if (bufferType == BufferType.NORMAL) 728 bufferType = BufferType.SPANNABLE; 729 } 730 731 setCompoundDrawablesWithIntrinsicBounds( 732 drawableLeft, drawableTop, drawableRight, drawableBottom); 733 setCompoundDrawablePadding(drawablePadding); 734 735 if (singleLine) { 736 setSingleLine(); 737 738 if (mInput == null && ellipsize < 0) { 739 ellipsize = 3; // END 740 } 741 } 742 743 switch (ellipsize) { 744 case 1: 745 setEllipsize(TextUtils.TruncateAt.START); 746 break; 747 case 2: 748 setEllipsize(TextUtils.TruncateAt.MIDDLE); 749 break; 750 case 3: 751 setEllipsize(TextUtils.TruncateAt.END); 752 break; 753 case 4: 754 setHorizontalFadingEdgeEnabled(true); 755 setEllipsize(TextUtils.TruncateAt.MARQUEE); 756 break; 757 } 758 759 setTextColor(textColor != null ? textColor : ColorStateList.valueOf(0xFF000000)); 760 setHintTextColor(textColorHint); 761 setLinkTextColor(textColorLink); 762 if (textColorHighlight != 0) { 763 setHighlightColor(textColorHighlight); 764 } 765 setRawTextSize(textSize); 766 767 if (password) { 768 setTransformationMethod(PasswordTransformationMethod.getInstance()); 769 typefaceIndex = MONOSPACE; 770 } 771 772 setTypefaceByIndex(typefaceIndex, styleIndex); 773 774 if (shadowcolor != 0) { 775 setShadowLayer(r, dx, dy, shadowcolor); 776 } 777 778 if (maxlength >= 0) { 779 setFilters(new InputFilter[] { new InputFilter.LengthFilter(maxlength) }); 780 } else { 781 setFilters(NO_FILTERS); 782 } 783 784 setText(text, bufferType); 785 786 /* 787 * Views are not normally focusable unless specified to be. 788 * However, TextViews that have input or movement methods *are* 789 * focusable by default. 790 */ 791 a = context.obtainStyledAttributes(attrs, 792 com.android.internal.R.styleable.View, 793 defStyle, 0); 794 795 boolean focusable = mMovement != null || mInput != null; 796 boolean clickable = focusable; 797 boolean longClickable = focusable; 798 799 n = a.getIndexCount(); 800 for (int i = 0; i < n; i++) { 801 int attr = a.getIndex(i); 802 803 switch (attr) { 804 case com.android.internal.R.styleable.View_focusable: 805 focusable = a.getBoolean(attr, focusable); 806 break; 807 808 case com.android.internal.R.styleable.View_clickable: 809 clickable = a.getBoolean(attr, clickable); 810 break; 811 812 case com.android.internal.R.styleable.View_longClickable: 813 longClickable = a.getBoolean(attr, longClickable); 814 break; 815 } 816 } 817 a.recycle(); 818 819 setFocusable(focusable); 820 setClickable(clickable); 821 setLongClickable(longClickable); 822 } 823 824 private void setTypefaceByIndex(int typefaceIndex, int styleIndex) { 825 Typeface tf = null; 826 switch (typefaceIndex) { 827 case SANS: 828 tf = Typeface.SANS_SERIF; 829 break; 830 831 case SERIF: 832 tf = Typeface.SERIF; 833 break; 834 835 case MONOSPACE: 836 tf = Typeface.MONOSPACE; 837 break; 838 } 839 840 setTypeface(tf, styleIndex); 841 } 842 843 /** 844 * Sets the typeface and style in which the text should be displayed, 845 * and turns on the fake bold and italic bits in the Paint if the 846 * Typeface that you provided does not have all the bits in the 847 * style that you specified. 848 * 849 * @attr ref android.R.styleable#TextView_typeface 850 * @attr ref android.R.styleable#TextView_textStyle 851 */ 852 public void setTypeface(Typeface tf, int style) { 853 if (style > 0) { 854 if (tf == null) { 855 tf = Typeface.defaultFromStyle(style); 856 } else { 857 tf = Typeface.create(tf, style); 858 } 859 860 setTypeface(tf); 861 // now compute what (if any) algorithmic styling is needed 862 int typefaceStyle = tf != null ? tf.getStyle() : 0; 863 int need = style & ~typefaceStyle; 864 mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0); 865 mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0); 866 } else { 867 mTextPaint.setFakeBoldText(false); 868 mTextPaint.setTextSkewX(0); 869 setTypeface(tf); 870 } 871 } 872 873 /** 874 * Subclasses override this to specify that they have a KeyListener 875 * by default even if not specifically called for in the XML options. 876 */ 877 protected boolean getDefaultEditable() { 878 return false; 879 } 880 881 /** 882 * Subclasses override this to specify a default movement method. 883 */ 884 protected MovementMethod getDefaultMovementMethod() { 885 return null; 886 } 887 888 /** 889 * Return the text the TextView is displaying. If setText() was called with 890 * an argument of BufferType.SPANNABLE or BufferType.EDITABLE, you can cast 891 * the return value from this method to Spannable or Editable, respectively. 892 * 893 * Note: The content of the return value should not be modified. If you want 894 * a modifiable one, you should make your own copy first. 895 */ 896 @ViewDebug.CapturedViewProperty 897 public CharSequence getText() { 898 return mText; 899 } 900 901 /** 902 * Returns the length, in characters, of the text managed by this TextView 903 */ 904 public int length() { 905 return mText.length(); 906 } 907 908 /** 909 * Return the text the TextView is displaying as an Editable object. If 910 * the text is not editable, null is returned. 911 * 912 * @see #getText 913 */ 914 public Editable getEditableText() { 915 return (mText instanceof Editable) ? (Editable)mText : null; 916 } 917 918 /** 919 * @return the height of one standard line in pixels. Note that markup 920 * within the text can cause individual lines to be taller or shorter 921 * than this height, and the layout may contain additional first- 922 * or last-line padding. 923 */ 924 public int getLineHeight() { 925 return FastMath.round(mTextPaint.getFontMetricsInt(null) * mSpacingMult 926 + mSpacingAdd); 927 } 928 929 /** 930 * @return the Layout that is currently being used to display the text. 931 * This can be null if the text or width has recently changes. 932 */ 933 public final Layout getLayout() { 934 return mLayout; 935 } 936 937 /** 938 * @return the current key listener for this TextView. 939 * This will frequently be null for non-EditText TextViews. 940 */ 941 public final KeyListener getKeyListener() { 942 return mInput; 943 } 944 945 /** 946 * Sets the key listener to be used with this TextView. This can be null 947 * to disallow user input. Note that this method has significant and 948 * subtle interactions with soft keyboards and other input method: 949 * see {@link KeyListener#getInputType() KeyListener.getContentType()} 950 * for important details. Calling this method will replace the current 951 * content type of the text view with the content type returned by the 952 * key listener. 953 * <p> 954 * Be warned that if you want a TextView with a key listener or movement 955 * method not to be focusable, or if you want a TextView without a 956 * key listener or movement method to be focusable, you must call 957 * {@link #setFocusable} again after calling this to get the focusability 958 * back the way you want it. 959 * 960 * @attr ref android.R.styleable#TextView_numeric 961 * @attr ref android.R.styleable#TextView_digits 962 * @attr ref android.R.styleable#TextView_phoneNumber 963 * @attr ref android.R.styleable#TextView_inputMethod 964 * @attr ref android.R.styleable#TextView_capitalize 965 * @attr ref android.R.styleable#TextView_autoText 966 */ 967 public void setKeyListener(KeyListener input) { 968 setKeyListenerOnly(input); 969 fixFocusableAndClickableSettings(); 970 971 if (input != null) { 972 try { 973 mInputType = mInput.getInputType(); 974 } catch (IncompatibleClassChangeError e) { 975 mInputType = EditorInfo.TYPE_CLASS_TEXT; 976 } 977 if ((mInputType&EditorInfo.TYPE_MASK_CLASS) 978 == EditorInfo.TYPE_CLASS_TEXT) { 979 if (mSingleLine) { 980 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 981 } else { 982 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 983 } 984 } 985 } else { 986 mInputType = EditorInfo.TYPE_NULL; 987 } 988 989 InputMethodManager imm = InputMethodManager.peekInstance(); 990 if (imm != null) imm.restartInput(this); 991 } 992 993 private void setKeyListenerOnly(KeyListener input) { 994 mInput = input; 995 if (mInput != null && !(mText instanceof Editable)) 996 setText(mText); 997 998 setFilters((Editable) mText, mFilters); 999 } 1000 1001 /** 1002 * @return the movement method being used for this TextView. 1003 * This will frequently be null for non-EditText TextViews. 1004 */ 1005 public final MovementMethod getMovementMethod() { 1006 return mMovement; 1007 } 1008 1009 /** 1010 * Sets the movement method (arrow key handler) to be used for 1011 * this TextView. This can be null to disallow using the arrow keys 1012 * to move the cursor or scroll the view. 1013 * <p> 1014 * Be warned that if you want a TextView with a key listener or movement 1015 * method not to be focusable, or if you want a TextView without a 1016 * key listener or movement method to be focusable, you must call 1017 * {@link #setFocusable} again after calling this to get the focusability 1018 * back the way you want it. 1019 */ 1020 public final void setMovementMethod(MovementMethod movement) { 1021 mMovement = movement; 1022 1023 if (mMovement != null && !(mText instanceof Spannable)) 1024 setText(mText); 1025 1026 fixFocusableAndClickableSettings(); 1027 } 1028 1029 private void fixFocusableAndClickableSettings() { 1030 if ((mMovement != null) || mInput != null) { 1031 setFocusable(true); 1032 setClickable(true); 1033 setLongClickable(true); 1034 } else { 1035 setFocusable(false); 1036 setClickable(false); 1037 setLongClickable(false); 1038 } 1039 } 1040 1041 /** 1042 * @return the current transformation method for this TextView. 1043 * This will frequently be null except for single-line and password 1044 * fields. 1045 */ 1046 public final TransformationMethod getTransformationMethod() { 1047 return mTransformation; 1048 } 1049 1050 /** 1051 * Sets the transformation that is applied to the text that this 1052 * TextView is displaying. 1053 * 1054 * @attr ref android.R.styleable#TextView_password 1055 * @attr ref android.R.styleable#TextView_singleLine 1056 */ 1057 public final void setTransformationMethod(TransformationMethod method) { 1058 if (mTransformation != null) { 1059 if (mText instanceof Spannable) { 1060 ((Spannable) mText).removeSpan(mTransformation); 1061 } 1062 } 1063 1064 mTransformation = method; 1065 1066 setText(mText); 1067 } 1068 1069 /** 1070 * Returns the top padding of the view, plus space for the top 1071 * Drawable if any. 1072 */ 1073 public int getCompoundPaddingTop() { 1074 final Drawables dr = mDrawables; 1075 if (dr == null || dr.mDrawableTop == null) { 1076 return mPaddingTop; 1077 } else { 1078 return mPaddingTop + dr.mDrawablePadding + dr.mDrawableSizeTop; 1079 } 1080 } 1081 1082 /** 1083 * Returns the bottom padding of the view, plus space for the bottom 1084 * Drawable if any. 1085 */ 1086 public int getCompoundPaddingBottom() { 1087 final Drawables dr = mDrawables; 1088 if (dr == null || dr.mDrawableBottom == null) { 1089 return mPaddingBottom; 1090 } else { 1091 return mPaddingBottom + dr.mDrawablePadding + dr.mDrawableSizeBottom; 1092 } 1093 } 1094 1095 /** 1096 * Returns the left padding of the view, plus space for the left 1097 * Drawable if any. 1098 */ 1099 public int getCompoundPaddingLeft() { 1100 final Drawables dr = mDrawables; 1101 if (dr == null || dr.mDrawableLeft == null) { 1102 return mPaddingLeft; 1103 } else { 1104 return mPaddingLeft + dr.mDrawablePadding + dr.mDrawableSizeLeft; 1105 } 1106 } 1107 1108 /** 1109 * Returns the right padding of the view, plus space for the right 1110 * Drawable if any. 1111 */ 1112 public int getCompoundPaddingRight() { 1113 final Drawables dr = mDrawables; 1114 if (dr == null || dr.mDrawableRight == null) { 1115 return mPaddingRight; 1116 } else { 1117 return mPaddingRight + dr.mDrawablePadding + dr.mDrawableSizeRight; 1118 } 1119 } 1120 1121 /** 1122 * Returns the extended top padding of the view, including both the 1123 * top Drawable if any and any extra space to keep more than maxLines 1124 * of text from showing. It is only valid to call this after measuring. 1125 */ 1126 public int getExtendedPaddingTop() { 1127 if (mMaxMode != LINES) { 1128 return getCompoundPaddingTop(); 1129 } 1130 1131 if (mLayout.getLineCount() <= mMaximum) { 1132 return getCompoundPaddingTop(); 1133 } 1134 1135 int top = getCompoundPaddingTop(); 1136 int bottom = getCompoundPaddingBottom(); 1137 int viewht = getHeight() - top - bottom; 1138 int layoutht = mLayout.getLineTop(mMaximum); 1139 1140 if (layoutht >= viewht) { 1141 return top; 1142 } 1143 1144 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1145 if (gravity == Gravity.TOP) { 1146 return top; 1147 } else if (gravity == Gravity.BOTTOM) { 1148 return top + viewht - layoutht; 1149 } else { // (gravity == Gravity.CENTER_VERTICAL) 1150 return top + (viewht - layoutht) / 2; 1151 } 1152 } 1153 1154 /** 1155 * Returns the extended bottom padding of the view, including both the 1156 * bottom Drawable if any and any extra space to keep more than maxLines 1157 * of text from showing. It is only valid to call this after measuring. 1158 */ 1159 public int getExtendedPaddingBottom() { 1160 if (mMaxMode != LINES) { 1161 return getCompoundPaddingBottom(); 1162 } 1163 1164 if (mLayout.getLineCount() <= mMaximum) { 1165 return getCompoundPaddingBottom(); 1166 } 1167 1168 int top = getCompoundPaddingTop(); 1169 int bottom = getCompoundPaddingBottom(); 1170 int viewht = getHeight() - top - bottom; 1171 int layoutht = mLayout.getLineTop(mMaximum); 1172 1173 if (layoutht >= viewht) { 1174 return bottom; 1175 } 1176 1177 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 1178 if (gravity == Gravity.TOP) { 1179 return bottom + viewht - layoutht; 1180 } else if (gravity == Gravity.BOTTOM) { 1181 return bottom; 1182 } else { // (gravity == Gravity.CENTER_VERTICAL) 1183 return bottom + (viewht - layoutht) / 2; 1184 } 1185 } 1186 1187 /** 1188 * Returns the total left padding of the view, including the left 1189 * Drawable if any. 1190 */ 1191 public int getTotalPaddingLeft() { 1192 return getCompoundPaddingLeft(); 1193 } 1194 1195 /** 1196 * Returns the total right padding of the view, including the right 1197 * Drawable if any. 1198 */ 1199 public int getTotalPaddingRight() { 1200 return getCompoundPaddingRight(); 1201 } 1202 1203 /** 1204 * Returns the total top padding of the view, including the top 1205 * Drawable if any, the extra space to keep more than maxLines 1206 * from showing, and the vertical offset for gravity, if any. 1207 */ 1208 public int getTotalPaddingTop() { 1209 return getExtendedPaddingTop() + getVerticalOffset(true); 1210 } 1211 1212 /** 1213 * Returns the total bottom padding of the view, including the bottom 1214 * Drawable if any, the extra space to keep more than maxLines 1215 * from showing, and the vertical offset for gravity, if any. 1216 */ 1217 public int getTotalPaddingBottom() { 1218 return getExtendedPaddingBottom() + getBottomVerticalOffset(true); 1219 } 1220 1221 /** 1222 * Sets the Drawables (if any) to appear to the left of, above, 1223 * to the right of, and below the text. Use null if you do not 1224 * want a Drawable there. The Drawables must already have had 1225 * {@link Drawable#setBounds} called. 1226 * 1227 * @attr ref android.R.styleable#TextView_drawableLeft 1228 * @attr ref android.R.styleable#TextView_drawableTop 1229 * @attr ref android.R.styleable#TextView_drawableRight 1230 * @attr ref android.R.styleable#TextView_drawableBottom 1231 */ 1232 public void setCompoundDrawables(Drawable left, Drawable top, 1233 Drawable right, Drawable bottom) { 1234 Drawables dr = mDrawables; 1235 1236 final boolean drawables = left != null || top != null 1237 || right != null || bottom != null; 1238 1239 if (!drawables) { 1240 // Clearing drawables... can we free the data structure? 1241 if (dr != null) { 1242 if (dr.mDrawablePadding == 0) { 1243 mDrawables = null; 1244 } else { 1245 // We need to retain the last set padding, so just clear 1246 // out all of the fields in the existing structure. 1247 dr.mDrawableLeft = null; 1248 dr.mDrawableTop = null; 1249 dr.mDrawableRight = null; 1250 dr.mDrawableBottom = null; 1251 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1252 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1253 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1254 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1255 } 1256 } 1257 } else { 1258 if (dr == null) { 1259 mDrawables = dr = new Drawables(); 1260 } 1261 1262 dr.mDrawableLeft = left; 1263 dr.mDrawableTop = top; 1264 dr.mDrawableRight = right; 1265 dr.mDrawableBottom = bottom; 1266 1267 final Rect compoundRect = dr.mCompoundRect; 1268 int[] state = null; 1269 1270 state = getDrawableState(); 1271 1272 if (left != null) { 1273 left.setState(state); 1274 left.copyBounds(compoundRect); 1275 dr.mDrawableSizeLeft = compoundRect.width(); 1276 dr.mDrawableHeightLeft = compoundRect.height(); 1277 } else { 1278 dr.mDrawableSizeLeft = dr.mDrawableHeightLeft = 0; 1279 } 1280 1281 if (right != null) { 1282 right.setState(state); 1283 right.copyBounds(compoundRect); 1284 dr.mDrawableSizeRight = compoundRect.width(); 1285 dr.mDrawableHeightRight = compoundRect.height(); 1286 } else { 1287 dr.mDrawableSizeRight = dr.mDrawableHeightRight = 0; 1288 } 1289 1290 if (top != null) { 1291 top.setState(state); 1292 top.copyBounds(compoundRect); 1293 dr.mDrawableSizeTop = compoundRect.height(); 1294 dr.mDrawableWidthTop = compoundRect.width(); 1295 } else { 1296 dr.mDrawableSizeTop = dr.mDrawableWidthTop = 0; 1297 } 1298 1299 if (bottom != null) { 1300 bottom.setState(state); 1301 bottom.copyBounds(compoundRect); 1302 dr.mDrawableSizeBottom = compoundRect.height(); 1303 dr.mDrawableWidthBottom = compoundRect.width(); 1304 } else { 1305 dr.mDrawableSizeBottom = dr.mDrawableWidthBottom = 0; 1306 } 1307 } 1308 1309 invalidate(); 1310 requestLayout(); 1311 } 1312 1313 /** 1314 * Sets the Drawables (if any) to appear to the left of, above, 1315 * to the right of, and below the text. Use 0 if you do not 1316 * want a Drawable there. The Drawables' bounds will be set to 1317 * their intrinsic bounds. 1318 * 1319 * @param left Resource identifier of the left Drawable. 1320 * @param top Resource identifier of the top Drawable. 1321 * @param right Resource identifier of the right Drawable. 1322 * @param bottom Resource identifier of the bottom Drawable. 1323 * 1324 * @attr ref android.R.styleable#TextView_drawableLeft 1325 * @attr ref android.R.styleable#TextView_drawableTop 1326 * @attr ref android.R.styleable#TextView_drawableRight 1327 * @attr ref android.R.styleable#TextView_drawableBottom 1328 */ 1329 public void setCompoundDrawablesWithIntrinsicBounds(int left, int top, int right, int bottom) { 1330 final Resources resources = getContext().getResources(); 1331 setCompoundDrawables(left != 0 ? resources.getDrawable(left) : null, 1332 top != 0 ? resources.getDrawable(top) : null, 1333 right != 0 ? resources.getDrawable(right) : null, 1334 bottom != 0 ? resources.getDrawable(bottom) : null); 1335 } 1336 1337 /** 1338 * Sets the Drawables (if any) to appear to the left of, above, 1339 * to the right of, and below the text. Use null if you do not 1340 * want a Drawable there. The Drawables' bounds will be set to 1341 * their intrinsic bounds. 1342 * 1343 * @attr ref android.R.styleable#TextView_drawableLeft 1344 * @attr ref android.R.styleable#TextView_drawableTop 1345 * @attr ref android.R.styleable#TextView_drawableRight 1346 * @attr ref android.R.styleable#TextView_drawableBottom 1347 */ 1348 public void setCompoundDrawablesWithIntrinsicBounds(Drawable left, Drawable top, 1349 Drawable right, Drawable bottom) { 1350 1351 if (left != null) { 1352 left.setBounds(0, 0, left.getIntrinsicWidth(), left.getIntrinsicHeight()); 1353 } 1354 if (right != null) { 1355 right.setBounds(0, 0, right.getIntrinsicWidth(), right.getIntrinsicHeight()); 1356 } 1357 if (top != null) { 1358 top.setBounds(0, 0, top.getIntrinsicWidth(), top.getIntrinsicHeight()); 1359 } 1360 if (bottom != null) { 1361 bottom.setBounds(0, 0, bottom.getIntrinsicWidth(), bottom.getIntrinsicHeight()); 1362 } 1363 setCompoundDrawables(left, top, right, bottom); 1364 } 1365 1366 /** 1367 * Returns drawables for the left, top, right, and bottom borders. 1368 */ 1369 public Drawable[] getCompoundDrawables() { 1370 final Drawables dr = mDrawables; 1371 if (dr != null) { 1372 return new Drawable[] { 1373 dr.mDrawableLeft, dr.mDrawableTop, dr.mDrawableRight, dr.mDrawableBottom 1374 }; 1375 } else { 1376 return new Drawable[] { null, null, null, null }; 1377 } 1378 } 1379 1380 /** 1381 * Sets the size of the padding between the compound drawables and 1382 * the text. 1383 * 1384 * @attr ref android.R.styleable#TextView_drawablePadding 1385 */ 1386 public void setCompoundDrawablePadding(int pad) { 1387 Drawables dr = mDrawables; 1388 if (pad == 0) { 1389 if (dr != null) { 1390 dr.mDrawablePadding = pad; 1391 } 1392 } else { 1393 if (dr == null) { 1394 mDrawables = dr = new Drawables(); 1395 } 1396 dr.mDrawablePadding = pad; 1397 } 1398 1399 invalidate(); 1400 requestLayout(); 1401 } 1402 1403 /** 1404 * Returns the padding between the compound drawables and the text. 1405 */ 1406 public int getCompoundDrawablePadding() { 1407 final Drawables dr = mDrawables; 1408 return dr != null ? dr.mDrawablePadding : 0; 1409 } 1410 1411 @Override 1412 public void setPadding(int left, int top, int right, int bottom) { 1413 if (left != getPaddingLeft() || 1414 right != getPaddingRight() || 1415 top != getPaddingTop() || 1416 bottom != getPaddingBottom()) { 1417 nullLayouts(); 1418 } 1419 1420 // the super call will requestLayout() 1421 super.setPadding(left, top, right, bottom); 1422 invalidate(); 1423 } 1424 1425 /** 1426 * Gets the autolink mask of the text. See {@link 1427 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 1428 * possible values. 1429 * 1430 * @attr ref android.R.styleable#TextView_autoLink 1431 */ 1432 public final int getAutoLinkMask() { 1433 return mAutoLinkMask; 1434 } 1435 1436 /** 1437 * Sets the text color, size, style, hint color, and highlight color 1438 * from the specified TextAppearance resource. 1439 */ 1440 public void setTextAppearance(Context context, int resid) { 1441 TypedArray appearance = 1442 context.obtainStyledAttributes(resid, 1443 com.android.internal.R.styleable.TextAppearance); 1444 1445 int color; 1446 ColorStateList colors; 1447 int ts; 1448 1449 color = appearance.getColor(com.android.internal.R.styleable.TextAppearance_textColorHighlight, 0); 1450 if (color != 0) { 1451 setHighlightColor(color); 1452 } 1453 1454 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1455 TextAppearance_textColor); 1456 if (colors != null) { 1457 setTextColor(colors); 1458 } 1459 1460 ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable. 1461 TextAppearance_textSize, 0); 1462 if (ts != 0) { 1463 setRawTextSize(ts); 1464 } 1465 1466 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1467 TextAppearance_textColorHint); 1468 if (colors != null) { 1469 setHintTextColor(colors); 1470 } 1471 1472 colors = appearance.getColorStateList(com.android.internal.R.styleable. 1473 TextAppearance_textColorLink); 1474 if (colors != null) { 1475 setLinkTextColor(colors); 1476 } 1477 1478 int typefaceIndex, styleIndex; 1479 1480 typefaceIndex = appearance.getInt(com.android.internal.R.styleable. 1481 TextAppearance_typeface, -1); 1482 styleIndex = appearance.getInt(com.android.internal.R.styleable. 1483 TextAppearance_textStyle, -1); 1484 1485 setTypefaceByIndex(typefaceIndex, styleIndex); 1486 appearance.recycle(); 1487 } 1488 1489 /** 1490 * @return the size (in pixels) of the default text size in this TextView. 1491 */ 1492 public float getTextSize() { 1493 return mTextPaint.getTextSize(); 1494 } 1495 1496 /** 1497 * Set the default text size to the given value, interpreted as "scaled 1498 * pixel" units. This size is adjusted based on the current density and 1499 * user font size preference. 1500 * 1501 * @param size The scaled pixel size. 1502 * 1503 * @attr ref android.R.styleable#TextView_textSize 1504 */ 1505 public void setTextSize(float size) { 1506 setTextSize(TypedValue.COMPLEX_UNIT_SP, size); 1507 } 1508 1509 /** 1510 * Set the default text size to a given unit and value. See {@link 1511 * TypedValue} for the possible dimension units. 1512 * 1513 * @param unit The desired dimension unit. 1514 * @param size The desired size in the given units. 1515 * 1516 * @attr ref android.R.styleable#TextView_textSize 1517 */ 1518 public void setTextSize(int unit, float size) { 1519 Context c = getContext(); 1520 Resources r; 1521 1522 if (c == null) 1523 r = Resources.getSystem(); 1524 else 1525 r = c.getResources(); 1526 1527 setRawTextSize(TypedValue.applyDimension( 1528 unit, size, r.getDisplayMetrics())); 1529 } 1530 1531 private void setRawTextSize(float size) { 1532 if (size != mTextPaint.getTextSize()) { 1533 mTextPaint.setTextSize(size); 1534 1535 if (mLayout != null) { 1536 nullLayouts(); 1537 requestLayout(); 1538 invalidate(); 1539 } 1540 } 1541 } 1542 1543 /** 1544 * @return the extent by which text is currently being stretched 1545 * horizontally. This will usually be 1. 1546 */ 1547 public float getTextScaleX() { 1548 return mTextPaint.getTextScaleX(); 1549 } 1550 1551 /** 1552 * Sets the extent by which text should be stretched horizontally. 1553 * 1554 * @attr ref android.R.styleable#TextView_textScaleX 1555 */ 1556 public void setTextScaleX(float size) { 1557 if (size != mTextPaint.getTextScaleX()) { 1558 mTextPaint.setTextScaleX(size); 1559 1560 if (mLayout != null) { 1561 nullLayouts(); 1562 requestLayout(); 1563 invalidate(); 1564 } 1565 } 1566 } 1567 1568 /** 1569 * Sets the typeface and style in which the text should be displayed. 1570 * Note that not all Typeface families actually have bold and italic 1571 * variants, so you may need to use 1572 * {@link #setTypeface(Typeface, int)} to get the appearance 1573 * that you actually want. 1574 * 1575 * @attr ref android.R.styleable#TextView_typeface 1576 * @attr ref android.R.styleable#TextView_textStyle 1577 */ 1578 public void setTypeface(Typeface tf) { 1579 if (mTextPaint.getTypeface() != tf) { 1580 mTextPaint.setTypeface(tf); 1581 1582 if (mLayout != null) { 1583 nullLayouts(); 1584 requestLayout(); 1585 invalidate(); 1586 } 1587 } 1588 } 1589 1590 /** 1591 * @return the current typeface and style in which the text is being 1592 * displayed. 1593 */ 1594 public Typeface getTypeface() { 1595 return mTextPaint.getTypeface(); 1596 } 1597 1598 /** 1599 * Sets the text color for all the states (normal, selected, 1600 * focused) to be this color. 1601 * 1602 * @attr ref android.R.styleable#TextView_textColor 1603 */ 1604 public void setTextColor(int color) { 1605 mTextColor = ColorStateList.valueOf(color); 1606 updateTextColors(); 1607 } 1608 1609 /** 1610 * Sets the text color. 1611 * 1612 * @attr ref android.R.styleable#TextView_textColor 1613 */ 1614 public void setTextColor(ColorStateList colors) { 1615 if (colors == null) { 1616 throw new NullPointerException(); 1617 } 1618 1619 mTextColor = colors; 1620 updateTextColors(); 1621 } 1622 1623 /** 1624 * Return the set of text colors. 1625 * 1626 * @return Returns the set of text colors. 1627 */ 1628 public final ColorStateList getTextColors() { 1629 return mTextColor; 1630 } 1631 1632 /** 1633 * <p>Return the current color selected for normal text.</p> 1634 * 1635 * @return Returns the current text color. 1636 */ 1637 public final int getCurrentTextColor() { 1638 return mCurTextColor; 1639 } 1640 1641 /** 1642 * Sets the color used to display the selection highlight. 1643 * 1644 * @attr ref android.R.styleable#TextView_textColorHighlight 1645 */ 1646 public void setHighlightColor(int color) { 1647 if (mHighlightColor != color) { 1648 mHighlightColor = color; 1649 invalidate(); 1650 } 1651 } 1652 1653 /** 1654 * Gives the text a shadow of the specified radius and color, the specified 1655 * distance from its normal position. 1656 * 1657 * @attr ref android.R.styleable#TextView_shadowColor 1658 * @attr ref android.R.styleable#TextView_shadowDx 1659 * @attr ref android.R.styleable#TextView_shadowDy 1660 * @attr ref android.R.styleable#TextView_shadowRadius 1661 */ 1662 public void setShadowLayer(float radius, float dx, float dy, int color) { 1663 mTextPaint.setShadowLayer(radius, dx, dy, color); 1664 1665 mShadowRadius = radius; 1666 mShadowDx = dx; 1667 mShadowDy = dy; 1668 1669 invalidate(); 1670 } 1671 1672 /** 1673 * @return the base paint used for the text. Please use this only to 1674 * consult the Paint's properties and not to change them. 1675 */ 1676 public TextPaint getPaint() { 1677 return mTextPaint; 1678 } 1679 1680 /** 1681 * Sets the autolink mask of the text. See {@link 1682 * android.text.util.Linkify#ALL Linkify.ALL} and peers for 1683 * possible values. 1684 * 1685 * @attr ref android.R.styleable#TextView_autoLink 1686 */ 1687 public final void setAutoLinkMask(int mask) { 1688 mAutoLinkMask = mask; 1689 } 1690 1691 /** 1692 * Sets whether the movement method will automatically be set to 1693 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 1694 * set to nonzero and links are detected in {@link #setText}. 1695 * The default is true. 1696 * 1697 * @attr ref android.R.styleable#TextView_linksClickable 1698 */ 1699 public final void setLinksClickable(boolean whether) { 1700 mLinksClickable = whether; 1701 } 1702 1703 /** 1704 * Returns whether the movement method will automatically be set to 1705 * {@link LinkMovementMethod} if {@link #setAutoLinkMask} has been 1706 * set to nonzero and links are detected in {@link #setText}. 1707 * The default is true. 1708 * 1709 * @attr ref android.R.styleable#TextView_linksClickable 1710 */ 1711 public final boolean getLinksClickable() { 1712 return mLinksClickable; 1713 } 1714 1715 /** 1716 * Returns the list of URLSpans attached to the text 1717 * (by {@link Linkify} or otherwise) if any. You can call 1718 * {@link URLSpan#getURL} on them to find where they link to 1719 * or use {@link Spanned#getSpanStart} and {@link Spanned#getSpanEnd} 1720 * to find the region of the text they are attached to. 1721 */ 1722 public URLSpan[] getUrls() { 1723 if (mText instanceof Spanned) { 1724 return ((Spanned) mText).getSpans(0, mText.length(), URLSpan.class); 1725 } else { 1726 return new URLSpan[0]; 1727 } 1728 } 1729 1730 /** 1731 * Sets the color of the hint text. 1732 * 1733 * @attr ref android.R.styleable#TextView_textColorHint 1734 */ 1735 public final void setHintTextColor(int color) { 1736 mHintTextColor = ColorStateList.valueOf(color); 1737 updateTextColors(); 1738 } 1739 1740 /** 1741 * Sets the color of the hint text. 1742 * 1743 * @attr ref android.R.styleable#TextView_textColorHint 1744 */ 1745 public final void setHintTextColor(ColorStateList colors) { 1746 mHintTextColor = colors; 1747 updateTextColors(); 1748 } 1749 1750 /** 1751 * <p>Return the color used to paint the hint text.</p> 1752 * 1753 * @return Returns the list of hint text colors. 1754 */ 1755 public final ColorStateList getHintTextColors() { 1756 return mHintTextColor; 1757 } 1758 1759 /** 1760 * <p>Return the current color selected to paint the hint text.</p> 1761 * 1762 * @return Returns the current hint text color. 1763 */ 1764 public final int getCurrentHintTextColor() { 1765 return mHintTextColor != null ? mCurHintTextColor : mCurTextColor; 1766 } 1767 1768 /** 1769 * Sets the color of links in the text. 1770 * 1771 * @attr ref android.R.styleable#TextView_textColorLink 1772 */ 1773 public final void setLinkTextColor(int color) { 1774 mLinkTextColor = ColorStateList.valueOf(color); 1775 updateTextColors(); 1776 } 1777 1778 /** 1779 * Sets the color of links in the text. 1780 * 1781 * @attr ref android.R.styleable#TextView_textColorLink 1782 */ 1783 public final void setLinkTextColor(ColorStateList colors) { 1784 mLinkTextColor = colors; 1785 updateTextColors(); 1786 } 1787 1788 /** 1789 * <p>Returns the color used to paint links in the text.</p> 1790 * 1791 * @return Returns the list of link text colors. 1792 */ 1793 public final ColorStateList getLinkTextColors() { 1794 return mLinkTextColor; 1795 } 1796 1797 /** 1798 * Sets the horizontal alignment of the text and the 1799 * vertical gravity that will be used when there is extra space 1800 * in the TextView beyond what is required for the text itself. 1801 * 1802 * @see android.view.Gravity 1803 * @attr ref android.R.styleable#TextView_gravity 1804 */ 1805 public void setGravity(int gravity) { 1806 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == 0) { 1807 gravity |= Gravity.LEFT; 1808 } 1809 if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 1810 gravity |= Gravity.TOP; 1811 } 1812 1813 boolean newLayout = false; 1814 1815 if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) != 1816 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) { 1817 newLayout = true; 1818 } 1819 1820 if (gravity != mGravity) { 1821 invalidate(); 1822 } 1823 1824 mGravity = gravity; 1825 1826 if (mLayout != null && newLayout) { 1827 // XXX this is heavy-handed because no actual content changes. 1828 int want = mLayout.getWidth(); 1829 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 1830 1831 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 1832 mRight - mLeft - getCompoundPaddingLeft() - 1833 getCompoundPaddingRight(), true); 1834 } 1835 } 1836 1837 /** 1838 * Returns the horizontal and vertical alignment of this TextView. 1839 * 1840 * @see android.view.Gravity 1841 * @attr ref android.R.styleable#TextView_gravity 1842 */ 1843 public int getGravity() { 1844 return mGravity; 1845 } 1846 1847 /** 1848 * @return the flags on the Paint being used to display the text. 1849 * @see Paint#getFlags 1850 */ 1851 public int getPaintFlags() { 1852 return mTextPaint.getFlags(); 1853 } 1854 1855 /** 1856 * Sets flags on the Paint being used to display the text and 1857 * reflows the text if they are different from the old flags. 1858 * @see Paint#setFlags 1859 */ 1860 public void setPaintFlags(int flags) { 1861 if (mTextPaint.getFlags() != flags) { 1862 mTextPaint.setFlags(flags); 1863 1864 if (mLayout != null) { 1865 nullLayouts(); 1866 requestLayout(); 1867 invalidate(); 1868 } 1869 } 1870 } 1871 1872 /** 1873 * Sets whether the text should be allowed to be wider than the 1874 * View is. If false, it will be wrapped to the width of the View. 1875 * 1876 * @attr ref android.R.styleable#TextView_scrollHorizontally 1877 */ 1878 public void setHorizontallyScrolling(boolean whether) { 1879 mHorizontallyScrolling = whether; 1880 1881 if (mLayout != null) { 1882 nullLayouts(); 1883 requestLayout(); 1884 invalidate(); 1885 } 1886 } 1887 1888 /** 1889 * Makes the TextView at least this many lines tall 1890 * 1891 * @attr ref android.R.styleable#TextView_minLines 1892 */ 1893 public void setMinLines(int minlines) { 1894 mMinimum = minlines; 1895 mMinMode = LINES; 1896 1897 requestLayout(); 1898 invalidate(); 1899 } 1900 1901 /** 1902 * Makes the TextView at least this many pixels tall 1903 * 1904 * @attr ref android.R.styleable#TextView_minHeight 1905 */ 1906 public void setMinHeight(int minHeight) { 1907 mMinimum = minHeight; 1908 mMinMode = PIXELS; 1909 1910 requestLayout(); 1911 invalidate(); 1912 } 1913 1914 /** 1915 * Makes the TextView at most this many lines tall 1916 * 1917 * @attr ref android.R.styleable#TextView_maxLines 1918 */ 1919 public void setMaxLines(int maxlines) { 1920 mMaximum = maxlines; 1921 mMaxMode = LINES; 1922 1923 requestLayout(); 1924 invalidate(); 1925 } 1926 1927 /** 1928 * Makes the TextView at most this many pixels tall 1929 * 1930 * @attr ref android.R.styleable#TextView_maxHeight 1931 */ 1932 public void setMaxHeight(int maxHeight) { 1933 mMaximum = maxHeight; 1934 mMaxMode = PIXELS; 1935 1936 requestLayout(); 1937 invalidate(); 1938 } 1939 1940 /** 1941 * Makes the TextView exactly this many lines tall 1942 * 1943 * @attr ref android.R.styleable#TextView_lines 1944 */ 1945 public void setLines(int lines) { 1946 mMaximum = mMinimum = lines; 1947 mMaxMode = mMinMode = LINES; 1948 1949 requestLayout(); 1950 invalidate(); 1951 } 1952 1953 /** 1954 * Makes the TextView exactly this many pixels tall. 1955 * You could do the same thing by specifying this number in the 1956 * LayoutParams. 1957 * 1958 * @attr ref android.R.styleable#TextView_height 1959 */ 1960 public void setHeight(int pixels) { 1961 mMaximum = mMinimum = pixels; 1962 mMaxMode = mMinMode = PIXELS; 1963 1964 requestLayout(); 1965 invalidate(); 1966 } 1967 1968 /** 1969 * Makes the TextView at least this many ems wide 1970 * 1971 * @attr ref android.R.styleable#TextView_minEms 1972 */ 1973 public void setMinEms(int minems) { 1974 mMinWidth = minems; 1975 mMinWidthMode = EMS; 1976 1977 requestLayout(); 1978 invalidate(); 1979 } 1980 1981 /** 1982 * Makes the TextView at least this many pixels wide 1983 * 1984 * @attr ref android.R.styleable#TextView_minWidth 1985 */ 1986 public void setMinWidth(int minpixels) { 1987 mMinWidth = minpixels; 1988 mMinWidthMode = PIXELS; 1989 1990 requestLayout(); 1991 invalidate(); 1992 } 1993 1994 /** 1995 * Makes the TextView at most this many ems wide 1996 * 1997 * @attr ref android.R.styleable#TextView_maxEms 1998 */ 1999 public void setMaxEms(int maxems) { 2000 mMaxWidth = maxems; 2001 mMaxWidthMode = EMS; 2002 2003 requestLayout(); 2004 invalidate(); 2005 } 2006 2007 /** 2008 * Makes the TextView at most this many pixels wide 2009 * 2010 * @attr ref android.R.styleable#TextView_maxWidth 2011 */ 2012 public void setMaxWidth(int maxpixels) { 2013 mMaxWidth = maxpixels; 2014 mMaxWidthMode = PIXELS; 2015 2016 requestLayout(); 2017 invalidate(); 2018 } 2019 2020 /** 2021 * Makes the TextView exactly this many ems wide 2022 * 2023 * @attr ref android.R.styleable#TextView_ems 2024 */ 2025 public void setEms(int ems) { 2026 mMaxWidth = mMinWidth = ems; 2027 mMaxWidthMode = mMinWidthMode = EMS; 2028 2029 requestLayout(); 2030 invalidate(); 2031 } 2032 2033 /** 2034 * Makes the TextView exactly this many pixels wide. 2035 * You could do the same thing by specifying this number in the 2036 * LayoutParams. 2037 * 2038 * @attr ref android.R.styleable#TextView_width 2039 */ 2040 public void setWidth(int pixels) { 2041 mMaxWidth = mMinWidth = pixels; 2042 mMaxWidthMode = mMinWidthMode = PIXELS; 2043 2044 requestLayout(); 2045 invalidate(); 2046 } 2047 2048 2049 /** 2050 * Sets line spacing for this TextView. Each line will have its height 2051 * multiplied by <code>mult</code> and have <code>add</code> added to it. 2052 * 2053 * @attr ref android.R.styleable#TextView_lineSpacingExtra 2054 * @attr ref android.R.styleable#TextView_lineSpacingMultiplier 2055 */ 2056 public void setLineSpacing(float add, float mult) { 2057 mSpacingMult = mult; 2058 mSpacingAdd = add; 2059 2060 if (mLayout != null) { 2061 nullLayouts(); 2062 requestLayout(); 2063 invalidate(); 2064 } 2065 } 2066 2067 /** 2068 * Convenience method: Append the specified text to the TextView's 2069 * display buffer, upgrading it to BufferType.EDITABLE if it was 2070 * not already editable. 2071 */ 2072 public final void append(CharSequence text) { 2073 append(text, 0, text.length()); 2074 } 2075 2076 /** 2077 * Convenience method: Append the specified text slice to the TextView's 2078 * display buffer, upgrading it to BufferType.EDITABLE if it was 2079 * not already editable. 2080 */ 2081 public void append(CharSequence text, int start, int end) { 2082 if (!(mText instanceof Editable)) { 2083 setText(mText, BufferType.EDITABLE); 2084 } 2085 2086 ((Editable) mText).append(text, start, end); 2087 } 2088 2089 private void updateTextColors() { 2090 boolean inval = false; 2091 int color = mTextColor.getColorForState(getDrawableState(), 0); 2092 if (color != mCurTextColor) { 2093 mCurTextColor = color; 2094 inval = true; 2095 } 2096 if (mLinkTextColor != null) { 2097 color = mLinkTextColor.getColorForState(getDrawableState(), 0); 2098 if (color != mTextPaint.linkColor) { 2099 mTextPaint.linkColor = color; 2100 inval = true; 2101 } 2102 } 2103 if (mHintTextColor != null) { 2104 color = mHintTextColor.getColorForState(getDrawableState(), 0); 2105 if (color != mCurHintTextColor && mText.length() == 0) { 2106 mCurHintTextColor = color; 2107 inval = true; 2108 } 2109 } 2110 if (inval) { 2111 invalidate(); 2112 } 2113 } 2114 2115 @Override 2116 protected void drawableStateChanged() { 2117 super.drawableStateChanged(); 2118 if (mTextColor != null && mTextColor.isStateful() 2119 || (mHintTextColor != null && mHintTextColor.isStateful()) 2120 || (mLinkTextColor != null && mLinkTextColor.isStateful())) { 2121 updateTextColors(); 2122 } 2123 2124 final Drawables dr = mDrawables; 2125 if (dr != null) { 2126 int[] state = getDrawableState(); 2127 if (dr.mDrawableTop != null && dr.mDrawableTop.isStateful()) { 2128 dr.mDrawableTop.setState(state); 2129 } 2130 if (dr.mDrawableBottom != null && dr.mDrawableBottom.isStateful()) { 2131 dr.mDrawableBottom.setState(state); 2132 } 2133 if (dr.mDrawableLeft != null && dr.mDrawableLeft.isStateful()) { 2134 dr.mDrawableLeft.setState(state); 2135 } 2136 if (dr.mDrawableRight != null && dr.mDrawableRight.isStateful()) { 2137 dr.mDrawableRight.setState(state); 2138 } 2139 } 2140 } 2141 2142 /** 2143 * User interface state that is stored by TextView for implementing 2144 * {@link View#onSaveInstanceState}. 2145 */ 2146 public static class SavedState extends BaseSavedState { 2147 int selStart; 2148 int selEnd; 2149 CharSequence text; 2150 boolean frozenWithFocus; 2151 2152 SavedState(Parcelable superState) { 2153 super(superState); 2154 } 2155 2156 @Override 2157 public void writeToParcel(Parcel out, int flags) { 2158 super.writeToParcel(out, flags); 2159 out.writeInt(selStart); 2160 out.writeInt(selEnd); 2161 out.writeInt(frozenWithFocus ? 1 : 0); 2162 TextUtils.writeToParcel(text, out, flags); 2163 } 2164 2165 @Override 2166 public String toString() { 2167 String str = "TextView.SavedState{" 2168 + Integer.toHexString(System.identityHashCode(this)) 2169 + " start=" + selStart + " end=" + selEnd; 2170 if (text != null) { 2171 str += " text=" + text; 2172 } 2173 return str + "}"; 2174 } 2175 2176 public static final Parcelable.Creator<SavedState> CREATOR 2177 = new Parcelable.Creator<SavedState>() { 2178 public SavedState createFromParcel(Parcel in) { 2179 return new SavedState(in); 2180 } 2181 2182 public SavedState[] newArray(int size) { 2183 return new SavedState[size]; 2184 } 2185 }; 2186 2187 private SavedState(Parcel in) { 2188 super(in); 2189 selStart = in.readInt(); 2190 selEnd = in.readInt(); 2191 frozenWithFocus = (in.readInt() != 0); 2192 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 2193 } 2194 } 2195 2196 @Override 2197 public Parcelable onSaveInstanceState() { 2198 Parcelable superState = super.onSaveInstanceState(); 2199 2200 // Save state if we are forced to 2201 boolean save = mFreezesText; 2202 int start = 0; 2203 int end = 0; 2204 2205 if (mText != null) { 2206 start = Selection.getSelectionStart(mText); 2207 end = Selection.getSelectionEnd(mText); 2208 if (start >= 0 || end >= 0) { 2209 // Or save state if there is a selection 2210 save = true; 2211 } 2212 } 2213 2214 if (save) { 2215 SavedState ss = new SavedState(superState); 2216 // XXX Should also save the current scroll position! 2217 ss.selStart = start; 2218 ss.selEnd = end; 2219 2220 if (mText instanceof Spanned) { 2221 /* 2222 * Calling setText() strips off any ChangeWatchers; 2223 * strip them now to avoid leaking references. 2224 * But do it to a copy so that if there are any 2225 * further changes to the text of this view, it 2226 * won't get into an inconsistent state. 2227 */ 2228 2229 Spannable sp = new SpannableString(mText); 2230 2231 for (ChangeWatcher cw : 2232 sp.getSpans(0, sp.length(), ChangeWatcher.class)) { 2233 sp.removeSpan(cw); 2234 } 2235 2236 ss.text = sp; 2237 } else { 2238 ss.text = mText.toString(); 2239 } 2240 2241 if (isFocused() && start >= 0 && end >= 0) { 2242 ss.frozenWithFocus = true; 2243 } 2244 2245 return ss; 2246 } 2247 2248 return superState; 2249 } 2250 2251 @Override 2252 public void onRestoreInstanceState(Parcelable state) { 2253 if (!(state instanceof SavedState)) { 2254 super.onRestoreInstanceState(state); 2255 return; 2256 } 2257 2258 SavedState ss = (SavedState)state; 2259 super.onRestoreInstanceState(ss.getSuperState()); 2260 2261 // XXX restore buffer type too, as well as lots of other stuff 2262 if (ss.text != null) { 2263 setText(ss.text); 2264 } 2265 2266 if (ss.selStart >= 0 && ss.selEnd >= 0) { 2267 if (mText instanceof Spannable) { 2268 int len = mText.length(); 2269 2270 if (ss.selStart > len || ss.selEnd > len) { 2271 String restored = ""; 2272 2273 if (ss.text != null) { 2274 restored = "(restored) "; 2275 } 2276 2277 Log.e("TextView", "Saved cursor position " + ss.selStart + 2278 "/" + ss.selEnd + " out of range for " + restored + 2279 "text " + mText); 2280 } else { 2281 Selection.setSelection((Spannable) mText, ss.selStart, 2282 ss.selEnd); 2283 2284 if (ss.frozenWithFocus) { 2285 mFrozenWithFocus = true; 2286 } 2287 } 2288 } 2289 } 2290 } 2291 2292 /** 2293 * Control whether this text view saves its entire text contents when 2294 * freezing to an icicle, in addition to dynamic state such as cursor 2295 * position. By default this is false, not saving the text. Set to true 2296 * if the text in the text view is not being saved somewhere else in 2297 * persistent storage (such as in a content provider) so that if the 2298 * view is later thawed the user will not lose their data. 2299 * 2300 * @param freezesText Controls whether a frozen icicle should include the 2301 * entire text data: true to include it, false to not. 2302 * 2303 * @attr ref android.R.styleable#TextView_freezesText 2304 */ 2305 public void setFreezesText(boolean freezesText) { 2306 mFreezesText = freezesText; 2307 } 2308 2309 /** 2310 * Return whether this text view is including its entire text contents 2311 * in frozen icicles. 2312 * 2313 * @return Returns true if text is included, false if it isn't. 2314 * 2315 * @see #setFreezesText 2316 */ 2317 public boolean getFreezesText() { 2318 return mFreezesText; 2319 } 2320 2321 /////////////////////////////////////////////////////////////////////////// 2322 2323 /** 2324 * Sets the Factory used to create new Editables. 2325 */ 2326 public final void setEditableFactory(Editable.Factory factory) { 2327 mEditableFactory = factory; 2328 setText(mText); 2329 } 2330 2331 /** 2332 * Sets the Factory used to create new Spannables. 2333 */ 2334 public final void setSpannableFactory(Spannable.Factory factory) { 2335 mSpannableFactory = factory; 2336 setText(mText); 2337 } 2338 2339 /** 2340 * Sets the string value of the TextView. TextView <em>does not</em> accept 2341 * HTML-like formatting, which you can do with text strings in XML resource files. 2342 * To style your strings, attach android.text.style.* objects to a 2343 * {@link android.text.SpannableString SpannableString}, or see 2344 * <a href="{@docRoot}reference/available-resources.html#stringresources"> 2345 * String Resources</a> for an example of setting formatted text in the XML resource file. 2346 * 2347 * @attr ref android.R.styleable#TextView_text 2348 */ 2349 public final void setText(CharSequence text) { 2350 setText(text, mBufferType); 2351 } 2352 2353 /** 2354 * Like {@link #setText(CharSequence)}, 2355 * except that the cursor position (if any) is retained in the new text. 2356 * 2357 * @param text The new text to place in the text view. 2358 * 2359 * @see #setText(CharSequence) 2360 */ 2361 public final void setTextKeepState(CharSequence text) { 2362 setTextKeepState(text, mBufferType); 2363 } 2364 2365 /** 2366 * Sets the text that this TextView is to display (see 2367 * {@link #setText(CharSequence)}) and also sets whether it is stored 2368 * in a styleable/spannable buffer and whether it is editable. 2369 * 2370 * @attr ref android.R.styleable#TextView_text 2371 * @attr ref android.R.styleable#TextView_bufferType 2372 */ 2373 public void setText(CharSequence text, BufferType type) { 2374 setText(text, type, true, 0); 2375 2376 if (mCharWrapper != null) { 2377 mCharWrapper.mChars = null; 2378 } 2379 } 2380 2381 private void setText(CharSequence text, BufferType type, 2382 boolean notifyBefore, int oldlen) { 2383 if (text == null) { 2384 text = ""; 2385 } 2386 2387 if (text instanceof Spanned && 2388 ((Spanned) text).getSpanStart(TextUtils.TruncateAt.MARQUEE) >= 0) { 2389 setHorizontalFadingEdgeEnabled(true); 2390 setEllipsize(TextUtils.TruncateAt.MARQUEE); 2391 } 2392 2393 int n = mFilters.length; 2394 for (int i = 0; i < n; i++) { 2395 CharSequence out = mFilters[i].filter(text, 0, text.length(), 2396 EMPTY_SPANNED, 0, 0); 2397 if (out != null) { 2398 text = out; 2399 } 2400 } 2401 2402 if (notifyBefore) { 2403 if (mText != null) { 2404 oldlen = mText.length(); 2405 sendBeforeTextChanged(mText, 0, oldlen, text.length()); 2406 } else { 2407 sendBeforeTextChanged("", 0, 0, text.length()); 2408 } 2409 } 2410 2411 boolean needEditableForNotification = false; 2412 2413 if (mListeners != null && mListeners.size() != 0) { 2414 needEditableForNotification = true; 2415 } 2416 2417 if (type == BufferType.EDITABLE || mInput != null || 2418 needEditableForNotification) { 2419 Editable t = mEditableFactory.newEditable(text); 2420 text = t; 2421 setFilters(t, mFilters); 2422 InputMethodManager imm = InputMethodManager.peekInstance(); 2423 if (imm != null) imm.restartInput(this); 2424 } else if (type == BufferType.SPANNABLE || mMovement != null) { 2425 text = mSpannableFactory.newSpannable(text); 2426 } else if (!(text instanceof CharWrapper)) { 2427 text = TextUtils.stringOrSpannedString(text); 2428 } 2429 2430 if (mAutoLinkMask != 0) { 2431 Spannable s2; 2432 2433 if (type == BufferType.EDITABLE || text instanceof Spannable) { 2434 s2 = (Spannable) text; 2435 } else { 2436 s2 = mSpannableFactory.newSpannable(text); 2437 } 2438 2439 if (Linkify.addLinks(s2, mAutoLinkMask)) { 2440 text = s2; 2441 type = (type == BufferType.EDITABLE) ? BufferType.EDITABLE : BufferType.SPANNABLE; 2442 2443 /* 2444 * We must go ahead and set the text before changing the 2445 * movement method, because setMovementMethod() may call 2446 * setText() again to try to upgrade the buffer type. 2447 */ 2448 mText = text; 2449 2450 if (mLinksClickable) { 2451 setMovementMethod(LinkMovementMethod.getInstance()); 2452 } 2453 } 2454 } 2455 2456 mBufferType = type; 2457 mText = text; 2458 2459 if (mTransformation == null) 2460 mTransformed = text; 2461 else 2462 mTransformed = mTransformation.getTransformation(text, this); 2463 2464 final int textLength = text.length(); 2465 2466 if (text instanceof Spannable) { 2467 Spannable sp = (Spannable) text; 2468 2469 // Remove any ChangeWatchers that might have come 2470 // from other TextViews. 2471 final ChangeWatcher[] watchers = sp.getSpans(0, sp.length(), ChangeWatcher.class); 2472 final int count = watchers.length; 2473 for (int i = 0; i < count; i++) 2474 sp.removeSpan(watchers[i]); 2475 2476 if (mChangeWatcher == null) 2477 mChangeWatcher = new ChangeWatcher(); 2478 2479 sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE | 2480 (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT)); 2481 2482 if (mInput != null) { 2483 sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2484 } 2485 2486 if (mTransformation != null) { 2487 sp.setSpan(mTransformation, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2488 2489 } 2490 2491 if (mMovement != null) { 2492 mMovement.initialize(this, (Spannable) text); 2493 2494 /* 2495 * Initializing the movement method will have set the 2496 * selection, so reset mSelectionMoved to keep that from 2497 * interfering with the normal on-focus selection-setting. 2498 */ 2499 mSelectionMoved = false; 2500 } 2501 } 2502 2503 if (mLayout != null) { 2504 checkForRelayout(); 2505 } 2506 2507 sendOnTextChanged(text, 0, oldlen, textLength); 2508 onTextChanged(text, 0, oldlen, textLength); 2509 2510 if (needEditableForNotification) { 2511 sendAfterTextChanged((Editable) text); 2512 } 2513 } 2514 2515 /** 2516 * Sets the TextView to display the specified slice of the specified 2517 * char array. You must promise that you will not change the contents 2518 * of the array except for right before another call to setText(), 2519 * since the TextView has no way to know that the text 2520 * has changed and that it needs to invalidate and re-layout. 2521 */ 2522 public final void setText(char[] text, int start, int len) { 2523 int oldlen = 0; 2524 2525 if (start < 0 || len < 0 || start + len > text.length) { 2526 throw new IndexOutOfBoundsException(start + ", " + len); 2527 } 2528 2529 /* 2530 * We must do the before-notification here ourselves because if 2531 * the old text is a CharWrapper we destroy it before calling 2532 * into the normal path. 2533 */ 2534 if (mText != null) { 2535 oldlen = mText.length(); 2536 sendBeforeTextChanged(mText, 0, oldlen, len); 2537 } else { 2538 sendBeforeTextChanged("", 0, 0, len); 2539 } 2540 2541 if (mCharWrapper == null) { 2542 mCharWrapper = new CharWrapper(text, start, len); 2543 } else { 2544 mCharWrapper.set(text, start, len); 2545 } 2546 2547 setText(mCharWrapper, mBufferType, false, oldlen); 2548 } 2549 2550 private static class CharWrapper 2551 implements CharSequence, GetChars, GraphicsOperations { 2552 private char[] mChars; 2553 private int mStart, mLength; 2554 2555 public CharWrapper(char[] chars, int start, int len) { 2556 mChars = chars; 2557 mStart = start; 2558 mLength = len; 2559 } 2560 2561 /* package */ void set(char[] chars, int start, int len) { 2562 mChars = chars; 2563 mStart = start; 2564 mLength = len; 2565 } 2566 2567 public int length() { 2568 return mLength; 2569 } 2570 2571 public char charAt(int off) { 2572 return mChars[off + mStart]; 2573 } 2574 2575 public String toString() { 2576 return new String(mChars, mStart, mLength); 2577 } 2578 2579 public CharSequence subSequence(int start, int end) { 2580 if (start < 0 || end < 0 || start > mLength || end > mLength) { 2581 throw new IndexOutOfBoundsException(start + ", " + end); 2582 } 2583 2584 return new String(mChars, start + mStart, end - start); 2585 } 2586 2587 public void getChars(int start, int end, char[] buf, int off) { 2588 if (start < 0 || end < 0 || start > mLength || end > mLength) { 2589 throw new IndexOutOfBoundsException(start + ", " + end); 2590 } 2591 2592 System.arraycopy(mChars, start + mStart, buf, off, end - start); 2593 } 2594 2595 public void drawText(Canvas c, int start, int end, 2596 float x, float y, Paint p) { 2597 c.drawText(mChars, start + mStart, end - start, x, y, p); 2598 } 2599 2600 public float measureText(int start, int end, Paint p) { 2601 return p.measureText(mChars, start + mStart, end - start); 2602 } 2603 2604 public int getTextWidths(int start, int end, float[] widths, Paint p) { 2605 return p.getTextWidths(mChars, start + mStart, end - start, widths); 2606 } 2607 } 2608 2609 /** 2610 * Like {@link #setText(CharSequence, android.widget.TextView.BufferType)}, 2611 * except that the cursor position (if any) is retained in the new text. 2612 * 2613 * @see #setText(CharSequence, android.widget.TextView.BufferType) 2614 */ 2615 public final void setTextKeepState(CharSequence text, BufferType type) { 2616 int start = getSelectionStart(); 2617 int end = getSelectionEnd(); 2618 int len = text.length(); 2619 2620 setText(text, type); 2621 2622 if (start >= 0 || end >= 0) { 2623 if (mText instanceof Spannable) { 2624 Selection.setSelection((Spannable) mText, 2625 Math.max(0, Math.min(start, len)), 2626 Math.max(0, Math.min(end, len))); 2627 } 2628 } 2629 } 2630 2631 public final void setText(int resid) { 2632 setText(getContext().getResources().getText(resid)); 2633 } 2634 2635 public final void setText(int resid, BufferType type) { 2636 setText(getContext().getResources().getText(resid), type); 2637 } 2638 2639 /** 2640 * Sets the text to be displayed when the text of the TextView is empty. 2641 * Null means to use the normal empty text. The hint does not currently 2642 * participate in determining the size of the view. 2643 * 2644 * This method is deprecated. Use {link #setHint(int, String)} or 2645 * {link #setHint(CharSequence, String)} instead. 2646 * 2647 * @attr ref android.R.styleable#TextView_hint 2648 */ 2649 public final void setHint(CharSequence hint) { 2650 mHint = TextUtils.stringOrSpannedString(hint); 2651 2652 if (mLayout != null) { 2653 checkForRelayout(); 2654 } 2655 2656 if (mText.length() == 0) 2657 invalidate(); 2658 } 2659 2660 /** 2661 * Sets the text to be displayed when the text of the TextView is empty, 2662 * from a resource. 2663 * 2664 * This method is deprecated. Use {link #setHint(int, String)} or 2665 * {link #setHint(CharSequence, String)} instead. 2666 * 2667 * @attr ref android.R.styleable#TextView_hint 2668 */ 2669 public final void setHint(int resid) { 2670 setHint(getContext().getResources().getText(resid)); 2671 } 2672 2673 /** 2674 * Returns the hint that is displayed when the text of the TextView 2675 * is empty. 2676 * 2677 * @attr ref android.R.styleable#TextView_hint 2678 */ 2679 @ViewDebug.CapturedViewProperty 2680 public CharSequence getHint() { 2681 return mHint; 2682 } 2683 2684 /** 2685 * Set the type of the content with a constant as defined for 2686 * {@link EditorInfo#inputType}. This will take care of changing 2687 * the key listener, by calling {@link #setKeyListener(KeyListener)}, to 2688 * match the given content type. If the given content type is 2689 * {@link EditorInfo#TYPE_NULL} then a soft keyboard will 2690 * not be displayed for this text view. 2691 * 2692 * @see #getInputType() 2693 * @see #setRawInputType(int) 2694 * @see android.text.InputType 2695 * @attr ref android.R.styleable#TextView_inputType 2696 */ 2697 public void setInputType(int type) { 2698 setInputType(type, false); 2699 if ((type&(EditorInfo.TYPE_MASK_CLASS 2700 |EditorInfo.TYPE_MASK_VARIATION)) 2701 == (EditorInfo.TYPE_CLASS_TEXT 2702 |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { 2703 setTransformationMethod(PasswordTransformationMethod.getInstance()); 2704 setTypefaceByIndex(MONOSPACE, 0); 2705 } 2706 boolean multiLine = (type&(EditorInfo.TYPE_MASK_CLASS 2707 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE)) == 2708 (EditorInfo.TYPE_CLASS_TEXT 2709 | EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE); 2710 if (mSingleLine == multiLine) { 2711 setSingleLine(!multiLine); 2712 } 2713 InputMethodManager imm = InputMethodManager.peekInstance(); 2714 if (imm != null) imm.restartInput(this); 2715 } 2716 2717 /** 2718 * Directly change the content type integer of the text view, without 2719 * modifying any other state. 2720 * @see #setContentType 2721 * @see android.text.InputType 2722 * @attr ref android.R.styleable#TextView_inputType 2723 */ 2724 public void setRawInputType(int type) { 2725 mInputType = type; 2726 } 2727 2728 private void setInputType(int type, boolean direct) { 2729 final int cls = type & EditorInfo.TYPE_MASK_CLASS; 2730 KeyListener input; 2731 if (cls == EditorInfo.TYPE_CLASS_TEXT) { 2732 boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) 2733 != 0; 2734 TextKeyListener.Capitalize cap; 2735 if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { 2736 cap = TextKeyListener.Capitalize.CHARACTERS; 2737 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS) != 0) { 2738 cap = TextKeyListener.Capitalize.WORDS; 2739 } else if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES) != 0) { 2740 cap = TextKeyListener.Capitalize.SENTENCES; 2741 } else { 2742 cap = TextKeyListener.Capitalize.NONE; 2743 } 2744 input = TextKeyListener.getInstance(autotext, cap); 2745 } else if (cls == EditorInfo.TYPE_CLASS_NUMBER) { 2746 input = DigitsKeyListener.getInstance( 2747 (type & EditorInfo.TYPE_NUMBER_FLAG_SIGNED) != 0, 2748 (type & EditorInfo.TYPE_NUMBER_FLAG_DECIMAL) != 0); 2749 } else if (cls == EditorInfo.TYPE_CLASS_DATETIME) { 2750 switch (type & EditorInfo.TYPE_MASK_VARIATION) { 2751 case EditorInfo.TYPE_DATETIME_VARIATION_DATE: 2752 input = DateKeyListener.getInstance(); 2753 break; 2754 case EditorInfo.TYPE_DATETIME_VARIATION_TIME: 2755 input = TimeKeyListener.getInstance(); 2756 break; 2757 default: 2758 input = DateTimeKeyListener.getInstance(); 2759 break; 2760 } 2761 } else if (cls == EditorInfo.TYPE_CLASS_PHONE) { 2762 input = DialerKeyListener.getInstance(); 2763 } else { 2764 input = TextKeyListener.getInstance(); 2765 } 2766 mInputType = type; 2767 if (direct) mInput = input; 2768 else { 2769 setKeyListenerOnly(input); 2770 } 2771 } 2772 2773 /** 2774 * Get the type of the content. 2775 * 2776 * @see #setInputType(int) 2777 * @see android.text.InputType 2778 */ 2779 public int getInputType() { 2780 return mInputType; 2781 } 2782 2783 /** 2784 * Set the private content type of the text, which is the 2785 * {@link EditorInfo#privateContentType TextBoxAttribute.privateContentType} 2786 * field that will be filled in when creating an input connection. 2787 * 2788 * @see #getPrivateContentType() 2789 * @see EditorInfo#privateContentType 2790 * @attr ref android.R.styleable#TextView_editorPrivateContentType 2791 */ 2792 public void setPrivateContentType(String type) { 2793 if (mInputContentType == null) mInputContentType = new InputContentType(); 2794 mInputContentType.privateContentType = type; 2795 } 2796 2797 /** 2798 * Get the private type of the content. 2799 * 2800 * @see #setPrivateContentType(String) 2801 * @see EditorInfo#privateContentType 2802 */ 2803 public String getPrivateContentType() { 2804 return mInputContentType != null 2805 ? mInputContentType.privateContentType : null; 2806 } 2807 2808 /** 2809 * Set the extra input data of the text, which is the 2810 * {@link EditorInfo#extras TextBoxAttribute.extras} 2811 * Bundle that will be filled in when creating an input connection. The 2812 * given integer is the resource ID of an XML resource holding an 2813 * {@link android.R.styleable#InputExtras <input-extras>} XML tree. 2814 * 2815 * @see #getInputExtras() 2816 * @see EditorInfo#extras 2817 * @attr ref android.R.styleable#TextView_editorExtras 2818 */ 2819 public void setInputExtras(int xmlResId) 2820 throws XmlPullParserException, IOException { 2821 XmlResourceParser parser = getResources().getXml(xmlResId); 2822 if (mInputContentType == null) mInputContentType = new InputContentType(); 2823 mInputContentType.extras = new Bundle(); 2824 getResources().parseBundleExtras(parser, mInputContentType.extras); 2825 } 2826 2827 /** 2828 * Retrieve the input extras currently associated with the text view, which 2829 * can be viewed as well as modified. 2830 * 2831 * @param create If true, the extras will be created if they don't already 2832 * exist. Otherwise, null will be returned if none have been created. 2833 * @see #setInputExtras(int) 2834 * @see EditorInfo#extras 2835 * @attr ref android.R.styleable#TextView_editorExtras 2836 */ 2837 public Bundle getInputExtras(boolean create) { 2838 if (mInputContentType == null) { 2839 if (!create) return null; 2840 mInputContentType = new InputContentType(); 2841 } 2842 if (mInputContentType.extras == null) { 2843 if (!create) return null; 2844 mInputContentType.extras = new Bundle(); 2845 } 2846 return mInputContentType.extras; 2847 } 2848 2849 /** 2850 * Returns the error message that was set to be displayed with 2851 * {@link #setError}, or <code>null</code> if no error was set 2852 * or if it the error was cleared by the widget after user input. 2853 */ 2854 public CharSequence getError() { 2855 return mError; 2856 } 2857 2858 /** 2859 * Sets the right-hand compound drawable of the TextView to the "error" 2860 * icon and sets an error message that will be displayed in a popup when 2861 * the TextView has focus. The icon and error message will be reset to 2862 * null when any key events cause changes to the TextView's text. If the 2863 * <code>error</code> is <code>null</code>, the error message and icon 2864 * will be cleared. 2865 */ 2866 public void setError(CharSequence error) { 2867 if (error == null) { 2868 setError(null, null); 2869 } else { 2870 Drawable dr = getContext().getResources(). 2871 getDrawable(com.android.internal.R.drawable. 2872 indicator_input_error); 2873 2874 dr.setBounds(0, 0, dr.getIntrinsicWidth(), dr.getIntrinsicHeight()); 2875 setError(error, dr); 2876 } 2877 } 2878 2879 /** 2880 * Sets the right-hand compound drawable of the TextView to the specified 2881 * icon and sets an error message that will be displayed in a popup when 2882 * the TextView has focus. The icon and error message will be reset to 2883 * null when any key events cause changes to the TextView's text. The 2884 * drawable must already have had {@link Drawable#setBounds} set on it. 2885 * If the <code>error</code> is <code>null</code>, the error message will 2886 * be cleared (and you should provide a <code>null</code> icon as well). 2887 */ 2888 public void setError(CharSequence error, Drawable icon) { 2889 error = TextUtils.stringOrSpannedString(error); 2890 2891 mError = error; 2892 mErrorWasChanged = true; 2893 final Drawables dr = mDrawables; 2894 if (dr != null) { 2895 setCompoundDrawables(dr.mDrawableLeft, dr.mDrawableTop, 2896 icon, dr.mDrawableBottom); 2897 } else { 2898 setCompoundDrawables(null, null, icon, null); 2899 } 2900 2901 if (error == null) { 2902 if (mPopup != null) { 2903 if (mPopup.isShowing()) { 2904 mPopup.dismiss(); 2905 } 2906 2907 mPopup = null; 2908 } 2909 } else { 2910 if (isFocused()) { 2911 showError(); 2912 } 2913 } 2914 } 2915 2916 private void showError() { 2917 if (mPopup == null) { 2918 LayoutInflater inflater = LayoutInflater.from(getContext()); 2919 TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint, 2920 null); 2921 2922 mPopup = new PopupWindow(err, 200, 50); 2923 mPopup.setFocusable(false); 2924 } 2925 2926 TextView tv = (TextView) mPopup.getContentView(); 2927 chooseSize(mPopup, mError, tv); 2928 tv.setText(mError); 2929 2930 mPopup.showAsDropDown(this, getErrorX(), getErrorY()); 2931 } 2932 2933 /** 2934 * Returns the Y offset to make the pointy top of the error point 2935 * at the middle of the error icon. 2936 */ 2937 private int getErrorX() { 2938 /* 2939 * The "25" is the distance between the point and the right edge 2940 * of the background 2941 */ 2942 2943 final Drawables dr = mDrawables; 2944 return getWidth() - mPopup.getWidth() 2945 - getPaddingRight() 2946 - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25; 2947 } 2948 2949 /** 2950 * Returns the Y offset to make the pointy top of the error point 2951 * at the bottom of the error icon. 2952 */ 2953 private int getErrorY() { 2954 /* 2955 * Compound, not extended, because the icon is not clipped 2956 * if the text height is smaller. 2957 */ 2958 int vspace = mBottom - mTop - 2959 getCompoundPaddingBottom() - getCompoundPaddingTop(); 2960 2961 final Drawables dr = mDrawables; 2962 int icontop = getCompoundPaddingTop() 2963 + (vspace - (dr != null ? dr.mDrawableHeightRight : 0)) / 2; 2964 2965 /* 2966 * The "2" is the distance between the point and the top edge 2967 * of the background. 2968 */ 2969 2970 return icontop + (dr != null ? dr.mDrawableHeightRight : 0) 2971 - getHeight() - 2; 2972 } 2973 2974 private void hideError() { 2975 if (mPopup != null) { 2976 if (mPopup.isShowing()) { 2977 mPopup.dismiss(); 2978 } 2979 } 2980 } 2981 2982 private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) { 2983 int wid = tv.getPaddingLeft() + tv.getPaddingRight(); 2984 int ht = tv.getPaddingTop() + tv.getPaddingBottom(); 2985 2986 /* 2987 * Figure out how big the text would be if we laid it out to the 2988 * full width of this view minus the border. 2989 */ 2990 int cap = getWidth() - wid; 2991 if (cap < 0) { 2992 cap = 200; // We must not be measured yet -- setFrame() will fix it. 2993 } 2994 2995 Layout l = new StaticLayout(text, tv.getPaint(), cap, 2996 Layout.Alignment.ALIGN_NORMAL, 1, 0, true); 2997 float max = 0; 2998 for (int i = 0; i < l.getLineCount(); i++) { 2999 max = Math.max(max, l.getLineWidth(i)); 3000 } 3001 3002 /* 3003 * Now set the popup size to be big enough for the text plus the border. 3004 */ 3005 pop.setWidth(wid + (int) Math.ceil(max)); 3006 pop.setHeight(ht + l.getHeight()); 3007 } 3008 3009 3010 @Override 3011 protected boolean setFrame(int l, int t, int r, int b) { 3012 boolean result = super.setFrame(l, t, r, b); 3013 3014 if (mPopup != null) { 3015 TextView tv = (TextView) mPopup.getContentView(); 3016 chooseSize(mPopup, mError, tv); 3017 mPopup.update(this, getErrorX(), getErrorY(), -1, -1); 3018 } 3019 3020 if (mRestartMarquee && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 3021 mRestartMarquee = false; 3022 startMarquee(); 3023 } 3024 3025 return result; 3026 } 3027 3028 /** 3029 * Sets the list of input filters that will be used if the buffer is 3030 * Editable. Has no effect otherwise. 3031 * 3032 * @attr ref android.R.styleable#TextView_maxLength 3033 */ 3034 public void setFilters(InputFilter[] filters) { 3035 if (filters == null) { 3036 throw new IllegalArgumentException(); 3037 } 3038 3039 mFilters = filters; 3040 3041 if (mText instanceof Editable) { 3042 setFilters((Editable) mText, filters); 3043 } 3044 } 3045 3046 /** 3047 * Sets the list of input filters on the specified Editable, 3048 * and includes mInput in the list if it is an InputFilter. 3049 */ 3050 private void setFilters(Editable e, InputFilter[] filters) { 3051 if (mInput instanceof InputFilter) { 3052 InputFilter[] nf = new InputFilter[filters.length + 1]; 3053 3054 System.arraycopy(filters, 0, nf, 0, filters.length); 3055 nf[filters.length] = (InputFilter) mInput; 3056 3057 e.setFilters(nf); 3058 } else { 3059 e.setFilters(filters); 3060 } 3061 } 3062 3063 /** 3064 * Returns the current list of input filters. 3065 */ 3066 public InputFilter[] getFilters() { 3067 return mFilters; 3068 } 3069 3070 ///////////////////////////////////////////////////////////////////////// 3071 3072 private int getVerticalOffset(boolean forceNormal) { 3073 int voffset = 0; 3074 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3075 3076 Layout l = mLayout; 3077 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 3078 l = mHintLayout; 3079 } 3080 3081 if (gravity != Gravity.TOP) { 3082 int boxht; 3083 3084 if (l == mHintLayout) { 3085 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 3086 getCompoundPaddingBottom(); 3087 } else { 3088 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 3089 getExtendedPaddingBottom(); 3090 } 3091 int textht = l.getHeight(); 3092 3093 if (textht < boxht) { 3094 if (gravity == Gravity.BOTTOM) 3095 voffset = boxht - textht; 3096 else // (gravity == Gravity.CENTER_VERTICAL) 3097 voffset = (boxht - textht) >> 1; 3098 } 3099 } 3100 return voffset; 3101 } 3102 3103 private int getBottomVerticalOffset(boolean forceNormal) { 3104 int voffset = 0; 3105 final int gravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 3106 3107 Layout l = mLayout; 3108 if (!forceNormal && mText.length() == 0 && mHintLayout != null) { 3109 l = mHintLayout; 3110 } 3111 3112 if (gravity != Gravity.BOTTOM) { 3113 int boxht; 3114 3115 if (l == mHintLayout) { 3116 boxht = getMeasuredHeight() - getCompoundPaddingTop() - 3117 getCompoundPaddingBottom(); 3118 } else { 3119 boxht = getMeasuredHeight() - getExtendedPaddingTop() - 3120 getExtendedPaddingBottom(); 3121 } 3122 int textht = l.getHeight(); 3123 3124 if (textht < boxht) { 3125 if (gravity == Gravity.TOP) 3126 voffset = boxht - textht; 3127 else // (gravity == Gravity.CENTER_VERTICAL) 3128 voffset = (boxht - textht) >> 1; 3129 } 3130 } 3131 return voffset; 3132 } 3133 3134 private void invalidateCursorPath() { 3135 if (mHighlightPathBogus) { 3136 invalidateCursor(); 3137 } else { 3138 synchronized (sTempRect) { 3139 /* 3140 * The reason for this concern about the thickness of the 3141 * cursor and doing the floor/ceil on the coordinates is that 3142 * some EditTexts (notably textfields in the Browser) have 3143 * anti-aliased text where not all the characters are 3144 * necessarily at integer-multiple locations. This should 3145 * make sure the entire cursor gets invalidated instead of 3146 * sometimes missing half a pixel. 3147 */ 3148 3149 float thick = FloatMath.ceil(mTextPaint.getStrokeWidth()); 3150 if (thick < 1.0f) { 3151 thick = 1.0f; 3152 } 3153 3154 thick /= 2; 3155 3156 mHighlightPath.computeBounds(sTempRect, false); 3157 3158 int left = getCompoundPaddingLeft(); 3159 int top = getExtendedPaddingTop() + getVerticalOffset(true); 3160 3161 invalidate((int) FloatMath.floor(left + sTempRect.left - thick), 3162 (int) FloatMath.floor(top + sTempRect.top - thick), 3163 (int) FloatMath.ceil(left + sTempRect.right + thick), 3164 (int) FloatMath.ceil(top + sTempRect.bottom + thick)); 3165 } 3166 } 3167 } 3168 3169 private void invalidateCursor() { 3170 int where = Selection.getSelectionEnd(mText); 3171 3172 invalidateCursor(where, where, where); 3173 } 3174 3175 private void invalidateCursor(int a, int b, int c) { 3176 if (mLayout == null) { 3177 invalidate(); 3178 } else { 3179 if (a >= 0 || b >= 0 || c >= 0) { 3180 int first = Math.min(Math.min(a, b), c); 3181 int last = Math.max(Math.max(a, b), c); 3182 3183 int line = mLayout.getLineForOffset(first); 3184 int top = mLayout.getLineTop(line); 3185 3186 // This is ridiculous, but the descent from the line above 3187 // can hang down into the line we really want to redraw, 3188 // so we have to invalidate part of the line above to make 3189 // sure everything that needs to be redrawn really is. 3190 // (But not the whole line above, because that would cause 3191 // the same problem with the descenders on the line above it!) 3192 if (line > 0) { 3193 top -= mLayout.getLineDescent(line - 1); 3194 } 3195 3196 int line2; 3197 3198 if (first == last) 3199 line2 = line; 3200 else 3201 line2 = mLayout.getLineForOffset(last); 3202 3203 int bottom = mLayout.getLineTop(line2 + 1); 3204 int voffset = getVerticalOffset(true); 3205 3206 int left = getCompoundPaddingLeft() + mScrollX; 3207 invalidate(left, top + voffset + getExtendedPaddingTop(), 3208 left + getWidth() - getCompoundPaddingLeft() - 3209 getCompoundPaddingRight(), 3210 bottom + voffset + getExtendedPaddingTop()); 3211 } 3212 } 3213 } 3214 3215 private void registerForPreDraw() { 3216 final ViewTreeObserver observer = getViewTreeObserver(); 3217 if (observer == null) { 3218 return; 3219 } 3220 3221 if (mPreDrawState == PREDRAW_NOT_REGISTERED) { 3222 observer.addOnPreDrawListener(this); 3223 mPreDrawState = PREDRAW_PENDING; 3224 } else if (mPreDrawState == PREDRAW_DONE) { 3225 mPreDrawState = PREDRAW_PENDING; 3226 } 3227 3228 // else state is PREDRAW_PENDING, so keep waiting. 3229 } 3230 3231 /** 3232 * {@inheritDoc} 3233 */ 3234 public boolean onPreDraw() { 3235 if (mPreDrawState != PREDRAW_PENDING) { 3236 return true; 3237 } 3238 3239 if (mLayout == null) { 3240 assumeLayout(); 3241 } 3242 3243 boolean changed = false; 3244 3245 if (mMovement != null) { 3246 int curs = Selection.getSelectionEnd(mText); 3247 3248 /* 3249 * TODO: This should really only keep the end in view if 3250 * it already was before the text changed. I'm not sure 3251 * of a good way to tell from here if it was. 3252 */ 3253 if (curs < 0 && 3254 (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 3255 curs = mText.length(); 3256 } 3257 3258 if (curs >= 0) { 3259 changed = bringPointIntoView(curs); 3260 } 3261 } else { 3262 changed = bringTextIntoView(); 3263 } 3264 3265 mPreDrawState = PREDRAW_DONE; 3266 return !changed; 3267 } 3268 3269 @Override 3270 protected void onDetachedFromWindow() { 3271 super.onDetachedFromWindow(); 3272 3273 if (mPreDrawState != PREDRAW_NOT_REGISTERED) { 3274 final ViewTreeObserver observer = getViewTreeObserver(); 3275 if (observer != null) { 3276 observer.removeOnPreDrawListener(this); 3277 mPreDrawState = PREDRAW_NOT_REGISTERED; 3278 } 3279 } 3280 3281 if (mError != null) { 3282 hideError(); 3283 } 3284 } 3285 3286 @Override 3287 protected boolean isPaddingOffsetRequired() { 3288 return mShadowRadius != 0; 3289 } 3290 3291 @Override 3292 protected int getLeftPaddingOffset() { 3293 return (int) Math.min(0, mShadowDx - mShadowRadius); 3294 } 3295 3296 @Override 3297 protected int getTopPaddingOffset() { 3298 return (int) Math.min(0, mShadowDy - mShadowRadius); 3299 } 3300 3301 @Override 3302 protected int getBottomPaddingOffset() { 3303 return (int) Math.max(0, mShadowDy + mShadowRadius); 3304 } 3305 3306 @Override 3307 protected int getRightPaddingOffset() { 3308 return (int) Math.max(0, mShadowDx + mShadowRadius); 3309 } 3310 3311 @Override 3312 protected void onDraw(Canvas canvas) { 3313 // Draw the background for this view 3314 super.onDraw(canvas); 3315 3316 final int compoundPaddingLeft = getCompoundPaddingLeft(); 3317 final int compoundPaddingTop = getCompoundPaddingTop(); 3318 final int compoundPaddingRight = getCompoundPaddingRight(); 3319 final int compoundPaddingBottom = getCompoundPaddingBottom(); 3320 final int scrollX = mScrollX; 3321 final int scrollY = mScrollY; 3322 final int right = mRight; 3323 final int left = mLeft; 3324 final int bottom = mBottom; 3325 final int top = mTop; 3326 3327 final Drawables dr = mDrawables; 3328 if (dr != null) { 3329 /* 3330 * Compound, not extended, because the icon is not clipped 3331 * if the text height is smaller. 3332 */ 3333 3334 int vspace = bottom - top - compoundPaddingBottom - compoundPaddingTop; 3335 int hspace = right - left - compoundPaddingRight - compoundPaddingLeft; 3336 3337 if (dr.mDrawableLeft != null) { 3338 canvas.save(); 3339 canvas.translate(scrollX + mPaddingLeft, 3340 scrollY + compoundPaddingTop + 3341 (vspace - dr.mDrawableHeightLeft) / 2); 3342 dr.mDrawableLeft.draw(canvas); 3343 canvas.restore(); 3344 } 3345 3346 if (dr.mDrawableRight != null) { 3347 canvas.save(); 3348 canvas.translate(scrollX + right - left - mPaddingRight - dr.mDrawableSizeRight, 3349 scrollY + compoundPaddingTop + (vspace - dr.mDrawableHeightRight) / 2); 3350 dr.mDrawableRight.draw(canvas); 3351 canvas.restore(); 3352 } 3353 3354 if (dr.mDrawableTop != null) { 3355 canvas.save(); 3356 canvas.translate(scrollX + compoundPaddingLeft + (hspace - dr.mDrawableWidthTop) / 2, 3357 scrollY + mPaddingTop); 3358 dr.mDrawableTop.draw(canvas); 3359 canvas.restore(); 3360 } 3361 3362 if (dr.mDrawableBottom != null) { 3363 canvas.save(); 3364 canvas.translate(scrollX + compoundPaddingLeft + 3365 (hspace - dr.mDrawableWidthBottom) / 2, 3366 scrollY + bottom - top - mPaddingBottom - dr.mDrawableSizeBottom); 3367 dr.mDrawableBottom.draw(canvas); 3368 canvas.restore(); 3369 } 3370 } 3371 3372 if (mPreDrawState == PREDRAW_DONE) { 3373 final ViewTreeObserver observer = getViewTreeObserver(); 3374 if (observer != null) { 3375 observer.removeOnPreDrawListener(this); 3376 mPreDrawState = PREDRAW_NOT_REGISTERED; 3377 } 3378 } 3379 3380 int color = mCurTextColor; 3381 3382 if (mLayout == null) { 3383 assumeLayout(); 3384 } 3385 3386 Layout layout = mLayout; 3387 int cursorcolor = color; 3388 3389 if (mHint != null && mText.length() == 0) { 3390 if (mHintTextColor != null) { 3391 color = mCurHintTextColor; 3392 } 3393 3394 layout = mHintLayout; 3395 } 3396 3397 mTextPaint.setColor(color); 3398 mTextPaint.drawableState = getDrawableState(); 3399 3400 canvas.save(); 3401 /* Would be faster if we didn't have to do this. Can we chop the 3402 (displayable) text so that we don't need to do this ever? 3403 */ 3404 3405 int extendedPaddingTop = getExtendedPaddingTop(); 3406 int extendedPaddingBottom = getExtendedPaddingBottom(); 3407 3408 float clipLeft = compoundPaddingLeft + scrollX; 3409 float clipTop = extendedPaddingTop + scrollY; 3410 float clipRight = right - left - compoundPaddingRight + scrollX; 3411 float clipBottom = bottom - top - extendedPaddingBottom + scrollY; 3412 3413 if (mShadowRadius != 0) { 3414 clipLeft += Math.min(0, mShadowDx - mShadowRadius); 3415 clipRight += Math.max(0, mShadowDx + mShadowRadius); 3416 3417 clipTop += Math.min(0, mShadowDy - mShadowRadius); 3418 clipBottom += Math.max(0, mShadowDy + mShadowRadius); 3419 } 3420 3421 canvas.clipRect(clipLeft, clipTop, clipRight, clipBottom); 3422 3423 int voffsetText = 0; 3424 int voffsetCursor = 0; 3425 3426 // translate in by our padding 3427 { 3428 /* shortcircuit calling getVerticaOffset() */ 3429 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3430 voffsetText = getVerticalOffset(false); 3431 voffsetCursor = getVerticalOffset(true); 3432 } 3433 canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText); 3434 } 3435 3436 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 3437 if (!mSingleLine && getLineCount() == 1 && canMarquee() && 3438 (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) != Gravity.LEFT) { 3439 canvas.translate(mLayout.getLineRight(0) - (mRight - mLeft - 3440 getCompoundPaddingLeft() - getCompoundPaddingRight()), 0.0f); 3441 } 3442 3443 if (mMarquee != null && mMarquee.isRunning()) { 3444 canvas.translate(-mMarquee.mScroll, 0.0f); 3445 } 3446 } 3447 3448 Path highlight = null; 3449 int selStart = -1, selEnd = -1; 3450 3451 // If there is no movement method, then there can be no selection. 3452 // Check that first and attempt to skip everything having to do with 3453 // the cursor. 3454 // XXX This is not strictly true -- a program could set the 3455 // selection manually if it really wanted to. 3456 if (mMovement != null && (isFocused() || isPressed())) { 3457 selStart = Selection.getSelectionStart(mText); 3458 selEnd = Selection.getSelectionEnd(mText); 3459 3460 if (mCursorVisible && selStart >= 0 && isEnabled()) { 3461 if (mHighlightPath == null) 3462 mHighlightPath = new Path(); 3463 3464 if (selStart == selEnd) { 3465 if ((SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) 3466 < BLINK) { 3467 if (mHighlightPathBogus) { 3468 mHighlightPath.reset(); 3469 mLayout.getCursorPath(selStart, mHighlightPath, mText); 3470 mHighlightPathBogus = false; 3471 } 3472 3473 // XXX should pass to skin instead of drawing directly 3474 mHighlightPaint.setColor(cursorcolor); 3475 mHighlightPaint.setStyle(Paint.Style.STROKE); 3476 3477 highlight = mHighlightPath; 3478 } 3479 } else { 3480 if (mHighlightPathBogus) { 3481 mHighlightPath.reset(); 3482 mLayout.getSelectionPath(selStart, selEnd, mHighlightPath); 3483 mHighlightPathBogus = false; 3484 } 3485 3486 // XXX should pass to skin instead of drawing directly 3487 mHighlightPaint.setColor(mHighlightColor); 3488 mHighlightPaint.setStyle(Paint.Style.FILL); 3489 3490 highlight = mHighlightPath; 3491 } 3492 } 3493 } 3494 3495 /* Comment out until we decide what to do about animations 3496 boolean isLinearTextOn = false; 3497 if (currentTransformation != null) { 3498 isLinearTextOn = mTextPaint.isLinearTextOn(); 3499 Matrix m = currentTransformation.getMatrix(); 3500 if (!m.isIdentity()) { 3501 // mTextPaint.setLinearTextOn(true); 3502 } 3503 } 3504 */ 3505 3506 InputMethodManager imm = InputMethodManager.peekInstance(); 3507 if (highlight != null && mInputMethodState != null && imm != null) { 3508 imm.updateSelection(this, selStart, selEnd); 3509 3510 if (imm.isWatchingCursor(this)) { 3511 final InputMethodState ims = mInputMethodState; 3512 highlight.computeBounds(ims.mTmpRectF, true); 3513 ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0; 3514 3515 canvas.getMatrix().mapPoints(ims.mTmpOffset); 3516 ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]); 3517 3518 ims.mTmpRectF.offset(0, voffsetCursor - voffsetText); 3519 3520 ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5), 3521 (int)(ims.mTmpRectF.top + 0.5), 3522 (int)(ims.mTmpRectF.right + 0.5), 3523 (int)(ims.mTmpRectF.bottom + 0.5)); 3524 3525 imm.updateCursor(this, 3526 ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top, 3527 ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom); 3528 } 3529 } 3530 3531 layout.draw(canvas, highlight, mHighlightPaint, voffsetCursor - voffsetText); 3532 3533 /* Comment out until we decide what to do about animations 3534 if (currentTransformation != null) { 3535 mTextPaint.setLinearTextOn(isLinearTextOn); 3536 } 3537 */ 3538 3539 canvas.restore(); 3540 } 3541 3542 @Override 3543 public void getFocusedRect(Rect r) { 3544 if (mLayout == null) { 3545 super.getFocusedRect(r); 3546 return; 3547 } 3548 3549 int sel = getSelectionEnd(); 3550 if (sel < 0) { 3551 super.getFocusedRect(r); 3552 return; 3553 } 3554 3555 int line = mLayout.getLineForOffset(sel); 3556 r.top = mLayout.getLineTop(line); 3557 r.bottom = mLayout.getLineBottom(line); 3558 3559 r.left = (int) mLayout.getPrimaryHorizontal(sel); 3560 r.right = r.left + 1; 3561 3562 // Adjust for padding and gravity. 3563 int paddingLeft = getCompoundPaddingLeft(); 3564 int paddingTop = getExtendedPaddingTop(); 3565 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3566 paddingTop += getVerticalOffset(false); 3567 } 3568 r.offset(paddingLeft, paddingTop); 3569 } 3570 3571 /** 3572 * Return the number of lines of text, or 0 if the internal Layout has not 3573 * been built. 3574 */ 3575 public int getLineCount() { 3576 return mLayout != null ? mLayout.getLineCount() : 0; 3577 } 3578 3579 /** 3580 * Return the baseline for the specified line (0...getLineCount() - 1) 3581 * If bounds is not null, return the top, left, right, bottom extents 3582 * of the specified line in it. If the internal Layout has not been built, 3583 * return 0 and set bounds to (0, 0, 0, 0) 3584 * @param line which line to examine (0..getLineCount() - 1) 3585 * @param bounds Optional. If not null, it returns the extent of the line 3586 * @return the Y-coordinate of the baseline 3587 */ 3588 public int getLineBounds(int line, Rect bounds) { 3589 if (mLayout == null) { 3590 if (bounds != null) { 3591 bounds.set(0, 0, 0, 0); 3592 } 3593 return 0; 3594 } 3595 else { 3596 int baseline = mLayout.getLineBounds(line, bounds); 3597 3598 int voffset = getExtendedPaddingTop(); 3599 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3600 voffset += getVerticalOffset(true); 3601 } 3602 if (bounds != null) { 3603 bounds.offset(getCompoundPaddingLeft(), voffset); 3604 } 3605 return baseline + voffset; 3606 } 3607 } 3608 3609 @Override 3610 public int getBaseline() { 3611 if (mLayout == null) { 3612 return super.getBaseline(); 3613 } 3614 3615 int voffset = 0; 3616 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 3617 voffset = getVerticalOffset(true); 3618 } 3619 3620 return getExtendedPaddingTop() + voffset + mLayout.getLineBaseline(0); 3621 } 3622 3623 @Override 3624 public boolean onKeyDown(int keyCode, KeyEvent event) { 3625 int which = doKeyDown(keyCode, event); 3626 if (which == 0) { 3627 // Go through default dispatching. 3628 return super.onKeyDown(keyCode, event); 3629 } 3630 3631 return true; 3632 } 3633 3634 @Override 3635 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 3636 KeyEvent down = new KeyEvent(event, KeyEvent.ACTION_DOWN); 3637 3638 int which = doKeyDown(keyCode, down); 3639 if (which == 0) { 3640 // Go through default dispatching. 3641 return super.onKeyMultiple(keyCode, repeatCount, event); 3642 } 3643 3644 // We are going to dispatch the remaining events to either the input 3645 // or movement method. To do this, we will just send a repeated stream 3646 // of down and up events until we have done the complete repeatCount. 3647 // It would be nice if those interfaces had an onKeyMultiple() method, 3648 // but adding that is a more complicated change. 3649 KeyEvent up = new KeyEvent(event, KeyEvent.ACTION_UP); 3650 if (which == 1) { 3651 mInput.onKeyUp(this, (Editable)mText, keyCode, up); 3652 while (--repeatCount > 0) { 3653 mInput.onKeyDown(this, (Editable)mText, keyCode, down); 3654 mInput.onKeyUp(this, (Editable)mText, keyCode, up); 3655 } 3656 if (mError != null && !mErrorWasChanged) { 3657 setError(null, null); 3658 } 3659 3660 } else if (which == 2) { 3661 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 3662 while (--repeatCount > 0) { 3663 mMovement.onKeyDown(this, (Spannable)mText, keyCode, down); 3664 mMovement.onKeyUp(this, (Spannable)mText, keyCode, up); 3665 } 3666 } 3667 3668 return true; 3669 } 3670 3671 private int doKeyDown(int keyCode, KeyEvent event) { 3672 if (!isEnabled()) { 3673 return 0; 3674 } 3675 3676 switch (keyCode) { 3677 case KeyEvent.KEYCODE_DPAD_CENTER: 3678 case KeyEvent.KEYCODE_ENTER: 3679 if (mSingleLine && mInput != null) { 3680 return 0; 3681 } 3682 } 3683 3684 if (mInput != null) { 3685 /* 3686 * Keep track of what the error was before doing the input 3687 * so that if an input filter changed the error, we leave 3688 * that error showing. Otherwise, we take down whatever 3689 * error was showing when the user types something. 3690 */ 3691 mErrorWasChanged = false; 3692 3693 if (mInput.onKeyDown(this, (Editable) mText, keyCode, event)) { 3694 if (mError != null && !mErrorWasChanged) { 3695 setError(null, null); 3696 } 3697 return 1; 3698 } 3699 } 3700 3701 // bug 650865: sometimes we get a key event before a layout. 3702 // don't try to move around if we don't know the layout. 3703 3704 if (mMovement != null && mLayout != null) 3705 if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) 3706 return 2; 3707 3708 return 0; 3709 } 3710 3711 @Override 3712 public boolean onKeyUp(int keyCode, KeyEvent event) { 3713 if (!isEnabled()) { 3714 return super.onKeyUp(keyCode, event); 3715 } 3716 3717 switch (keyCode) { 3718 case KeyEvent.KEYCODE_DPAD_CENTER: 3719 case KeyEvent.KEYCODE_ENTER: 3720 if (mSingleLine && mInput != null) { 3721 /* 3722 * If there is a click listener, just call through to 3723 * super, which will invoke it. 3724 * 3725 * If there isn't a click listener, try to advance focus, 3726 * but still call through to super, which will reset the 3727 * pressed state and longpress state. (It will also 3728 * call performClick(), but that won't do anything in 3729 * this case.) 3730 */ 3731 if (mOnClickListener == null) { 3732 View v = focusSearch(FOCUS_DOWN); 3733 3734 if (v != null) { 3735 if (!v.requestFocus(FOCUS_DOWN)) { 3736 throw new IllegalStateException("focus search returned a view " + 3737 "that wasn't able to take focus!"); 3738 } 3739 3740 /* 3741 * Return true because we handled the key; super 3742 * will return false because there was no click 3743 * listener. 3744 */ 3745 super.onKeyUp(keyCode, event); 3746 return true; 3747 } 3748 } 3749 3750 return super.onKeyUp(keyCode, event); 3751 } 3752 } 3753 3754 if (mInput != null) 3755 if (mInput.onKeyUp(this, (Editable) mText, keyCode, event)) 3756 return true; 3757 3758 if (mMovement != null && mLayout != null) 3759 if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) 3760 return true; 3761 3762 return super.onKeyUp(keyCode, event); 3763 } 3764 3765 @Override public InputConnection createInputConnection(EditorInfo outAttrs) { 3766 if (mInputType != EditorInfo.TYPE_NULL) { 3767 if (mInputMethodState == null) { 3768 mInputMethodState = new InputMethodState(); 3769 } 3770 outAttrs.inputType = mInputType; 3771 outAttrs.hintText = mHint; 3772 if (mInputContentType != null) { 3773 outAttrs.privateContentType = mInputContentType.privateContentType; 3774 outAttrs.extras = mInputContentType.extras; 3775 } 3776 if (mText instanceof Editable) { 3777 InputConnection ic = new EditableInputConnection(this); 3778 outAttrs.initialSelStart = Selection.getSelectionStart(mText); 3779 outAttrs.initialSelEnd = Selection.getSelectionEnd(mText); 3780 outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType); 3781 return ic; 3782 } 3783 } 3784 return null; 3785 } 3786 3787 /** 3788 * If this TextView contains editable content, extract a portion of it 3789 * based on the information in <var>request</var> in to <var>outText</var>. 3790 * @return Returns true if the text is editable and was successfully 3791 * extracted, else false. 3792 */ 3793 public boolean extractText(ExtractedTextRequest request, 3794 ExtractedText outText) { 3795 Editable content = getEditableText(); 3796 if (content != null) { 3797 outText.text = content.subSequence(0, content.length()); 3798 outText.startOffset = 0; 3799 outText.selectionStart = Selection.getSelectionStart(content); 3800 outText.selectionEnd = Selection.getSelectionEnd(content); 3801 return true; 3802 } 3803 return false; 3804 } 3805 3806 void reportExtractedText() { 3807 if (mInputMethodState != null) { 3808 final ExtractedTextRequest req = mInputMethodState.mExtracting; 3809 if (req != null) { 3810 InputMethodManager imm = InputMethodManager.peekInstance(); 3811 if (imm != null) { 3812 if (extractText(req, mInputMethodState.mTmpExtracted)) { 3813 imm.updateExtractedText(this, req.token, 3814 mInputMethodState.mTmpExtracted); 3815 } 3816 } 3817 } 3818 } 3819 } 3820 3821 /** 3822 * Apply to this text view the given extracted text, as previously 3823 * returned by {@link #extractText(ExtractedTextRequest, ExtractedText)}. 3824 */ 3825 public void setExtractedText(ExtractedText text) { 3826 setText(text.text, TextView.BufferType.EDITABLE); 3827 Selection.setSelection((Spannable)getText(), 3828 text.selectionStart, text.selectionEnd); 3829 } 3830 3831 /** 3832 * @hide 3833 */ 3834 public void setExtracting(ExtractedTextRequest req) { 3835 if (mInputMethodState != null) { 3836 mInputMethodState.mExtracting = req; 3837 } 3838 } 3839 3840 /** 3841 * Called by the framework in response to a text completion from 3842 * the current input method, provided by it calling 3843 * {@link InputConnection#commitCompletion 3844 * InputConnection.commitCompletion()}. The default implementation does 3845 * nothing; text views that are supporting auto-completion should override 3846 * this to do their desired behavior. 3847 * 3848 * @param text The auto complete text the user has selected. 3849 */ 3850 public void onCommitCompletion(CompletionInfo text) { 3851 } 3852 3853 /** 3854 * Called by the framework in response to a private command from the 3855 * current method, provided by it calling 3856 * {@link InputConnection#performPrivateCommand 3857 * InputConnection.performPrivateCommand()}. 3858 * 3859 * @param action The action name of the command. 3860 * @param data Any additional data for the command. This may be null. 3861 * @return Return true if you handled the command, else false. 3862 */ 3863 public boolean onPrivateIMECommand(String action, Bundle data) { 3864 return false; 3865 } 3866 3867 private void nullLayouts() { 3868 if (mLayout instanceof BoringLayout && mSavedLayout == null) { 3869 mSavedLayout = (BoringLayout) mLayout; 3870 } 3871 if (mHintLayout instanceof BoringLayout && mSavedHintLayout == null) { 3872 mSavedHintLayout = (BoringLayout) mHintLayout; 3873 } 3874 3875 mLayout = mHintLayout = null; 3876 } 3877 3878 /** 3879 * Make a new Layout based on the already-measured size of the view, 3880 * on the assumption that it was measured correctly at some point. 3881 */ 3882 private void assumeLayout() { 3883 int width = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 3884 3885 if (width < 1) { 3886 width = 0; 3887 } 3888 3889 int physicalWidth = width; 3890 3891 if (mHorizontallyScrolling) { 3892 width = VERY_WIDE; 3893 } 3894 3895 makeNewLayout(width, physicalWidth, UNKNOWN_BORING, UNKNOWN_BORING, 3896 physicalWidth, false); 3897 } 3898 3899 /** 3900 * The width passed in is now the desired layout width, 3901 * not the full view width with padding. 3902 * {@hide} 3903 */ 3904 protected void makeNewLayout(int w, int hintWidth, 3905 BoringLayout.Metrics boring, 3906 BoringLayout.Metrics hintBoring, 3907 int ellipsisWidth, boolean bringIntoView) { 3908 stopMarquee(); 3909 3910 mHighlightPathBogus = true; 3911 3912 if (w < 0) { 3913 w = 0; 3914 } 3915 if (hintWidth < 0) { 3916 hintWidth = 0; 3917 } 3918 3919 Layout.Alignment alignment; 3920 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 3921 case Gravity.CENTER_HORIZONTAL: 3922 alignment = Layout.Alignment.ALIGN_CENTER; 3923 break; 3924 3925 case Gravity.RIGHT: 3926 alignment = Layout.Alignment.ALIGN_OPPOSITE; 3927 break; 3928 3929 default: 3930 alignment = Layout.Alignment.ALIGN_NORMAL; 3931 } 3932 3933 if (mText instanceof Spannable) { 3934 mLayout = new DynamicLayout(mText, mTransformed, mTextPaint, w, 3935 alignment, mSpacingMult, 3936 mSpacingAdd, mIncludePad, mEllipsize, 3937 ellipsisWidth); 3938 } else { 3939 if (boring == UNKNOWN_BORING) { 3940 boring = BoringLayout.isBoring(mTransformed, mTextPaint, 3941 mBoring); 3942 if (boring != null) { 3943 mBoring = boring; 3944 } 3945 } 3946 3947 if (boring != null) { 3948 if (boring.width <= w && 3949 (mEllipsize == null || boring.width <= ellipsisWidth)) { 3950 if (mSavedLayout != null) { 3951 mLayout = mSavedLayout. 3952 replaceOrMake(mTransformed, mTextPaint, 3953 w, alignment, mSpacingMult, mSpacingAdd, 3954 boring, mIncludePad); 3955 } else { 3956 mLayout = BoringLayout.make(mTransformed, mTextPaint, 3957 w, alignment, mSpacingMult, mSpacingAdd, 3958 boring, mIncludePad); 3959 } 3960 // Log.e("aaa", "Boring: " + mTransformed); 3961 3962 mSavedLayout = (BoringLayout) mLayout; 3963 } else if (mEllipsize != null && boring.width <= w) { 3964 if (mSavedLayout != null) { 3965 mLayout = mSavedLayout. 3966 replaceOrMake(mTransformed, mTextPaint, 3967 w, alignment, mSpacingMult, mSpacingAdd, 3968 boring, mIncludePad, mEllipsize, 3969 ellipsisWidth); 3970 } else { 3971 mLayout = BoringLayout.make(mTransformed, mTextPaint, 3972 w, alignment, mSpacingMult, mSpacingAdd, 3973 boring, mIncludePad, mEllipsize, 3974 ellipsisWidth); 3975 } 3976 } else if (mEllipsize != null) { 3977 mLayout = new StaticLayout(mTransformed, 3978 0, mTransformed.length(), 3979 mTextPaint, w, alignment, mSpacingMult, 3980 mSpacingAdd, mIncludePad, mEllipsize, 3981 ellipsisWidth); 3982 } else { 3983 mLayout = new StaticLayout(mTransformed, mTextPaint, 3984 w, alignment, mSpacingMult, mSpacingAdd, 3985 mIncludePad); 3986 // Log.e("aaa", "Boring but wide: " + mTransformed); 3987 } 3988 } else if (mEllipsize != null) { 3989 mLayout = new StaticLayout(mTransformed, 3990 0, mTransformed.length(), 3991 mTextPaint, w, alignment, mSpacingMult, 3992 mSpacingAdd, mIncludePad, mEllipsize, 3993 ellipsisWidth); 3994 } else { 3995 mLayout = new StaticLayout(mTransformed, mTextPaint, 3996 w, alignment, mSpacingMult, mSpacingAdd, 3997 mIncludePad); 3998 } 3999 } 4000 4001 mHintLayout = null; 4002 4003 if (mHint != null) { 4004 if (hintBoring == UNKNOWN_BORING) { 4005 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, 4006 mHintBoring); 4007 if (hintBoring != null) { 4008 mHintBoring = hintBoring; 4009 } 4010 } 4011 4012 if (hintBoring != null) { 4013 if (hintBoring.width <= hintWidth) { 4014 if (mSavedHintLayout != null) { 4015 mHintLayout = mSavedHintLayout. 4016 replaceOrMake(mHint, mTextPaint, 4017 hintWidth, alignment, mSpacingMult, 4018 mSpacingAdd, hintBoring, mIncludePad); 4019 } else { 4020 mHintLayout = BoringLayout.make(mHint, mTextPaint, 4021 hintWidth, alignment, mSpacingMult, 4022 mSpacingAdd, hintBoring, mIncludePad); 4023 } 4024 4025 mSavedHintLayout = (BoringLayout) mHintLayout; 4026 } else { 4027 mHintLayout = new StaticLayout(mHint, mTextPaint, 4028 hintWidth, alignment, mSpacingMult, mSpacingAdd, 4029 mIncludePad); 4030 } 4031 } else { 4032 mHintLayout = new StaticLayout(mHint, mTextPaint, 4033 hintWidth, alignment, mSpacingMult, mSpacingAdd, 4034 mIncludePad); 4035 } 4036 } 4037 4038 if (bringIntoView) { 4039 registerForPreDraw(); 4040 } 4041 4042 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4043 final int height = mLayoutParams.height; 4044 // If the size of the view does not depend on the size of the text, try to 4045 // start the marquee immediately 4046 if (height != LayoutParams.WRAP_CONTENT && height != LayoutParams.FILL_PARENT) { 4047 startMarquee(); 4048 } else { 4049 // Defer the start of the marquee until we know our width (see setFrame()) 4050 mRestartMarquee = true; 4051 } 4052 } 4053 } 4054 4055 private static int desired(Layout layout) { 4056 int n = layout.getLineCount(); 4057 CharSequence text = layout.getText(); 4058 float max = 0; 4059 4060 // if any line was wrapped, we can't use it. 4061 // but it's ok for the last line not to have a newline 4062 4063 for (int i = 0; i < n - 1; i++) { 4064 if (text.charAt(layout.getLineEnd(i) - 1) != '\n') 4065 return -1; 4066 } 4067 4068 for (int i = 0; i < n; i++) { 4069 max = Math.max(max, layout.getLineWidth(i)); 4070 } 4071 4072 return (int) FloatMath.ceil(max); 4073 } 4074 4075 /** 4076 * Set whether the TextView includes extra top and bottom padding to make 4077 * room for accents that go above the normal ascent and descent. 4078 * The default is true. 4079 * 4080 * @attr ref android.R.styleable#TextView_includeFontPadding 4081 */ 4082 public void setIncludeFontPadding(boolean includepad) { 4083 mIncludePad = includepad; 4084 4085 if (mLayout != null) { 4086 nullLayouts(); 4087 requestLayout(); 4088 invalidate(); 4089 } 4090 } 4091 4092 private static final BoringLayout.Metrics UNKNOWN_BORING = 4093 new BoringLayout.Metrics(); 4094 4095 @Override 4096 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 4097 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 4098 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 4099 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 4100 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 4101 4102 int width; 4103 int height; 4104 4105 BoringLayout.Metrics boring = UNKNOWN_BORING; 4106 BoringLayout.Metrics hintBoring = UNKNOWN_BORING; 4107 4108 int des = -1; 4109 boolean fromexisting = false; 4110 4111 if (widthMode == MeasureSpec.EXACTLY) { 4112 // Parent has told us how big to be. So be it. 4113 width = widthSize; 4114 } else { 4115 if (mLayout != null && mEllipsize == null) { 4116 des = desired(mLayout); 4117 } 4118 4119 if (des < 0) { 4120 boring = BoringLayout.isBoring(mTransformed, mTextPaint, 4121 mBoring); 4122 if (boring != null) { 4123 mBoring = boring; 4124 } 4125 } else { 4126 fromexisting = true; 4127 } 4128 4129 if (boring == null || boring == UNKNOWN_BORING) { 4130 if (des < 0) { 4131 des = (int) FloatMath.ceil(Layout. 4132 getDesiredWidth(mTransformed, mTextPaint)); 4133 } 4134 4135 width = des; 4136 } else { 4137 width = boring.width; 4138 } 4139 4140 final Drawables dr = mDrawables; 4141 if (dr != null) { 4142 width = Math.max(width, dr.mDrawableWidthTop); 4143 width = Math.max(width, dr.mDrawableWidthBottom); 4144 } 4145 4146 if (mHint != null) { 4147 int hintDes = -1; 4148 int hintWidth; 4149 4150 if (mHintLayout != null) { 4151 hintDes = desired(mHintLayout); 4152 } 4153 4154 if (hintDes < 0) { 4155 hintBoring = BoringLayout.isBoring(mHint, mTextPaint, 4156 mHintBoring); 4157 if (hintBoring != null) { 4158 mHintBoring = hintBoring; 4159 } 4160 } 4161 4162 if (hintBoring == null || hintBoring == UNKNOWN_BORING) { 4163 if (hintDes < 0) { 4164 hintDes = (int) FloatMath.ceil(Layout. 4165 getDesiredWidth(mHint, mTextPaint)); 4166 } 4167 4168 hintWidth = hintDes; 4169 } else { 4170 hintWidth = hintBoring.width; 4171 } 4172 4173 if (hintWidth > width) { 4174 width = hintWidth; 4175 } 4176 } 4177 4178 width += getCompoundPaddingLeft() + getCompoundPaddingRight(); 4179 4180 if (mMaxWidthMode == EMS) { 4181 width = Math.min(width, mMaxWidth * getLineHeight()); 4182 } else { 4183 width = Math.min(width, mMaxWidth); 4184 } 4185 4186 if (mMinWidthMode == EMS) { 4187 width = Math.max(width, mMinWidth * getLineHeight()); 4188 } else { 4189 width = Math.max(width, mMinWidth); 4190 } 4191 4192 // Check against our minimum width 4193 width = Math.max(width, getSuggestedMinimumWidth()); 4194 4195 if (widthMode == MeasureSpec.AT_MOST) { 4196 width = Math.min(widthSize, width); 4197 } 4198 } 4199 4200 int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight(); 4201 int unpaddedWidth = want; 4202 int hintWant = want; 4203 4204 if (mHorizontallyScrolling) 4205 want = VERY_WIDE; 4206 4207 int hintWidth = mHintLayout == null ? hintWant : mHintLayout.getWidth(); 4208 4209 if (mLayout == null) { 4210 makeNewLayout(want, hintWant, boring, hintBoring, 4211 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), 4212 false); 4213 } else if ((mLayout.getWidth() != want) || (hintWidth != hintWant) || 4214 (mLayout.getEllipsizedWidth() != 4215 width - getCompoundPaddingLeft() - getCompoundPaddingRight())) { 4216 if (mHint == null && mEllipsize == null && 4217 want > mLayout.getWidth() && 4218 (mLayout instanceof BoringLayout || 4219 (fromexisting && des >= 0 && des <= want))) { 4220 mLayout.increaseWidthTo(want); 4221 } else { 4222 makeNewLayout(want, hintWant, boring, hintBoring, 4223 width - getCompoundPaddingLeft() - getCompoundPaddingRight(), 4224 false); 4225 } 4226 } else { 4227 // Width has not changed. 4228 } 4229 4230 if (heightMode == MeasureSpec.EXACTLY) { 4231 // Parent has told us how big to be. So be it. 4232 height = heightSize; 4233 mDesiredHeightAtMeasure = -1; 4234 } else { 4235 int desired = getDesiredHeight(); 4236 4237 height = desired; 4238 mDesiredHeightAtMeasure = desired; 4239 4240 if (heightMode == MeasureSpec.AT_MOST) { 4241 height = Math.min(desired, height); 4242 } 4243 } 4244 4245 int unpaddedHeight = height - getCompoundPaddingTop() - 4246 getCompoundPaddingBottom(); 4247 if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) { 4248 unpaddedHeight = Math.min(unpaddedHeight, 4249 mLayout.getLineTop(mMaximum)); 4250 } 4251 4252 /* 4253 * We didn't let makeNewLayout() register to bring the cursor into view, 4254 * so do it here if there is any possibility that it is needed. 4255 */ 4256 if (mMovement != null || 4257 mLayout.getWidth() > unpaddedWidth || 4258 mLayout.getHeight() > unpaddedHeight) { 4259 registerForPreDraw(); 4260 } else { 4261 scrollTo(0, 0); 4262 } 4263 4264 setMeasuredDimension(width, height); 4265 } 4266 4267 private int getDesiredHeight() { 4268 return Math.max(getDesiredHeight(mLayout, true), 4269 getDesiredHeight(mHintLayout, false)); 4270 } 4271 4272 private int getDesiredHeight(Layout layout, boolean cap) { 4273 if (layout == null) { 4274 return 0; 4275 } 4276 4277 int linecount = layout.getLineCount(); 4278 int pad = getCompoundPaddingTop() + getCompoundPaddingBottom(); 4279 int desired = layout.getLineTop(linecount); 4280 4281 final Drawables dr = mDrawables; 4282 if (dr != null) { 4283 desired = Math.max(desired, dr.mDrawableHeightLeft); 4284 desired = Math.max(desired, dr.mDrawableHeightRight); 4285 } 4286 4287 desired += pad; 4288 4289 if (mMaxMode == LINES) { 4290 /* 4291 * Don't cap the hint to a certain number of lines. 4292 * (Do cap it, though, if we have a maximum pixel height.) 4293 */ 4294 if (cap) { 4295 if (linecount > mMaximum) { 4296 desired = layout.getLineTop(mMaximum) + 4297 layout.getBottomPadding(); 4298 4299 if (dr != null) { 4300 desired = Math.max(desired, dr.mDrawableHeightLeft); 4301 desired = Math.max(desired, dr.mDrawableHeightRight); 4302 } 4303 4304 desired += pad; 4305 linecount = mMaximum; 4306 } 4307 } 4308 } else { 4309 desired = Math.min(desired, mMaximum); 4310 } 4311 4312 if (mMinMode == LINES) { 4313 if (linecount < mMinimum) { 4314 desired += getLineHeight() * (mMinimum - linecount); 4315 } 4316 } else { 4317 desired = Math.max(desired, mMinimum); 4318 } 4319 4320 // Check against our minimum height 4321 desired = Math.max(desired, getSuggestedMinimumHeight()); 4322 4323 return desired; 4324 } 4325 4326 /** 4327 * Check whether a change to the existing text layout requires a 4328 * new view layout. 4329 */ 4330 private void checkForResize() { 4331 boolean sizeChanged = false; 4332 4333 if (mLayout != null) { 4334 // Check if our width changed 4335 if (mLayoutParams.width == LayoutParams.WRAP_CONTENT) { 4336 sizeChanged = true; 4337 invalidate(); 4338 } 4339 4340 // Check if our height changed 4341 if (mLayoutParams.height == LayoutParams.WRAP_CONTENT) { 4342 int desiredHeight = getDesiredHeight(); 4343 4344 if (desiredHeight != this.getHeight()) { 4345 sizeChanged = true; 4346 } 4347 } else if (mLayoutParams.height == LayoutParams.FILL_PARENT) { 4348 if (mDesiredHeightAtMeasure >= 0) { 4349 int desiredHeight = getDesiredHeight(); 4350 4351 if (desiredHeight != mDesiredHeightAtMeasure) { 4352 sizeChanged = true; 4353 } 4354 } 4355 } 4356 } 4357 4358 if (sizeChanged) { 4359 requestLayout(); 4360 // caller will have already invalidated 4361 } 4362 } 4363 4364 /** 4365 * Check whether entirely new text requires a new view layout 4366 * or merely a new text layout. 4367 */ 4368 private void checkForRelayout() { 4369 // If we have a fixed width, we can just swap in a new text layout 4370 // if the text height stays the same or if the view height is fixed. 4371 4372 if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT || 4373 (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth)) && 4374 (mHint == null || mHintLayout != null) && 4375 (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) { 4376 // Static width, so try making a new text layout. 4377 4378 int oldht = mLayout.getHeight(); 4379 int want = mLayout.getWidth(); 4380 int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth(); 4381 4382 /* 4383 * No need to bring the text into view, since the size is not 4384 * changing (unless we do the requestLayout(), in which case it 4385 * will happen at measure). 4386 */ 4387 makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING, 4388 mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(), false); 4389 4390 // In a fixed-height view, so use our new text layout. 4391 if (mLayoutParams.height != LayoutParams.WRAP_CONTENT && 4392 mLayoutParams.height != LayoutParams.FILL_PARENT) { 4393 invalidate(); 4394 return; 4395 } 4396 4397 // Dynamic height, but height has stayed the same, 4398 // so use our new text layout. 4399 if (mLayout.getHeight() == oldht && 4400 (mHintLayout == null || mHintLayout.getHeight() == oldht)) { 4401 invalidate(); 4402 return; 4403 } 4404 4405 // We lose: the height has changed and we have a dynamic height. 4406 // Request a new view layout using our new text layout. 4407 requestLayout(); 4408 invalidate(); 4409 } else { 4410 // Dynamic width, so we have no choice but to request a new 4411 // view layout with a new text layout. 4412 4413 nullLayouts(); 4414 requestLayout(); 4415 invalidate(); 4416 } 4417 } 4418 4419 /** 4420 * Returns true if anything changed. 4421 */ 4422 private boolean bringTextIntoView() { 4423 int line = 0; 4424 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 4425 line = mLayout.getLineCount() - 1; 4426 } 4427 4428 Layout.Alignment a = mLayout.getParagraphAlignment(line); 4429 int dir = mLayout.getParagraphDirection(line); 4430 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 4431 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 4432 int ht = mLayout.getHeight(); 4433 4434 int scrollx, scrolly; 4435 4436 if (a == Layout.Alignment.ALIGN_CENTER) { 4437 /* 4438 * Keep centered if possible, or, if it is too wide to fit, 4439 * keep leading edge in view. 4440 */ 4441 4442 int left = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4443 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4444 4445 if (right - left < hspace) { 4446 scrollx = (right + left) / 2 - hspace / 2; 4447 } else { 4448 if (dir < 0) { 4449 scrollx = right - hspace; 4450 } else { 4451 scrollx = left; 4452 } 4453 } 4454 } else if (a == Layout.Alignment.ALIGN_NORMAL) { 4455 /* 4456 * Keep leading edge in view. 4457 */ 4458 4459 if (dir < 0) { 4460 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4461 scrollx = right - hspace; 4462 } else { 4463 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4464 } 4465 } else /* a == Layout.Alignment.ALIGN_OPPOSITE */ { 4466 /* 4467 * Keep trailing edge in view. 4468 */ 4469 4470 if (dir < 0) { 4471 scrollx = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4472 } else { 4473 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4474 scrollx = right - hspace; 4475 } 4476 } 4477 4478 if (ht < vspace) { 4479 scrolly = 0; 4480 } else { 4481 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { 4482 scrolly = ht - vspace; 4483 } else { 4484 scrolly = 0; 4485 } 4486 } 4487 4488 if (scrollx != mScrollX || scrolly != mScrollY) { 4489 scrollTo(scrollx, scrolly); 4490 return true; 4491 } else { 4492 return false; 4493 } 4494 } 4495 4496 /** 4497 * Returns true if anything changed. 4498 */ 4499 private boolean bringPointIntoView(int offset) { 4500 boolean changed = false; 4501 4502 int line = mLayout.getLineForOffset(offset); 4503 4504 // FIXME: Is it okay to truncate this, or should we round? 4505 final int x = (int)mLayout.getPrimaryHorizontal(offset); 4506 final int top = mLayout.getLineTop(line); 4507 final int bottom = mLayout.getLineTop(line+1); 4508 4509 int left = (int) FloatMath.floor(mLayout.getLineLeft(line)); 4510 int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); 4511 int ht = mLayout.getHeight(); 4512 4513 int grav; 4514 4515 switch (mLayout.getParagraphAlignment(line)) { 4516 case ALIGN_NORMAL: 4517 grav = 1; 4518 break; 4519 4520 case ALIGN_OPPOSITE: 4521 grav = -1; 4522 break; 4523 4524 default: 4525 grav = 0; 4526 } 4527 4528 grav *= mLayout.getParagraphDirection(line); 4529 4530 int hspace = mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(); 4531 int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); 4532 4533 int hslack = (bottom - top) / 2; 4534 int vslack = hslack; 4535 4536 if (vslack > vspace / 4) 4537 vslack = vspace / 4; 4538 if (hslack > hspace / 4) 4539 hslack = hspace / 4; 4540 4541 int hs = mScrollX; 4542 int vs = mScrollY; 4543 4544 if (top - vs < vslack) 4545 vs = top - vslack; 4546 if (bottom - vs > vspace - vslack) 4547 vs = bottom - (vspace - vslack); 4548 if (ht - vs < vspace) 4549 vs = ht - vspace; 4550 if (0 - vs > 0) 4551 vs = 0; 4552 4553 if (grav != 0) { 4554 if (x - hs < hslack) { 4555 hs = x - hslack; 4556 } 4557 if (x - hs > hspace - hslack) { 4558 hs = x - (hspace - hslack); 4559 } 4560 } 4561 4562 if (grav < 0) { 4563 if (left - hs > 0) 4564 hs = left; 4565 if (right - hs < hspace) 4566 hs = right - hspace; 4567 } else if (grav > 0) { 4568 if (right - hs < hspace) 4569 hs = right - hspace; 4570 if (left - hs > 0) 4571 hs = left; 4572 } else /* grav == 0 */ { 4573 if (right - left <= hspace) { 4574 /* 4575 * If the entire text fits, center it exactly. 4576 */ 4577 hs = left - (hspace - (right - left)) / 2; 4578 } else if (x > right - hslack) { 4579 /* 4580 * If we are near the right edge, keep the right edge 4581 * at the edge of the view. 4582 */ 4583 hs = right - hspace; 4584 } else if (x < left + hslack) { 4585 /* 4586 * If we are near the left edge, keep the left edge 4587 * at the edge of the view. 4588 */ 4589 hs = left; 4590 } else if (left > hs) { 4591 /* 4592 * Is there whitespace visible at the left? Fix it if so. 4593 */ 4594 hs = left; 4595 } else if (right < hs + hspace) { 4596 /* 4597 * Is there whitespace visible at the right? Fix it if so. 4598 */ 4599 hs = right - hspace; 4600 } else { 4601 /* 4602 * Otherwise, float as needed. 4603 */ 4604 if (x - hs < hslack) { 4605 hs = x - hslack; 4606 } 4607 if (x - hs > hspace - hslack) { 4608 hs = x - (hspace - hslack); 4609 } 4610 } 4611 } 4612 4613 if (hs != mScrollX || vs != mScrollY) { 4614 if (mScroller == null) { 4615 scrollTo(hs, vs); 4616 } else { 4617 long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 4618 int dx = hs - mScrollX; 4619 int dy = vs - mScrollY; 4620 4621 if (duration > ANIMATED_SCROLL_GAP) { 4622 mScroller.startScroll(mScrollX, mScrollY, dx, dy); 4623 invalidate(); 4624 } else { 4625 if (!mScroller.isFinished()) { 4626 mScroller.abortAnimation(); 4627 } 4628 4629 scrollBy(dx, dy); 4630 } 4631 4632 mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 4633 } 4634 4635 changed = true; 4636 } 4637 4638 if (isFocused()) { 4639 // This offsets because getInterestingRect() is in terms of 4640 // viewport coordinates, but requestRectangleOnScreen() 4641 // is in terms of content coordinates. 4642 4643 Rect r = new Rect(); 4644 getInterestingRect(r, x, top, bottom, line); 4645 r.offset(mScrollX, mScrollY); 4646 4647 if (requestRectangleOnScreen(r)) { 4648 changed = true; 4649 } 4650 } 4651 4652 return changed; 4653 } 4654 4655 @Override 4656 public void computeScroll() { 4657 if (mScroller != null) { 4658 if (mScroller.computeScrollOffset()) { 4659 mScrollX = mScroller.getCurrX(); 4660 mScrollY = mScroller.getCurrY(); 4661 postInvalidate(); // So we draw again 4662 } 4663 } 4664 } 4665 4666 private void getInterestingRect(Rect r, int h, int top, int bottom, 4667 int line) { 4668 int paddingTop = getExtendedPaddingTop(); 4669 if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { 4670 paddingTop += getVerticalOffset(false); 4671 } 4672 top += paddingTop; 4673 bottom += paddingTop; 4674 h += getCompoundPaddingLeft(); 4675 4676 if (line == 0) 4677 top -= getExtendedPaddingTop(); 4678 if (line == mLayout.getLineCount() - 1) 4679 bottom += getExtendedPaddingBottom(); 4680 4681 r.set(h, top, h+1, bottom); 4682 r.offset(-mScrollX, -mScrollY); 4683 } 4684 4685 @Override 4686 public void debug(int depth) { 4687 super.debug(depth); 4688 4689 String output = debugIndent(depth); 4690 output += "frame={" + mLeft + ", " + mTop + ", " + mRight 4691 + ", " + mBottom + "} scroll={" + mScrollX + ", " + mScrollY 4692 + "} "; 4693 4694 if (mText != null) { 4695 4696 output += "mText=\"" + mText + "\" "; 4697 if (mLayout != null) { 4698 output += "mLayout width=" + mLayout.getWidth() 4699 + " height=" + mLayout.getHeight(); 4700 } 4701 } else { 4702 output += "mText=NULL"; 4703 } 4704 Log.d(VIEW_LOG_TAG, output); 4705 } 4706 4707 /** 4708 * Convenience for {@link Selection#getSelectionStart}. 4709 */ 4710 public int getSelectionStart() { 4711 return Selection.getSelectionStart(getText()); 4712 } 4713 4714 /** 4715 * Convenience for {@link Selection#getSelectionEnd}. 4716 */ 4717 public int getSelectionEnd() { 4718 return Selection.getSelectionEnd(getText()); 4719 } 4720 4721 /** 4722 * Return true iff there is a selection inside this text view. 4723 */ 4724 public boolean hasSelection() { 4725 return getSelectionStart() != getSelectionEnd(); 4726 } 4727 4728 /** 4729 * Sets the properties of this field (lines, horizontally scrolling, 4730 * transformation method) to be for a single-line input. 4731 * 4732 * @attr ref android.R.styleable#TextView_singleLine 4733 */ 4734 public void setSingleLine() { 4735 setSingleLine(true); 4736 } 4737 4738 /** 4739 * If true, sets the properties of this field (lines, horizontally 4740 * scrolling, transformation method) to be for a single-line input; 4741 * if false, restores these to the default conditions. 4742 * Note that calling this with false restores default conditions, 4743 * not necessarily those that were in effect prior to calling 4744 * it with true. 4745 * 4746 * @attr ref android.R.styleable#TextView_singleLine 4747 */ 4748 public void setSingleLine(boolean singleLine) { 4749 mSingleLine = singleLine; 4750 if ((mInputType&EditorInfo.TYPE_MASK_CLASS) 4751 == EditorInfo.TYPE_CLASS_TEXT) { 4752 if (singleLine) { 4753 mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 4754 } else { 4755 mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; 4756 } 4757 } 4758 4759 if (singleLine) { 4760 setLines(1); 4761 setHorizontallyScrolling(true); 4762 setTransformationMethod(SingleLineTransformationMethod. 4763 getInstance()); 4764 } else { 4765 setMaxLines(Integer.MAX_VALUE); 4766 setHorizontallyScrolling(false); 4767 setTransformationMethod(null); 4768 } 4769 } 4770 4771 /** 4772 * Causes words in the text that are longer than the view is wide 4773 * to be ellipsized instead of broken in the middle. You may also 4774 * want to {@link #setSingleLine} or {@link #setHorizontallyScrolling} 4775 * to constrain the text toa single line. Use <code>null</code> 4776 * to turn off ellipsizing. 4777 * 4778 * @attr ref android.R.styleable#TextView_ellipsize 4779 */ 4780 public void setEllipsize(TextUtils.TruncateAt where) { 4781 mEllipsize = where; 4782 4783 if (mLayout != null) { 4784 nullLayouts(); 4785 requestLayout(); 4786 invalidate(); 4787 } 4788 } 4789 4790 /** 4791 * Sets how many times to repeat the marquee animation. Only applied if the 4792 * TextView has marquee enabled. Set to -1 to repeat indefinitely. 4793 * 4794 * @attr ref android.R.styleable#TextView_marqueeRepeatLimit 4795 */ 4796 public void setMarqueeRepeatLimit(int marqueeLimit) { 4797 mMarqueeRepeatLimit = marqueeLimit; 4798 } 4799 4800 /** 4801 * Returns where, if anywhere, words that are longer than the view 4802 * is wide should be ellipsized. 4803 */ 4804 @ViewDebug.ExportedProperty 4805 public TextUtils.TruncateAt getEllipsize() { 4806 return mEllipsize; 4807 } 4808 4809 /** 4810 * Set the TextView so that when it takes focus, all the text is 4811 * selected. 4812 * 4813 * @attr ref android.R.styleable#TextView_selectAllOnFocus 4814 */ 4815 public void setSelectAllOnFocus(boolean selectAllOnFocus) { 4816 mSelectAllOnFocus = selectAllOnFocus; 4817 4818 if (selectAllOnFocus && !(mText instanceof Spannable)) { 4819 setText(mText, BufferType.SPANNABLE); 4820 } 4821 } 4822 4823 /** 4824 * Set whether the cursor is visible. The default is true. 4825 * 4826 * @attr ref android.R.styleable#TextView_cursorVisible 4827 */ 4828 public void setCursorVisible(boolean visible) { 4829 mCursorVisible = visible; 4830 invalidate(); 4831 4832 if (visible) { 4833 makeBlink(); 4834 } else if (mBlink != null) { 4835 mBlink.removeCallbacks(mBlink); 4836 } 4837 } 4838 4839 private boolean canMarquee() { 4840 int width = (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight()); 4841 return width > 0 && mLayout.getLineWidth(0) > width; 4842 } 4843 4844 private void startMarquee() { 4845 if ((mMarquee == null || mMarquee.isStopped()) && (isFocused() || isSelected()) && 4846 getLineCount() == 1 && canMarquee()) { 4847 if (mMarquee == null) mMarquee = new Marquee(this); 4848 mMarquee.start(mMarqueeRepeatLimit); 4849 } 4850 } 4851 4852 private void stopMarquee() { 4853 if (mMarquee != null && !mMarquee.isStopped()) { 4854 mMarquee.stop(); 4855 } 4856 } 4857 4858 private void startStopMarquee(boolean start) { 4859 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 4860 if (start) { 4861 startMarquee(); 4862 } else { 4863 stopMarquee(); 4864 } 4865 } 4866 } 4867 4868 private static final class Marquee extends Handler { 4869 // TODO: Add an option to configure this 4870 private static final int MARQUEE_DELAY = 1200; 4871 private static final int MARQUEE_RESTART_DELAY = 1200; 4872 private static final int MARQUEE_RESOLUTION = 1000 / 30; 4873 private static final int MARQUEE_PIXELS_PER_SECOND = 30; 4874 4875 private static final byte MARQUEE_STOPPED = 0x0; 4876 private static final byte MARQUEE_STARTING = 0x1; 4877 private static final byte MARQUEE_RUNNING = 0x2; 4878 4879 private static final int MESSAGE_START = 0x1; 4880 private static final int MESSAGE_TICK = 0x2; 4881 private static final int MESSAGE_RESTART = 0x3; 4882 4883 private final WeakReference<TextView> mView; 4884 4885 private byte mStatus = MARQUEE_STOPPED; 4886 private float mScrollUnit; 4887 private float mMaxScroll; 4888 private int mRepeatLimit; 4889 4890 float mScroll; 4891 4892 Marquee(TextView v) { 4893 mView = new WeakReference<TextView>(v); 4894 } 4895 4896 @Override 4897 public void handleMessage(Message msg) { 4898 switch (msg.what) { 4899 case MESSAGE_START: 4900 mStatus = MARQUEE_RUNNING; 4901 tick(); 4902 break; 4903 case MESSAGE_TICK: 4904 tick(); 4905 break; 4906 case MESSAGE_RESTART: 4907 if (mStatus == MARQUEE_RUNNING) { 4908 if (mRepeatLimit >= 0) { 4909 mRepeatLimit--; 4910 } 4911 start(mRepeatLimit); 4912 } 4913 break; 4914 } 4915 } 4916 4917 void tick() { 4918 if (mStatus != MARQUEE_RUNNING) { 4919 return; 4920 } 4921 4922 removeMessages(MESSAGE_TICK); 4923 4924 final TextView textView = mView.get(); 4925 if (textView != null && (textView.isFocused() || textView.isSelected())) { 4926 mScroll += mScrollUnit; 4927 if (mScroll > mMaxScroll) { 4928 mScroll = mMaxScroll; 4929 sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY); 4930 } else { 4931 sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION); 4932 } 4933 textView.invalidate(); 4934 } 4935 } 4936 4937 void stop() { 4938 mStatus = MARQUEE_STOPPED; 4939 removeMessages(MESSAGE_START); 4940 removeMessages(MESSAGE_RESTART); 4941 removeMessages(MESSAGE_TICK); 4942 resetScroll(); 4943 } 4944 4945 private void resetScroll() { 4946 mScroll = 0.0f; 4947 final TextView textView = mView.get(); 4948 if (textView != null) textView.invalidate(); 4949 } 4950 4951 void start(int repeatLimit) { 4952 if (repeatLimit == 0) { 4953 stop(); 4954 return; 4955 } 4956 mRepeatLimit = repeatLimit; 4957 final TextView textView = mView.get(); 4958 if (textView != null && textView.mLayout != null) { 4959 mStatus = MARQUEE_STARTING; 4960 mScroll = 0.0f; 4961 mScrollUnit = MARQUEE_PIXELS_PER_SECOND / (float) MARQUEE_RESOLUTION; 4962 mMaxScroll = textView.mLayout.getLineWidth(0) - (textView.getWidth() - 4963 textView.getCompoundPaddingLeft() - textView.getCompoundPaddingRight()); 4964 textView.invalidate(); 4965 sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY); 4966 } 4967 } 4968 4969 boolean isRunning() { 4970 return mStatus == MARQUEE_RUNNING; 4971 } 4972 4973 boolean isStopped() { 4974 return mStatus == MARQUEE_STOPPED; 4975 } 4976 } 4977 4978 /** 4979 * This method is called when the text is changed, in case any 4980 * subclasses would like to know. 4981 * 4982 * @param text The text the TextView is displaying. 4983 * @param start The offset of the start of the range of the text 4984 * that was modified. 4985 * @param before The offset of the former end of the range of the 4986 * text that was modified. If text was simply inserted, 4987 * this will be the same as <code>start</code>. 4988 * If text was replaced with new text or deleted, the 4989 * length of the old text was <code>before-start</code>. 4990 * @param after The offset of the end of the range of the text 4991 * that was modified. If text was simply deleted, 4992 * this will be the same as <code>start</code>. 4993 * If text was replaced with new text or inserted, 4994 * the length of the new text is <code>after-start</code>. 4995 */ 4996 protected void onTextChanged(CharSequence text, 4997 int start, int before, int after) { 4998 } 4999 5000 /** 5001 * Adds a TextWatcher to the list of those whose methods are called 5002 * whenever this TextView's text changes. 5003 * <p> 5004 * In 1.0, the {@link TextWatcher#afterTextChanged} method was erroneously 5005 * not called after {@link #setText} calls. Now, doing {@link #setText} 5006 * if there are any text changed listeners forces the buffer type to 5007 * Editable if it would not otherwise be and does call this method. 5008 */ 5009 public void addTextChangedListener(TextWatcher watcher) { 5010 if (mListeners == null) { 5011 mListeners = new ArrayList<TextWatcher>(); 5012 } 5013 5014 mListeners.add(watcher); 5015 } 5016 5017 /** 5018 * Removes the specified TextWatcher from the list of those whose 5019 * methods are called 5020 * whenever this TextView's text changes. 5021 */ 5022 public void removeTextChangedListener(TextWatcher watcher) { 5023 if (mListeners != null) { 5024 int i = mListeners.indexOf(watcher); 5025 5026 if (i >= 0) { 5027 mListeners.remove(i); 5028 } 5029 } 5030 } 5031 5032 private void sendBeforeTextChanged(CharSequence text, int start, int before, 5033 int after) { 5034 if (mListeners != null) { 5035 final ArrayList<TextWatcher> list = mListeners; 5036 final int count = list.size(); 5037 for (int i = 0; i < count; i++) { 5038 list.get(i).beforeTextChanged(text, start, before, after); 5039 } 5040 } 5041 } 5042 5043 /** 5044 * Not private so it can be called from an inner class without going 5045 * through a thunk. 5046 */ 5047 void sendOnTextChanged(CharSequence text, int start, int before, 5048 int after) { 5049 if (mListeners != null) { 5050 final ArrayList<TextWatcher> list = mListeners; 5051 final int count = list.size(); 5052 for (int i = 0; i < count; i++) { 5053 list.get(i).onTextChanged(text, start, before, after); 5054 } 5055 } 5056 } 5057 5058 /** 5059 * Not private so it can be called from an inner class without going 5060 * through a thunk. 5061 */ 5062 void sendAfterTextChanged(Editable text) { 5063 if (mListeners != null) { 5064 final ArrayList<TextWatcher> list = mListeners; 5065 final int count = list.size(); 5066 for (int i = 0; i < count; i++) { 5067 list.get(i).afterTextChanged(text); 5068 } 5069 } 5070 } 5071 5072 /** 5073 * Not private so it can be called from an inner class without going 5074 * through a thunk. 5075 */ 5076 void handleTextChanged(CharSequence buffer, int start, 5077 int before, int after) { 5078 invalidate(); 5079 5080 int curs = Selection.getSelectionStart(buffer); 5081 5082 if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == 5083 Gravity.BOTTOM) { 5084 registerForPreDraw(); 5085 } 5086 5087 if (curs >= 0) { 5088 mHighlightPathBogus = true; 5089 5090 if (isFocused()) { 5091 mShowCursor = SystemClock.uptimeMillis(); 5092 makeBlink(); 5093 } 5094 } 5095 5096 checkForResize(); 5097 5098 sendOnTextChanged(buffer, start, before, after); 5099 onTextChanged(buffer, start, before, after); 5100 } 5101 5102 /** 5103 * Not private so it can be called from an inner class without going 5104 * through a thunk. 5105 */ 5106 void spanChange(Spanned buf, Object what, int o, int n) { 5107 // XXX Make the start and end move together if this ends up 5108 // spending too much time invalidating. 5109 5110 if (what == Selection.SELECTION_END) { 5111 mHighlightPathBogus = true; 5112 5113 if (!isFocused()) { 5114 mSelectionMoved = true; 5115 } 5116 5117 if (o >= 0 || n >= 0) { 5118 invalidateCursor(Selection.getSelectionStart(buf), o, n); 5119 registerForPreDraw(); 5120 5121 if (isFocused()) { 5122 mShowCursor = SystemClock.uptimeMillis(); 5123 makeBlink(); 5124 } 5125 } 5126 } 5127 5128 if (what == Selection.SELECTION_START) { 5129 mHighlightPathBogus = true; 5130 5131 if (!isFocused()) { 5132 mSelectionMoved = true; 5133 } 5134 5135 if (o >= 0 || n >= 0) { 5136 invalidateCursor(Selection.getSelectionEnd(buf), o, n); 5137 } 5138 } 5139 5140 if (what instanceof UpdateLayout || 5141 what instanceof ParagraphStyle) { 5142 invalidate(); 5143 mHighlightPathBogus = true; 5144 checkForResize(); 5145 } 5146 5147 if (MetaKeyKeyListener.isMetaTracker(buf, what)) { 5148 mHighlightPathBogus = true; 5149 5150 if (Selection.getSelectionStart(buf) >= 0) { 5151 invalidateCursor(); 5152 } 5153 } 5154 } 5155 5156 private class ChangeWatcher 5157 implements TextWatcher, SpanWatcher { 5158 public void beforeTextChanged(CharSequence buffer, int start, 5159 int before, int after) { 5160 TextView.this.sendBeforeTextChanged(buffer, start, before, after); 5161 } 5162 5163 public void onTextChanged(CharSequence buffer, int start, 5164 int before, int after) { 5165 TextView.this.handleTextChanged(buffer, start, before, after); 5166 } 5167 5168 public void afterTextChanged(Editable buffer) { 5169 TextView.this.sendAfterTextChanged(buffer); 5170 TextView.this.reportExtractedText(); 5171 } 5172 5173 public void onSpanChanged(Spannable buf, 5174 Object what, int s, int e, int st, int en) { 5175 TextView.this.spanChange(buf, what, s, st); 5176 } 5177 5178 public void onSpanAdded(Spannable buf, Object what, int s, int e) { 5179 TextView.this.spanChange(buf, what, -1, s); 5180 } 5181 5182 public void onSpanRemoved(Spannable buf, Object what, int s, int e) { 5183 TextView.this.spanChange(buf, what, s, -1); 5184 } 5185 } 5186 5187 private void makeBlink() { 5188 if (!mCursorVisible) { 5189 if (mBlink != null) { 5190 mBlink.removeCallbacks(mBlink); 5191 } 5192 5193 return; 5194 } 5195 5196 if (mBlink == null) 5197 mBlink = new Blink(this); 5198 5199 mBlink.removeCallbacks(mBlink); 5200 mBlink.postAtTime(mBlink, mShowCursor + BLINK); 5201 } 5202 5203 @Override 5204 protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { 5205 mShowCursor = SystemClock.uptimeMillis(); 5206 5207 if (focused) { 5208 int selStart = getSelectionStart(); 5209 int selEnd = getSelectionEnd(); 5210 5211 if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { 5212 boolean selMoved = mSelectionMoved; 5213 5214 if (mMovement != null) { 5215 mMovement.onTakeFocus(this, (Spannable) mText, direction); 5216 } 5217 5218 if (mSelectAllOnFocus) { 5219 Selection.setSelection((Spannable) mText, 0, mText.length()); 5220 } 5221 5222 if (selMoved && selStart >= 0 && selEnd >= 0) { 5223 /* 5224 * Someone intentionally set the selection, so let them 5225 * do whatever it is that they wanted to do instead of 5226 * the default on-focus behavior. We reset the selection 5227 * here instead of just skipping the onTakeFocus() call 5228 * because some movement methods do something other than 5229 * just setting the selection in theirs and we still 5230 * need to go through that path. 5231 */ 5232 5233 Selection.setSelection((Spannable) mText, selStart, selEnd); 5234 } 5235 } 5236 5237 mFrozenWithFocus = false; 5238 mSelectionMoved = false; 5239 5240 if (mText instanceof Spannable) { 5241 Spannable sp = (Spannable) mText; 5242 MetaKeyKeyListener.resetMetaState(sp); 5243 } 5244 5245 makeBlink(); 5246 5247 if (mError != null) { 5248 showError(); 5249 } 5250 } else { 5251 if (mError != null) { 5252 hideError(); 5253 } 5254 } 5255 5256 startStopMarquee(focused); 5257 5258 if (mTransformation != null) { 5259 mTransformation.onFocusChanged(this, mText, focused, direction, 5260 previouslyFocusedRect); 5261 } 5262 5263 super.onFocusChanged(focused, direction, previouslyFocusedRect); 5264 } 5265 5266 @Override 5267 public void onWindowFocusChanged(boolean hasWindowFocus) { 5268 super.onWindowFocusChanged(hasWindowFocus); 5269 5270 if (hasWindowFocus) { 5271 if (mBlink != null) { 5272 mBlink.uncancel(); 5273 5274 if (isFocused()) { 5275 mShowCursor = SystemClock.uptimeMillis(); 5276 makeBlink(); 5277 } 5278 } 5279 } else { 5280 if (mBlink != null) { 5281 mBlink.cancel(); 5282 } 5283 } 5284 5285 startStopMarquee(hasWindowFocus); 5286 } 5287 5288 @Override 5289 public void setSelected(boolean selected) { 5290 boolean wasSelected = isSelected(); 5291 5292 super.setSelected(selected); 5293 5294 if (selected != wasSelected && mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5295 if (selected) { 5296 startMarquee(); 5297 } else { 5298 stopMarquee(); 5299 } 5300 } 5301 } 5302 5303 @Override 5304 public boolean onTouchEvent(MotionEvent event) { 5305 final boolean superResult = super.onTouchEvent(event); 5306 5307 /* 5308 * Don't handle the release after a long press, because it will 5309 * move the selection away from whatever the menu action was 5310 * trying to affect. 5311 */ 5312 if (mEatTouchRelease && event.getAction() == MotionEvent.ACTION_UP) { 5313 mEatTouchRelease = false; 5314 return superResult; 5315 } 5316 5317 if (mMovement != null && mText instanceof Spannable && 5318 mLayout != null) { 5319 boolean moved = mMovement.onTouchEvent(this, (Spannable) mText, event); 5320 5321 if (mText instanceof Editable 5322 && mInputType != EditorInfo.TYPE_NULL) { 5323 if (event.getAction() == MotionEvent.ACTION_UP && isFocused()) { 5324 InputMethodManager imm = (InputMethodManager) 5325 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 5326 imm.showSoftInput(this); 5327 } 5328 } 5329 5330 if (moved) { 5331 return true; 5332 } 5333 } 5334 5335 return superResult; 5336 } 5337 5338 @Override 5339 public boolean onTrackballEvent(MotionEvent event) { 5340 if (mMovement != null && mText instanceof Spannable && 5341 mLayout != null) { 5342 if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) { 5343 return true; 5344 } 5345 } 5346 5347 return super.onTrackballEvent(event); 5348 } 5349 5350 public void setScroller(Scroller s) { 5351 mScroller = s; 5352 } 5353 5354 private static class Blink extends Handler 5355 implements Runnable { 5356 private WeakReference<TextView> mView; 5357 private boolean mCancelled; 5358 5359 public Blink(TextView v) { 5360 mView = new WeakReference<TextView>(v); 5361 } 5362 5363 public void run() { 5364 if (mCancelled) { 5365 return; 5366 } 5367 5368 removeCallbacks(Blink.this); 5369 5370 TextView tv = mView.get(); 5371 5372 if (tv != null && tv.isFocused()) { 5373 int st = Selection.getSelectionStart(tv.mText); 5374 int en = Selection.getSelectionEnd(tv.mText); 5375 5376 if (st == en && st >= 0 && en >= 0) { 5377 if (tv.mLayout != null) { 5378 tv.invalidateCursorPath(); 5379 } 5380 5381 postAtTime(this, SystemClock.uptimeMillis() + BLINK); 5382 } 5383 } 5384 } 5385 5386 void cancel() { 5387 if (!mCancelled) { 5388 removeCallbacks(Blink.this); 5389 mCancelled = true; 5390 } 5391 } 5392 5393 void uncancel() { 5394 mCancelled = false; 5395 } 5396 } 5397 5398 @Override 5399 protected float getLeftFadingEdgeStrength() { 5400 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5401 if (mMarquee != null && !mMarquee.isStopped()) { 5402 final Marquee marquee = mMarquee; 5403 return marquee.mScroll / getHorizontalFadingEdgeLength(); 5404 } else if (getLineCount() == 1) { 5405 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 5406 case Gravity.LEFT: 5407 return 0.0f; 5408 case Gravity.RIGHT: 5409 return (mLayout.getLineRight(0) - (mRight - mLeft) - 5410 getCompoundPaddingLeft() - getCompoundPaddingRight() - 5411 mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); 5412 case Gravity.CENTER_HORIZONTAL: 5413 return 0.0f; 5414 } 5415 } 5416 } 5417 return super.getLeftFadingEdgeStrength(); 5418 } 5419 5420 @Override 5421 protected float getRightFadingEdgeStrength() { 5422 if (mEllipsize == TextUtils.TruncateAt.MARQUEE) { 5423 if (mMarquee != null && !mMarquee.isStopped()) { 5424 final Marquee marquee = mMarquee; 5425 return (marquee.mMaxScroll - marquee.mScroll) / getHorizontalFadingEdgeLength(); 5426 } else if (getLineCount() == 1) { 5427 switch (mGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 5428 case Gravity.LEFT: 5429 return (mLayout.getLineRight(0) - mScrollX - (mRight - mLeft) - 5430 getCompoundPaddingLeft() - getCompoundPaddingRight()) / 5431 getHorizontalFadingEdgeLength(); 5432 case Gravity.RIGHT: 5433 return 0.0f; 5434 case Gravity.CENTER_HORIZONTAL: 5435 return (mLayout.getLineWidth(0) - ((mRight - mLeft) - 5436 getCompoundPaddingLeft() - getCompoundPaddingRight())) / 5437 getHorizontalFadingEdgeLength(); 5438 } 5439 } 5440 } 5441 return super.getRightFadingEdgeStrength(); 5442 } 5443 5444 @Override 5445 protected int computeHorizontalScrollRange() { 5446 if (mLayout != null) 5447 return mLayout.getWidth(); 5448 5449 return super.computeHorizontalScrollRange(); 5450 } 5451 5452 @Override 5453 protected int computeVerticalScrollRange() { 5454 if (mLayout != null) 5455 return mLayout.getHeight(); 5456 5457 return super.computeVerticalScrollRange(); 5458 } 5459 5460 public enum BufferType { 5461 NORMAL, SPANNABLE, EDITABLE, 5462 } 5463 5464 /** 5465 * Returns the TextView_textColor attribute from the 5466 * Resources.StyledAttributes, if set, or the TextAppearance_textColor 5467 * from the TextView_textAppearance attribute, if TextView_textColor 5468 * was not set directly. 5469 */ 5470 public static ColorStateList getTextColors(Context context, TypedArray attrs) { 5471 ColorStateList colors; 5472 colors = attrs.getColorStateList(com.android.internal.R.styleable. 5473 TextView_textColor); 5474 5475 if (colors == null) { 5476 int ap = attrs.getResourceId(com.android.internal.R.styleable. 5477 TextView_textAppearance, -1); 5478 if (ap != -1) { 5479 TypedArray appearance; 5480 appearance = context.obtainStyledAttributes(ap, 5481 com.android.internal.R.styleable.TextAppearance); 5482 colors = appearance.getColorStateList(com.android.internal.R.styleable. 5483 TextAppearance_textColor); 5484 appearance.recycle(); 5485 } 5486 } 5487 5488 return colors; 5489 } 5490 5491 /** 5492 * Returns the default color from the TextView_textColor attribute 5493 * from the AttributeSet, if set, or the default color from the 5494 * TextAppearance_textColor from the TextView_textAppearance attribute, 5495 * if TextView_textColor was not set directly. 5496 */ 5497 public static int getTextColor(Context context, 5498 TypedArray attrs, 5499 int def) { 5500 ColorStateList colors = getTextColors(context, attrs); 5501 5502 if (colors == null) { 5503 return def; 5504 } else { 5505 return colors.getDefaultColor(); 5506 } 5507 } 5508 5509 @Override 5510 public boolean onKeyShortcut(int keyCode, KeyEvent event) { 5511 switch (keyCode) { 5512 case KeyEvent.KEYCODE_A: 5513 if (canSelectAll()) { 5514 return onMenu(ID_SELECT_ALL); 5515 } 5516 5517 break; 5518 5519 case KeyEvent.KEYCODE_X: 5520 if (canCut()) { 5521 return onMenu(ID_CUT); 5522 } 5523 5524 break; 5525 5526 case KeyEvent.KEYCODE_C: 5527 if (canCopy()) { 5528 return onMenu(ID_COPY); 5529 } 5530 5531 break; 5532 5533 case KeyEvent.KEYCODE_V: 5534 if (canPaste()) { 5535 return onMenu(ID_PASTE); 5536 } 5537 5538 break; 5539 } 5540 5541 return super.onKeyShortcut(keyCode, event); 5542 } 5543 5544 private boolean canSelectAll() { 5545 if (mText instanceof Spannable && mText.length() != 0 && 5546 mMovement != null && mMovement.canSelectArbitrarily()) { 5547 return true; 5548 } 5549 5550 return false; 5551 } 5552 5553 private boolean canCut() { 5554 if (mTransformation instanceof PasswordTransformationMethod) { 5555 return false; 5556 } 5557 5558 if (mText.length() > 0 && getSelectionStart() >= 0) { 5559 if (mText instanceof Editable && mInput != null) { 5560 return true; 5561 } 5562 } 5563 5564 return false; 5565 } 5566 5567 private boolean canCopy() { 5568 if (mTransformation instanceof PasswordTransformationMethod) { 5569 return false; 5570 } 5571 5572 if (mText.length() > 0 && getSelectionStart() >= 0) { 5573 return true; 5574 } 5575 5576 return false; 5577 } 5578 5579 private boolean canPaste() { 5580 if (mText instanceof Editable && mInput != null && 5581 getSelectionStart() >= 0 && getSelectionEnd() >= 0) { 5582 ClipboardManager clip = (ClipboardManager)getContext() 5583 .getSystemService(Context.CLIPBOARD_SERVICE); 5584 if (clip.hasText()) { 5585 return true; 5586 } 5587 } 5588 5589 return false; 5590 } 5591 5592 @Override 5593 protected void onCreateContextMenu(ContextMenu menu) { 5594 super.onCreateContextMenu(menu); 5595 boolean added = false; 5596 5597 if (!isFocused()) { 5598 if (isFocusable() && mInput != null) { 5599 if (canCopy()) { 5600 MenuHandler handler = new MenuHandler(); 5601 int name = com.android.internal.R.string.copyAll; 5602 5603 menu.add(0, ID_COPY, 0, name). 5604 setOnMenuItemClickListener(handler). 5605 setAlphabeticShortcut('c'); 5606 menu.setHeaderTitle(com.android.internal.R.string. 5607 editTextMenuTitle); 5608 } 5609 } 5610 5611 return; 5612 } 5613 5614 MenuHandler handler = new MenuHandler(); 5615 5616 if (canSelectAll()) { 5617 menu.add(0, ID_SELECT_ALL, 0, 5618 com.android.internal.R.string.selectAll). 5619 setOnMenuItemClickListener(handler). 5620 setAlphabeticShortcut('a'); 5621 added = true; 5622 } 5623 5624 boolean selection = getSelectionStart() != getSelectionEnd(); 5625 5626 if (canCut()) { 5627 int name; 5628 if (selection) { 5629 name = com.android.internal.R.string.cut; 5630 } else { 5631 name = com.android.internal.R.string.cutAll; 5632 } 5633 5634 menu.add(0, ID_CUT, 0, name). 5635 setOnMenuItemClickListener(handler). 5636 setAlphabeticShortcut('x'); 5637 added = true; 5638 } 5639 5640 if (canCopy()) { 5641 int name; 5642 if (selection) { 5643 name = com.android.internal.R.string.copy; 5644 } else { 5645 name = com.android.internal.R.string.copyAll; 5646 } 5647 5648 menu.add(0, ID_COPY, 0, name). 5649 setOnMenuItemClickListener(handler). 5650 setAlphabeticShortcut('c'); 5651 added = true; 5652 } 5653 5654 if (canPaste()) { 5655 menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). 5656 setOnMenuItemClickListener(handler). 5657 setAlphabeticShortcut('v'); 5658 added = true; 5659 } 5660 5661 if (mText instanceof Spanned) { 5662 int selStart = getSelectionStart(); 5663 int selEnd = getSelectionEnd(); 5664 5665 int min = Math.min(selStart, selEnd); 5666 int max = Math.max(selStart, selEnd); 5667 5668 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, 5669 URLSpan.class); 5670 if (urls.length == 1) { 5671 menu.add(0, ID_COPY_URL, 0, 5672 com.android.internal.R.string.copyUrl). 5673 setOnMenuItemClickListener(handler); 5674 added = true; 5675 } 5676 } 5677 5678 InputMethodManager imm = InputMethodManager.peekInstance(); 5679 if (imm != null && imm.isActive(this)) { 5680 menu.add(1, ID_SWITCH_IME, 0, com.android.internal.R.string.inputMethod). 5681 setOnMenuItemClickListener(handler); 5682 added = true; 5683 } 5684 5685 if (added) { 5686 menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle); 5687 } 5688 } 5689 5690 private static final int ID_SELECT_ALL = com.android.internal.R.id.selectAll; 5691 private static final int ID_CUT = com.android.internal.R.id.cut; 5692 private static final int ID_COPY = com.android.internal.R.id.copy; 5693 private static final int ID_PASTE = com.android.internal.R.id.paste; 5694 private static final int ID_COPY_URL = com.android.internal.R.id.copyUrl; 5695 private static final int ID_SWITCH_IME = com.android.internal.R.id.inputMethod; 5696 5697 private class MenuHandler implements MenuItem.OnMenuItemClickListener { 5698 public boolean onMenuItemClick(MenuItem item) { 5699 return onMenu(item.getItemId()); 5700 } 5701 } 5702 5703 private boolean onMenu(int id) { 5704 int selStart = getSelectionStart(); 5705 int selEnd = getSelectionEnd(); 5706 5707 if (!isFocused()) { 5708 selStart = 0; 5709 selEnd = mText.length(); 5710 } 5711 5712 int min = Math.min(selStart, selEnd); 5713 int max = Math.max(selStart, selEnd); 5714 5715 if (min < 0) { 5716 min = 0; 5717 } 5718 if (max < 0) { 5719 max = 0; 5720 } 5721 5722 ClipboardManager clip = (ClipboardManager)getContext() 5723 .getSystemService(Context.CLIPBOARD_SERVICE); 5724 5725 switch (id) { 5726 case ID_SELECT_ALL: 5727 Selection.setSelection((Spannable) mText, 0, 5728 mText.length()); 5729 return true; 5730 5731 case ID_CUT: 5732 if (min == max) { 5733 min = 0; 5734 max = mText.length(); 5735 } 5736 5737 clip.setText(mTransformed.subSequence(min, max)); 5738 ((Editable) mText).delete(min, max); 5739 return true; 5740 5741 case ID_COPY: 5742 if (min == max) { 5743 min = 0; 5744 max = mText.length(); 5745 } 5746 5747 clip.setText(mTransformed.subSequence(min, max)); 5748 return true; 5749 5750 case ID_PASTE: 5751 CharSequence paste = clip.getText(); 5752 5753 if (paste != null) { 5754 Selection.setSelection((Spannable) mText, max); 5755 ((Editable) mText).replace(min, max, paste); 5756 } 5757 5758 return true; 5759 5760 case ID_COPY_URL: 5761 URLSpan[] urls = ((Spanned) mText).getSpans(min, max, 5762 URLSpan.class); 5763 if (urls.length == 1) { 5764 clip.setText(urls[0].getURL()); 5765 } 5766 5767 return true; 5768 5769 case ID_SWITCH_IME: 5770 InputMethodManager imm = InputMethodManager.peekInstance(); 5771 if (imm != null) { 5772 imm.showInputMethodPicker(); 5773 } 5774 return true; 5775 } 5776 5777 return false; 5778 } 5779 5780 public boolean performLongClick() { 5781 if (super.performLongClick()) { 5782 mEatTouchRelease = true; 5783 return true; 5784 } 5785 5786 return false; 5787 } 5788 5789 private boolean mEatTouchRelease = false; 5790 5791 @ViewDebug.ExportedProperty 5792 private CharSequence mText; 5793 private CharSequence mTransformed; 5794 private BufferType mBufferType = BufferType.NORMAL; 5795 5796 private int mInputType = EditorInfo.TYPE_NULL; 5797 private CharSequence mHint; 5798 private Layout mHintLayout; 5799 5800 private KeyListener mInput; 5801 5802 private MovementMethod mMovement; 5803 private TransformationMethod mTransformation; 5804 private ChangeWatcher mChangeWatcher; 5805 5806 private ArrayList<TextWatcher> mListeners = null; 5807 5808 // display attributes 5809 private TextPaint mTextPaint; 5810 private Paint mHighlightPaint; 5811 private int mHighlightColor = 0xFFBBDDFF; 5812 private Layout mLayout; 5813 5814 private long mShowCursor; 5815 private Blink mBlink; 5816 private boolean mCursorVisible = true; 5817 5818 private boolean mSelectAllOnFocus = false; 5819 5820 private int mGravity = Gravity.TOP | Gravity.LEFT; 5821 private boolean mHorizontallyScrolling; 5822 5823 private int mAutoLinkMask; 5824 private boolean mLinksClickable = true; 5825 5826 private float mSpacingMult = 1; 5827 private float mSpacingAdd = 0; 5828 5829 private static final int LINES = 1; 5830 private static final int EMS = LINES; 5831 private static final int PIXELS = 2; 5832 5833 private int mMaximum = Integer.MAX_VALUE; 5834 private int mMaxMode = LINES; 5835 private int mMinimum = 0; 5836 private int mMinMode = LINES; 5837 5838 private int mMaxWidth = Integer.MAX_VALUE; 5839 private int mMaxWidthMode = PIXELS; 5840 private int mMinWidth = 0; 5841 private int mMinWidthMode = PIXELS; 5842 5843 private boolean mSingleLine; 5844 private int mDesiredHeightAtMeasure = -1; 5845 private boolean mIncludePad = true; 5846 5847 // tmp primitives, so we don't alloc them on each draw 5848 private Path mHighlightPath; 5849 private boolean mHighlightPathBogus = true; 5850 private static final RectF sTempRect = new RectF(); 5851 5852 // XXX should be much larger 5853 private static final int VERY_WIDE = 16384; 5854 5855 private static final int BLINK = 500; 5856 5857 private static final int ANIMATED_SCROLL_GAP = 250; 5858 private long mLastScroll; 5859 private Scroller mScroller = null; 5860 5861 private BoringLayout.Metrics mBoring; 5862 private BoringLayout.Metrics mHintBoring; 5863 5864 private BoringLayout mSavedLayout, mSavedHintLayout; 5865 5866 private static final InputFilter[] NO_FILTERS = new InputFilter[0]; 5867 private InputFilter[] mFilters = NO_FILTERS; 5868 private static final Spanned EMPTY_SPANNED = new SpannedString(""); 5869} 5870