com.musenkishi.wally.util.PaletteLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.musenkishi.wally.util.PaletteLoader.java

Source

/*
 * Copyright (C) 2014 Freddie (Musenkishi) Lust-Hed
 *
 * 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.musenkishi.wally.util;

import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.v7.graphics.Palette;
import android.util.Pair;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

import org.apache.commons.collections4.map.LRUMap;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * A class that can apply colors to Views lazily.
 * Can be used in ListViews.
 *
 * Created by Freddie (Musenkishi) Lust-Hed on 2014-10-21.
 */
public class PaletteLoader {

    private static final int MSG_RENDER_PALETTE = 4194;
    private static final int MSG_DISPLAY_PALETTE = 4195;

    private static final int MAX_ITEMS_IN_CACHE = 100;
    private static final int MAX_CONCURRENT_THREADS = 5;

    private static int TRUE = 1;
    private static int FALSE = 0;

    private static Handler uiHandler;
    private static Handler backgroundHandler;

    private static Map<String, Palette> colorSchemeCache;
    private static ExecutorService executorService = Executors.newFixedThreadPool(MAX_CONCURRENT_THREADS);

    private PaletteLoader() {
        //Don't use
    }

    public static PaletteBuilder with(Context context, String id) {
        if (colorSchemeCache == null) {
            colorSchemeCache = Collections.synchronizedMap(new LRUMap<String, Palette>(MAX_ITEMS_IN_CACHE));
        }
        if (uiHandler == null || backgroundHandler == null) {
            setupHandlers(context);
        }
        return new PaletteBuilder(id);
    }

    private static void setupHandlers(Context context) {
        HandlerThread handlerThread = new HandlerThread("palette-loader-background");
        handlerThread.start();
        backgroundHandler = new Handler(handlerThread.getLooper(), sCallback);
        uiHandler = new Handler(context.getMainLooper(), sCallback);
    }

