Java tutorial
/* * Copyright (C) 2015 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.atelier; import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.ColorStateList; 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.design.widget.FloatingActionButton; import android.support.v7.graphics.Palette; import android.support.v7.widget.CardView; import android.util.Pair; import android.view.View; import android.widget.ImageView; import android.widget.TextView; import com.musenkishi.atelier.swatch.Swatch; import com.musenkishi.atelier.swatch.VibrantSwatch; 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. * <p/> * Created by Freddie (Musenkishi) Lust-Hed on 2014-10-21. */ public class Atelier { 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 final int viewTagKey = R.string.atelier_view_tag; private static int TRUE = 1; private static int FALSE = 0; private static Handler uiHandler; private static Handler backgroundHandler; private static Map<String, Palette> paletteCache; private static ExecutorService executorService = Executors.newFixedThreadPool(MAX_CONCURRENT_THREADS); private static Handler.Callback callback = 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.from(pair.first).generate(); paletteCache.put(pair.second.getId(), palette); Message uiMessage = uiHandler.obtainMessage(); uiMessage.what = MSG_DISPLAY_PALETTE; uiMessage.obj = new Pair<>(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); callListener(pairDisplay.first, pairDisplay.second.getSwatch().getColor(pairDisplay.first), pairDisplay.second.getListener()); break; } return false; } }; private Atelier() { //Don't use } public static AtelierBuilder with(String id) { if (paletteCache == null) { paletteCache = Collections.synchronizedMap(new LRUMap<String, Palette>(MAX_ITEMS_IN_CACHE)); } return new AtelierBuilder(id); } private static void setupHandlers(Context context) { HandlerThread handlerThread = new HandlerThread("palette-loader-background"); handlerThread.start(); backgroundHandler = new Handler(handlerThread.getLooper(), callback); uiHandler = new Handler(context.getMainLooper(), callback); } private static void applyColorToView(PaletteTarget target, Palette palette, boolean fromCache) { if (!isViewRecycled(target)) { applyColorToView(target, target.getSwatch().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); } else if (target.getView() instanceof CardView) { applyColorToView((CardView) target.getView(), color, fromCache); } else if (target.getView() instanceof FloatingActionButton) { applyColorToView((FloatingActionButton) target.getView(), color, fromCache, target.shouldMaskDrawable()); } else if (target.getView() instanceof ImageView) { applyColorToView((ImageView) target.getView(), color, fromCache, target.shouldMaskDrawable()); } else { if (fromCache) { target.getView().setBackgroundColor(color); } 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(); } } private static void applyColorToView(final CardView cardView, int color, boolean fromCache) { if (fromCache) { cardView.setCardBackgroundColor(color); } else { Integer colorFrom = Color.parseColor("#FFFAFAFA"); //Default light CardView color. Integer colorTo = color; ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { cardView.setCardBackgroundColor((Integer) animator.getAnimatedValue()); } }); colorAnimation.setDuration(300); colorAnimation.start(); } } private static void applyColorToView(final FloatingActionButton floatingActionButton, int color, boolean fromCache, boolean shouldMask) { if (fromCache) { if (shouldMask) { if (floatingActionButton.getDrawable() != null) { floatingActionButton.getDrawable().mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY); } else if (floatingActionButton.getBackground() != null) { floatingActionButton.getBackground().mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY); } } else { ColorStateList colorStateList = ColorStateList.valueOf(color); floatingActionButton.setBackgroundTintList(colorStateList); } } else { if (shouldMask) { Integer colorFrom; ValueAnimator.AnimatorUpdateListener imageAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { if (floatingActionButton.getDrawable() != null) { floatingActionButton.getDrawable().mutate().setColorFilter( (Integer) valueAnimator.getAnimatedValue(), PorterDuff.Mode.MULTIPLY); } else if (floatingActionButton.getBackground() != null) { floatingActionButton.getBackground().mutate().setColorFilter( (Integer) valueAnimator.getAnimatedValue(), PorterDuff.Mode.MULTIPLY); } } }; ValueAnimator.AnimatorUpdateListener animatorUpdateListener; PaletteTag paletteTag = (PaletteTag) floatingActionButton.getTag(viewTagKey); animatorUpdateListener = imageAnimatorUpdateListener; colorFrom = paletteTag.getColor(); floatingActionButton.setTag(viewTagKey, 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 { Integer colorFrom = Color.parseColor("#FFFAFAFA"); ColorStateList colorStateList = floatingActionButton.getBackgroundTintList(); if (colorStateList != null) { colorFrom = colorStateList.getDefaultColor(); } Integer colorTo = color; ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animator) { int color = (Integer) animator.getAnimatedValue(); floatingActionButton.setBackgroundTintList(ColorStateList.valueOf(color)); } }); colorAnimation.setDuration(300); colorAnimation.start(); } } } private static void applyColorToView(final ImageView imageView, int color, boolean fromCache, boolean shouldMask) { if (fromCache) { if (shouldMask) { if (imageView.getDrawable() != null) { imageView.getDrawable().mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY); } else if (imageView.getBackground() != null) { imageView.getBackground().mutate().setColorFilter(color, PorterDuff.Mode.MULTIPLY); } } else { imageView.setBackgroundColor(color); } } else { if (shouldMask) { Integer colorFrom; ValueAnimator.AnimatorUpdateListener imageAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { if (imageView.getDrawable() != null) { imageView.getDrawable().mutate().setColorFilter( (Integer) valueAnimator.getAnimatedValue(), PorterDuff.Mode.MULTIPLY); } else if (imageView.getBackground() != null) { imageView.getBackground().mutate().setColorFilter( (Integer) valueAnimator.getAnimatedValue(), PorterDuff.Mode.MULTIPLY); } } }; ValueAnimator.AnimatorUpdateListener animatorUpdateListener; PaletteTag paletteTag = (PaletteTag) imageView.getTag(viewTagKey); animatorUpdateListener = imageAnimatorUpdateListener; colorFrom = paletteTag.getColor(); imageView.setTag(viewTagKey, 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 (imageView.getBackground() == null) { preDrawable = new ColorDrawable(Color.TRANSPARENT); } else { preDrawable = imageView.getBackground(); } TransitionDrawable transitionDrawable = new TransitionDrawable( new Drawable[] { preDrawable, new ColorDrawable(color) }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { imageView.setBackground(transitionDrawable); } else { imageView.setBackgroundDrawable(transitionDrawable); } transitionDrawable.startTransition(300); } } } /** * 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/> * <p>Checks whether the view has been recycled or not.</p> * * @param target A {@link Atelier.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(viewTagKey) != null) { if (target.getView().getTag(viewTagKey) instanceof PaletteTag) { return !target.getId().equals(((PaletteTag) target.getView().getTag(viewTagKey)).getId()); } else { throw new NoPaletteTagFoundException("Atelier couldn't determine whether" + " a View has been reused or not. Atelier uses View.setTag(key, object) and " + "View.getTag(key) for keeping check if a View has been reused and it's " + "recommended to refrain from setting tags to Views Atelier is using."); } } else { return false; } } private static void callListener(Palette palette, int generatedColor, OnPaletteRenderedListener onPaletteRenderedListener) { if (onPaletteRenderedListener != null) { onPaletteRenderedListener.onRendered(palette, generatedColor); } } public interface OnPaletteRenderedListener { void onRendered(Palette palette, int generatedColor); } public static class AtelierBuilder { private String id; private Bitmap bitmap; private boolean maskDrawable; private int fallbackColor = Color.TRANSPARENT; private Swatch swatch = new VibrantSwatch(ColorType.BACKGROUND); private Palette palette; private OnPaletteRenderedListener onPaletteRenderedListener; public AtelierBuilder(String id) { this.id = id; } public AtelierBuilder load(Bitmap bitmap) { this.bitmap = bitmap; return this; } public AtelierBuilder load(Palette palette) { this.palette = palette; return this; } public AtelierBuilder mask() { maskDrawable = true; return this; } public AtelierBuilder fallbackColor(int fallbackColor) { this.fallbackColor = fallbackColor; return this; } public AtelierBuilder swatch(Swatch swatch) { this.swatch = swatch; return this; } public AtelierBuilder listener(OnPaletteRenderedListener onPaletteRenderedListener) { this.onPaletteRenderedListener = onPaletteRenderedListener; return this; } public void into(View... views) { for (View view : views) { start(view); } } private void start(View view) { if (uiHandler == null || backgroundHandler == null) { setupHandlers(view.getContext().getApplicationContext()); } final PaletteTarget paletteTarget = new PaletteTarget(id, swatch, view, maskDrawable, fallbackColor, onPaletteRenderedListener); if (palette != null) { paletteCache.put(paletteTarget.getId(), palette); applyColorToView(paletteTarget, palette, false); callListener(palette, swatch.getColor(palette), onPaletteRenderedListener); } else { if (paletteCache.get(id) != null) { Palette palette = paletteCache.get(id); applyColorToView(paletteTarget, palette, true); callListener(palette, swatch.getColor(palette), onPaletteRenderedListener); } else { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { executorService.submit(new PaletteRenderer(bitmap, paletteTarget)); } else { Message bgMessage = backgroundHandler.obtainMessage(); bgMessage.what = MSG_RENDER_PALETTE; bgMessage.obj = new Pair<>(bitmap, paletteTarget); backgroundHandler.sendMessage(bgMessage); } } } } } 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 palette = Palette.from(bitmap).generate(); paletteCache.put(paletteTarget.getId(), palette); PalettePresenter palettePresenter = new PalettePresenter(paletteTarget, palette, 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); callListener(palette, paletteTarget.getSwatch().getColor(palette), paletteTarget.getListener()); } } private static class PaletteTarget { private String id; private Swatch swatch; private View view; private boolean maskDrawable; private OnPaletteRenderedListener onPaletteRenderedListener; private PaletteTarget(String id, Swatch swatch, View view, boolean maskDrawable, int fallbackColor, OnPaletteRenderedListener onPaletteRenderedListener) { this.id = id; this.swatch = swatch; this.view = view; this.view.setTag(viewTagKey, new PaletteTag(this.id, fallbackColor)); this.maskDrawable = maskDrawable; this.onPaletteRenderedListener = onPaletteRenderedListener; } public String getId() { return id; } public Swatch getSwatch() { return swatch; } public View getView() { return view; } public boolean shouldMaskDrawable() { return maskDrawable; } public OnPaletteRenderedListener getListener() { return onPaletteRenderedListener; } } 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); } } }