/* * 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.v7.widget; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.RequiresApi; import android.support.annotation.RestrictTo; import android.support.v4.os.BuildCompat; import android.support.v4.widget.TextViewCompat; import android.support.v7.appcompat.R; import android.text.method.PasswordTransformationMethod; import android.util.AttributeSet; import android.util.TypedValue; import android.widget.TextView; @RequiresApi(9) class AppCompatTextHelper { static AppCompatTextHelper create(TextView textView) { if (Build.VERSION.SDK_INT >= 17) { return new AppCompatTextHelperV17(textView); } return new AppCompatTextHelper(textView); } final TextView mView; private TintInfo mDrawableLeftTint; private TintInfo mDrawableTopTint; private TintInfo mDrawableRightTint; private TintInfo mDrawableBottomTint; private final @NonNull AppCompatTextViewAutoSizeHelper mAutoSizeTextHelper; private int mStyle = Typeface.NORMAL; private Typeface mFontTypeface; AppCompatTextHelper(TextView view) { mView = view; mAutoSizeTextHelper = new AppCompatTextViewAutoSizeHelper(mView); } void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { final Context context = mView.getContext(); final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get(); // First read the TextAppearance style id TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.AppCompatTextHelper, defStyleAttr, 0); final int ap = a.getResourceId(R.styleable.AppCompatTextHelper_android_textAppearance, -1); // Now read the compound drawable and grab any tints if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) { mDrawableLeftTint = createTintInfo(context, drawableManager, a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableLeft, 0)); } if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableTop)) { mDrawableTopTint = createTintInfo(context, drawableManager, a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableTop, 0)); } if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableRight)) { mDrawableRightTint = createTintInfo(context, drawableManager, a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableRight, 0)); } if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableBottom)) { mDrawableBottomTint = createTintInfo(context, drawableManager, a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableBottom, 0)); } a.recycle(); // PasswordTransformationMethod wipes out all other TransformationMethod instances // in TextView's constructor, so we should only set a new transformation method // if we don't have a PasswordTransformationMethod currently... final boolean hasPwdTm = mView.getTransformationMethod() instanceof PasswordTransformationMethod; boolean allCaps = false; boolean allCapsSet = false; ColorStateList textColor = null; ColorStateList textColorHint = null; ColorStateList textColorLink = null; // First check TextAppearance's textAllCaps value if (ap != -1) { a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance); if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) { allCapsSet = true; allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false); } updateTypefaceAndStyle(context, a); if (Build.VERSION.SDK_INT < 23) { // If we're running on < API 23, the text color may contain theme references // so let's re-set using our own inflater if (a.hasValue(R.styleable.TextAppearance_android_textColor)) { textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor); } if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) { textColorHint = a.getColorStateList( R.styleable.TextAppearance_android_textColorHint); } if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) { textColorLink = a.getColorStateList( R.styleable.TextAppearance_android_textColorLink); } } a.recycle(); } // Now read the style's values a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance, defStyleAttr, 0); if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) { allCapsSet = true; allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false); } if (Build.VERSION.SDK_INT < 23) { // If we're running on < API 23, the text color may contain theme references // so let's re-set using our own inflater if (a.hasValue(R.styleable.TextAppearance_android_textColor)) { textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor); } if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) { textColorHint = a.getColorStateList( R.styleable.TextAppearance_android_textColorHint); } if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) { textColorLink = a.getColorStateList( R.styleable.TextAppearance_android_textColorLink); } } updateTypefaceAndStyle(context, a); a.recycle(); if (textColor != null) { mView.setTextColor(textColor); } if (textColorHint != null) { mView.setHintTextColor(textColorHint); } if (textColorLink != null) { mView.setLinkTextColor(textColorLink); } if (!hasPwdTm && allCapsSet) { setAllCaps(allCaps); } if (mFontTypeface != null) { mView.setTypeface(mFontTypeface, mStyle); } mAutoSizeTextHelper.loadFromAttributes(attrs, defStyleAttr); if (BuildCompat.isAtLeastO()) { // Delegate auto-size functionality to the framework implementation. if (mAutoSizeTextHelper.getAutoSizeTextType() != TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE) { final int[] autoSizeTextSizesInPx = mAutoSizeTextHelper.getAutoSizeTextAvailableSizes(); if (autoSizeTextSizesInPx.length > 0) { if (mView.getAutoSizeStepGranularity() != AppCompatTextViewAutoSizeHelper .UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { // Configured with granularity, preserve details. mView.setAutoSizeTextTypeUniformWithConfiguration( mAutoSizeTextHelper.getAutoSizeMinTextSize(), mAutoSizeTextHelper.getAutoSizeMaxTextSize(), mAutoSizeTextHelper.getAutoSizeStepGranularity(), TypedValue.COMPLEX_UNIT_PX); } else { mView.setAutoSizeTextTypeUniformWithPresetSizes( autoSizeTextSizesInPx, TypedValue.COMPLEX_UNIT_PX); } } } } } private void updateTypefaceAndStyle(Context context, TintTypedArray a) { mStyle = a.getInt(R.styleable.TextAppearance_android_textStyle, mStyle); if (a.hasValue(R.styleable.TextAppearance_android_fontFamily) || a.hasValue(R.styleable.TextAppearance_fontFamily)) { int fontFamilyId = a.hasValue(R.styleable.TextAppearance_android_fontFamily) ? R.styleable.TextAppearance_android_fontFamily : R.styleable.TextAppearance_fontFamily; if (!context.isRestricted()) { try { mFontTypeface = a.getFont(fontFamilyId, mStyle, mView); } catch (UnsupportedOperationException | Resources.NotFoundException e) { // Expected if it is not a font resource. } } if (mFontTypeface == null) { // Try with String. This is done by TextView JB+, but fails in ICS String fontFamilyName = a.getString(fontFamilyId); mFontTypeface = Typeface.create(fontFamilyName, mStyle); } } } void onSetTextAppearance(Context context, int resId) { final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, resId, R.styleable.TextAppearance); if (a.hasValue(R.styleable.TextAppearance_textAllCaps)) { // This breaks away slightly from the logic in TextView.setTextAppearance that serves // as an "overlay" on the current state of the TextView. Since android:textAllCaps // may have been set to true in this text appearance, we need to make sure that // app:textAllCaps has the chance to override it setAllCaps(a.getBoolean(R.styleable.TextAppearance_textAllCaps, false)); } if (Build.VERSION.SDK_INT < 23 && a.hasValue(R.styleable.TextAppearance_android_textColor)) { // If we're running on < API 23, the text color may contain theme references // so let's re-set using our own inflater final ColorStateList textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor); if (textColor != null) { mView.setTextColor(textColor); } } updateTypefaceAndStyle(context, a); a.recycle(); if (mFontTypeface != null) { mView.setTypeface(mFontTypeface, mStyle); } } void setAllCaps(boolean allCaps) { mView.setAllCaps(allCaps); } void applyCompoundDrawablesTints() { if (mDrawableLeftTint != null || mDrawableTopTint != null || mDrawableRightTint != null || mDrawableBottomTint != null) { final Drawable[] compoundDrawables = mView.getCompoundDrawables(); applyCompoundDrawableTint(compoundDrawables[0], mDrawableLeftTint); applyCompoundDrawableTint(compoundDrawables[1], mDrawableTopTint); applyCompoundDrawableTint(compoundDrawables[2], mDrawableRightTint); applyCompoundDrawableTint(compoundDrawables[3], mDrawableBottomTint); } } final void applyCompoundDrawableTint(Drawable drawable, TintInfo info) { if (drawable != null && info != null) { AppCompatDrawableManager.tintDrawable(drawable, info, mView.getDrawableState()); } } protected static TintInfo createTintInfo(Context context, AppCompatDrawableManager drawableManager, int drawableId) { final ColorStateList tintList = drawableManager.getTintList(context, drawableId); if (tintList != null) { final TintInfo tintInfo = new TintInfo(); tintInfo.mHasTintList = true; tintInfo.mTintList = tintList; return tintInfo; } return null; } /** @hide */ @RestrictTo(LIBRARY_GROUP) void onLayout(boolean changed, int left, int top, int right, int bottom) { // Auto-size is supported by the framework starting from Android O. if (!BuildCompat.isAtLeastO()) { if (isAutoSizeEnabled()) { if (getNeedsAutoSizeText()) { // Call auto-size after the width and height have been calculated. autoSizeText(); } // Always try to auto-size if enabled. Functions that do not want to trigger // auto-sizing after the next layout round should set this to false. setNeedsAutoSizeText(true); } } } /** @hide */ @RestrictTo(LIBRARY_GROUP) void setTextSize(int unit, float size) { if (!BuildCompat.isAtLeastO()) { if (!isAutoSizeEnabled()) { setTextSizeInternal(unit, size); } } } private boolean isAutoSizeEnabled() { return mAutoSizeTextHelper.isAutoSizeEnabled(); } private boolean getNeedsAutoSizeText() { return mAutoSizeTextHelper.getNeedsAutoSizeText(); } private void setNeedsAutoSizeText(boolean needsAutoSizeText) { mAutoSizeTextHelper.setNeedsAutoSizeText(needsAutoSizeText); } private void autoSizeText() { mAutoSizeTextHelper.autoSizeText(); } private void setTextSizeInternal(int unit, float size) { mAutoSizeTextHelper.setTextSizeInternal(unit, size); } void setAutoSizeTextTypeWithDefaults(@TextViewCompat.AutoSizeTextType int autoSizeTextType) { mAutoSizeTextHelper.setAutoSizeTextTypeWithDefaults(autoSizeTextType); } void setAutoSizeTextTypeUniformWithConfiguration( int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) throws IllegalArgumentException { mAutoSizeTextHelper.setAutoSizeTextTypeUniformWithConfiguration( autoSizeMinTextSize, autoSizeMaxTextSize, autoSizeStepGranularity, unit); } void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) throws IllegalArgumentException { mAutoSizeTextHelper.setAutoSizeTextTypeUniformWithPresetSizes(presetSizes, unit); } @TextViewCompat.AutoSizeTextType int getAutoSizeTextType() { return mAutoSizeTextHelper.getAutoSizeTextType(); } int getAutoSizeStepGranularity() { return mAutoSizeTextHelper.getAutoSizeStepGranularity(); } int getAutoSizeMinTextSize() { return mAutoSizeTextHelper.getAutoSizeMinTextSize(); } int getAutoSizeMaxTextSize() { return mAutoSizeTextHelper.getAutoSizeMaxTextSize(); } int[] getAutoSizeTextAvailableSizes() { return mAutoSizeTextHelper.getAutoSizeTextAvailableSizes(); } }