blob: 500c028ed4c617b081d33d1baaee48d592fc2c2b [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* 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.android.internal.colorextraction.drawable;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.view.animation.DecelerateInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.graphics.ColorUtils;
/**
* Draws a gradient based on a Palette
*/
public class GradientDrawable extends Drawable {
private static final String TAG = "GradientDrawable";
private static final float CENTRALIZED_CIRCLE_1 = -2;
private static final int GRADIENT_RADIUS = 480; // in dp
private static final long COLOR_ANIMATION_DURATION = 2000;
private int mAlpha = 255;
private float mDensity;
private final Paint mPaint;
private final Rect mWindowBounds;
private final Splat mSplat;
private int mMainColor;
private int mSecondaryColor;
private ValueAnimator mColorAnimation;
public GradientDrawable(@NonNull Context context) {
mDensity = context.getResources().getDisplayMetrics().density;
mSplat = new Splat(0.50f, 1.00f, GRADIENT_RADIUS, CENTRALIZED_CIRCLE_1);
mWindowBounds = new Rect();
mPaint = new Paint();
mPaint.setStyle(Paint.Style.FILL);
}
public void setColors(@NonNull ColorExtractor.GradientColors colors) {
setColors(colors.getMainColor(), colors.getSecondaryColor(), true);
}
public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) {
setColors(colors.getMainColor(), colors.getSecondaryColor(), animated);
}
public void setColors(int mainColor, int secondaryColor, boolean animated) {
if (mainColor == mMainColor && secondaryColor == mSecondaryColor) {
return;
}
if (mColorAnimation != null && mColorAnimation.isRunning()) {
mColorAnimation.cancel();
}
if (animated) {
final int mainFrom = mMainColor;
final int secFrom = mSecondaryColor;
ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
anim.setDuration(COLOR_ANIMATION_DURATION);
anim.addUpdateListener(animation -> {
float ratio = (float) animation.getAnimatedValue();
mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
mSecondaryColor = ColorUtils.blendARGB(secFrom, secondaryColor, ratio);
buildPaints();
invalidateSelf();
});
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
if (mColorAnimation == animation) {
mColorAnimation = null;
}
}
});
anim.setInterpolator(new DecelerateInterpolator());
anim.start();
mColorAnimation = anim;
} else {
mMainColor = mainColor;
mSecondaryColor = secondaryColor;
buildPaints();
invalidateSelf();
}
}
@Override
public void setAlpha(int alpha) {
if (alpha != mAlpha) {
mAlpha = alpha;
mPaint.setAlpha(mAlpha);
invalidateSelf();
}
}
@Override
public int getAlpha() {
return mAlpha;
}
@Override
public void setXfermode(@Nullable Xfermode mode) {
mPaint.setXfermode(mode);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public ColorFilter getColorFilter() {
return mPaint.getColorFilter();
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
public void setScreenSize(int width, int height) {
mWindowBounds.set(0, 0, width, height);
setBounds(0, 0, width, height);
buildPaints();
}
private void buildPaints() {
Rect bounds = mWindowBounds;
if (bounds.width() == 0) {
return;
}
float w = bounds.width();
float h = bounds.height();
float x = mSplat.x * w;
float y = mSplat.y * h;
float radius = mSplat.radius * mDensity;
// When we have only a single alpha gradient, we increase quality
// (avoiding banding) by merging the background solid color into
// the gradient directly
RadialGradient radialGradient = new RadialGradient(x, y, radius,
mSecondaryColor, mMainColor, Shader.TileMode.CLAMP);
mPaint.setShader(radialGradient);
}
@Override
public void draw(@NonNull Canvas canvas) {
Rect bounds = mWindowBounds;
if (bounds.width() == 0) {
throw new IllegalStateException("You need to call setScreenSize before drawing.");
}
// Splat each gradient
float w = bounds.width();
float h = bounds.height();
float x = mSplat.x * w;
float y = mSplat.y * h;
float radius = Math.max(w, h);
canvas.drawRect(x - radius, y - radius, x + radius, y + radius, mPaint);
}
@VisibleForTesting
public int getMainColor() {
return mMainColor;
}
@VisibleForTesting
public int getSecondaryColor() {
return mSecondaryColor;
}
static final class Splat {
final float x;
final float y;
final float radius;
final float colorIndex;
Splat(float x, float y, float radius, float colorIndex) {
this.x = x;
this.y = y;
this.radius = radius;
this.colorIndex = colorIndex;
}
}
}