package com.github.florent37.glidepalette; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.TransitionDrawable; import android.os.Build; import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.util.LruCache; import android.support.v4.util.Pair; import android.support.v7.graphics.Palette; import android.util.Log; import android.view.View; import android.widget.TextView; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.LinkedList; public abstract class BitmapPalette { private static final String TAG = "BitmapPalette"; public interface CallBack { void onPaletteLoaded(@Nullable Palette palette); } public interface PaletteBuilderInterceptor { @NonNull Palette.Builder intercept(Palette.Builder builder); } @IntDef({Profile.VIBRANT, Profile.VIBRANT_DARK, Profile.VIBRANT_LIGHT, Profile.MUTED, Profile.MUTED_DARK, Profile.MUTED_LIGHT}) @Retention(RetentionPolicy.SOURCE) public @interface Profile { int VIBRANT = 0; int VIBRANT_DARK = 1; int VIBRANT_LIGHT = 2; int MUTED = 3; int MUTED_DARK = 4; int MUTED_LIGHT = 5; } @IntDef({Swatch.RGB, Swatch.TITLE_TEXT_COLOR, Swatch.BODY_TEXT_COLOR}) @Retention(RetentionPolicy.SOURCE) public @interface Swatch { int RGB = 0; int TITLE_TEXT_COLOR = 1; int BODY_TEXT_COLOR = 2; } static final LruCache<String, Palette> CACHE = new LruCache<>(40); protected String url; protected LinkedList<PaletteTarget> targets = new LinkedList<>(); protected ArrayList<BitmapPalette.CallBack> callbacks = new ArrayList<>(); private PaletteBuilderInterceptor interceptor; private boolean skipCache; public BitmapPalette use(@Profile int paletteProfile) { this.targets.add(new PaletteTarget(paletteProfile)); return this; } protected BitmapPalette intoBackground(View view, @Swatch int paletteSwatch) { assertTargetsIsNotEmpty(); this.targets.getLast().targetsBackground.add(new Pair<>(view, paletteSwatch)); return this; } protected BitmapPalette intoTextColor(TextView textView, @Swatch int paletteSwatch) { assertTargetsIsNotEmpty(); this.targets.getLast().targetsText.add(new Pair<>(textView, paletteSwatch)); return this; } protected BitmapPalette crossfade(boolean crossfade) { assertTargetsIsNotEmpty(); this.targets.getLast().targetCrossfade = crossfade; return this; } protected BitmapPalette crossfade(boolean crossfade, int crossfadeSpeed) { assertTargetsIsNotEmpty(); this.targets.getLast().targetCrossfadeSpeed = crossfadeSpeed; return this.crossfade(crossfade); } private void assertTargetsIsNotEmpty() { if (this.targets.isEmpty()) { throw new UnsupportedOperationException("You must specify a palette with use(Profile.Profile)"); } } protected BitmapPalette intoCallBack(BitmapPalette.CallBack callBack) { if (callBack != null) callbacks.add(callBack); return this; } protected BitmapPalette skipPaletteCache(boolean skipCache) { this.skipCache = skipCache; return this; } protected BitmapPalette setPaletteBuilderInterceptor(PaletteBuilderInterceptor interceptor) { this.interceptor = interceptor; return this; } /* * Apply the Palette Profile & Swatch to our current targets * * palette the palette to apply * cacheHit true if the palette was retrieved from the cache, else false */ protected void apply(Palette palette, boolean cacheHit) { for (CallBack c : callbacks) { c.onPaletteLoaded(palette); } if (palette == null) return; for (PaletteTarget target : targets) { Palette.Swatch swatch = null; switch (target.paletteProfile) { case Profile.VIBRANT: swatch = palette.getVibrantSwatch(); break; case Profile.VIBRANT_DARK: swatch = palette.getDarkVibrantSwatch(); break; case Profile.VIBRANT_LIGHT: swatch = palette.getLightVibrantSwatch(); break; case Profile.MUTED: swatch = palette.getMutedSwatch(); break; case Profile.MUTED_DARK: swatch = palette.getDarkMutedSwatch(); break; case Profile.MUTED_LIGHT: swatch = palette.getLightMutedSwatch(); break; } if (swatch == null) { swatch = new Palette.Swatch(Color.BLACK, 1); } for (Pair<View, Integer> t : target.targetsBackground) { int color = getColor(swatch, t.second); //Only crossfade if we're not coming from a cache hit. if (!cacheHit && target.targetCrossfade) { crossfadeTargetBackground(target, t, color); } else { t.first.setBackgroundColor(color); } } for (Pair<TextView, Integer> t : target.targetsText) { int color = getColor(swatch, t.second); t.first.setTextColor(color); } target.clear(); this.callbacks = null; } } private void crossfadeTargetBackground(PaletteTarget target, Pair<View, Integer> t, int newColor) { final Drawable oldColor = t.first.getBackground(); final Drawable[] drawables = new Drawable[2]; drawables[0] = oldColor != null ? oldColor : new ColorDrawable(t.first.getSolidColor()); drawables[1] = new ColorDrawable(newColor); TransitionDrawable transitionDrawable = new TransitionDrawable(drawables); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { t.first.setBackground(transitionDrawable); } else { //noinspection deprecation t.first.setBackgroundDrawable(transitionDrawable); } transitionDrawable.startTransition(target.targetCrossfadeSpeed); } protected static int getColor(Palette.Swatch swatch, @Swatch int paletteSwatch) { if (swatch != null) { switch (paletteSwatch) { case Swatch.RGB: return swatch.getRgb(); case Swatch.TITLE_TEXT_COLOR: return swatch.getTitleTextColor(); case Swatch.BODY_TEXT_COLOR: return swatch.getBodyTextColor(); } } else { Log.e(TAG, "error while generating Palette, null palette returned"); } return 0; } protected void start(@NonNull final Bitmap bitmap) { final boolean skipCache = this.skipCache; if (!skipCache) { Palette palette = CACHE.get(url); if (palette != null) { apply(palette, true); return; } } Palette.Builder builder = new Palette.Builder(bitmap); if (interceptor != null) { builder = interceptor.intercept(builder); } builder.generate(new Palette.PaletteAsyncListener() { @Override public void onGenerated(Palette palette) { if (!skipCache) { CACHE.put(url, palette); } apply(palette, false); } }); } }