android appcompat目录,AppCompatDrawableManager.java

/*

* Copyright (C) 2014 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 androidx.appcompat.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;

import static androidx.appcompat.content.res.AppCompatResources.getColorStateList;

import static androidx.appcompat.widget.ThemeUtils.getDisabledThemeAttrColor;

import static androidx.appcompat.widget.ThemeUtils.getThemeAttrColor;

import static androidx.appcompat.widget.ThemeUtils.getThemeAttrColorStateList;

import static androidx.core.graphics.ColorUtils.compositeColors;

import android.annotation.SuppressLint;

import android.content.Context;

import android.content.res.ColorStateList;

import android.content.res.Resources;

import android.graphics.Color;

import android.graphics.PorterDuff;

import android.graphics.PorterDuffColorFilter;

import android.graphics.drawable.Drawable;

import android.graphics.drawable.Drawable.ConstantState;

import android.graphics.drawable.LayerDrawable;

import android.os.Build;

import android.util.AttributeSet;

import android.util.Log;

import android.util.TypedValue;

import android.util.Xml;

import androidx.annotation.ColorInt;

import androidx.annotation.DrawableRes;

import androidx.annotation.NonNull;

import androidx.annotation.Nullable;

import androidx.annotation.RestrictTo;

import androidx.appcompat.R;

import androidx.collection.ArrayMap;

import androidx.collection.LongSparseArray;

import androidx.collection.LruCache;

import androidx.collection.SparseArrayCompat;

import androidx.core.content.ContextCompat;

import androidx.core.graphics.drawable.DrawableCompat;

import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;

import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;

import org.xmlpull.v1.XmlPullParser;

import org.xmlpull.v1.XmlPullParserException;

import java.lang.ref.WeakReference;

import java.util.WeakHashMap;

/**

* @hide

*/

@RestrictTo(LIBRARY_GROUP)

public final class AppCompatDrawableManager {

private interface InflateDelegate {

Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,

@NonNull AttributeSet attrs, @Nullable Resources.Theme theme);

}

private static final String TAG = "AppCompatDrawableManag";

private static final boolean DEBUG = false;

private static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;

private static final String SKIP_DRAWABLE_TAG = "appcompat_skip_skip";

private static final String PLATFORM_VD_CLAZZ = "android.graphics.drawable.VectorDrawable";

private static AppCompatDrawableManager INSTANCE;

public static AppCompatDrawableManager get() {

if (INSTANCE == null) {

INSTANCE = new AppCompatDrawableManager();

installDefaultInflateDelegates(INSTANCE);

}

return INSTANCE;

}

private static void installDefaultInflateDelegates(@NonNull AppCompatDrawableManager manager) {

// This sdk version check will affect src:appCompat code path.

// Although VectorDrawable exists in Android framework from Lollipop, AppCompat will use the

// VectorDrawableCompat before Nougat to utilize the bug fixes in VectorDrawableCompat.

if (Build.VERSION.SDK_INT < 24) {

manager.addDelegate("vector", new VdcInflateDelegate());

manager.addDelegate("animated-vector", new AvdcInflateDelegate());

}

}

private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6);

/**

* Drawables which should be tinted with the value of {@code R.attr.colorControlNormal},

* using the default mode using a raw color filter.

*/

private static final int[] COLORFILTER_TINT_COLOR_CONTROL_NORMAL = {

R.drawable.abc_textfield_search_default_mtrl_alpha,

R.drawable.abc_textfield_default_mtrl_alpha,

R.drawable.abc_ab_share_pack_mtrl_alpha

};

/**

* Drawables which should be tinted with the value of {@code R.attr.colorControlNormal}, using

* {@link DrawableCompat}'s tinting functionality.

*/

private static final int[] TINT_COLOR_CONTROL_NORMAL = {

R.drawable.abc_ic_commit_search_api_mtrl_alpha,

R.drawable.abc_seekbar_tick_mark_material,

R.drawable.abc_ic_menu_share_mtrl_alpha,

R.drawable.abc_ic_menu_copy_mtrl_am_alpha,

R.drawable.abc_ic_menu_cut_mtrl_alpha,

R.drawable.abc_ic_menu_selectall_mtrl_alpha,

R.drawable.abc_ic_menu_paste_mtrl_am_alpha

};

/**

* Drawables which should be tinted with the value of {@code R.attr.colorControlActivated},

* using a color filter.

*/

private static final int[] COLORFILTER_COLOR_CONTROL_ACTIVATED = {

R.drawable.abc_textfield_activated_mtrl_alpha,

R.drawable.abc_textfield_search_activated_mtrl_alpha,

R.drawable.abc_cab_background_top_mtrl_alpha,

R.drawable.abc_text_cursor_material,

R.drawable.abc_text_select_handle_left_mtrl_dark,

R.drawable.abc_text_select_handle_middle_mtrl_dark,

R.drawable.abc_text_select_handle_right_mtrl_dark,

R.drawable.abc_text_select_handle_left_mtrl_light,

R.drawable.abc_text_select_handle_middle_mtrl_light,

R.drawable.abc_text_select_handle_right_mtrl_light

};

/**

* Drawables which should be tinted with the value of {@code android.R.attr.colorBackground},

* using the {@link android.graphics.PorterDuff.Mode#MULTIPLY} mode and a color filter.

*/

private static final int[] COLORFILTER_COLOR_BACKGROUND_MULTIPLY = {

R.drawable.abc_popup_background_mtrl_mult,

R.drawable.abc_cab_background_internal_bg,

R.drawable.abc_menu_hardkey_panel_mtrl_mult

};

/**

* Drawables which should be tinted using a state list containing values of

* {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated}

*/

private static final int[] TINT_COLOR_CONTROL_STATE_LIST = {

R.drawable.abc_tab_indicator_material,

R.drawable.abc_textfield_search_material

};

/**

* Drawables which should be tinted using a state list containing values of

* {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated} for the checked

* state.

*/

private static final int[] TINT_CHECKABLE_BUTTON_LIST = {

R.drawable.abc_btn_check_material,

R.drawable.abc_btn_radio_material

};

private WeakHashMap> mTintLists;

private ArrayMapmDelegates;

private SparseArrayCompatmKnownDrawableIdTags;

private final Object mDrawableCacheLock = new Object();

private final WeakHashMap>>

mDrawableCaches = new WeakHashMap<>(0);

