1091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes/* 2091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * Copyright (C) 2015 The Android Open Source Project 3091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * 4091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * Licensed under the Apache License, Version 2.0 (the "License"); 5091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * you may not use this file except in compliance with the License. 6091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * You may obtain a copy of the License at 7091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * 8091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * http://www.apache.org/licenses/LICENSE-2.0 9091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * 10091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * Unless required by applicable law or agreed to in writing, software 11091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * distributed under the License is distributed on an "AS IS" BASIS, 12091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * See the License for the specific language governing permissions and 14091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes * limitations under the License. 15091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes */ 16091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 17091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banespackage android.support.v7.widget; 18091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 19091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.content.Context; 2089a45a915143f97174f5b6d45273c80503a7efcdChris Banesimport android.content.res.ColorStateList; 21f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banesimport android.graphics.drawable.Drawable; 22091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.os.Build; 23091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.support.v7.appcompat.R; 2466698bb15ba0f873aa1c2290cc50d6bb839a474aChris Banesimport android.support.v7.text.AllCapsTransformationMethod; 2540b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banesimport android.text.method.PasswordTransformationMethod; 26091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.util.AttributeSet; 27091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesimport android.widget.TextView; 28091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 29091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banesclass AppCompatTextHelper { 30091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 31f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes static AppCompatTextHelper create(TextView textView) { 32f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes if (Build.VERSION.SDK_INT >= 17) { 33f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes return new AppCompatTextHelperV17(textView); 34f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 35f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes return new AppCompatTextHelper(textView); 36f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 37f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes 38f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes final TextView mView; 39f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes 40f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes private TintInfo mDrawableLeftTint; 41f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes private TintInfo mDrawableTopTint; 42f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes private TintInfo mDrawableRightTint; 43f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes private TintInfo mDrawableBottomTint; 44091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 45091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes AppCompatTextHelper(TextView view) { 46091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes mView = view; 47091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes } 48091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 49091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { 50f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes final Context context = mView.getContext(); 517e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get(); 52091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 53091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes // First read the TextAppearance style id 5419509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, 5531a373d741c19f383578224434372a8f7a5b7a83Chris Banes R.styleable.AppCompatTextHelper, defStyleAttr, 0); 5631a373d741c19f383578224434372a8f7a5b7a83Chris Banes final int ap = a.getResourceId(R.styleable.AppCompatTextHelper_android_textAppearance, -1); 57f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes // Now read the compound drawable and grab any tints 5848dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) { 5931a373d741c19f383578224434372a8f7a5b7a83Chris Banes mDrawableLeftTint = createTintInfo(context, drawableManager, 6031a373d741c19f383578224434372a8f7a5b7a83Chris Banes a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableLeft, 0)); 61f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 6248dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableTop)) { 6331a373d741c19f383578224434372a8f7a5b7a83Chris Banes mDrawableTopTint = createTintInfo(context, drawableManager, 6431a373d741c19f383578224434372a8f7a5b7a83Chris Banes a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableTop, 0)); 65f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 6648dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableRight)) { 6731a373d741c19f383578224434372a8f7a5b7a83Chris Banes mDrawableRightTint = createTintInfo(context, drawableManager, 6831a373d741c19f383578224434372a8f7a5b7a83Chris Banes a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableRight, 0)); 69f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 7048dc926b92140d43cf35497d9a2ddad347fba2f3Chris Banes if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableBottom)) { 7131a373d741c19f383578224434372a8f7a5b7a83Chris Banes mDrawableBottomTint = createTintInfo(context, drawableManager, 7231a373d741c19f383578224434372a8f7a5b7a83Chris Banes a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableBottom, 0)); 73f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 74091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes a.recycle(); 75091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 7619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes // PasswordTransformationMethod wipes out all other TransformationMethod instances 7719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes // in TextView's constructor, so we should only set a new transformation method 7819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes // if we don't have a PasswordTransformationMethod currently... 7919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes final boolean hasPwdTm = 8019509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes mView.getTransformationMethod() instanceof PasswordTransformationMethod; 8119509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes boolean allCaps = false; 82464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes boolean allCapsSet = false; 839eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes ColorStateList textColor = null; 84286a5138bee6a1597c093e84e52b510c439302d7Chris Banes ColorStateList textColorHint = null; 8519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes 8619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes // First check TextAppearance's textAllCaps value 8719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes if (ap != -1) { 8819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance); 8919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) { 905a044fddba6ba7295599dddc2ff397925823efa8Chris Banes allCapsSet = true; 9119509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false); 92091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes } 93286a5138bee6a1597c093e84e52b510c439302d7Chris Banes if (Build.VERSION.SDK_INT < 23) { 949eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes // If we're running on < API 23, the text color may contain theme references 959eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes // so let's re-set using our own inflater 96286a5138bee6a1597c093e84e52b510c439302d7Chris Banes if (a.hasValue(R.styleable.TextAppearance_android_textColor)) { 97286a5138bee6a1597c093e84e52b510c439302d7Chris Banes textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor); 98286a5138bee6a1597c093e84e52b510c439302d7Chris Banes } 99286a5138bee6a1597c093e84e52b510c439302d7Chris Banes if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) { 100286a5138bee6a1597c093e84e52b510c439302d7Chris Banes textColorHint = a.getColorStateList( 101286a5138bee6a1597c093e84e52b510c439302d7Chris Banes R.styleable.TextAppearance_android_textColorHint); 102286a5138bee6a1597c093e84e52b510c439302d7Chris Banes } 10340b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banes } 10440b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banes a.recycle(); 10519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes } 10640b7a2ff65bc04688e0017edd8c99ec6596e30e3Chris Banes 10719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes // Now read the style's values 10819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance, 10919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes defStyleAttr, 0); 11019509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) { 111464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes allCapsSet = true; 11219509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false); 11319509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes } 114286a5138bee6a1597c093e84e52b510c439302d7Chris Banes if (Build.VERSION.SDK_INT < 23) { 1159eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes // If we're running on < API 23, the text color may contain theme references 1169eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes // so let's re-set using our own inflater 117286a5138bee6a1597c093e84e52b510c439302d7Chris Banes if (a.hasValue(R.styleable.TextAppearance_android_textColor)) { 118286a5138bee6a1597c093e84e52b510c439302d7Chris Banes textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor); 119286a5138bee6a1597c093e84e52b510c439302d7Chris Banes } 120286a5138bee6a1597c093e84e52b510c439302d7Chris Banes if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) { 121286a5138bee6a1597c093e84e52b510c439302d7Chris Banes textColorHint = a.getColorStateList( 122286a5138bee6a1597c093e84e52b510c439302d7Chris Banes R.styleable.TextAppearance_android_textColorHint); 123286a5138bee6a1597c093e84e52b510c439302d7Chris Banes } 124091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes } 12519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes a.recycle(); 12619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes 1279eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes if (textColor != null) { 1289eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes mView.setTextColor(textColor); 1299eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes } 130286a5138bee6a1597c093e84e52b510c439302d7Chris Banes if (textColorHint != null) { 131286a5138bee6a1597c093e84e52b510c439302d7Chris Banes mView.setHintTextColor(textColorHint); 132286a5138bee6a1597c093e84e52b510c439302d7Chris Banes } 133464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes if (!hasPwdTm && allCapsSet) { 134464d8397d0d19c4432eba2f5c1cc02b59a34b314Chris Banes setAllCaps(allCaps); 13519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes } 136091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes } 137091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 138091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes void onSetTextAppearance(Context context, int resId) { 13919509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, 14019509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes resId, R.styleable.TextAppearance); 14139e275d4c81cd3470f5ed78fa147d214f70ee580Chris Banes if (a.hasValue(R.styleable.TextAppearance_textAllCaps)) { 1422a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes // This breaks away slightly from the logic in TextView.setTextAppearance that serves 1432a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes // as an "overlay" on the current state of the TextView. Since android:textAllCaps 1442a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes // may have been set to true in this text appearance, we need to make sure that 1452a1d775c02ced0fb89c6056a0fa63a915d8543c1Chris Banes // app:textAllCaps has the chance to override it 14639e275d4c81cd3470f5ed78fa147d214f70ee580Chris Banes setAllCaps(a.getBoolean(R.styleable.TextAppearance_textAllCaps, false)); 147091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes } 1489eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes if (Build.VERSION.SDK_INT < 23 1499eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes && a.hasValue(R.styleable.TextAppearance_android_textColor)) { 1509eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes // If we're running on < API 23, the text color may contain theme references 1519eee6ba8edef2ab70d99fbaea6349f29d28b8fb6Chris Banes // so let's re-set using our own inflater 15219509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes final ColorStateList textColor 15319509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes = a.getColorStateList(R.styleable.TextAppearance_android_textColor); 15419509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes if (textColor != null) { 15519509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes mView.setTextColor(textColor); 15619509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes } 15719509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes } 15819509c838e3e9a34df29163f17ef3a7ea6375b14Chris Banes a.recycle(); 159091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes } 160091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes 161091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes void setAllCaps(boolean allCaps) { 162091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes mView.setTransformationMethod(allCaps 163091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes ? new AllCapsTransformationMethod(mView.getContext()) 164091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes : null); 165091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes } 166f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes 167f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes void applyCompoundDrawablesTints() { 168f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes if (mDrawableLeftTint != null || mDrawableTopTint != null || 169f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes mDrawableRightTint != null || mDrawableBottomTint != null) { 170f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes final Drawable[] compoundDrawables = mView.getCompoundDrawables(); 171f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes applyCompoundDrawableTint(compoundDrawables[0], mDrawableLeftTint); 172f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes applyCompoundDrawableTint(compoundDrawables[1], mDrawableTopTint); 173f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes applyCompoundDrawableTint(compoundDrawables[2], mDrawableRightTint); 174f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes applyCompoundDrawableTint(compoundDrawables[3], mDrawableBottomTint); 175f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 176f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 177f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes 178f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes final void applyCompoundDrawableTint(Drawable drawable, TintInfo info) { 179f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes if (drawable != null && info != null) { 1807e4e8b664820f773bc96e37ee1d2bbf500d64e69Chris Banes AppCompatDrawableManager.tintDrawable(drawable, info, mView.getDrawableState()); 181f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 182f7b73431b366b76bcf58536b7b1086489e4683b2Chris Banes } 18389a45a915143f97174f5b6d45273c80503a7efcdChris Banes 184a466cf9e5e70e638097a1753dd454a98fed05068Chris Banes protected static TintInfo createTintInfo(Context context, 18589a45a915143f97174f5b6d45273c80503a7efcdChris Banes AppCompatDrawableManager drawableManager, int drawableId) { 18689a45a915143f97174f5b6d45273c80503a7efcdChris Banes final ColorStateList tintList = drawableManager.getTintList(context, drawableId); 18789a45a915143f97174f5b6d45273c80503a7efcdChris Banes if (tintList != null) { 18889a45a915143f97174f5b6d45273c80503a7efcdChris Banes final TintInfo tintInfo = new TintInfo(); 18989a45a915143f97174f5b6d45273c80503a7efcdChris Banes tintInfo.mHasTintList = true; 19089a45a915143f97174f5b6d45273c80503a7efcdChris Banes tintInfo.mTintList = tintList; 191e23a1bdaf16bf535a1a6bab469e3d63bb6d73712Chris Banes return tintInfo; 19289a45a915143f97174f5b6d45273c80503a7efcdChris Banes } 19389a45a915143f97174f5b6d45273c80503a7efcdChris Banes return null; 19489a45a915143f97174f5b6d45273c80503a7efcdChris Banes } 195091b0f935e68ce9bfecc2422e60eada33fa3b09cChris Banes} 196