com.bilibili.magicasakura.utils.TintManager.java Source code

Java tutorial

Introduction

Here is the source code for com.bilibili.magicasakura.utils.TintManager.java

Source

/*
 * Copyright (C) 2016 Bilibili
 *
 * 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 com.bilibili.magicasakura.utils;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.LruCache;
import android.support.v7.view.ContextThemeWrapper;
import android.util.Log;
import android.util.SparseArray;
import android.view.View;

import com.bilibili.magicasakura.drawables.FilterableStateListDrawable;

import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.WeakHashMap;

/**
 * @author xyczero617@gmail.com
 * @time 15/9/15
 */
public class TintManager {

    private static final String TAG = "TintManager";
    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 WeakHashMap<Context, com.bilibili.magicasakura.utils.TintManager> INSTANCE_CACHE = new WeakHashMap<>();
    private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6);

    private final Object mDrawableCacheLock = new Object();

    private WeakReference<Context> mContextRef;
    private SparseArray<ColorStateList> mCacheTintList;
    private SparseArray<WeakReference<Drawable.ConstantState>> mCacheDrawables;
    private SparseArray<String> mSkipDrawableIdTags;

    public static com.bilibili.magicasakura.utils.TintManager get(Context context) {
        if (context == null)
            return null;

        if (context instanceof ContextThemeWrapper) {
            context = ((ContextThemeWrapper) context).getBaseContext();
        }
        if (context instanceof android.view.ContextThemeWrapper) {
            context = ((android.view.ContextThemeWrapper) context).getBaseContext();
        }
        com.bilibili.magicasakura.utils.TintManager tm = INSTANCE_CACHE.get(context);
        if (tm == null) {
            tm = new com.bilibili.magicasakura.utils.TintManager(context);
            INSTANCE_CACHE.put(context, tm);
            printLog("[get TintManager] create new TintManager.");
        }
        return tm;
    }

    private TintManager(Context context) {
        mContextRef = new WeakReference<>(context);
    }

    public static void clearTintCache() {
        for (Map.Entry<Context, com.bilibili.magicasakura.utils.TintManager> entry : INSTANCE_CACHE.entrySet()) {
            com.bilibili.magicasakura.utils.TintManager tm = entry.getValue();
            if (tm != null)
                tm.clear();
        }
        COLOR_FILTER_CACHE.evictAll();
    }

    private void clear() {
        if (mCacheTintList != null) {
            mCacheTintList.clear();
        }
        if (mCacheDrawables != null) {
            mCacheDrawables.clear();
        }
        if (mSkipDrawableIdTags != null) {
            mSkipDrawableIdTags.clear();
        }
    }

    @Nullable
    public ColorStateList getColorStateList(@ColorRes int resId) {
        if (resId == 0)
            return null;

        final Context context = mContextRef.get();
        if (context == null)
            return null;

        ColorStateList colorStateList = mCacheTintList != null ? mCacheTintList.get(resId) : null;
        if (colorStateList == null) {
            colorStateList = ColorStateListUtils.createColorStateList(context, resId);
            if (colorStateList != null) {
                if (mCacheTintList == null) {
                    mCacheTintList = new SparseArray<>();
                }
                mCacheTintList.append(resId, colorStateList);
            }
        }
        return colorStateList;
    }

    @Nullable
    public Drawable getDrawable(@DrawableRes int resId) {
        final Context context = mContextRef.get();
        if (context == null)
            return null;

        if (resId == 0)
            return null;
        if (mSkipDrawableIdTags != null) {
            final String cachedTagName = mSkipDrawableIdTags.get(resId);
            if (SKIP_DRAWABLE_TAG.equals(cachedTagName)) {
                printLog("[Match Skip DrawableTag] Skip the drawable which is matched with the skip tag.");
                return null;
            }
        } else {
            // Create an id cache as we'll need one later
            mSkipDrawableIdTags = new SparseArray<>();
        }

        // Try the cache first (if it exists)
        Drawable drawable = getCacheDrawable(context, resId);
        if (drawable == null) {
            drawable = DrawableUtils.createDrawable(context, resId);
            if (drawable != null && !(drawable instanceof ColorDrawable)) {
                if (addCachedDrawable(resId, drawable)) {
                    printLog("[loadDrawable] Saved drawable to cache: "
                            + context.getResources().getResourceName(resId));
                }
            }
        }

        if (drawable == null) {
            mSkipDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);
        }
        return drawable;
    }

    private Drawable getCacheDrawable(@NonNull final Context context, final int key) {
        synchronized (mDrawableCacheLock) {
            if (mCacheDrawables == null)
                return null;

            final WeakReference<Drawable.ConstantState> weakReference = mCacheDrawables.get(key);
            if (weakReference != null) {
                Drawable.ConstantState cs = weakReference.get();
                if (cs != null) {
                    printLog("[getCacheDrawable] Get drawable from cache: "
                            + context.getResources().getResourceName(key));
                    return cs.newDrawable();
                } else {
                    mCacheDrawables.delete(key);
                }
            }
        }
        return null;
    }

    private boolean addCachedDrawable(final int key, @NonNull final Drawable drawable) {
        if (drawable instanceof FilterableStateListDrawable) {
            return false;
        }
        final Drawable.ConstantState cs = drawable.getConstantState();
        if (cs != null) {
            synchronized (mDrawableCacheLock) {
                if (mCacheDrawables == null) {
                    mCacheDrawables = new SparseArray<>();
                }
                mCacheDrawables.put(key, new WeakReference<>(cs));
            }
            return true;
        }
        return false;
    }

    private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {

        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;
        }
    }

    public static void tintViewBackground(View view, com.bilibili.magicasakura.utils.TintInfo tint) {
        Drawable background;
        if (view == null || (background = view.getBackground()) == null)
            return;

        if (tint.mHasTintList || tint.mHasTintMode) {
            background.mutate();
            if (background instanceof ColorDrawable) {
                ((ColorDrawable) background).setColor(ThemeUtils.replaceColor(view.getContext(), tint.mTintList
                        .getColorForState(view.getDrawableState(), tint.mTintList.getDefaultColor())));
            } else {
                background.setColorFilter(
                        createTintFilter(view.getContext(), tint.mHasTintList ? tint.mTintList : null,
                                tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE, view.getDrawableState()));
            }
        } else {
            background.clearColorFilter();
        }

        if (Build.VERSION.SDK_INT <= 23) {
            // On Gingerbread, GradientDrawable does not invalidate itself when it's ColorFilter
            // has changed, so we need to force an invalidation
            background.invalidateSelf();
        }
    }

    public static void tintViewDrawable(View view, Drawable drawable, TintInfo tint) {
        if (view == null || drawable == null)
            return;
        if (tint.mHasTintList || tint.mHasTintMode) {
            drawable.mutate();
            if (drawable instanceof ColorDrawable) {
                ((ColorDrawable) drawable).setColor(ThemeUtils.replaceColor(view.getContext(), tint.mTintList
                        .getColorForState(view.getDrawableState(), tint.mTintList.getDefaultColor())));
            } else {
                drawable.setColorFilter(
                        createTintFilter(view.getContext(), tint.mHasTintList ? tint.mTintList : null,
                                tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE, view.getDrawableState()));
            }
        } else {
            drawable.clearColorFilter();
        }

        if (Build.VERSION.SDK_INT <= 23) {
            // On Gingerbread, GradientDrawable does not invalidate itself when it's ColorFilter
            // has changed, so we need to force an invalidation
            drawable.invalidateSelf();
        }
    }

    private static PorterDuffColorFilter createTintFilter(Context context, ColorStateList tint,
            PorterDuff.Mode tintMode, final int[] state) {
        if (tint == null || tintMode == null) {
            return null;
        }
        final int color = ThemeUtils.replaceColor(context, tint.getColorForState(state, tint.getDefaultColor()));
        return getPorterDuffColorFilter(color, tintMode);
    }

    private 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 printLog(String msg) {
        if (DEBUG) {
            Log.i(TAG, msg);
        }
    }
}