private TypedValue mTypedValue;

private boolean mHasCheckedVectorDrawableSetup;

public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {

return getDrawable(context, resId, false);

}

Drawable getDrawable(@NonNull Context context, @DrawableRes int resId,

boolean failIfNotKnown) {

checkVectorDrawableSetup(context);

Drawable drawable = loadDrawableFromDelegates(context, resId);

if (drawable == null) {

drawable = createDrawableIfNeeded(context, resId);

}

if (drawable == null) {

drawable = ContextCompat.getDrawable(context, resId);

}

if (drawable != null) {

// Tint it if needed

drawable = tintDrawable(context, resId, failIfNotKnown, drawable);

}

if (drawable != null) {

// See if we need to 'fix' the drawable

DrawableUtils.fixDrawable(drawable);

}

return drawable;

}

public void onConfigurationChanged(@NonNull Context context) {

synchronized (mDrawableCacheLock) {

LongSparseArray> cache = mDrawableCaches.get(context);

if (cache != null) {

// Crude, but we'll just clear the cache when the configuration changes

cache.clear();

}

}

}

private static long createCacheKey(TypedValue tv) {

return (((long) tv.assetCookie) << 32) | tv.data;

}

private Drawable createDrawableIfNeeded(@NonNull Context context,

@DrawableRes final int resId) {

if (mTypedValue == null) {

mTypedValue = new TypedValue();

}

final TypedValue tv = mTypedValue;

context.getResources().getValue(resId, tv, true);

final long key = createCacheKey(tv);

Drawable dr = getCachedDrawable(context, key);

if (dr != null) {

// If we got a cached drawable, return it

return dr;

}

// Else we need to try and create one...

if (resId == R.drawable.abc_cab_background_top_material) {

dr = new LayerDrawable(new Drawable[]{

getDrawable(context, R.drawable.abc_cab_background_internal_bg),

getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)

});

}

if (dr != null) {

dr.setChangingConfigurations(tv.changingConfigurations);

// If we reached here then we created a new drawable, add it to the cache

addDrawableToCache(context, key, dr);

}

return dr;

}

private Drawable tintDrawable(@NonNull Context context, @DrawableRes int resId,

boolean failIfNotKnown, @NonNull Drawable drawable) {

final ColorStateList tintList = getTintList(context, resId);

if (tintList != null) {

// First mutate the Drawable, then wrap it and set the tint list

if (DrawableUtils.canSafelyMutateDrawable(drawable)) {

drawable = drawable.mutate();

}

drawable = DrawableCompat.wrap(drawable);

DrawableCompat.setTintList(drawable, tintList);

// If there is a blending mode specified for the drawable, use it

final PorterDuff.Mode tintMode = getTintMode(resId);

if (tintMode != null) {

DrawableCompat.setTintMode(drawable, tintMode);

}

} else if (resId == R.drawable.abc_seekbar_track_material) {

LayerDrawable ld = (LayerDrawable) drawable;

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),

getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),

getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),

getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);

} else if (resId == R.drawable.abc_ratingbar_material

|| resId == R.drawable.abc_ratingbar_indicator_material

|| resId == R.drawable.abc_ratingbar_small_material) {

LayerDrawable ld = (LayerDrawable) drawable;

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),

getDisabledThemeAttrColor(context, R.attr.colorControlNormal),

DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),

getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),

getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);

} else {

final boolean tinted = tintDrawableUsingColorFilter(context, resId, drawable);

if (!tinted && failIfNotKnown) {

// If we didn't tint using a ColorFilter, and we're set to fail if we don't

// know the id, return null

drawable = null;

}

}

return drawable;

}

private Drawable loadDrawableFromDelegates(@NonNull Context context, @DrawableRes int resId) {

if (mDelegates != null && !mDelegates.isEmpty()) {

if (mKnownDrawableIdTags != null) {

final String cachedTagName = mKnownDrawableIdTags.get(resId);

if (SKIP_DRAWABLE_TAG.equals(cachedTagName)

|| (cachedTagName != null && mDelegates.get(cachedTagName) == null)) {

// If we don't have a delegate for the drawable tag, or we've been set to

// skip it, fail fast and return null

if (DEBUG) {

Log.d(TAG, "[loadDrawableFromDelegates] Skipping drawable: "

+ context.getResources().getResourceName(resId));

}

return null;

}

} else {

// Create an id cache as we'll need one later

mKnownDrawableIdTags = new SparseArrayCompat<>();

}

if (mTypedValue == null) {

mTypedValue = new TypedValue();

}

final TypedValue tv = mTypedValue;

final Resources res = context.getResources();

res.getValue(resId, tv, true);

final long key = createCacheKey(tv);

Drawable dr = getCachedDrawable(context, key);

if (dr != null) {

if (DEBUG) {

Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +

context.getResources().getResourceName(resId));

}

// We have a cached drawable, return it!

return dr;

}

if (tv.string != null && tv.string.toString().endsWith(".xml")) {

// If the resource is an XML file, let's try and parse it

try {

@SuppressLint("ResourceType") final XmlPullParser parser = res.getXml(resId);

final AttributeSet attrs = Xml.asAttributeSet(parser);

int type;

while ((type = parser.next()) != XmlPullParser.START_TAG &&

type != XmlPullParser.END_DOCUMENT) {

// Empty loop

}

if (type != XmlPullParser.START_TAG) {

throw new XmlPullParserException("No start tag found");

}

final String tagName = parser.getName();

// Add the tag name to the cache

mKnownDrawableIdTags.append(resId, tagName);

// Now try and find a delegate for the tag name and inflate if found

final InflateDelegate delegate = mDelegates.get(tagName);

if (delegate != null) {

dr = delegate.createFromXmlInner(context, parser, attrs,

context.getTheme());

}

if (dr != null) {

// Add it to the drawable cache

dr.setChangingConfigurations(tv.changingConfigurations);

if (addDrawableToCache(context, key, dr) && DEBUG) {

Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +

context.getResources().getResourceName(resId));

}

}

} catch (Exception e) {

Log.e(TAG, "Exception while inflating drawable", e);

}

}

if (dr == null) {

// If we reach here then the delegate inflation of the resource failed. Mark it as

// bad so we skip the id next time

mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);

}

return dr;

}

return null;

}

