Home | History | Annotate | Download | only in drawable
      1 /*
      2  * Copyright (C) 2017 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 
     17 package com.android.internal.colorextraction.drawable;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ValueAnimator;
     22 import android.annotation.NonNull;
     23 import android.annotation.Nullable;
     24 import android.content.Context;
     25 import android.graphics.Canvas;
     26 import android.graphics.ColorFilter;
     27 import android.graphics.Paint;
     28 import android.graphics.PixelFormat;
     29 import android.graphics.RadialGradient;
     30 import android.graphics.Rect;
     31 import android.graphics.Shader;
     32 import android.graphics.Xfermode;
     33 import android.graphics.drawable.Drawable;
     34 import android.view.animation.DecelerateInterpolator;
     35 
     36 import com.android.internal.annotations.VisibleForTesting;
     37 import com.android.internal.colorextraction.ColorExtractor;
     38 import com.android.internal.graphics.ColorUtils;
     39 
     40 /**
     41  * Draws a gradient based on a Palette
     42  */
     43 public class GradientDrawable extends Drawable {
     44     private static final String TAG = "GradientDrawable";
     45 
     46     private static final float CENTRALIZED_CIRCLE_1 = -2;
     47     private static final int GRADIENT_RADIUS = 480; // in dp
     48     private static final long COLOR_ANIMATION_DURATION = 2000;
     49 
     50     private int mAlpha = 255;
     51 
     52     private float mDensity;
     53     private final Paint mPaint;
     54     private final Rect mWindowBounds;
     55     private final Splat mSplat;
     56 
     57     private int mMainColor;
     58     private int mSecondaryColor;
     59     private ValueAnimator mColorAnimation;
     60     private int mMainColorTo;
     61     private int mSecondaryColorTo;
     62 
     63     public GradientDrawable(@NonNull Context context) {
     64         mDensity = context.getResources().getDisplayMetrics().density;
     65         mSplat = new Splat(0.50f, 1.00f, GRADIENT_RADIUS, CENTRALIZED_CIRCLE_1);
     66         mWindowBounds = new Rect();
     67 
     68         mPaint = new Paint();
     69         mPaint.setStyle(Paint.Style.FILL);
     70     }
     71 
     72     public void setColors(@NonNull ColorExtractor.GradientColors colors) {
     73         setColors(colors.getMainColor(), colors.getSecondaryColor(), true);
     74     }
     75 
     76     public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) {
     77         setColors(colors.getMainColor(), colors.getSecondaryColor(), animated);
     78     }
     79 
     80     public void setColors(int mainColor, int secondaryColor, boolean animated) {
     81         if (mainColor == mMainColorTo && secondaryColor == mSecondaryColorTo) {
     82             return;
     83         }
     84 
     85         if (mColorAnimation != null && mColorAnimation.isRunning()) {
     86             mColorAnimation.cancel();
     87         }
     88 
     89         mMainColorTo = mainColor;
     90         mSecondaryColorTo = mainColor;
     91 
     92         if (animated) {
     93             final int mainFrom = mMainColor;
     94             final int secFrom = mSecondaryColor;
     95 
     96             ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
     97             anim.setDuration(COLOR_ANIMATION_DURATION);
     98             anim.addUpdateListener(animation -> {
     99                 float ratio = (float) animation.getAnimatedValue();
    100                 mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
    101                 mSecondaryColor = ColorUtils.blendARGB(secFrom, secondaryColor, ratio);
    102                 buildPaints();
    103                 invalidateSelf();
    104             });
    105             anim.addListener(new AnimatorListenerAdapter() {
    106                 @Override
    107                 public void onAnimationEnd(Animator animation, boolean isReverse) {
    108                     if (mColorAnimation == animation) {
    109                         mColorAnimation = null;
    110                     }
    111                 }
    112             });
    113             anim.setInterpolator(new DecelerateInterpolator());
    114             anim.start();
    115             mColorAnimation = anim;
    116         } else {
    117             mMainColor = mainColor;
    118             mSecondaryColor = secondaryColor;
    119             buildPaints();
    120             invalidateSelf();
    121         }
    122     }
    123 
    124     @Override
    125     public void setAlpha(int alpha) {
    126         if (alpha != mAlpha) {
    127             mAlpha = alpha;
    128             mPaint.setAlpha(mAlpha);
    129             invalidateSelf();
    130         }
    131     }
    132 
    133     @Override
    134     public int getAlpha() {
    135         return mAlpha;
    136     }
    137 
    138     @Override
    139     public void setXfermode(@Nullable Xfermode mode) {
    140         mPaint.setXfermode(mode);
    141         invalidateSelf();
    142     }
    143 
    144     @Override
    145     public void setColorFilter(ColorFilter colorFilter) {
    146         mPaint.setColorFilter(colorFilter);
    147     }
    148 
    149     @Override
    150     public ColorFilter getColorFilter() {
    151         return mPaint.getColorFilter();
    152     }
    153 
    154     @Override
    155     public int getOpacity() {
    156         return PixelFormat.TRANSLUCENT;
    157     }
    158 
    159     public void setScreenSize(int width, int height) {
    160         mWindowBounds.set(0, 0, width, height);
    161         setBounds(0, 0, width, height);
    162         buildPaints();
    163     }
    164 
    165     private void buildPaints() {
    166         Rect bounds = mWindowBounds;
    167         if (bounds.width() == 0) {
    168             return;
    169         }
    170 
    171         float w = bounds.width();
    172         float h = bounds.height();
    173 
    174         float x = mSplat.x * w;
    175         float y = mSplat.y * h;
    176 
    177         float radius = mSplat.radius * mDensity;
    178 
    179         // When we have only a single alpha gradient, we increase quality
    180         // (avoiding banding) by merging the background solid color into
    181         // the gradient directly
    182         RadialGradient radialGradient = new RadialGradient(x, y, radius,
    183                 mSecondaryColor, mMainColor, Shader.TileMode.CLAMP);
    184         mPaint.setShader(radialGradient);
    185     }
    186 
    187     @Override
    188     public void draw(@NonNull Canvas canvas) {
    189         Rect bounds = mWindowBounds;
    190         if (bounds.width() == 0) {
    191             throw new IllegalStateException("You need to call setScreenSize before drawing.");
    192         }
    193 
    194         // Splat each gradient
    195         float w = bounds.width();
    196         float h = bounds.height();
    197 
    198         float x = mSplat.x * w;
    199         float y = mSplat.y * h;
    200 
    201         float radius = Math.max(w, h);
    202         canvas.drawRect(x - radius, y - radius, x + radius, y + radius, mPaint);
    203     }
    204 
    205     @VisibleForTesting
    206     public int getMainColor() {
    207         return mMainColor;
    208     }
    209 
    210     @VisibleForTesting
    211     public int getSecondaryColor() {
    212         return mSecondaryColor;
    213     }
    214 
    215     static final class Splat {
    216         final float x;
    217         final float y;
    218         final float radius;
    219         final float colorIndex;
    220 
    221         Splat(float x, float y, float radius, float colorIndex) {
    222             this.x = x;
    223             this.y = y;
    224             this.radius = radius;
    225             this.colorIndex = colorIndex;
    226         }
    227     }
    228 }