| /* |
| * Copyright (C) 2014 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 android.support.v4.graphics.drawable; |
| |
| import android.content.res.Resources; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapShader; |
| import android.graphics.Canvas; |
| import android.graphics.ColorFilter; |
| import android.graphics.Matrix; |
| 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 android.util.DisplayMetrics; |
| import android.view.Gravity; |
| |
| /** |
| * A Drawable that wraps a bitmap and can be drawn with rounded corners. You can create a |
| * RoundedBitmapDrawable from a file path, an input stream, or from a |
| * {@link android.graphics.Bitmap} object. |
| * <p> |
| * Also see the {@link android.graphics.Bitmap} class, which handles the management and |
| * transformation of raw bitmap graphics, and should be used when drawing to a |
| * {@link android.graphics.Canvas}. |
| * </p> |
| */ |
| public abstract class RoundedBitmapDrawable extends Drawable { |
| private static final int DEFAULT_PAINT_FLAGS = |
| Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG; |
| final Bitmap mBitmap; |
| private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; |
| private int mGravity = Gravity.FILL; |
| private final Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); |
| private final BitmapShader mBitmapShader; |
| private final Matrix mShaderMatrix = new Matrix(); |
| private float mCornerRadius; |
| |
| final Rect mDstRect = new Rect(); // Gravity.apply() sets this |
| private final RectF mDstRectF = new RectF(); |
| |
| private boolean mApplyGravity = true; |
| private boolean mIsCircular; |
| |
| // These are scaled to match the target density. |
| private int mBitmapWidth; |
| private int mBitmapHeight; |
| |
| /** |
| * Returns the paint used to render this drawable. |
| */ |
| public final Paint getPaint() { |
| return mPaint; |
| } |
| |
| /** |
| * Returns the bitmap used by this drawable to render. May be null. |
| */ |
| public final Bitmap getBitmap() { |
| return mBitmap; |
| } |
| |
| private void computeBitmapSize() { |
| mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity); |
| mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity); |
| } |
| |
| /** |
| * Set the density scale at which this drawable will be rendered. This |
| * method assumes the drawable will be rendered at the same density as the |
| * specified canvas. |
| * |
| * @param canvas The Canvas from which the density scale must be obtained. |
| * |
| * @see android.graphics.Bitmap#setDensity(int) |
| * @see android.graphics.Bitmap#getDensity() |
| */ |
| public void setTargetDensity(Canvas canvas) { |
| setTargetDensity(canvas.getDensity()); |
| } |
| |
| /** |
| * Set the density scale at which this drawable will be rendered. |
| * |
| * @param metrics The DisplayMetrics indicating the density scale for this drawable. |
| * |
| * @see android.graphics.Bitmap#setDensity(int) |
| * @see android.graphics.Bitmap#getDensity() |
| */ |
| public void setTargetDensity(DisplayMetrics metrics) { |
| setTargetDensity(metrics.densityDpi); |
| } |
| |
| /** |
| * Set the density at which this drawable will be rendered. |
| * |
| * @param density The density scale for this drawable. |
| * |
| * @see android.graphics.Bitmap#setDensity(int) |
| * @see android.graphics.Bitmap#getDensity() |
| */ |
| public void setTargetDensity(int density) { |
| if (mTargetDensity != density) { |
| mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; |
| if (mBitmap != null) { |
| computeBitmapSize(); |
| } |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * Get the gravity used to position/stretch the bitmap within its bounds. |
| * |
| * @return the gravity applied to the bitmap |
| * |
| * @see android.view.Gravity |
| */ |
| public int getGravity() { |
| return mGravity; |
| } |
| |
| /** |
| * Set the gravity used to position/stretch the bitmap within its bounds. |
| * |
| * @param gravity the gravity |
| * |
| * @see android.view.Gravity |
| */ |
| public void setGravity(int gravity) { |
| if (mGravity != gravity) { |
| mGravity = gravity; |
| mApplyGravity = true; |
| invalidateSelf(); |
| } |
| } |
| |
| /** |
| * Enables or disables the mipmap hint for this drawable's bitmap. |
| * See {@link Bitmap#setHasMipMap(boolean)} for more information. |
| * |
| * If the bitmap is null, or the current API version does not support setting a mipmap hint, |
| * calling this method has no effect. |
| * |
| * @param mipMap True if the bitmap should use mipmaps, false otherwise. |
| * |
| * @see #hasMipMap() |
| */ |
| public void setMipMap(boolean mipMap) { |
| throw new UnsupportedOperationException(); // must be overridden in subclasses |
| } |
| |
| /** |
| * Indicates whether the mipmap hint is enabled on this drawable's bitmap. |
| * |
| * @return True if the mipmap hint is set, false otherwise. If the bitmap |
| * is null, this method always returns false. |
| * |
| * @see #setMipMap(boolean) |
| */ |
| public boolean hasMipMap() { |
| throw new UnsupportedOperationException(); // must be overridden in subclasses |
| } |
| |
| /** |
| * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects |
| * the edges of the bitmap only so it applies only when the drawable is rotated. |
| * |
| * @param aa True if the bitmap should be anti-aliased, false otherwise. |
| * |
| * @see #hasAntiAlias() |
| */ |
| public void setAntiAlias(boolean aa) { |
| mPaint.setAntiAlias(aa); |
| invalidateSelf(); |
| } |
| |
| /** |
| * Indicates whether anti-aliasing is enabled for this drawable. |
| * |
| * @return True if anti-aliasing is enabled, false otherwise. |
| * |
| * @see #setAntiAlias(boolean) |
| */ |
| public boolean hasAntiAlias() { |
| return mPaint.isAntiAlias(); |
| } |
| |
| @Override |
| public void setFilterBitmap(boolean filter) { |
| mPaint.setFilterBitmap(filter); |
| invalidateSelf(); |
| } |
| |
| @Override |
| public void setDither(boolean dither) { |
| mPaint.setDither(dither); |
| invalidateSelf(); |
| } |
| |
| void gravityCompatApply(int gravity, int bitmapWidth, int bitmapHeight, |
| Rect bounds, Rect outRect) { |
| throw new UnsupportedOperationException(); |
| } |
| |
| void updateDstRect() { |
| if (mApplyGravity) { |
| if (mIsCircular) { |
| final int minDimen = Math.min(mBitmapWidth, mBitmapHeight); |
| gravityCompatApply(mGravity, minDimen, minDimen, getBounds(), mDstRect); |
| |
| // inset the drawing rectangle to the largest contained square, |
| // so that a circle will be drawn |
| final int minDrawDimen = Math.min(mDstRect.width(), mDstRect.height()); |
| final int insetX = Math.max(0, (mDstRect.width() - minDrawDimen) / 2); |
| final int insetY = Math.max(0, (mDstRect.height() - minDrawDimen) / 2); |
| mDstRect.inset(insetX, insetY); |
| mCornerRadius = 0.5f * minDrawDimen; |
| } else { |
| gravityCompatApply(mGravity, mBitmapWidth, mBitmapHeight, getBounds(), mDstRect); |
| } |
| mDstRectF.set(mDstRect); |
| |
| if (mBitmapShader != null) { |
| // setup shader matrix |
| mShaderMatrix.setTranslate(mDstRectF.left,mDstRectF.top); |
| mShaderMatrix.preScale( |
| mDstRectF.width() / mBitmap.getWidth(), |
| mDstRectF.height() / mBitmap.getHeight()); |
| mBitmapShader.setLocalMatrix(mShaderMatrix); |
| mPaint.setShader(mBitmapShader); |
| } |
| |
| mApplyGravity = false; |
| } |
| } |
| |
| @Override |
| public void draw(Canvas canvas) { |
| final Bitmap bitmap = mBitmap; |
| if (bitmap == null) { |
| return; |
| } |
| |
| updateDstRect(); |
| if (mPaint.getShader() == null) { |
| canvas.drawBitmap(bitmap, null, mDstRect, mPaint); |
| } else { |
| canvas.drawRoundRect(mDstRectF, mCornerRadius, mCornerRadius, mPaint); |
| } |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { |
| final int oldAlpha = mPaint.getAlpha(); |
| if (alpha != oldAlpha) { |
| mPaint.setAlpha(alpha); |
| invalidateSelf(); |
| } |
| } |
| |
| public int getAlpha() { |
| return mPaint.getAlpha(); |
| } |
| |
| @Override |
| public void setColorFilter(ColorFilter cf) { |
| mPaint.setColorFilter(cf); |
| invalidateSelf(); |
| } |
| |
| public ColorFilter getColorFilter() { |
| return mPaint.getColorFilter(); |
| } |
| |
| /** |
| * Sets the image shape to circular. |
| * <p>This overwrites any calls made to {@link #setCornerRadius(float)} so far.</p> |
| */ |
| public void setCircular(boolean circular) { |
| mIsCircular = circular; |
| mApplyGravity = true; |
| if (circular) { |
| updateCircularCornerRadius(); |
| mPaint.setShader(mBitmapShader); |
| invalidateSelf(); |
| } else { |
| setCornerRadius(0); |
| } |
| } |
| |
| private void updateCircularCornerRadius() { |
| final int minCircularSize = Math.min(mBitmapHeight, mBitmapWidth); |
| mCornerRadius = minCircularSize / 2; |
| } |
| |
| /** |
| * @return <code>true</code> if the image is circular, else <code>false</code>. |
| */ |
| public boolean isCircular() { |
| return mIsCircular; |
| } |
| |
| /** |
| * Sets the corner radius to be applied when drawing the bitmap. |
| */ |
| public void setCornerRadius(float cornerRadius) { |
| if (mCornerRadius == cornerRadius) return; |
| |
| mIsCircular = false; |
| if (isGreaterThanZero(cornerRadius)) { |
| mPaint.setShader(mBitmapShader); |
| } else { |
| mPaint.setShader(null); |
| } |
| |
| mCornerRadius = cornerRadius; |
| invalidateSelf(); |
| } |
| |
| @Override |
| protected void onBoundsChange(Rect bounds) { |
| super.onBoundsChange(bounds); |
| if (mIsCircular) { |
| updateCircularCornerRadius(); |
| } |
| mApplyGravity = true; |
| } |
| |
| /** |
| * @return The corner radius applied when drawing the bitmap. |
| */ |
| public float getCornerRadius() { |
| return mCornerRadius; |
| } |
| |
| @Override |
| public int getIntrinsicWidth() { |
| return mBitmapWidth; |
| } |
| |
| @Override |
| public int getIntrinsicHeight() { |
| return mBitmapHeight; |
| } |
| |
| @Override |
| public int getOpacity() { |
| if (mGravity != Gravity.FILL || mIsCircular) { |
| return PixelFormat.TRANSLUCENT; |
| } |
| Bitmap bm = mBitmap; |
| return (bm == null |
| || bm.hasAlpha() |
| || mPaint.getAlpha() < 255 |
| || isGreaterThanZero(mCornerRadius)) |
| ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; |
| } |
| |
| RoundedBitmapDrawable(Resources res, Bitmap bitmap) { |
| if (res != null) { |
| mTargetDensity = res.getDisplayMetrics().densityDpi; |
| } |
| |
| mBitmap = bitmap; |
| if (mBitmap != null) { |
| computeBitmapSize(); |
| mBitmapShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); |
| } else { |
| mBitmapWidth = mBitmapHeight = -1; |
| mBitmapShader = null; |
| } |
| } |
| |
| private static boolean isGreaterThanZero(float toCompare) { |
| return toCompare > 0.05f; |
| } |
| } |