private Drawable getCachedDrawable(@NonNull final Context context, final long key) {

synchronized (mDrawableCacheLock) {

final LongSparseArray> cache

= mDrawableCaches.get(context);

if (cache == null) {

return null;

}

final WeakReferencewr = cache.get(key);

if (wr != null) {

// We have the key, and the secret

ConstantState entry = wr.get();

if (entry != null) {

return entry.newDrawable(context.getResources());

} else {

// Our entry has been purged

cache.delete(key);

}

}

}

return null;

}

private boolean addDrawableToCache(@NonNull final Context context, final long key,

@NonNull final Drawable drawable) {

final ConstantState cs = drawable.getConstantState();

if (cs != null) {

synchronized (mDrawableCacheLock) {

LongSparseArray> cache = mDrawableCaches.get(context);

if (cache == null) {

cache = new LongSparseArray<>();

mDrawableCaches.put(context, cache);

}

cache.put(key, new WeakReference<>(cs));

}

return true;

}

return false;

}

Drawable onDrawableLoadedFromResources(@NonNull Context context,

@NonNull VectorEnabledTintResources resources, @DrawableRes final int resId) {

Drawable drawable = loadDrawableFromDelegates(context, resId);

if (drawable == null) {

drawable = resources.superGetDrawable(resId);

}

if (drawable != null) {

return tintDrawable(context, resId, false, drawable);

}

return null;

}

static boolean tintDrawableUsingColorFilter(@NonNull Context context,

@DrawableRes final int resId, @NonNull Drawable drawable) {

PorterDuff.Mode tintMode = DEFAULT_MODE;

boolean colorAttrSet = false;

int colorAttr = 0;

int alpha = -1;

if (arrayContains(COLORFILTER_TINT_COLOR_CONTROL_NORMAL, resId)) {

colorAttr = R.attr.colorControlNormal;

colorAttrSet = true;

} else if (arrayContains(COLORFILTER_COLOR_CONTROL_ACTIVATED, resId)) {

colorAttr = R.attr.colorControlActivated;

colorAttrSet = true;

} else if (arrayContains(COLORFILTER_COLOR_BACKGROUND_MULTIPLY, resId)) {

colorAttr = android.R.attr.colorBackground;

colorAttrSet = true;

tintMode = PorterDuff.Mode.MULTIPLY;

} else if (resId == R.drawable.abc_list_divider_mtrl_alpha) {

colorAttr = android.R.attr.colorForeground;

colorAttrSet = true;

alpha = Math.round(0.16f * 255);

} else if (resId == R.drawable.abc_dialog_material_background) {

colorAttr = android.R.attr.colorBackground;

colorAttrSet = true;

}

if (colorAttrSet) {

if (DrawableUtils.canSafelyMutateDrawable(drawable)) {

drawable = drawable.mutate();

}

final int color = getThemeAttrColor(context, colorAttr);

drawable.setColorFilter(getPorterDuffColorFilter(color, tintMode));

if (alpha != -1) {

drawable.setAlpha(alpha);

}

if (DEBUG) {

Log.d(TAG, "[tintDrawableUsingColorFilter] Tinted "

+ context.getResources().getResourceName(resId) +

" with color: #" + Integer.toHexString(color));

}

return true;

}

return false;

}

private void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {

if (mDelegates == null) {

mDelegates = new ArrayMap<>();

}

mDelegates.put(tagName, delegate);

}

private void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {

if (mDelegates != null && mDelegates.get(tagName) == delegate) {

mDelegates.remove(tagName);

}

}

private static boolean arrayContains(int[] array, int value) {

for (int id : array) {

if (id == value) {

return true;

}

}

return false;

}

static PorterDuff.Mode getTintMode(final int resId) {

PorterDuff.Mode mode = null;

if (resId == R.drawable.abc_switch_thumb_material) {

mode = PorterDuff.Mode.MULTIPLY;

}

return mode;

}

ColorStateList getTintList(@NonNull Context context, @DrawableRes int resId) {

// Try the cache first (if it exists)

ColorStateList tint = getTintListFromCache(context, resId);

if (tint == null) {

// ...if the cache did not contain a color state list, try and create one

if (resId == R.drawable.abc_edit_text_material) {

tint = getColorStateList(context, R.color.abc_tint_edittext);

} else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {

tint = getColorStateList(context, R.color.abc_tint_switch_track);

} else if (resId == R.drawable.abc_switch_thumb_material) {

tint = createSwitchThumbColorStateList(context);

} else if (resId == R.drawable.abc_btn_default_mtrl_shape) {

tint = createDefaultButtonColorStateList(context);

} else if (resId == R.drawable.abc_btn_borderless_material) {

tint = createBorderlessButtonColorStateList(context);

} else if (resId == R.drawable.abc_btn_colored_material) {

tint = createColoredButtonColorStateList(context);

} else if (resId == R.drawable.abc_spinner_mtrl_am_alpha

|| resId == R.drawable.abc_spinner_textfield_background_material) {

tint = getColorStateList(context, R.color.abc_tint_spinner);

} else if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) {

tint = getThemeAttrColorStateList(context, R.attr.colorControlNormal);

} else if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {

tint = getColorStateList(context, R.color.abc_tint_default);

} else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) {

tint = getColorStateList(context, R.color.abc_tint_btn_checkable);

} else if (resId == R.drawable.abc_seekbar_thumb_material) {

tint = getColorStateList(context, R.color.abc_tint_seek_thumb);

}

if (tint != null) {

addTintListToCache(context, resId, tint);

}

}

return tint;

}

private ColorStateList getTintListFromCache(@NonNull Context context, @DrawableRes int resId) {

if (mTintLists != null) {

final SparseArrayCompattints = mTintLists.get(context);

return tints != null ? tints.get(resId) : null;

}

return null;

}

private void addTintListToCache(@NonNull Context context, @DrawableRes int resId,

@NonNull ColorStateList tintList) {

if (mTintLists == null) {

mTintLists = new WeakHashMap<>();

}

SparseArrayCompatthemeTints = mTintLists.get(context);

if (themeTints == null) {

themeTints = new SparseArrayCompat<>();

mTintLists.put(context, themeTints);

}

themeTints.append(resId, tintList);

}

private ColorStateList createDefaultButtonColorStateList(@NonNull Context context) {

return createButtonColorStateList(context,

getThemeAttrColor(context, R.attr.colorButtonNormal));

}

