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}