AppCompatDrawableManager.java revision e6c800a4b82cad69b8a6f6aaae0be47823eddf8f
1/* 2 * Copyright (C) 2014 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.v7.widget; 18 19import org.xmlpull.v1.XmlPullParser; 20import org.xmlpull.v1.XmlPullParserException; 21 22import android.content.Context; 23import android.content.res.ColorStateList; 24import android.content.res.Resources; 25import android.graphics.Color; 26import android.graphics.PorterDuff; 27import android.graphics.PorterDuffColorFilter; 28import android.graphics.drawable.Drawable; 29import android.graphics.drawable.Drawable.ConstantState; 30import android.graphics.drawable.DrawableContainer; 31import android.graphics.drawable.GradientDrawable; 32import android.graphics.drawable.InsetDrawable; 33import android.graphics.drawable.LayerDrawable; 34import android.graphics.drawable.StateListDrawable; 35import android.os.Build; 36import android.support.annotation.DrawableRes; 37import android.support.annotation.NonNull; 38import android.support.annotation.Nullable; 39import android.support.graphics.drawable.AnimatedVectorDrawableCompat; 40import android.support.graphics.drawable.VectorDrawableCompat; 41import android.support.v4.content.ContextCompat; 42import android.support.v4.graphics.ColorUtils; 43import android.support.v4.graphics.drawable.DrawableCompat; 44import android.support.v4.util.ArrayMap; 45import android.support.v4.util.LongSparseArray; 46import android.support.v4.util.LruCache; 47import android.support.v7.appcompat.R; 48import android.util.AttributeSet; 49import android.util.Log; 50import android.util.SparseArray; 51import android.util.TypedValue; 52import android.util.Xml; 53 54import java.lang.ref.WeakReference; 55import java.util.WeakHashMap; 56 57import static android.support.v7.widget.ColorStateListUtils.getColorStateList; 58import static android.support.v7.widget.ThemeUtils.getDisabledThemeAttrColor; 59import static android.support.v7.widget.ThemeUtils.getThemeAttrColor; 60import static android.support.v7.widget.ThemeUtils.getThemeAttrColorStateList; 61 62/** 63 * @hide 64 */ 65public final class AppCompatDrawableManager { 66 67 private interface InflateDelegate { 68 Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser, 69 @NonNull AttributeSet attrs, @Nullable Resources.Theme theme); 70 } 71 72 private static final String TAG = "AppCompatDrawableManager"; 73 private static final boolean DEBUG = false; 74 private static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN; 75 private static final String SKIP_DRAWABLE_TAG = "appcompat_skip_skip"; 76 77 private static AppCompatDrawableManager INSTANCE; 78 79 public static AppCompatDrawableManager get() { 80 if (INSTANCE == null) { 81 INSTANCE = new AppCompatDrawableManager(); 82 installDefaultInflateDelegates(INSTANCE); 83 } 84 return INSTANCE; 85 } 86 87 private static void installDefaultInflateDelegates(@NonNull AppCompatDrawableManager manager) { 88 final int sdk = Build.VERSION.SDK_INT; 89 if (sdk < 21) { 90 // We only want to use the automatic VectorDrawableCompat handling where it's 91 // needed: on devices running before Lollipop 92 manager.addDelegate("vector", new VdcInflateDelegate()); 93 94 if (sdk >= 11) { 95 // AnimatedVectorDrawableCompat only works on API v11+ 96 manager.addDelegate("animated-vector", new AvdcInflateDelegate()); 97 } 98 } 99 } 100 101 private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6); 102 103 /** 104 * Drawables which should be tinted with the value of {@code R.attr.colorControlNormal}, 105 * using the default mode using a raw color filter. 106 */ 107 private static final int[] COLORFILTER_TINT_COLOR_CONTROL_NORMAL = { 108 R.drawable.abc_textfield_search_default_mtrl_alpha, 109 R.drawable.abc_textfield_default_mtrl_alpha, 110 R.drawable.abc_ab_share_pack_mtrl_alpha 111 }; 112 113 /** 114 * Drawables which should be tinted with the value of {@code R.attr.colorControlNormal}, using 115 * {@link DrawableCompat}'s tinting functionality. 116 */ 117 private static final int[] TINT_COLOR_CONTROL_NORMAL = { 118 R.drawable.abc_ic_search_api_mtrl_alpha, 119 R.drawable.abc_ic_commit_search_api_mtrl_alpha, 120 }; 121 122 /** 123 * Drawables which should be tinted with the value of {@code R.attr.colorControlActivated}, 124 * using a color filter. 125 */ 126 private static final int[] COLORFILTER_COLOR_CONTROL_ACTIVATED = { 127 R.drawable.abc_textfield_activated_mtrl_alpha, 128 R.drawable.abc_textfield_search_activated_mtrl_alpha, 129 R.drawable.abc_cab_background_top_mtrl_alpha, 130 R.drawable.abc_text_cursor_material 131 }; 132 133 /** 134 * Drawables which should be tinted with the value of {@code android.R.attr.colorBackground}, 135 * using the {@link android.graphics.PorterDuff.Mode#MULTIPLY} mode and a color filter. 136 */ 137 private static final int[] COLORFILTER_COLOR_BACKGROUND_MULTIPLY = { 138 R.drawable.abc_popup_background_mtrl_mult, 139 R.drawable.abc_cab_background_internal_bg, 140 R.drawable.abc_menu_hardkey_panel_mtrl_mult 141 }; 142 143 /** 144 * Drawables which should be tinted using a state list containing values of 145 * {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated} 146 */ 147 private static final int[] TINT_COLOR_CONTROL_STATE_LIST = { 148 R.drawable.abc_tab_indicator_material, 149 R.drawable.abc_textfield_search_material, 150 R.drawable.abc_ratingbar_full_material 151 }; 152 153 /** 154 * Drawables which should be tinted using a state list containing values of 155 * {@code R.attr.colorControlNormal} and {@code R.attr.colorControlActivated} for the checked 156 * state. 157 */ 158 private static final int[] TINT_CHECKABLE_BUTTON_LIST = { 159 R.drawable.abc_btn_check_material, 160 R.drawable.abc_btn_radio_material 161 }; 162 163 private WeakHashMap<Context, SparseArray<ColorStateList>> mTintLists; 164 private ArrayMap<String, InflateDelegate> mDelegates; 165 private SparseArray<String> mKnownDrawableIdTags; 166 167 private final Object mDelegateDrawableCacheLock = new Object(); 168 private final WeakHashMap<Context, LongSparseArray<WeakReference<Drawable.ConstantState>>> 169 mDelegateDrawableCaches = new WeakHashMap<>(0); 170 171 private TypedValue mTypedValue; 172 173 public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) { 174 return getDrawable(context, resId, false); 175 } 176 177 public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId, 178 boolean failIfNotKnown) { 179 Drawable drawable = loadDrawableFromDelegates(context, resId); 180 if (drawable == null) { 181 drawable = ContextCompat.getDrawable(context, resId); 182 } 183 if (drawable != null) { 184 // Tint it if needed 185 drawable = tintDrawable(context, resId, failIfNotKnown, drawable); 186 } 187 if (drawable != null) { 188 // See if we need to 'fix' the drawable 189 DrawableUtils.fixDrawable(drawable); 190 } 191 return drawable; 192 } 193 194 private Drawable tintDrawable(@NonNull Context context, @DrawableRes int resId, 195 boolean failIfNotKnown, @NonNull Drawable drawable) { 196 final ColorStateList tintList = getTintList(context, resId); 197 if (tintList != null) { 198 // First mutate the Drawable, then wrap it and set the tint list 199 if (DrawableUtils.canSafelyMutateDrawable(drawable)) { 200 drawable = drawable.mutate(); 201 } 202 drawable = DrawableCompat.wrap(drawable); 203 DrawableCompat.setTintList(drawable, tintList); 204 205 // If there is a blending mode specified for the drawable, use it 206 final PorterDuff.Mode tintMode = getTintMode(resId); 207 if (tintMode != null) { 208 DrawableCompat.setTintMode(drawable, tintMode); 209 } 210 } else if (resId == R.drawable.abc_cab_background_top_material) { 211 return new LayerDrawable(new Drawable[]{ 212 getDrawable(context, R.drawable.abc_cab_background_internal_bg), 213 getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha) 214 }); 215 } else if (resId == R.drawable.abc_seekbar_track_material) { 216 LayerDrawable ld = (LayerDrawable) drawable; 217 setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background), 218 getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE); 219 setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress), 220 getThemeAttrColor(context, R.attr.colorControlNormal), DEFAULT_MODE); 221 setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress), 222 getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE); 223 } else if (resId == R.drawable.abc_ratingbar_indicator_material 224 || resId == R.drawable.abc_ratingbar_small_material) { 225 LayerDrawable ld = (LayerDrawable) drawable; 226 setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.background), 227 getDisabledThemeAttrColor(context, R.attr.colorControlNormal), 228 DEFAULT_MODE); 229 setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.secondaryProgress), 230 getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE); 231 setPorterDuffColorFilter(ld.findDrawableByLayerId(android.R.id.progress), 232 getThemeAttrColor(context, R.attr.colorControlActivated), DEFAULT_MODE); 233 } else { 234 final boolean tinted = tintDrawableUsingColorFilter(context, resId, drawable); 235 if (!tinted && failIfNotKnown) { 236 // If we didn't tint using a ColorFilter, and we're set to fail if we don't 237 // know the id, return null 238 drawable = null; 239 } 240 } 241 return drawable; 242 } 243 244 private Drawable loadDrawableFromDelegates(@NonNull Context context, @DrawableRes int resId) { 245 if (mDelegates != null && !mDelegates.isEmpty()) { 246 if (mKnownDrawableIdTags != null) { 247 final String cachedTagName = mKnownDrawableIdTags.get(resId); 248 if (SKIP_DRAWABLE_TAG.equals(cachedTagName) 249 || (cachedTagName != null && mDelegates.get(cachedTagName) == null)) { 250 // If we don't have a delegate for the drawable tag, or we've been set to 251 // skip it, fail fast and return null 252 if (DEBUG) { 253 Log.d(TAG, "[loadDrawableFromDelegates] Skipping drawable: " 254 + context.getResources().getResourceName(resId)); 255 } 256 return null; 257 } 258 } else { 259 // Create an id cache as we'll need one later 260 mKnownDrawableIdTags = new SparseArray<>(); 261 } 262 263 if (mTypedValue == null) { 264 mTypedValue = new TypedValue(); 265 } 266 267 final TypedValue tv = mTypedValue; 268 final Resources res = context.getResources(); 269 res.getValue(resId, tv, true); 270 271 final long key = (((long) tv.assetCookie) << 32) | tv.data; 272 273 Drawable dr = getCachedDelegateDrawable(context, key); 274 if (dr != null) { 275 if (DEBUG) { 276 Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " + 277 context.getResources().getResourceName(resId)); 278 } 279 // We have a cached drawable, return it! 280 return dr; 281 } 282 283 if (tv.string != null && tv.string.toString().endsWith(".xml")) { 284 // If the resource is an XML file, let's try and parse it 285 try { 286 final XmlPullParser parser = res.getXml(resId); 287 final AttributeSet attrs = Xml.asAttributeSet(parser); 288 int type; 289 while ((type = parser.next()) != XmlPullParser.START_TAG && 290 type != XmlPullParser.END_DOCUMENT) { 291 // Empty loop 292 } 293 if (type != XmlPullParser.START_TAG) { 294 throw new XmlPullParserException("No start tag found"); 295 } 296 297 final String tagName = parser.getName(); 298 // Add the tag name to the cache 299 mKnownDrawableIdTags.append(resId, tagName); 300 301 // Now try and find a delegate for the tag name and inflate if found 302 final InflateDelegate delegate = mDelegates.get(tagName); 303 if (delegate != null) { 304 dr = delegate.createFromXmlInner(context, parser, attrs, 305 context.getTheme()); 306 } 307 if (dr != null) { 308 // Add it to the drawable cache 309 dr.setChangingConfigurations(tv.changingConfigurations); 310 if (addCachedDelegateDrawable(context, key, dr) && DEBUG) { 311 Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " + 312 context.getResources().getResourceName(resId)); 313 } 314 } 315 } catch (Exception e) { 316 Log.e(TAG, "Exception while inflating drawable", e); 317 } 318 } 319 if (dr == null) { 320 // If we reach here then the delegate inflation of the resource failed. Mark it as 321 // bad so we skip the id next time 322 mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG); 323 } 324 return dr; 325 } 326 327 return null; 328 } 329 330 private Drawable getCachedDelegateDrawable(@NonNull final Context context, final long key) { 331 synchronized (mDelegateDrawableCacheLock) { 332 final LongSparseArray<WeakReference<ConstantState>> cache 333 = mDelegateDrawableCaches.get(context); 334 if (cache == null) { 335 return null; 336 } 337 338 final WeakReference<ConstantState> wr = cache.get(key); 339 if (wr != null) { 340 // We have the key, and the secret 341 ConstantState entry = wr.get(); 342 if (entry != null) { 343 return entry.newDrawable(context.getResources()); 344 } else { 345 // Our entry has been purged 346 cache.delete(key); 347 } 348 } 349 } 350 return null; 351 } 352 353 private boolean addCachedDelegateDrawable(@NonNull final Context context, final long key, 354 @NonNull final Drawable drawable) { 355 final ConstantState cs = drawable.getConstantState(); 356 if (cs != null) { 357 synchronized (mDelegateDrawableCacheLock) { 358 LongSparseArray<WeakReference<ConstantState>> cache 359 = mDelegateDrawableCaches.get(context); 360 if (cache == null) { 361 cache = new LongSparseArray<>(); 362 mDelegateDrawableCaches.put(context, cache); 363 } 364 cache.put(key, new WeakReference<ConstantState>(cs)); 365 } 366 return true; 367 } 368 return false; 369 } 370 371 public final Drawable onDrawableLoadedFromResources(@NonNull Context context, 372 @NonNull TintResources resources, @DrawableRes final int resId) { 373 Drawable drawable = loadDrawableFromDelegates(context, resId); 374 if (drawable == null) { 375 drawable = resources.superGetDrawable(resId); 376 } 377 if (drawable != null) { 378 return tintDrawable(context, resId, false, drawable); 379 } 380 return null; 381 } 382 383 private static boolean tintDrawableUsingColorFilter(@NonNull Context context, 384 @DrawableRes final int resId, @NonNull Drawable drawable) { 385 PorterDuff.Mode tintMode = DEFAULT_MODE; 386 boolean colorAttrSet = false; 387 int colorAttr = 0; 388 int alpha = -1; 389 390 if (arrayContains(COLORFILTER_TINT_COLOR_CONTROL_NORMAL, resId)) { 391 colorAttr = R.attr.colorControlNormal; 392 colorAttrSet = true; 393 } else if (arrayContains(COLORFILTER_COLOR_CONTROL_ACTIVATED, resId)) { 394 colorAttr = R.attr.colorControlActivated; 395 colorAttrSet = true; 396 } else if (arrayContains(COLORFILTER_COLOR_BACKGROUND_MULTIPLY, resId)) { 397 colorAttr = android.R.attr.colorBackground; 398 colorAttrSet = true; 399 tintMode = PorterDuff.Mode.MULTIPLY; 400 } else if (resId == R.drawable.abc_list_divider_mtrl_alpha) { 401 colorAttr = android.R.attr.colorForeground; 402 colorAttrSet = true; 403 alpha = Math.round(0.16f * 255); 404 } 405 406 if (colorAttrSet) { 407 if (DrawableUtils.canSafelyMutateDrawable(drawable)) { 408 drawable = drawable.mutate(); 409 } 410 411 final int color = getThemeAttrColor(context, colorAttr); 412 drawable.setColorFilter(getPorterDuffColorFilter(color, tintMode)); 413 414 if (alpha != -1) { 415 drawable.setAlpha(alpha); 416 } 417 418 if (DEBUG) { 419 Log.d(TAG, "[tintDrawableUsingColorFilter] Tinted " 420 + context.getResources().getResourceName(resId) + 421 " with color: #" + Integer.toHexString(color)); 422 } 423 return true; 424 } 425 return false; 426 } 427 428 private void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) { 429 if (mDelegates == null) { 430 mDelegates = new ArrayMap<>(); 431 } 432 mDelegates.put(tagName, delegate); 433 } 434 435 private void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) { 436 if (mDelegates != null && mDelegates.get(tagName) == delegate) { 437 mDelegates.remove(tagName); 438 } 439 } 440 441 private static boolean arrayContains(int[] array, int value) { 442 for (int id : array) { 443 if (id == value) { 444 return true; 445 } 446 } 447 return false; 448 } 449 450 final PorterDuff.Mode getTintMode(final int resId) { 451 PorterDuff.Mode mode = null; 452 453 if (resId == R.drawable.abc_switch_thumb_material) { 454 mode = PorterDuff.Mode.MULTIPLY; 455 } 456 457 return mode; 458 } 459 460 public final ColorStateList getTintList(@NonNull Context context, @DrawableRes int resId) { 461 // Try the cache first (if it exists) 462 ColorStateList tint = getTintListFromCache(context, resId); 463 464 if (tint == null) { 465 // ...if the cache did not contain a color state list, try and create one 466 if (resId == R.drawable.abc_edit_text_material) { 467 tint = getColorStateList(context, R.color.abc_tint_edittext); 468 } else if (resId == R.drawable.abc_switch_track_mtrl_alpha) { 469 tint = getColorStateList(context, R.color.abc_tint_switch_track); 470 } else if (resId == R.drawable.abc_switch_thumb_material) { 471 tint = getColorStateList(context, R.color.abc_tint_switch_thumb); 472 } else if (resId == R.drawable.abc_btn_default_mtrl_shape 473 || resId == R.drawable.abc_btn_borderless_material) { 474 tint = createDefaultButtonColorStateList(context); 475 } else if (resId == R.drawable.abc_btn_colored_material) { 476 tint = createColoredButtonColorStateList(context); 477 } else if (resId == R.drawable.abc_spinner_mtrl_am_alpha 478 || resId == R.drawable.abc_spinner_textfield_background_material) { 479 tint = getColorStateList(context, R.color.abc_tint_spinner); 480 } else if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) { 481 tint = getThemeAttrColorStateList(context, R.attr.colorControlNormal); 482 } else if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) { 483 tint = getColorStateList(context, R.color.abc_tint_default); 484 } else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) { 485 tint = getColorStateList(context, R.color.abc_tint_btn_checkable); 486 } else if (resId == R.drawable.abc_seekbar_thumb_material) { 487 tint = getColorStateList(context, R.color.abc_tint_seek_thumb); 488 } 489 490 if (tint != null) { 491 addTintListToCache(context, resId, tint); 492 } 493 } 494 return tint; 495 } 496 497 private ColorStateList getTintListFromCache(@NonNull Context context, @DrawableRes int resId) { 498 if (mTintLists != null) { 499 final SparseArray<ColorStateList> tints = mTintLists.get(context); 500 return tints != null ? tints.get(resId) : null; 501 } 502 return null; 503 } 504 505 private void addTintListToCache(@NonNull Context context, @DrawableRes int resId, 506 @NonNull ColorStateList tintList) { 507 if (mTintLists == null) { 508 mTintLists = new WeakHashMap<>(); 509 } 510 SparseArray<ColorStateList> themeTints = mTintLists.get(context); 511 if (themeTints == null) { 512 themeTints = new SparseArray<>(); 513 mTintLists.put(context, themeTints); 514 } 515 themeTints.append(resId, tintList); 516 } 517 518 private ColorStateList createDefaultButtonColorStateList(Context context) { 519 return createButtonColorStateList(context, R.attr.colorButtonNormal); 520 } 521 522 private ColorStateList createColoredButtonColorStateList(Context context) { 523 return createButtonColorStateList(context, R.attr.colorAccent); 524 } 525 526 private ColorStateList createButtonColorStateList(Context context, int baseColorAttr) { 527 final int[][] states = new int[4][]; 528 final int[] colors = new int[4]; 529 int i = 0; 530 531 final int baseColor = getThemeAttrColor(context, baseColorAttr); 532 final int colorControlHighlight = getThemeAttrColor(context, R.attr.colorControlHighlight); 533 534 // Disabled state 535 states[i] = ThemeUtils.DISABLED_STATE_SET; 536 colors[i] = getDisabledThemeAttrColor(context, R.attr.colorButtonNormal); 537 i++; 538 539 states[i] = ThemeUtils.PRESSED_STATE_SET; 540 colors[i] = ColorUtils.compositeColors(colorControlHighlight, baseColor); 541 i++; 542 543 states[i] = ThemeUtils.FOCUSED_STATE_SET; 544 colors[i] = ColorUtils.compositeColors(colorControlHighlight, baseColor); 545 i++; 546 547 // Default enabled state 548 states[i] = ThemeUtils.EMPTY_STATE_SET; 549 colors[i] = baseColor; 550 i++; 551 552 return new ColorStateList(states, colors); 553 } 554 555 private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> { 556 557 public ColorFilterLruCache(int maxSize) { 558 super(maxSize); 559 } 560 561 PorterDuffColorFilter get(int color, PorterDuff.Mode mode) { 562 return get(generateCacheKey(color, mode)); 563 } 564 565 PorterDuffColorFilter put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter) { 566 return put(generateCacheKey(color, mode), filter); 567 } 568 569 private static int generateCacheKey(int color, PorterDuff.Mode mode) { 570 int hashCode = 1; 571 hashCode = 31 * hashCode + color; 572 hashCode = 31 * hashCode + mode.hashCode(); 573 return hashCode; 574 } 575 } 576 577 public static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) { 578 if (DrawableUtils.canSafelyMutateDrawable(drawable) 579 && drawable.mutate() != drawable) { 580 Log.d(TAG, "Mutated drawable is not the same instance as the input."); 581 return; 582 } 583 584 if (tint.mHasTintList || tint.mHasTintMode) { 585 drawable.setColorFilter(createTintFilter( 586 tint.mHasTintList ? tint.mTintList : null, 587 tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE, 588 state)); 589 } else { 590 drawable.clearColorFilter(); 591 } 592 593 if (Build.VERSION.SDK_INT <= 23) { 594 // Pre-v23 there is no guarantee that a state change will invoke an invalidation, 595 // so we force it ourselves 596 drawable.invalidateSelf(); 597 } 598 } 599 600 private static PorterDuffColorFilter createTintFilter(ColorStateList tint, 601 PorterDuff.Mode tintMode, final int[] state) { 602 if (tint == null || tintMode == null) { 603 return null; 604 } 605 final int color = tint.getColorForState(state, Color.TRANSPARENT); 606 return getPorterDuffColorFilter(color, tintMode); 607 } 608 609 public static PorterDuffColorFilter getPorterDuffColorFilter(int color, PorterDuff.Mode mode) { 610 // First, lets see if the cache already contains the color filter 611 PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode); 612 613 if (filter == null) { 614 // Cache miss, so create a color filter and add it to the cache 615 filter = new PorterDuffColorFilter(color, mode); 616 COLOR_FILTER_CACHE.put(color, mode, filter); 617 } 618 619 return filter; 620 } 621 622 private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) { 623 if (DrawableUtils.canSafelyMutateDrawable(d)) { 624 d = d.mutate(); 625 } 626 d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode)); 627 } 628 629 private static class VdcInflateDelegate implements InflateDelegate { 630 @Override 631 public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser, 632 @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) { 633 try { 634 return VectorDrawableCompat 635 .createFromXmlInner(context.getResources(), parser, attrs, theme); 636 } catch (Exception e) { 637 Log.e("VdcInflateDelegate", "Exception while inflating <vector>", e); 638 return null; 639 } 640 } 641 } 642 643 private static class AvdcInflateDelegate implements InflateDelegate { 644 @Override 645 public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser, 646 @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) { 647 try { 648 return AnimatedVectorDrawableCompat 649 .createFromXmlInner(context, context.getResources(), parser, attrs, theme); 650 } catch (Exception e) { 651 Log.e("AvdcInflateDelegate", "Exception while inflating <animated-vector>", e); 652 return null; 653 } 654 } 655 } 656} 657