From a366c0ffadd28dd1cd6c450cbbd05ee55c017ef8 Mon Sep 17 00:00:00 2001 From: Chris Banes Date: Wed, 30 Nov 2016 01:45:09 +0000 Subject: [PATCH] Merge "Workaround TextInputLayout state changes becoming permanent" into mnc-ub-dev am: 459155dd30 am: 810efa6b4f * commit '810efa6b4ffc687db1cfe717f690afe0612920d1': Workaround TextInputLayout state changes becoming permanent GitOrigin-RevId=962f72d4691edb762f658a92fb971bc559ba18e6 PiperOrigin-RevId: 140551357 --- .../support/design/widget/DrawableUtils.java | 99 +++++++++++++++++++ .../design/widget/TextInputLayout.java | 64 ++++++++++-- 2 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 src/android/support/design/widget/DrawableUtils.java diff --git a/src/android/support/design/widget/DrawableUtils.java b/src/android/support/design/widget/DrawableUtils.java new file mode 100644 index 000000000..1c46e6b21 --- /dev/null +++ b/src/android/support/design/widget/DrawableUtils.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.support.design.widget; + +import android.graphics.drawable.Drawable; +import android.graphics.drawable.DrawableContainer; +import android.os.Build; +import android.util.Log; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * Caution. Gross hacks ahead. + */ +class DrawableUtils { + + private static final String LOG_TAG = "DrawableUtils"; + + private static Method sSetConstantStateMethod; + private static boolean sSetConstantStateMethodFetched; + + private static Field sDrawableContainerStateField; + private static boolean sDrawableContainerStateFieldFetched; + + private DrawableUtils() {} + + static boolean setContainerConstantState(DrawableContainer drawable, + Drawable.ConstantState constantState) { + if (Build.VERSION.SDK_INT >= 9) { + // We can use getDeclaredMethod() on v9+ + return setContainerConstantStateV9(drawable, constantState); + } else { + // Else we'll just have to set the field directly + return setContainerConstantStateV7(drawable, constantState); + } + } + + private static boolean setContainerConstantStateV9(DrawableContainer drawable, + Drawable.ConstantState constantState) { + if (!sSetConstantStateMethodFetched) { + try { + sSetConstantStateMethod = DrawableContainer.class.getDeclaredMethod( + "setConstantState", DrawableContainer.DrawableContainerState.class); + sSetConstantStateMethod.setAccessible(true); + } catch (NoSuchMethodException e) { + Log.e(LOG_TAG, "Could not fetch setConstantState(). Oh well."); + } + sSetConstantStateMethodFetched = true; + } + if (sSetConstantStateMethod != null) { + try { + sSetConstantStateMethod.invoke(drawable, constantState); + return true; + } catch (Exception e) { + Log.e(LOG_TAG, "Could not invoke setConstantState(). Oh well."); + } + } + return false; + } + + private static boolean setContainerConstantStateV7(DrawableContainer drawable, + Drawable.ConstantState constantState) { + if (!sDrawableContainerStateFieldFetched) { + try { + sDrawableContainerStateField = DrawableContainer.class + .getDeclaredField("mDrawableContainerStateField"); + sDrawableContainerStateField.setAccessible(true); + } catch (NoSuchFieldException e) { + Log.e(LOG_TAG, "Could not fetch mDrawableContainerStateField. Oh well."); + } + sDrawableContainerStateFieldFetched = true; + } + if (sDrawableContainerStateField != null) { + try { + sDrawableContainerStateField.set(drawable, constantState); + return true; + } catch (Exception e) { + Log.e(LOG_TAG, "Could not set mDrawableContainerStateField. Oh well."); + } + } + return false; + } + +} diff --git a/src/android/support/design/widget/TextInputLayout.java b/src/android/support/design/widget/TextInputLayout.java index 162643e5d..b58394515 100644 --- a/src/android/support/design/widget/TextInputLayout.java +++ b/src/android/support/design/widget/TextInputLayout.java @@ -22,7 +22,10 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.DrawableContainer; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; @@ -89,6 +92,8 @@ public class TextInputLayout extends LinearLayout { private boolean mHintAnimationEnabled; private ValueAnimatorCompat mAnimator; + private boolean mHasReconstructedEditTextBackground; + public TextInputLayout(Context context) { this(context, null); } @@ -624,17 +629,60 @@ public class TextInputLayout extends LinearLayout { } private void updateEditTextBackground() { + ensureBackgroundDrawableStateWorkaround(); + + final Drawable editTextBackground = mEditText.getBackground(); + if (editTextBackground == null) { + return; + } + if (mErrorShown && mErrorView != null) { - // Set the EditText's background tint to the error color - ViewCompat.setBackgroundTintList(mEditText, - ColorStateList.valueOf(mErrorView.getCurrentTextColor())); + // Set a color filter of the error color + editTextBackground.setColorFilter( + AppCompatDrawableManager.getPorterDuffColorFilter( + mErrorView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN)); } else if (mCounterOverflowed && mCounterView != null) { - ViewCompat.setBackgroundTintList(mEditText, - ColorStateList.valueOf(mCounterView.getCurrentTextColor())); + // Set a color filter of the counter color + editTextBackground.setColorFilter( + AppCompatDrawableManager.getPorterDuffColorFilter( + mCounterView.getCurrentTextColor(), PorterDuff.Mode.SRC_IN)); } else { - ViewCompat.setBackgroundTintList(mEditText, - AppCompatDrawableManager.get() - .getTintList(getContext(), R.drawable.abc_edit_text_material)); + // Else reset the color filter and refresh the drawable state so that the + // normal tint is used + editTextBackground.clearColorFilter(); + mEditText.refreshDrawableState(); + } + } + + private void ensureBackgroundDrawableStateWorkaround() { + final Drawable bg = mEditText.getBackground(); + if (bg == null) { + return; + } + + if (!mHasReconstructedEditTextBackground) { + // This is gross. There is an issue in the platform which affects container Drawables + // where the first drawable retrieved from resources will propogate any changes + // (like color filter) to all instances from the cache. We'll try to workaround it... + + final Drawable newBg = bg.getConstantState().newDrawable(); + + if (bg instanceof DrawableContainer) { + // If we have a Drawable container, we can try and set it's constant state via + // reflection from the new Drawable + mHasReconstructedEditTextBackground = + DrawableUtils.setContainerConstantState( + (DrawableContainer) bg, newBg.getConstantState()); + } + + if (!mHasReconstructedEditTextBackground) { + // If we reach here then we just need to set a brand new instance of the Drawable + // as the background. This has the unfortunate side-effect of wiping out any + // user set padding, but I'd hope that use of custom padding on an EditText + // is limited. + mEditText.setBackgroundDrawable(newBg); + mHasReconstructedEditTextBackground = true; + } } }