    private static Handler.Callback sCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message message) {

            switch (message.what) {

            case MSG_RENDER_PALETTE:
                Pair<Bitmap, PaletteTarget> pair = (Pair<Bitmap, PaletteTarget>) message.obj;
                if (pair != null && !pair.first.isRecycled()) {
                    Palette palette = Palette.generate(pair.first);

                    colorSchemeCache.put(pair.second.getId(), palette);

                    Message uiMessage = uiHandler.obtainMessage();
                    uiMessage.what = MSG_DISPLAY_PALETTE;
                    uiMessage.obj = new Pair<Palette, PaletteTarget>(palette, pair.second);
                    uiMessage.arg1 = FALSE;

                    uiHandler.sendMessage(uiMessage);
                }
                break;

            case MSG_DISPLAY_PALETTE:
                Pair<Palette, PaletteTarget> pairDisplay = (Pair<Palette, PaletteTarget>) message.obj;
                boolean fromCache = message.arg1 == TRUE;
                applyColorToView(pairDisplay.second, pairDisplay.first, fromCache);

                break;

            }

            return false;
        }
    };

    public static class PaletteBuilder {

        private String id;
        private Bitmap bitmap;
        private boolean maskDrawable;
        private int fallbackColor = Color.TRANSPARENT;
        private PaletteRequest paletteRequest = new PaletteRequest(PaletteRequest.SwatchType.REGULAR_VIBRANT,
                PaletteRequest.SwatchColor.BACKGROUND);
        private Palette palette;

        public PaletteBuilder(String id) {
            this.id = id;
        }

        public PaletteBuilder load(Bitmap bitmap) {
            this.bitmap = bitmap;
            return this;
        }

        public PaletteBuilder load(Palette colorScheme) {
            this.palette = colorScheme;
            return this;
        }

        public PaletteBuilder mask() {
            maskDrawable = true;
            return this;
        }

        public PaletteBuilder fallbackColor(int fallbackColor) {
            this.fallbackColor = fallbackColor;
            return this;
        }

        public PaletteBuilder setPaletteRequest(PaletteRequest paletteRequest) {
            this.paletteRequest = paletteRequest;
            return this;
        }

        public void into(View view) {
            final PaletteTarget paletteTarget = new PaletteTarget(id, paletteRequest, view, maskDrawable,
                    fallbackColor);
            if (palette != null) {
                colorSchemeCache.put(paletteTarget.getId(), palette);
                applyColorToView(paletteTarget, palette, false);
            } else {
                if (colorSchemeCache.get(id) != null) {
                    Palette palette = colorSchemeCache.get(id);
                    applyColorToView(paletteTarget, palette, true);
                } else {
                    if (Build.VERSION.SDK_INT >= 21) {
                        executorService.submit(new PaletteRenderer(bitmap, paletteTarget));
                    } else {
                        Message bgMessage = backgroundHandler.obtainMessage();
                        bgMessage.what = MSG_RENDER_PALETTE;
                        bgMessage.obj = new Pair<Bitmap, PaletteTarget>(bitmap, paletteTarget);
                        backgroundHandler.sendMessage(bgMessage);
                    }
                }
            }
        }
    }

    private static void applyColorToView(PaletteTarget target, Palette palette, boolean fromCache) {
        if (!isViewRecycled(target)) {
            applyColorToView(target, target.getPaletteRequest().getColor(palette), fromCache);
        }
    }

    private static void applyColorToView(final PaletteTarget target, int color, boolean fromCache) {
        if (target.getView() instanceof TextView) {
            applyColorToView((TextView) target.getView(), color, fromCache);
            return;
        }
        if (fromCache) {
            if (target.getView() instanceof ImageView && target.shouldMaskDrawable()) {
                ((ImageView) target.getView()).getDrawable().mutate().setColorFilter(color,
                        PorterDuff.Mode.MULTIPLY);
            } else {
                target.getView().setBackgroundColor(color);
            }
        } else {
            if (target.getView() instanceof ImageView && target.shouldMaskDrawable()) {
                Integer colorFrom;
                ValueAnimator.AnimatorUpdateListener imageAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
                        ((ImageView) target.getView()).getDrawable().mutate().setColorFilter(
                                (Integer) valueAnimator.getAnimatedValue(), PorterDuff.Mode.MULTIPLY);
                    }
                };
                ValueAnimator.AnimatorUpdateListener animatorUpdateListener;

                PaletteTag paletteTag = (PaletteTag) target.getView().getTag();
                animatorUpdateListener = imageAnimatorUpdateListener;
                colorFrom = paletteTag.getColor();
                target.getView().setTag(new PaletteTag(paletteTag.getId(), color));

                Integer colorTo = color;
                ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
                colorAnimation.addUpdateListener(animatorUpdateListener);
                colorAnimation.setDuration(300);
                colorAnimation.start();
            } else {

                Drawable preDrawable;

                if (target.getView().getBackground() == null) {
                    preDrawable = new ColorDrawable(Color.TRANSPARENT);
                } else {
                    preDrawable = target.getView().getBackground();
                }

                TransitionDrawable transitionDrawable = new TransitionDrawable(
                        new Drawable[] { preDrawable, new ColorDrawable(color) });
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                    target.getView().setBackground(transitionDrawable);
                } else {
                    target.getView().setBackgroundDrawable(transitionDrawable);
                }
                transitionDrawable.startTransition(300);
            }
        }
    }

    private static void applyColorToView(final TextView textView, int color, boolean fromCache) {
        if (fromCache) {
            textView.setTextColor(color);
        } else {
            Integer colorFrom = textView.getCurrentTextColor();
            Integer colorTo = color;
            ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
            colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animator) {
                    textView.setTextColor((Integer) animator.getAnimatedValue());
                }
            });
            colorAnimation.start();
        }
    }

    /**
     * Is it null? Is that null? What about that one? Is that null too? What about this one?
     * And this one? Is this null? Is null even null? How can null be real if our eyes aren't real?
     *
     * <p>Checks whether the view has been recycled or not.</p>
     *
     * @param target A {@link com.musenkishi.wally.util.PaletteLoader.PaletteTarget} to check
     * @return true is view has been recycled, otherwise false.
     */
    private static boolean isViewRecycled(PaletteTarget target) {

        if (target != null && target.getId() != null && target.getView() != null
                && target.getView().getTag() != null) {
            if (target.getView().getTag() instanceof PaletteTag) {
                return !target.getId().equals(((PaletteTag) target.getView().getTag()).getId());
            } else {
                throw new NoPaletteTagFoundException("PaletteLoader couldn't determine whether"
                        + " a View has been reused or not. PaletteLoader uses View.setTag() and "
                        + "View.getTag() for keeping check if a View has been reused and it's "
                        + "recommended to refrain from setting tags to Views PaletteLoader is using.");
            }
        } else {
            return false;
        }
    }

    private static class PaletteRenderer implements Runnable {

        private Bitmap bitmap;
        private PaletteTarget paletteTarget;

        private PaletteRenderer(Bitmap bitmap, PaletteTarget paletteTarget) {
            this.bitmap = bitmap;
            this.paletteTarget = paletteTarget;
        }

        @Override
        public void run() {
            if (bitmap != null && !bitmap.isRecycled()) {
                Palette colorScheme = Palette.generate(bitmap);
                colorSchemeCache.put(paletteTarget.getId(), colorScheme);

                PalettePresenter palettePresenter = new PalettePresenter(paletteTarget, colorScheme, false);
                uiHandler.post(palettePresenter);
            }
        }
    }

    private static class PalettePresenter implements Runnable {

        private PaletteTarget paletteTarget;
        private Palette palette;
        private boolean fromCache;

        private PalettePresenter(PaletteTarget paletteTarget, Palette palette, boolean fromCache) {
            this.paletteTarget = paletteTarget;
            this.palette = palette;
            this.fromCache = fromCache;
        }

        @Override
        public void run() {
            applyColorToView(paletteTarget, palette, fromCache);
        }
    }

    private static class PaletteTarget {
        private String id;
        private PaletteRequest paletteRequest;
        private View view;
        private boolean maskDrawable;

        private PaletteTarget(String id, PaletteRequest paletteRequest, View view, boolean maskDrawable,
                int fallbackColor) {
            this.id = id;
            this.paletteRequest = paletteRequest;
            this.view = view;
            this.view.setTag(new PaletteTag(this.id, fallbackColor));
            this.maskDrawable = maskDrawable;
        }

        public String getId() {
            return id;
        }

        public PaletteRequest getPaletteRequest() {
            return paletteRequest;
        }

        public View getView() {
            return view;
        }

        public boolean shouldMaskDrawable() {
            return maskDrawable;
        }
    }

    private static class PaletteTag {
        private String id;
        private Integer color;

        private PaletteTag(String id, Integer color) {
            this.id = id;
            this.color = color;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public Integer getColor() {
            return color;
        }

        public void setColor(Integer color) {
            this.color = color;
        }
    }

    public static class NoPaletteTagFoundException extends NullPointerException {
        public NoPaletteTagFoundException() {
            super();
        }

        public NoPaletteTagFoundException(String message) {
            super(message);
        }
    }

}