private ColorStateList createBorderlessButtonColorStateList(@NonNull Context context) {

// We ignore the custom tint for borderless buttons

return createButtonColorStateList(context, Color.TRANSPARENT);

}

private ColorStateList createColoredButtonColorStateList(@NonNull Context context) {

return createButtonColorStateList(context,

getThemeAttrColor(context, R.attr.colorAccent));

}

private ColorStateList createButtonColorStateList(@NonNull final Context context,

@ColorInt final int baseColor) {

final int[][] states = new int[4][];

final int[] colors = new int[4];

int i = 0;

final int colorControlHighlight = getThemeAttrColor(context, R.attr.colorControlHighlight);

final int disabledColor = getDisabledThemeAttrColor(context, R.attr.colorButtonNormal);

// Disabled state

states[i] = ThemeUtils.DISABLED_STATE_SET;

colors[i] = disabledColor;

i++;

states[i] = ThemeUtils.PRESSED_STATE_SET;

colors[i] = compositeColors(colorControlHighlight, baseColor);

i++;

states[i] = ThemeUtils.FOCUSED_STATE_SET;

colors[i] = compositeColors(colorControlHighlight, baseColor);

i++;

// Default enabled state

states[i] = ThemeUtils.EMPTY_STATE_SET;

colors[i] = baseColor;

i++;

return new ColorStateList(states, colors);

}

private ColorStateList createSwitchThumbColorStateList(Context context) {

final int[][] states = new int[3][];

final int[] colors = new int[3];

int i = 0;

final ColorStateList thumbColor = getThemeAttrColorStateList(context,

R.attr.colorSwitchThumbNormal);

if (thumbColor != null && thumbColor.isStateful()) {

// If colorSwitchThumbNormal is a valid ColorStateList, extract the default and

// disabled colors from it

// Disabled state

states[i] = ThemeUtils.DISABLED_STATE_SET;

colors[i] = thumbColor.getColorForState(states[i], 0);

i++;

states[i] = ThemeUtils.CHECKED_STATE_SET;

colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);

i++;

// Default enabled state

states[i] = ThemeUtils.EMPTY_STATE_SET;

colors[i] = thumbColor.getDefaultColor();

i++;

} else {

// Else we'll use an approximation using the default disabled alpha

// Disabled state

states[i] = ThemeUtils.DISABLED_STATE_SET;

colors[i] = getDisabledThemeAttrColor(context, R.attr.colorSwitchThumbNormal);

i++;

states[i] = ThemeUtils.CHECKED_STATE_SET;

colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);

i++;

// Default enabled state

states[i] = ThemeUtils.EMPTY_STATE_SET;

colors[i] = getThemeAttrColor(context, R.attr.colorSwitchThumbNormal);

i++;

}

return new ColorStateList(states, colors);

}

private static class ColorFilterLruCache extends LruCache{

public ColorFilterLruCache(int maxSize) {

super(maxSize);

}

PorterDuffColorFilter get(int color, PorterDuff.Mode mode) {

return get(generateCacheKey(color, mode));

}

PorterDuffColorFilter put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter) {

return put(generateCacheKey(color, mode), filter);

}

private static int generateCacheKey(int color, PorterDuff.Mode mode) {

int hashCode = 1;

hashCode = 31 * hashCode + color;

hashCode = 31 * hashCode + mode.hashCode();

return hashCode;

}

}

static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {

if (DrawableUtils.canSafelyMutateDrawable(drawable)

&& drawable.mutate() != drawable) {

Log.d(TAG, "Mutated drawable is not the same instance as the input.");

return;

}

if (tint.mHasTintList || tint.mHasTintMode) {

drawable.setColorFilter(createTintFilter(

tint.mHasTintList ? tint.mTintList : null,

tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE,

state));

} else {

drawable.clearColorFilter();

}

if (Build.VERSION.SDK_INT <= 23) {

// Pre-v23 there is no guarantee that a state change will invoke an invalidation,

// so we force it ourselves

drawable.invalidateSelf();

}

}

private static PorterDuffColorFilter createTintFilter(ColorStateList tint,

PorterDuff.Mode tintMode, final int[] state) {

if (tint == null || tintMode == null) {

return null;

}

final int color = tint.getColorForState(state, Color.TRANSPARENT);

return getPorterDuffColorFilter(color, tintMode);

}

public static PorterDuffColorFilter getPorterDuffColorFilter(int color, PorterDuff.Mode mode) {

// First, lets see if the cache already contains the color filter

PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);

if (filter == null) {

// Cache miss, so create a color filter and add it to the cache

filter = new PorterDuffColorFilter(color, mode);

COLOR_FILTER_CACHE.put(color, mode, filter);

}

return filter;

}

private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {

if (DrawableUtils.canSafelyMutateDrawable(d)) {

d = d.mutate();

}

d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode));

}

private void checkVectorDrawableSetup(@NonNull Context context) {

if (mHasCheckedVectorDrawableSetup) {

// We've already checked so return now...

return;

}

// Here we will check that a known Vector drawable resource inside AppCompat can be

// correctly decoded

mHasCheckedVectorDrawableSetup = true;

final Drawable d = getDrawable(context, R.drawable.abc_vector_test);

if (d == null || !isVectorDrawable(d)) {

mHasCheckedVectorDrawableSetup = false;

throw new IllegalStateException("This app has been built with an incorrect "

+ "configuration. Please configure your build for VectorDrawableCompat.");

}

}

private static boolean isVectorDrawable(@NonNull Drawable d) {

return d instanceof VectorDrawableCompat

|| PLATFORM_VD_CLAZZ.equals(d.getClass().getName());

}

private static class VdcInflateDelegate implements InflateDelegate {

VdcInflateDelegate() {

}

@Override

public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,

@NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {

try {

return VectorDrawableCompat

.createFromXmlInner(context.getResources(), parser, attrs, theme);

} catch (Exception e) {

Log.e("VdcInflateDelegate", "Exception while inflating ", e);

return null;

}

}

}

private static class AvdcInflateDelegate implements InflateDelegate {

AvdcInflateDelegate() {

}

@Override

public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,

@NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {

try {

return AnimatedVectorDrawableCompat

.createFromXmlInner(context, context.getResources(), parser, attrs, theme);

} catch (Exception e) {

Log.e("AvdcInflateDelegate", "Exception while inflating ", e);

return null;

}

}

}

}

Java程序

