Signal-Android/app/src/main/java/org/thoughtcrime/securesms/components/RotatableGradientDrawable.java

146 wiersze
5.2 KiB
Java

package org.thoughtcrime.securesms.components;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Arrays;
import kotlin.jvm.functions.Function2;
/**
* Drawable which renders a gradient at a specified angle. Note that this drawable does
* not implement drawable state, and all the baggage that comes with a normal Drawable
* override, so this may not work in every scenario.
*
* Essentially, this drawable creates a LinearGradient shader using the given colors and
* positions, but makes it larger than the bounds, such that it can be rotated and still
* fill the bounds with a gradient.
*
* If you wish to apply clipping to this drawable, it is recommended to either use it with
* a MaterialCardView or utilize {@link org.thoughtcrime.securesms.util.CustomDrawWrapperKt#customizeOnDraw(Drawable, Function2)}
*/
public final class RotatableGradientDrawable extends Drawable {
/**
* From investigation into how Gradients are rendered vs how they are rendered in
* designs, in order to match spec, we need to rotate gradients by 225 degrees. (180 + 45)
*
* This puts 0 at the bottom (0, -1) of the surface area.
*/
private static final float DEGREE_OFFSET = 225f;
private final float degrees;
private final int[] colors;
private final float[] positions;
private final Rect fillRect = new Rect();
private final Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
/**
* @param degrees Gradient rotation in degrees, relative to a vector pointed from the center to bottom center
* @param colors The colors of the gradient
* @param positions The positions of the colors. Values should be between 0f and 1f and this array should be the
* same length as colors.
*/
public RotatableGradientDrawable(float degrees, int[] colors, @Nullable float[] positions) {
this.degrees = degrees + DEGREE_OFFSET;
this.colors = colors;
this.positions = positions;
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
Point topLeft = new Point(left, top);
Point topRight = new Point(right, top);
Point bottomLeft = new Point(left, bottom);
Point bottomRight = new Point(right, bottom);
Point origin = new Point(getBounds().width() / 2, getBounds().height() / 2);
Point rotationTopLeft = cornerPrime(origin, topLeft, degrees);
Point rotationTopRight = cornerPrime(origin, topRight, degrees);
Point rotationBottomLeft = cornerPrime(origin, bottomLeft, degrees);
Point rotationBottomRight = cornerPrime(origin, bottomRight, degrees);
fillRect.left = Integer.MAX_VALUE;
fillRect.top = Integer.MAX_VALUE;
fillRect.right = Integer.MIN_VALUE;
fillRect.bottom = Integer.MIN_VALUE;
for (Point point : Arrays.asList(topLeft, topRight, bottomLeft, bottomRight, rotationTopLeft, rotationTopRight, rotationBottomLeft, rotationBottomRight)) {
if (point.x < fillRect.left) {
fillRect.left = point.x;
}
if (point.x > fillRect.right) {
fillRect.right = point.x;
}
if (point.y < fillRect.top) {
fillRect.top = point.y;
}
if (point.y > fillRect.bottom) {
fillRect.bottom = point.y;
}
}
fillPaint.setShader(new LinearGradient(fillRect.left, fillRect.top, fillRect.right, fillRect.bottom, colors, positions, Shader.TileMode.CLAMP));
}
public void setXfermode(@NonNull Xfermode xfermode) {
fillPaint.setXfermode(xfermode);
}
private static Point cornerPrime(@NonNull Point origin, @NonNull Point corner, float degrees) {
return new Point(xPrime(origin, corner, Math.toRadians(degrees)), yPrime(origin, corner, Math.toRadians(degrees)));
}
private static int xPrime(@NonNull Point origin, @NonNull Point corner, double theta) {
return (int) Math.ceil(((corner.x - origin.x) * Math.cos(theta)) - ((corner.y - origin.y) * Math.sin(theta)) + origin.x);
}
private static int yPrime(@NonNull Point origin, @NonNull Point corner, double theta) {
return (int) Math.ceil(((corner.x - origin.x) * Math.sin(theta)) + ((corner.y - origin.y) * Math.cos(theta)) + origin.y);
}
@Override
public void draw(Canvas canvas) {
int save = canvas.save();
canvas.rotate(degrees, getBounds().width() / 2f, getBounds().height() / 2f);
int height = fillRect.height();
int width = fillRect.width();
canvas.drawRect(fillRect.left - width, fillRect.top - height, fillRect.right + width, fillRect.bottom + height, fillPaint);
canvas.restoreToCount(save);
}
@Override
public void setAlpha(int alpha) {
// Not supported
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
// Not supported
}
@Override
public int getOpacity() {
return PixelFormat.OPAQUE;
}
}