TextInputLayout.java revision e948c13b87d93495ffe88f2ac36a820bf1bcb256
1/* 2 * Copyright (C) 2015 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.support.design.widget; 18 19import android.content.Context; 20import android.content.res.ColorStateList; 21import android.content.res.TypedArray; 22import android.graphics.Canvas; 23import android.graphics.Color; 24import android.graphics.Paint; 25import android.graphics.Typeface; 26import android.support.annotation.NonNull; 27import android.support.annotation.Nullable; 28import android.support.annotation.StyleRes; 29import android.support.design.R; 30import android.support.v4.view.AccessibilityDelegateCompat; 31import android.support.v4.view.GravityCompat; 32import android.support.v4.view.ViewCompat; 33import android.support.v4.view.ViewPropertyAnimatorListenerAdapter; 34import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; 35import android.support.v7.internal.widget.AppCompatDrawableManager; 36import android.text.Editable; 37import android.text.TextUtils; 38import android.text.TextWatcher; 39import android.util.AttributeSet; 40import android.util.TypedValue; 41import android.view.Gravity; 42import android.view.View; 43import android.view.ViewGroup; 44import android.view.accessibility.AccessibilityEvent; 45import android.view.animation.AccelerateInterpolator; 46import android.widget.EditText; 47import android.widget.LinearLayout; 48import android.widget.TextView; 49 50/** 51 * Layout which wraps an {@link android.widget.EditText} (or descendant) to show a floating label 52 * when the hint is hidden due to the user inputting text. 53 * 54 * Also supports showing an error via {@link #setErrorEnabled(boolean)} and 55 * {@link #setError(CharSequence)}. 56 */ 57public class TextInputLayout extends LinearLayout { 58 59 private static final int ANIMATION_DURATION = 200; 60 private static final int INVALID_MAX_LENGTH = -1; 61 62 private EditText mEditText; 63 private CharSequence mHint; 64 65 private Paint mTmpPaint; 66 67 private LinearLayout mIndicatorArea; 68 69 private boolean mErrorEnabled; 70 private TextView mErrorView; 71 private int mErrorTextAppearance; 72 private boolean mErrorShown; 73 74 private boolean mCounterEnabled; 75 private TextView mCounterView; 76 private int mCounterMaxLength; 77 private int mCounterTextAppearance; 78 private int mCounterOverflowTextAppearance; 79 private boolean mCounterOverflowed; 80 81 private ColorStateList mDefaultTextColor; 82 private ColorStateList mFocusedTextColor; 83 84 private final CollapsingTextHelper mCollapsingTextHelper = new CollapsingTextHelper(this); 85 86 private boolean mHintAnimationEnabled; 87 private ValueAnimatorCompat mAnimator; 88 89 public TextInputLayout(Context context) { 90 this(context, null); 91 } 92 93 public TextInputLayout(Context context, AttributeSet attrs) { 94 this(context, attrs, 0); 95 } 96 97 public TextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) { 98 // Can't call through to super(Context, AttributeSet, int) since it doesn't exist on API 10 99 super(context, attrs); 100 101 ThemeUtils.checkAppCompatTheme(context); 102 103 setOrientation(VERTICAL); 104 setWillNotDraw(false); 105 setAddStatesFromChildren(true); 106 107 mCollapsingTextHelper.setTextSizeInterpolator(AnimationUtils.FAST_OUT_SLOW_IN_INTERPOLATOR); 108 mCollapsingTextHelper.setPositionInterpolator(new AccelerateInterpolator()); 109 mCollapsingTextHelper.setCollapsedTextGravity(Gravity.TOP | GravityCompat.START); 110 111 final TypedArray a = context.obtainStyledAttributes(attrs, 112 R.styleable.TextInputLayout, defStyleAttr, R.style.Widget_Design_TextInputLayout); 113 setHint(a.getText(R.styleable.TextInputLayout_android_hint)); 114 mHintAnimationEnabled = a.getBoolean( 115 R.styleable.TextInputLayout_hintAnimationEnabled, true); 116 117 if (a.hasValue(R.styleable.TextInputLayout_android_textColorHint)) { 118 mDefaultTextColor = mFocusedTextColor = 119 a.getColorStateList(R.styleable.TextInputLayout_android_textColorHint); 120 } 121 122 final int hintAppearance = a.getResourceId( 123 R.styleable.TextInputLayout_hintTextAppearance, -1); 124 if (hintAppearance != -1) { 125 setHintTextAppearance( 126 a.getResourceId(R.styleable.TextInputLayout_hintTextAppearance, 0)); 127 } 128 129 mErrorTextAppearance = a.getResourceId(R.styleable.TextInputLayout_errorTextAppearance, 0); 130 final boolean errorEnabled = a.getBoolean(R.styleable.TextInputLayout_errorEnabled, false); 131 132 final boolean counterEnabled = a.getBoolean( 133 R.styleable.TextInputLayout_counterEnabled, false); 134 setCounterMaxLength( 135 a.getInt(R.styleable.TextInputLayout_counterMaxLength, INVALID_MAX_LENGTH)); 136 mCounterTextAppearance = a.getResourceId( 137 R.styleable.TextInputLayout_counterTextAppearance, 0); 138 mCounterOverflowTextAppearance = a.getResourceId( 139 R.styleable.TextInputLayout_counterOverflowTextAppearance, 0); 140 a.recycle(); 141 142 setErrorEnabled(errorEnabled); 143 setCounterEnabled(counterEnabled); 144 145 if (ViewCompat.getImportantForAccessibility(this) 146 == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { 147 // Make sure we're important for accessibility if we haven't been explicitly not 148 ViewCompat.setImportantForAccessibility(this, 149 ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); 150 } 151 152 ViewCompat.setAccessibilityDelegate(this, new TextInputAccessibilityDelegate()); 153 } 154 155 @Override 156 public void addView(View child, int index, ViewGroup.LayoutParams params) { 157 if (child instanceof EditText) { 158 setEditText((EditText) child); 159 super.addView(child, 0, updateEditTextMargin(params)); 160 } else { 161 // Carry on adding the View... 162 super.addView(child, index, params); 163 } 164 } 165 166 /** 167 * Set the typeface to use for both the expanded and floating hint. 168 * 169 * @param typeface typeface to use, or {@code null} to use the default. 170 */ 171 public void setTypeface(@Nullable Typeface typeface) { 172 mCollapsingTextHelper.setTypefaces(typeface); 173 } 174 175 /** 176 * Returns the typeface used for both the expanded and floating hint. 177 */ 178 @NonNull 179 public Typeface getTypeface() { 180 // This could be either the collapsed or expanded 181 return mCollapsingTextHelper.getCollapsedTypeface(); 182 } 183 184 private void setEditText(EditText editText) { 185 // If we already have an EditText, throw an exception 186 if (mEditText != null) { 187 throw new IllegalArgumentException("We already have an EditText, can only have one"); 188 } 189 mEditText = editText; 190 191 // Use the EditText's typeface, and it's text size for our expanded text 192 mCollapsingTextHelper.setTypefaces(mEditText.getTypeface()); 193 mCollapsingTextHelper.setExpandedTextSize(mEditText.getTextSize()); 194 mCollapsingTextHelper.setExpandedTextGravity(mEditText.getGravity()); 195 196 // Add a TextWatcher so that we know when the text input has changed 197 mEditText.addTextChangedListener(new TextWatcher() { 198 @Override 199 public void afterTextChanged(Editable s) { 200 updateLabelVisibility(true); 201 if (mCounterEnabled) { 202 updateCounter(s.length()); 203 } 204 } 205 206 @Override 207 public void beforeTextChanged(CharSequence s, int start, int count, int after) {} 208 209 @Override 210 public void onTextChanged(CharSequence s, int start, int before, int count) {} 211 }); 212 213 // Use the EditText's hint colors if we don't have one set 214 if (mDefaultTextColor == null) { 215 mDefaultTextColor = mEditText.getHintTextColors(); 216 } 217 218 // If we do not have a valid hint, try and retrieve it from the EditText 219 if (TextUtils.isEmpty(mHint)) { 220 setHint(mEditText.getHint()); 221 // Clear the EditText's hint as we will display it ourselves 222 mEditText.setHint(null); 223 } 224 225 if (mCounterView != null) { 226 updateCounter(mEditText.getText().length()); 227 } 228 229 if (mIndicatorArea != null) { 230 adjustIndicatorPadding(); 231 } 232 233 // Update the label visibility with no animation 234 updateLabelVisibility(false); 235 } 236 237 private LayoutParams updateEditTextMargin(ViewGroup.LayoutParams lp) { 238 // Create/update the LayoutParams so that we can add enough top margin 239 // to the EditText so make room for the label 240 LayoutParams llp = lp instanceof LayoutParams ? (LayoutParams) lp : new LayoutParams(lp); 241 242 if (mTmpPaint == null) { 243 mTmpPaint = new Paint(); 244 } 245 mTmpPaint.setTypeface(mCollapsingTextHelper.getCollapsedTypeface()); 246 mTmpPaint.setTextSize(mCollapsingTextHelper.getCollapsedTextSize()); 247 llp.topMargin = (int) -mTmpPaint.ascent(); 248 249 return llp; 250 } 251 252 private void updateLabelVisibility(boolean animate) { 253 boolean hasText = mEditText != null && !TextUtils.isEmpty(mEditText.getText()); 254 boolean isFocused = arrayContains(getDrawableState(), android.R.attr.state_focused); 255 boolean isErrorShowing = !TextUtils.isEmpty(getError()); 256 257 258 if (mDefaultTextColor != null && mFocusedTextColor != null) { 259 mCollapsingTextHelper.setExpandedTextColor(mDefaultTextColor.getDefaultColor()); 260 mCollapsingTextHelper.setCollapsedTextColor(isFocused 261 ? mFocusedTextColor.getDefaultColor() 262 : mDefaultTextColor.getDefaultColor()); 263 } 264 265 if (hasText || isFocused || isErrorShowing) { 266 // We should be showing the label so do so if it isn't already 267 collapseHint(animate); 268 } else { 269 // We should not be showing the label so hide it 270 expandHint(animate); 271 } 272 } 273 274 /** 275 * Returns the {@link android.widget.EditText} used for text input. 276 */ 277 @Nullable 278 public EditText getEditText() { 279 return mEditText; 280 } 281 282 /** 283 * Set the hint to be displayed in the floating label 284 * 285 * @attr ref android.support.design.R.styleable#TextInputLayout_android_hint 286 */ 287 public void setHint(@Nullable CharSequence hint) { 288 mHint = hint; 289 mCollapsingTextHelper.setText(hint); 290 291 sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); 292 } 293 294 /** 295 * Returns the hint which is displayed in the floating label. 296 * 297 * @attr ref android.support.design.R.styleable#TextInputLayout_android_hint 298 */ 299 @Nullable 300 public CharSequence getHint() { 301 return mHint; 302 } 303 304 /** 305 * Sets the hint text color, size, style from the specified TextAppearance resource. 306 * 307 * @attr ref android.support.design.R.styleable#TextInputLayout_hintTextAppearance 308 */ 309 public void setHintTextAppearance(@StyleRes int resId) { 310 mCollapsingTextHelper.setCollapsedTextAppearance(resId); 311 mFocusedTextColor = ColorStateList.valueOf(mCollapsingTextHelper.getCollapsedTextColor()); 312 313 if (mEditText != null) { 314 updateLabelVisibility(false); 315 316 // Text size might have changed so update the top margin 317 LayoutParams lp = updateEditTextMargin(mEditText.getLayoutParams()); 318 mEditText.setLayoutParams(lp); 319 mEditText.requestLayout(); 320 } 321 } 322 323 private void addIndicator(TextView indicator, int index, LinearLayout.LayoutParams params) { 324 if (mIndicatorArea == null) { 325 mIndicatorArea = new LinearLayout(getContext()); 326 mIndicatorArea.setOrientation(LinearLayout.HORIZONTAL); 327 addView(mIndicatorArea); 328 if (mEditText != null) { 329 adjustIndicatorPadding(); 330 } 331 } 332 mIndicatorArea.addView(indicator, index, params); 333 } 334 335 private void adjustIndicatorPadding() { 336 // Add padding to the error and character counter so that they match the EditText 337 ViewCompat.setPaddingRelative(mIndicatorArea, ViewCompat.getPaddingStart(mEditText), 338 0, ViewCompat.getPaddingEnd(mEditText), mEditText.getPaddingBottom()); 339 } 340 341 private void removeIndicator(TextView indicator) { 342 mIndicatorArea.removeView(indicator); 343 if (mIndicatorArea.getChildCount() == 0) { 344 removeView(mIndicatorArea); 345 } 346 } 347 348 /** 349 * Whether the error functionality is enabled or not in this layout. Enabling this 350 * functionality before setting an error message via {@link #setError(CharSequence)}, will mean 351 * that this layout will not change size when an error is displayed. 352 * 353 * @attr ref android.support.design.R.styleable#TextInputLayout_errorEnabled 354 */ 355 public void setErrorEnabled(boolean enabled) { 356 if (mErrorEnabled != enabled) { 357 if (mErrorView != null) { 358 ViewCompat.animate(mErrorView).cancel(); 359 } 360 361 if (enabled) { 362 mErrorView = new TextView(getContext()); 363 mErrorView.setTextAppearance(getContext(), mErrorTextAppearance); 364 mErrorView.setVisibility(INVISIBLE); 365 ViewCompat.setAccessibilityLiveRegion(mErrorView, 366 ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE); 367 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 368 0, LinearLayout.LayoutParams.WRAP_CONTENT); 369 params.weight = 1.f; 370 addIndicator(mErrorView, 0, params); 371 } else { 372 mErrorShown = false; 373 updateEditTextBackground(); 374 removeIndicator(mErrorView); 375 mErrorView = null; 376 } 377 mErrorEnabled = enabled; 378 } 379 } 380 381 /** 382 * Returns whether the error functionality is enabled or not in this layout. 383 * 384 * @attr ref android.support.design.R.styleable#TextInputLayout_errorEnabled 385 * 386 * @see #setErrorEnabled(boolean) 387 */ 388 public boolean isErrorEnabled() { 389 return mErrorEnabled; 390 } 391 392 /** 393 * Sets an error message that will be displayed below our {@link EditText}. If the 394 * {@code error} is {@code null}, the error message will be cleared. 395 * <p> 396 * If the error functionality has not been enabled via {@link #setErrorEnabled(boolean)}, then 397 * it will be automatically enabled if {@code error} is not empty. 398 * 399 * @param error Error message to display, or null to clear 400 * 401 * @see #getError() 402 */ 403 public void setError(@Nullable CharSequence error) { 404 if (!mErrorEnabled) { 405 if (TextUtils.isEmpty(error)) { 406 // If error isn't enabled, and the error is empty, just return 407 return; 408 } 409 // Else, we'll assume that they want to enable the error functionality 410 setErrorEnabled(true); 411 } 412 413 if (!TextUtils.isEmpty(error)) { 414 ViewCompat.setAlpha(mErrorView, 0f); 415 mErrorView.setText(error); 416 ViewCompat.animate(mErrorView) 417 .alpha(1f) 418 .setDuration(ANIMATION_DURATION) 419 .setInterpolator(AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR) 420 .setListener(new ViewPropertyAnimatorListenerAdapter() { 421 @Override 422 public void onAnimationStart(View view) { 423 view.setVisibility(VISIBLE); 424 } 425 }) 426 .start(); 427 428 // Set the EditText's background tint to the error color 429 mErrorShown = true; 430 updateEditTextBackground(); 431 updateLabelVisibility(true); 432 } else { 433 if (mErrorView.getVisibility() == VISIBLE) { 434 ViewCompat.animate(mErrorView) 435 .alpha(0f) 436 .setDuration(ANIMATION_DURATION) 437 .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR) 438 .setListener(new ViewPropertyAnimatorListenerAdapter() { 439 @Override 440 public void onAnimationEnd(View view) { 441 view.setVisibility(INVISIBLE); 442 443 updateLabelVisibility(true); 444 } 445 }).start(); 446 447 // Restore the 'original' tint, using colorControlNormal and colorControlActivated 448 mErrorShown = false; 449 updateEditTextBackground(); 450 } 451 } 452 } 453 454 /** 455 * Whether the character counter functionality is enabled or not in this layout. 456 * 457 * @attr ref android.support.design.R.styleable#TextInputLayout_counterEnabled 458 */ 459 public void setCounterEnabled(boolean enabled) { 460 if (mCounterEnabled != enabled) { 461 if (enabled) { 462 mCounterView = new TextView(getContext()); 463 mCounterView.setMaxLines(1); 464 mCounterView.setTextAppearance(getContext(), mCounterTextAppearance); 465 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( 466 LinearLayout.LayoutParams.WRAP_CONTENT, 467 LinearLayout.LayoutParams.WRAP_CONTENT); 468 params.gravity = (params.gravity & Gravity.VERTICAL_GRAVITY_MASK) | 469 GravityCompat.END; 470 ViewCompat.setAccessibilityLiveRegion(mCounterView, 471 ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE); 472 addIndicator(mCounterView, -1, params); 473 if (mEditText == null) { 474 updateCounter(0); 475 } else { 476 updateCounter(mEditText.getText().length()); 477 } 478 } else { 479 removeIndicator(mCounterView); 480 mCounterView = null; 481 } 482 mCounterEnabled = enabled; 483 } 484 } 485 486 /** 487 * Returns whether the character counter functionality is enabled or not in this layout. 488 * 489 * @attr ref android.support.design.R.styleable#TextInputLayout_counterEnabled 490 * 491 * @see #setCounterEnabled(boolean) 492 */ 493 public boolean isCounterEnabled() { 494 return mCounterEnabled; 495 } 496 497 /** 498 * Sets the max length to display at the character counter. 499 * 500 * @param maxLength maxLength to display. Any value less than or equal to 0 will not be shown. 501 * 502 * @attr ref android.support.design.R.styleable#TextInputLayout_counterMaxLength 503 */ 504 public void setCounterMaxLength(int maxLength) { 505 if (mCounterMaxLength != maxLength) { 506 if (maxLength > 0) { 507 mCounterMaxLength = maxLength; 508 } else { 509 mCounterMaxLength = INVALID_MAX_LENGTH; 510 } 511 if (mCounterEnabled) { 512 updateCounter(mEditText == null ? 0 : mEditText.getText().length()); 513 } 514 } 515 } 516 517 /** 518 * Returns the max length shown at the character counter. 519 * 520 * @attr ref android.support.design.R.styleable#TextInputLayout_counterMaxLength 521 */ 522 public int getCounterMaxLength() { 523 return mCounterMaxLength; 524 } 525 526 private void updateCounter(int length) { 527 boolean wasCounterOverflowed = mCounterOverflowed; 528 if (mCounterMaxLength == INVALID_MAX_LENGTH) { 529 mCounterView.setText(String.valueOf(length)); 530 mCounterOverflowed = false; 531 } else { 532 mCounterOverflowed = length > mCounterMaxLength; 533 if (wasCounterOverflowed != mCounterOverflowed) { 534 mCounterView.setTextAppearance(getContext(), mCounterOverflowed ? 535 mCounterOverflowTextAppearance : mCounterTextAppearance); 536 } 537 mCounterView.setText(getContext().getString(R.string.character_counter_pattern, 538 length, mCounterMaxLength)); 539 } 540 if (mEditText != null && wasCounterOverflowed != mCounterOverflowed) { 541 updateEditTextBackground(); 542 } 543 } 544 545 private void updateEditTextBackground() { 546 if (mErrorShown && mErrorView != null) { 547 // Set the EditText's background tint to the error color 548 ViewCompat.setBackgroundTintList(mEditText, 549 ColorStateList.valueOf(mErrorView.getCurrentTextColor())); 550 } else if (mCounterOverflowed && mCounterView != null) { 551 ViewCompat.setBackgroundTintList(mEditText, 552 ColorStateList.valueOf(mCounterView.getCurrentTextColor())); 553 } else { 554 ViewCompat.setBackgroundTintList(mEditText, 555 AppCompatDrawableManager.get() 556 .getTintList(getContext(), R.drawable.abc_edit_text_material)); 557 } 558 } 559 560 /** 561 * Returns the error message that was set to be displayed with 562 * {@link #setError(CharSequence)}, or <code>null</code> if no error was set 563 * or if error displaying is not enabled. 564 * 565 * @see #setError(CharSequence) 566 */ 567 @Nullable 568 public CharSequence getError() { 569 if (mErrorEnabled && mErrorView != null && mErrorView.getVisibility() == VISIBLE) { 570 return mErrorView.getText(); 571 } 572 return null; 573 } 574 575 /** 576 * Returns whether any hint state changes, due to being focused or non-empty text, are 577 * animated. 578 * 579 * @see #setHintAnimationEnabled(boolean) 580 * 581 * @attr ref android.support.design.R.styleable#TextInputLayout_hintAnimationEnabled 582 */ 583 public boolean isHintAnimationEnabled() { 584 return mHintAnimationEnabled; 585 } 586 587 /** 588 * Set whether any hint state changes, due to being focused or non-empty text, are 589 * animated. 590 * 591 * @see #isHintAnimationEnabled() 592 * 593 * @attr ref android.support.design.R.styleable#TextInputLayout_hintAnimationEnabled 594 */ 595 public void setHintAnimationEnabled(boolean enabled) { 596 mHintAnimationEnabled = enabled; 597 } 598 599 @Override 600 public void draw(Canvas canvas) { 601 super.draw(canvas); 602 mCollapsingTextHelper.draw(canvas); 603 } 604 605 @Override 606 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 607 super.onLayout(changed, left, top, right, bottom); 608 609 if (mEditText != null) { 610 final int l = mEditText.getLeft() + mEditText.getCompoundPaddingLeft(); 611 final int r = mEditText.getRight() - mEditText.getCompoundPaddingRight(); 612 613 mCollapsingTextHelper.setExpandedBounds(l, 614 mEditText.getTop() + mEditText.getCompoundPaddingTop(), 615 r, mEditText.getBottom() - mEditText.getCompoundPaddingBottom()); 616 617 // Set the collapsed bounds to be the the full height (minus padding) to match the 618 // EditText's editable area 619 mCollapsingTextHelper.setCollapsedBounds(l, getPaddingTop(), 620 r, bottom - top - getPaddingBottom()); 621 622 mCollapsingTextHelper.recalculate(); 623 } 624 } 625 626 @Override 627 public void refreshDrawableState() { 628 super.refreshDrawableState(); 629 // Drawable state has changed so see if we need to update the label 630 updateLabelVisibility(ViewCompat.isLaidOut(this)); 631 } 632 633 private void collapseHint(boolean animate) { 634 if (mAnimator != null && mAnimator.isRunning()) { 635 mAnimator.cancel(); 636 } 637 if (animate && mHintAnimationEnabled) { 638 animateToExpansionFraction(1f); 639 } else { 640 mCollapsingTextHelper.setExpansionFraction(1f); 641 } 642 } 643 644 private void expandHint(boolean animate) { 645 if (mAnimator != null && mAnimator.isRunning()) { 646 mAnimator.cancel(); 647 } 648 if (animate && mHintAnimationEnabled) { 649 animateToExpansionFraction(0f); 650 } else { 651 mCollapsingTextHelper.setExpansionFraction(0f); 652 } 653 } 654 655 private void animateToExpansionFraction(final float target) { 656 if (mCollapsingTextHelper.getExpansionFraction() == target) { 657 return; 658 } 659 if (mAnimator == null) { 660 mAnimator = ViewUtils.createAnimator(); 661 mAnimator.setInterpolator(AnimationUtils.LINEAR_INTERPOLATOR); 662 mAnimator.setDuration(ANIMATION_DURATION); 663 mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() { 664 @Override 665 public void onAnimationUpdate(ValueAnimatorCompat animator) { 666 mCollapsingTextHelper.setExpansionFraction(animator.getAnimatedFloatValue()); 667 } 668 }); 669 } 670 mAnimator.setFloatValues(mCollapsingTextHelper.getExpansionFraction(), target); 671 mAnimator.start(); 672 } 673 674 private int getThemeAttrColor(int attr) { 675 TypedValue tv = new TypedValue(); 676 if (getContext().getTheme().resolveAttribute(attr, tv, true)) { 677 return tv.data; 678 } else { 679 return Color.MAGENTA; 680 } 681 } 682 683 private class TextInputAccessibilityDelegate extends AccessibilityDelegateCompat { 684 @Override 685 public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { 686 super.onInitializeAccessibilityEvent(host, event); 687 event.setClassName(TextInputLayout.class.getSimpleName()); 688 } 689 690 @Override 691 public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) { 692 super.onPopulateAccessibilityEvent(host, event); 693 694 final CharSequence text = mCollapsingTextHelper.getText(); 695 if (!TextUtils.isEmpty(text)) { 696 event.getText().add(text); 697 } 698 } 699 700 @Override 701 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 702 super.onInitializeAccessibilityNodeInfo(host, info); 703 info.setClassName(TextInputLayout.class.getSimpleName()); 704 705 final CharSequence text = mCollapsingTextHelper.getText(); 706 if (!TextUtils.isEmpty(text)) { 707 info.setText(text); 708 } 709 if (mEditText != null) { 710 info.setLabelFor(mEditText); 711 } 712 final CharSequence error = mErrorView != null ? mErrorView.getText() : null; 713 if (!TextUtils.isEmpty(error)) { 714 info.setContentInvalid(true); 715 info.setError(error); 716 } 717 } 718 } 719 720 private static boolean arrayContains(int[] array, int value) { 721 for (int v : array) { 722 if (v == value) { 723 return true; 724 } 725 } 726 return false; 727 } 728}