|

800行

|

32.32 KB

/*

* Copyright (C) 2014 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 androidx.appcompat.widget;

import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;

import static androidx.appcompat.content.res.AppCompatResources.getColorStateList;

import static androidx.appcompat.widget.ThemeUtils.getDisabledThemeAttrColor;

import static androidx.appcompat.widget.ThemeUtils.getThemeAttrColor;

import static androidx.appcompat.widget.ThemeUtils.getThemeAttrColorStateList;

import static androidx.core.graphics.ColorUtils.compositeColors;

import android.annotation.SuppressLint;

import android.content.Context;

import android.content.res.ColorStateList;

import android.content.res.Resources;

import android.graphics.Color;

import android.graphics.PorterDuff;

import android.graphics.PorterDuffColorFilter;

import android.graphics.drawable.Drawable;

import android.graphics.drawable.Drawable.ConstantState;

import android.graphics.drawable.LayerDrawable;

import android.os.Build;

import android.util.AttributeSet;

import android.util.Log;

import android.util.TypedValue;

import android.util.Xml;

import androidx.annotation.ColorInt;

import androidx.annotation.DrawableRes;

import androidx.annotation.NonNull;

import androidx.annotation.Nullable;

import androidx.annotation.RestrictTo;

import androidx.appcompat.R;

import androidx.collection.ArrayMap;

import androidx.collection.LongSparseArray;

import androidx.collection.LruCache;

import androidx.collection.SparseArrayCompat;

import androidx.core.content.ContextCompat;

import androidx.core.graphics.drawable.DrawableCompat;

import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;

import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;

import org.xmlpull.v1.XmlPullParser;

import org.xmlpull.v1.XmlPullParserException;

import java.lang.ref.WeakReference;

import java.util.WeakHashMap;

/**

* @hide

*/

@RestrictTo(LIBRARY_GROUP)

public final class AppCompatDrawableManager {

private interface InflateDelegate {

Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,

@NonNull AttributeSet attrs, @Nullable Resources.Theme theme);

}

private static final String TAG = "AppCompatDrawableManag";

private static final boolean DEBUG = false;

private static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;

private static final String SKIP_DRAWABLE_TAG = "appcompat_skip_skip";

private static final String PLATFORM_VD_CLAZZ = "android.graphics.drawable.VectorDrawable";

private static AppCompatDrawableManager INSTANCE;

public static AppCompatDrawableManager get() {

if (INSTANCE == null) {

INSTANCE = new AppCompatDrawableManager();

installDefaultInflateDelegates(INSTANCE);

}

return INSTANCE;

}

private static void installDefaultInflateDelegates(@NonNull AppCompatDrawableManager manager) {

// This sdk version check will affect src:appCompat code path.

// Although VectorDrawable exists in Android framework from Lollipop, AppCompat will use the

// VectorDrawableCompat before Nougat to utilize the bug fixes in VectorDrawableCompat.

if (Build.VERSION.SDK_INT < 24) {

manager.addDelegate("vector", new VdcInflateDelegate());

manager.addDelegate("animated-vector", new AvdcInflateDelegate());

}

}

private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6);

/**

* Drawables which should be tinted with the value of {@code R.attr.colorControlNormal},

* using the default mode using a raw color filter.

*/

private static final int[] COLORFILTER_TINT_COLOR_CONTROL_NORMAL = {

R.drawable.abc_textfield_search_default_mtrl_alpha,

R.drawable.abc_textfield_default_mtrl_alpha,

R.drawable.abc_ab_share_pack_mtrl_alpha

};

/**

* Drawables which should be tinted with the value of {@code R.attr.colorControlNormal}, using

* {@link DrawableCompat}'s tinting functionality.

*/

private static final int[] TINT_COLOR_CONTROL_NORMAL = {

R.drawable.abc_ic_commit_search_api_mtrl_alpha,

R.drawable.abc_seekbar_tick_mark_material,

R.drawable.abc_ic_menu_share_mtrl_alpha,

R.drawable.abc_ic_menu_copy_mtrl_am_alpha,

R.drawable.abc_ic_menu_cut_mtrl_alpha,

R.drawable.abc_ic_menu_selectall_mtrl_alpha,

R.drawable.abc_ic_menu_paste_mtrl_am_alpha

};

/**

* Drawables which should be tinted with the value of {@code R.attr.colorControlActivated},

* using a color filter.

*/

private static final int[] COLORFILTER_COLOR_CONTROL_ACTIVATED = {

R.drawable.abc_textfield_activated_mtrl_alpha,

R.drawable.abc_textfield_search_activated_mtrl_alpha,

R.drawable.abc_cab_background_top_mtrl_alpha,

R.drawable.abc_text_cursor_material,

R.drawable.abc_text_select_handle_left_mtrl_dark,

R.drawable.abc_text_select_handle_middle_mtrl_dark,

R.drawable.abc_text_select_handle_right_mtrl_dark,

R.drawable.abc_text_select_handle_left_mtrl_light,

R.drawable.abc_text_select_handle_middle_mtrl_light,

R.drawable.abc_text_select_handle_right_mtrl_light

};

/**

* Drawables which should be tinted with the value of {@code android.R.attr.colorBackground},

* using the {@link android.graphics.PorterDuff.Mode#MULTIPLY} mode and a color filter.

*/

private static final int[] COLORFILTER_COLOR_BACKGROUND_MULTIPLY = {

R.drawable.abc_popup_background_mtrl_mult,

R.drawable.abc_cab_background_internal_bg,

R.drawable.abc_menu_hardkey_panel_mtrl_mult

};

/**

* Drawables which should be tinted using a state list containing values of

* {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated}

*/

private static final int[] TINT_COLOR_CONTROL_STATE_LIST = {

R.drawable.abc_tab_indicator_material,

R.drawable.abc_textfield_search_material

};

/**

* Drawables which should be tinted using a state list containing values of

* {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated} for the checked

* state.

*/

private static final int[] TINT_CHECKABLE_BUTTON_LIST = {

R.drawable.abc_btn_check_material,

R.drawable.abc_btn_radio_material

};

private WeakHashMap> mTintLists;

private ArrayMap mDelegates;

private SparseArrayCompat mKnownDrawableIdTags;

private final Object mDrawableCacheLock = new Object();

private final WeakHashMap>>

mDrawableCaches = new WeakHashMap<>(0);

