RoundedDrawable.java
/*
* Copyright 2020 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 androidx.wear.watchface.complications.rendering;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.annotation.VisibleForTesting;
import java.util.Objects;
/**
* Class used to maintain and draw a rounded rectangular image. The given drawable will be drawn in
* a rounded rectangle within the specified bounds. The image will be cropped.
*
* @hide
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class RoundedDrawable extends Drawable {
@VisibleForTesting final Paint mPaint;
private Drawable mDrawable;
private int mRadius; // Radius in pixels
// Used to avoid creating new RectF object every time draw() is called
private final RectF mTmpBounds = new RectF();
RoundedDrawable() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
}
/**
* Sets the drawable to be rendered.
*
* @param drawable {@link Drawable} to be rendered
*/
public void setDrawable(@NonNull Drawable drawable) {
if (Objects.equals(mDrawable, drawable)) {
return;
}
mDrawable = drawable;
updateBitmapShader();
}
@Override
protected void onBoundsChange(@NonNull Rect bounds) {
mTmpBounds.right = bounds.width();
mTmpBounds.bottom = bounds.height();
updateBitmapShader();
}
@Override
public void draw(@NonNull Canvas canvas) {
Rect bounds = getBounds();
if (mDrawable == null || bounds.isEmpty()) {
return;
}
canvas.save();
canvas.translate(bounds.left, bounds.top);
// mTmpBounds is bounds translated to (0,0) and converted to RectF as drawRoundRect
// requires.
canvas.drawRoundRect(mTmpBounds, (float) mRadius, (float) mRadius, mPaint);
canvas.restore();
}
/** @deprecated This method is no longer used in graphics optimizations */
@Override
@Deprecated
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter cf) {
mPaint.setColorFilter(cf);
}
/**
* Sets the border radius to be applied when rendering drawable.
*
* @param radius border radius in pixels
*/
public void setRadius(int radius) {
mRadius = radius;
}
/**
* Updates the shader of the paint. To avoid scaling and creation of a BitmapShader every time,
* this method should be called only if the drawable or the bounds has changed.
*/
private void updateBitmapShader() {
if (mDrawable == null) {
return;
}
Rect bounds = getBounds();
if (!bounds.isEmpty()) {
Bitmap bitmap = drawableToBitmap(mDrawable, bounds.width(), bounds.height());
Shader shader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mPaint.setShader(shader);
}
}
/** Converts a drawable to a bitmap of specified width and height. */
@NonNull
private Bitmap drawableToBitmap(@NonNull Drawable drawable, int width, int height) {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
int intrinsicWidth = drawable.getIntrinsicWidth();
int intrinsicHeight = drawable.getIntrinsicHeight();
if (intrinsicWidth > intrinsicHeight) {
// Center crop the Drawable width wise
float aspectRatio = (float) intrinsicWidth / intrinsicHeight;
int scaledWidth = (int) (width * aspectRatio);
int offset = (scaledWidth - width) / 2;
drawable.setBounds(-offset, 0, width + offset, height);
} else {
// Center crop the Drawable height wise
float aspectRatio = (float) intrinsicHeight / intrinsicWidth;
int scaledHeight = (int) (height * aspectRatio);
int offset = (scaledHeight - height) / 2;
drawable.setBounds(0, -offset, width, height + offset);
}
drawable.draw(canvas);
return bitmap;
}
@Nullable
@VisibleForTesting
Drawable getDrawable() {
return mDrawable;
}
}