private TypedValue mTypedValue;

private boolean mHasCheckedVectorDrawableSetup;

public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {

return getDrawable(context, resId, false);

}

Drawable getDrawable(@NonNull Context context, @DrawableRes int resId,

boolean failIfNotKnown) {

checkVectorDrawableSetup(context);

Drawable drawable = loadDrawableFromDelegates(context, resId);

if (drawable == null) {

drawable = createDrawableIfNeeded(context, resId);

}

if (drawable == null) {

drawable = ContextCompat.getDrawable(context, resId);

}

if (drawable != null) {

// Tint it if needed

drawable = tintDrawable(context, resId, failIfNotKnown, drawable);

}

if (drawable != null) {

// See if we need to 'fix' the drawable

DrawableUtils.fixDrawable(drawable);

}

return drawable;

}

public void onConfigurationChanged(@NonNull Context context) {

synchronized (mDrawableCacheLock) {

LongSparseArray> cache = mDrawableCaches.get(context);

if (cache != null) {

// Crude, but we'll just clear the cache when the configuration changes

cache.clear();

}

}

}

private static long createCacheKey(TypedValue tv) {

return (((long) tv.assetCookie) << 32) | tv.data;

}

private Drawable createDrawableIfNeeded(@NonNull Context context,

@DrawableRes final int resId) {

if (mTypedValue == null) {

mTypedValue = new TypedValue();

}

final TypedValue tv = mTypedValue;

context.getResources().getValue(resId, tv, true);

final long key = createCacheKey(tv);

Drawable dr = getCachedDrawable(context, key);

if (dr != null) {

// If we got a cached drawable, return it

return dr;

}

// Else we need to try and create one...

if (resId == R.drawable.abc_cab_background_top_material) {

dr = new LayerDrawable(new Drawable[]{

getDrawable(context, R.drawable.abc_cab_background_internal_bg),

getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)

});

}

if (dr != null) {

dr.setChangingConfigurations(tv.changingConfigurations);

// If we reached here then we created a new drawable, add it to the cache

addDrawableToCache(context, key, dr);

}

return dr;

}

private Drawable tintDrawable(@NonNull Context context, @DrawableRes int resId,

boolean failIfNotKnown, @NonNull Drawable drawable) {

final ColorStateList tintList = getTintList(context, resId);

if (tintList != null) {

// First mutate the Drawable, then wrap it and set the tint list

if (DrawableUtils.canSafelyMutateDrawable(drawable)) {

drawable = drawable.mutate();

}

drawable = DrawableCompat.wrap(drawable);

DrawableCompat.setTintList(drawable, tintList);

// If there is a blending mode specified for the drawable, use it

final PorterDuff.Mode tintMode = getTintMode(resId);

if (tintMode != null) {

DrawableCompat.setTintMode(drawable, tintMode);

}

} else if (resId == R.drawable.abc_seekbar_track_material) {

LayerDrawable ld = (LayerDrawable) drawable;

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),

getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),

getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),

getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);

} else if (resId == R.drawable.abc_ratingbar_material

|| resId == R.drawable.abc_ratingbar_indicator_material

|| resId == R.drawable.abc_ratingbar_small_material) {

LayerDrawable ld = (LayerDrawable) drawable;

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background),

getDisabledThemeAttrColor(context, R.attr.colorControlNormal),

DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress),

getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);

setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress),

getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE);

} else {

final boolean tinted = tintDrawableUsingColorFilter(context, resId, drawable);

if (!tinted && failIfNotKnown) {

// If we didn't tint using a ColorFilter, and we're set to fail if we don't

// know the id, return null

drawable = null;

}

}

return drawable;

}

private Drawable loadDrawableFromDelegates(@NonNull Context context, @DrawableRes int resId) {

if (mDelegates != null && !mDelegates.isEmpty()) {

if (mKnownDrawableIdTags != null) {

final String cachedTagName = mKnownDrawableIdTags.get(resId);

if (SKIP_DRAWABLE_TAG.equals(cachedTagName)

|| (cachedTagName != null && mDelegates.get(cachedTagName) == null)) {

// If we don't have a delegate for the drawable tag, or we've been set to

// skip it, fail fast and return null

if (DEBUG) {

Log.d(TAG, "[loadDrawableFromDelegates] Skipping drawable: "

+ context.getResources().getResourceName(resId));

}

return null;

}

} else {

// Create an id cache as we'll need one later

mKnownDrawableIdTags = new SparseArrayCompat<>();

}

if (mTypedValue == null) {

mTypedValue = new TypedValue();

}

final TypedValue tv = mTypedValue;

final Resources res = context.getResources();

res.getValue(resId, tv, true);

final long key = createCacheKey(tv);

Drawable dr = getCachedDrawable(context, key);

if (dr != null) {

if (DEBUG) {

Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +

context.getResources().getResourceName(resId));

}

// We have a cached drawable, return it!

return dr;

}

if (tv.string != null && tv.string.toString().endsWith(".xml")) {

// If the resource is an XML file, let's try and parse it

try {

@SuppressLint("ResourceType") final XmlPullParser parser = res.getXml(resId);

final AttributeSet attrs = Xml.asAttributeSet(parser);

int type;

while ((type = parser.next()) != XmlPullParser.START_TAG &&

type != XmlPullParser.END_DOCUMENT) {

// Empty loop

}

if (type != XmlPullParser.START_TAG) {

throw new XmlPullParserException("No start tag found");

}

final String tagName = parser.getName();

// Add the tag name to the cache

mKnownDrawableIdTags.append(resId, tagName);

// Now try and find a delegate for the tag name and inflate if found

final InflateDelegate delegate = mDelegates.get(tagName);

if (delegate != null) {

dr = delegate.createFromXmlInner(context, parser, attrs,

context.getTheme());

}

if (dr != null) {

// Add it to the drawable cache

dr.setChangingConfigurations(tv.changingConfigurations);

if (addDrawableToCache(context, key, dr) && DEBUG) {

Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +

context.getResources().getResourceName(resId));

}

}

} catch (Exception e) {

Log.e(TAG, "Exception while inflating drawable", e);

}

}

if (dr == null) {

// If we reach here then the delegate inflation of the resource failed. Mark it as

// bad so we skip the id next time

mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);

}

return dr;

}

return null;

}

private Drawable getCachedDrawable(@NonNull final Context context, final long key) {

synchronized (mDrawableCacheLock) {

final LongSparseArray> cache

= mDrawableCaches.get(context);

if (cache == null) {

return null;

}

final WeakReference wr = cache.get(key);

if (wr != null) {

// We have the key, and the secret

ConstantState entry = wr.get();

if (entry != null) {

return entry.newDrawable(context.getResources());

} else {

// Our entry has been purged

cache.delete(key);

}

}

}

return null;

}

private boolean addDrawableToCache(@NonNull final Context context, final long key,

@NonNull final Drawable drawable) {

final ConstantState cs = drawable.getConstantState();

if (cs != null) {

synchronized (mDrawableCacheLock) {

LongSparseArray> cache = mDrawableCaches.get(context);

if (cache == null) {

cache = new LongSparseArray<>();

mDrawableCaches.put(context, cache);

}

cache.put(key, new WeakReference<>(cs));

}

return true;

}

return false;

}

Drawable onDrawableLoadedFromResources(@NonNull Context context,

@NonNull VectorEnabledTintResources resources, @DrawableRes final int resId) {

Drawable drawable = loadDrawableFromDelegates(context, resId);

if (drawable == null) {

drawable = resources.superGetDrawable(resId);

}

if (drawable != null) {

return tintDrawable(context, resId, false, drawable);

}

return null;

}

static boolean tintDrawableUsingColorFilter(@NonNull Context context,

@DrawableRes final int resId, @NonNull Drawable drawable) {

PorterDuff.Mode tintMode = DEFAULT_MODE;

boolean colorAttrSet = false;

int colorAttr = 0;

int alpha = -1;

if (arrayContains(COLORFILTER_TINT_COLOR_CONTROL_NORMAL, resId)) {

colorAttr = R.attr.colorControlNormal;

colorAttrSet = true;

} else if (arrayContains(COLORFILTER_COLOR_CONTROL_ACTIVATED, resId)) {

colorAttr = R.attr.colorControlActivated;

colorAttrSet = true;

} else if (arrayContains(COLORFILTER_COLOR_BACKGROUND_MULTIPLY, resId)) {

colorAttr = android.R.attr.colorBackground;

colorAttrSet = true;

tintMode = PorterDuff.Mode.MULTIPLY;

} else if (resId == R.drawable.abc_list_divider_mtrl_alpha) {

colorAttr = android.R.attr.colorForeground;

colorAttrSet = true;

alpha = Math.round(0.16f * 255);

} else if (resId == R.drawable.abc_dialog_material_background) {

colorAttr = android.R.attr.colorBackground;

colorAttrSet = true;

}

if (colorAttrSet) {

if (DrawableUtils.canSafelyMutateDrawable(drawable)) {

drawable = drawable.mutate();

}

final int color = getThemeAttrColor(context, colorAttr);

drawable.setColorFilter(getPorterDuffColorFilter(color, tintMode));

if (alpha != -1) {

drawable.setAlpha(alpha);

}

if (DEBUG) {

Log.d(TAG, "[tintDrawableUsingColorFilter] Tinted "

+ context.getResources().getResourceName(resId) +

" with color: #" + Integer.toHexString(color));

}

return true;

}

return false;

}

private void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {

if (mDelegates == null) {

mDelegates = new ArrayMap<>();

}

mDelegates.put(tagName, delegate);

}

private void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {

if (mDelegates != null && mDelegates.get(tagName) == delegate) {

mDelegates.remove(tagName);

}

}

private static boolean arrayContains(int[] array, int value) {

for (int id : array) {

if (id == value) {

return true;

}

}

return false;

}

static PorterDuff.Mode getTintMode(final int resId) {

PorterDuff.Mode mode = null;

if (resId == R.drawable.abc_switch_thumb_material) {

mode = PorterDuff.Mode.MULTIPLY;

}

return mode;

}

ColorStateList getTintList(@NonNull Context context, @DrawableRes int resId) {

// Try the cache first (if it exists)

ColorStateList tint = getTintListFromCache(context, resId);

if (tint == null) {

// ...if the cache did not contain a color state list, try and create one

if (resId == R.drawable.abc_edit_text_material) {

tint = getColorStateList(context, R.color.abc_tint_edittext);

} else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {

tint = getColorStateList(context, R.color.abc_tint_switch_track);

} else if (resId == R.drawable.abc_switch_thumb_material) {

tint = createSwitchThumbColorStateList(context);

} else if (resId == R.drawable.abc_btn_default_mtrl_shape) {

tint = createDefaultButtonColorStateList(context);

} else if (resId == R.drawable.abc_btn_borderless_material) {

tint = createBorderlessButtonColorStateList(context);

} else if (resId == R.drawable.abc_btn_colored_material) {

tint = createColoredButtonColorStateList(context);

} else if (resId == R.drawable.abc_spinner_mtrl_am_alpha

|| resId == R.drawable.abc_spinner_textfield_background_material) {

tint = getColorStateList(context, R.color.abc_tint_spinner);

} else if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) {

tint = getThemeAttrColorStateList(context, R.attr.colorControlNormal);

} else if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {

tint = getColorStateList(context, R.color.abc_tint_default);

} else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) {

tint = getColorStateList(context, R.color.abc_tint_btn_checkable);

} else if (resId == R.drawable.abc_seekbar_thumb_material) {

tint = getColorStateList(context, R.color.abc_tint_seek_thumb);

}

if (tint != null) {

addTintListToCache(context, resId, tint);

}

}

return tint;

}

private ColorStateList getTintListFromCache(@NonNull Context context, @DrawableRes int resId) {

if (mTintLists != null) {

final SparseArrayCompat tints = mTintLists.get(context);

return tints != null ? tints.get(resId) : null;

}

return null;

}

private void addTintListToCache(@NonNull Context context, @DrawableRes int resId,

@NonNull ColorStateList tintList) {

if (mTintLists == null) {

mTintLists = new WeakHashMap<>();

}

SparseArrayCompat themeTints = mTintLists.get(context);

if (themeTints == null) {

themeTints = new SparseArrayCompat<>();

mTintLists.put(context, themeTints);

}

themeTints.append(resId, tintList);

}

private ColorStateList createDefaultButtonColorStateList(@NonNull Context context) {

return createButtonColorStateList(context,

getThemeAttrColor(context, R.attr.colorButtonNormal));

}

private ColorStateList createBorderlessButtonColorStateList(@NonNull Context context) {

// We ignore the custom tint for borderless buttons

return createButtonColorStateList(context, Color.TRANSPARENT);

}

private ColorStateList createColoredButtonColorStateList(@NonNull Context context) {

return createButtonColorStateList(context,

getThemeAttrColor(context, R.attr.colorAccent));

}

private ColorStateList createButtonColorStateList(@NonNull final Context context,

@ColorInt final int baseColor) {

final int[][] states = new int[4][];

final int[] colors = new int[4];

int i = 0;

final int colorControlHighlight = getThemeAttrColor(context, R.attr.colorControlHighlight);

final int disabledColor = getDisabledThemeAttrColor(context, R.attr.colorButtonNormal);

// Disabled state

states[i] = ThemeUtils.DISABLED_STATE_SET;

colors[i] = disabledColor;

i++;

states[i] = ThemeUtils.PRESSED_STATE_SET;

colors[i] = compositeColors(colorControlHighlight, baseColor);

i++;

states[i] = ThemeUtils.FOCUSED_STATE_SET;

colors[i] = compositeColors(colorControlHighlight, baseColor);

i++;

// Default enabled state

states[i] = ThemeUtils.EMPTY_STATE_SET;

colors[i] = baseColor;

i++;

return new ColorStateList(states, colors);

}

private ColorStateList createSwitchThumbColorStateList(Context context) {

final int[][] states = new int[3][];

final int[] colors = new int[3];

int i = 0;

final ColorStateList thumbColor = getThemeAttrColorStateList(context,

R.attr.colorSwitchThumbNormal);

if (thumbColor != null && thumbColor.isStateful()) {

// If colorSwitchThumbNormal is a valid ColorStateList, extract the default and

// disabled colors from it

// Disabled state

states[i] = ThemeUtils.DISABLED_STATE_SET;

colors[i] = thumbColor.getColorForState(states[i], 0);

i++;

states[i] = ThemeUtils.CHECKED_STATE_SET;

colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);

i++;

// Default enabled state

states[i] = ThemeUtils.EMPTY_STATE_SET;

colors[i] = thumbColor.getDefaultColor();

i++;

} else {

// Else we'll use an approximation using the default disabled alpha

// Disabled state

states[i] = ThemeUtils.DISABLED_STATE_SET;

colors[i] = getDisabledThemeAttrColor(context, R.attr.colorSwitchThumbNormal);

i++;

states[i] = ThemeUtils.CHECKED_STATE_SET;

colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);

i++;

// Default enabled state

states[i] = ThemeUtils.EMPTY_STATE_SET;

colors[i] = getThemeAttrColor(context, R.attr.colorSwitchThumbNormal);

i++;

}

return new ColorStateList(states, colors);

}

private static class ColorFilterLruCache extends LruCache {

public ColorFilterLruCache(int maxSize) {

super(maxSize);

}

PorterDuffColorFilter get(int color, PorterDuff.Mode mode) {

return get(generateCacheKey(color, mode));

}

PorterDuffColorFilter put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter) {

return put(generateCacheKey(color, mode), filter);

}

private static int generateCacheKey(int color, PorterDuff.Mode mode) {

int hashCode = 1;

hashCode = 31 * hashCode + color;

hashCode = 31 * hashCode + mode.hashCode();

return hashCode;

}

}

static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {

if (DrawableUtils.canSafelyMutateDrawable(drawable)

&& drawable.mutate() != drawable) {

Log.d(TAG, "Mutated drawable is not the same instance as the input.");

return;

}

if (tint.mHasTintList || tint.mHasTintMode) {

drawable.setColorFilter(createTintFilter(

tint.mHasTintList ? tint.mTintList : null,

tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE,

state));

} else {

drawable.clearColorFilter();

}

if (Build.VERSION.SDK_INT <= 23) {

// Pre-v23 there is no guarantee that a state change will invoke an invalidation,

// so we force it ourselves

drawable.invalidateSelf();

}

}

private static PorterDuffColorFilter createTintFilter(ColorStateList tint,

PorterDuff.Mode tintMode, final int[] state) {

if (tint == null || tintMode == null) {

return null;

}

final int color = tint.getColorForState(state, Color.TRANSPARENT);

return getPorterDuffColorFilter(color, tintMode);

}

public static PorterDuffColorFilter getPorterDuffColorFilter(int color, PorterDuff.Mode mode) {

// First, lets see if the cache already contains the color filter

PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);

if (filter == null) {

// Cache miss, so create a color filter and add it to the cache

filter = new PorterDuffColorFilter(color, mode);

COLOR_FILTER_CACHE.put(color, mode, filter);

}

return filter;

}

private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {

if (DrawableUtils.canSafelyMutateDrawable(d)) {

d = d.mutate();

}

d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode));

}

private void checkVectorDrawableSetup(@NonNull Context context) {

if (mHasCheckedVectorDrawableSetup) {

// We've already checked so return now...

return;

}

// Here we will check that a known Vector drawable resource inside AppCompat can be

// correctly decoded

mHasCheckedVectorDrawableSetup = true;

final Drawable d = getDrawable(context, R.drawable.abc_vector_test);

if (d == null || !isVectorDrawable(d)) {

mHasCheckedVectorDrawableSetup = false;

throw new IllegalStateException("This app has been built with an incorrect "

+ "configuration. Please configure your build for VectorDrawableCompat.");

}

}

private static boolean isVectorDrawable(@NonNull Drawable d) {

return d instanceof VectorDrawableCompat

|| PLATFORM_VD_CLAZZ.equals(d.getClass().getName());

}

private static class VdcInflateDelegate implements InflateDelegate {

VdcInflateDelegate() {

}

@Override

public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,

@NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {

try {

return VectorDrawableCompat

.createFromXmlInner(context.getResources(), parser, attrs, theme);

} catch (Exception e) {

Log.e("VdcInflateDelegate", "Exception while inflating ", e);

return null;

}

}

}

private static class AvdcInflateDelegate implements InflateDelegate {

AvdcInflateDelegate() {

}

@Override

public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,

@NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {

try {

return AnimatedVectorDrawableCompat

.createFromXmlInner(context, context.getResources(), parser, attrs, theme);

} catch (Exception e) {

Log.e("AvdcInflateDelegate", "Exception while inflating ", e);

return null;

}

}

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值