summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Wei <markwei@google.com>2014-01-07 00:41:21 -0800
committerMark Wei <markwei@google.com>2014-01-07 15:49:24 -0800
commitad6ca3f895022ded1a11f3eedc50d70ea90cd4da (patch)
treeb45396307e40fdd31f948c7ce1b78e9c6a47a1be
parent5d6521e290594fe0851086b0c27413e9709e437f (diff)
downloadbitmap-ad6ca3f895022ded1a11f3eedc50d70ea90cd4da.tar.gz
Export some useful custom classes that I wrote back into the bitmap library.
Added: - CircularBitmapDrawable: Use this if you want to display circular images with an optional border stroke. This class uses BitmapShaders, the preferred way of masking a bitmap, instead of using a non-rectangular clip region or overlaying views. - StyledCornersBitmapDrawable: Use this if you want the corners of your images to be stylized. You can choose from rounded or flap corners, with an optional border stroke and optional flap color. This class uses a non-rectangular clip region. In the future, this may change to use a BitmapShader. - ResourceRequestKey: Use this if you want a simple RequestKey implementation that loads images from a resource asset. Change-Id: If64440f3389f56f8a5b799a32a2660f3f9a19385
-rw-r--r--src/com/android/bitmap/ResourceRequestKey.java94
-rw-r--r--src/com/android/bitmap/drawable/CircularBitmapDrawable.java136
-rw-r--r--src/com/android/bitmap/drawable/StyledCornersBitmapDrawable.java310
3 files changed, 540 insertions, 0 deletions
diff --git a/src/com/android/bitmap/ResourceRequestKey.java b/src/com/android/bitmap/ResourceRequestKey.java
new file mode 100644
index 0000000..7bb64d9
--- /dev/null
+++ b/src/com/android/bitmap/ResourceRequestKey.java
@@ -0,0 +1,94 @@
+/*
+ * 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 com.android.bitmap;
+
+import android.content.res.Resources;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Simple RequestKey for decoding from a resource id.
+ */
+public class ResourceRequestKey implements RequestKey {
+
+ private Resources mResources;
+ private int mResId;
+
+ /**
+ * Create a new request key with the given resource id. A resId of 0 will
+ * return a null request key.
+ */
+ public static ResourceRequestKey from(Resources res, int resId) {
+ if (resId != 0) {
+ return new ResourceRequestKey(res, resId);
+ }
+ return null;
+ }
+
+ private ResourceRequestKey(Resources res, int resId) {
+ mResources = res;
+ mResId = resId;
+ }
+
+ @Override
+ public Cancelable createFileDescriptorFactoryAsync(RequestKey requestKey, Callback callback) {
+ return null;
+ }
+
+ @Override
+ public InputStream createInputStream() throws IOException {
+ return mResources.openRawResource(mResId);
+ }
+
+ @Override
+ public boolean hasOrientationExif() throws IOException {
+ return false;
+ }
+
+ // START AUTO-GENERATED CODE
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ ResourceRequestKey that = (ResourceRequestKey) o;
+
+ if (mResId != that.mResId) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return mResId;
+ }
+
+ // END AUTO-GENERATED CODE
+
+ @Override
+ public String toString() {
+ return String.format("ResourceRequestKey: %d", mResId);
+ }
+}
diff --git a/src/com/android/bitmap/drawable/CircularBitmapDrawable.java b/src/com/android/bitmap/drawable/CircularBitmapDrawable.java
new file mode 100644
index 0000000..32eb460
--- /dev/null
+++ b/src/com/android/bitmap/drawable/CircularBitmapDrawable.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.android.bitmap.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapShader;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Rect;
+import android.graphics.Shader.TileMode;
+
+import com.android.bitmap.BitmapCache;
+
+/**
+ * Custom BasicBitmapDrawable implementation for circular images.
+ *
+ * This draws all bitmaps as a circle with an optional border stroke.
+ */
+public class CircularBitmapDrawable extends BasicBitmapDrawable {
+ private static Matrix sMatrix = new Matrix();
+
+ private final Paint mBitmapPaint = new Paint();
+ private final Paint mBorderPaint = new Paint();
+
+ private float mBorderWidth;
+
+ public CircularBitmapDrawable(Resources res,
+ BitmapCache cache, boolean limitDensity) {
+ super(res, cache, limitDensity);
+
+ mBitmapPaint.setAntiAlias(true);
+ mBitmapPaint.setFilterBitmap(true);
+ mBitmapPaint.setDither(true);
+
+ mBorderPaint.setColor(Color.TRANSPARENT);
+ mBorderPaint.setStyle(Style.STROKE);
+ mBorderPaint.setStrokeWidth(mBorderWidth);
+ mBorderPaint.setAntiAlias(true);
+ }
+
+ /**
+ * Set the border stroke width of this drawable.
+ */
+ public void setBorderWidth(final float borderWidth) {
+ final boolean changed = mBorderPaint.getStrokeWidth() != borderWidth;
+ mBorderPaint.setStrokeWidth(borderWidth);
+ mBorderWidth = borderWidth;
+
+ if (changed) {
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Set the border stroke color of this drawable. Set to {@link Color#TRANSPARENT} to disable.
+ */
+ public void setBorderColor(final int color) {
+ final boolean changed = mBorderPaint.getColor() != color;
+ mBorderPaint.setColor(color);
+
+ if (changed) {
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ protected void onDrawBitmap(final Canvas canvas, final Rect src,
+ final Rect dst) {
+ onDrawCircularBitmap(getBitmap().bmp, canvas, src, dst);
+ }
+
+ /**
+ * Call this method with a given bitmap to draw it onto the given canvas, masked by a circular
+ * BitmapShader.
+ */
+ protected void onDrawCircularBitmap(final Bitmap bitmap, final Canvas canvas,
+ final Rect src, final Rect dst) {
+ // Draw bitmap through shader first.
+ BitmapShader shader = new BitmapShader(bitmap, TileMode.CLAMP,
+ TileMode.CLAMP);
+ sMatrix.reset();
+
+ // Fit bitmap to bounds.
+ float scale = Math.max((float) dst.width() / src.width(),
+ (float) dst.height() / src.height());
+ sMatrix.postScale(scale, scale);
+
+ // Translate bitmap to dst bounds.
+ sMatrix.postTranslate(dst.left, dst.top);
+
+ shader.setLocalMatrix(sMatrix);
+ mBitmapPaint.setShader(shader);
+ canvas.drawCircle(dst.centerX(), dst.centerY(), dst.width() / 2,
+ mBitmapPaint);
+
+ // Then draw the border.
+ canvas.drawCircle(dst.centerX(), dst.centerY(),
+ dst.width() / 2f - mBorderWidth / 2, mBorderPaint);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ super.setAlpha(alpha);
+
+ final int old = mBitmapPaint.getAlpha();
+ mBitmapPaint.setAlpha(alpha);
+ if (alpha != old) {
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter cf) {
+ super.setColorFilter(cf);
+ mPaint.setColorFilter(cf);
+ }
+}
diff --git a/src/com/android/bitmap/drawable/StyledCornersBitmapDrawable.java b/src/com/android/bitmap/drawable/StyledCornersBitmapDrawable.java
new file mode 100644
index 0000000..d5d04b3
--- /dev/null
+++ b/src/com/android/bitmap/drawable/StyledCornersBitmapDrawable.java
@@ -0,0 +1,310 @@
+/*
+ * 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 com.android.bitmap.drawable;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.RectF;
+
+import com.android.bitmap.BitmapCache;
+
+/**
+ * A custom ExtendedBitmapDrawable that styles the corners in configurable ways.
+ *
+ * All four corners can be configured as {@link #CORNER_STYLE_SHARP},
+ * {@link #CORNER_STYLE_ROUND}, or {@link #CORNER_STYLE_FLAP}.
+ * This is accomplished applying a non-rectangular clip applied to the canvas.
+ *
+ * A border is draw that conforms to the styled corners.
+ *
+ * {@link #CORNER_STYLE_FLAP} corners have a colored flap drawn within the bounds.
+ */
+public class StyledCornersBitmapDrawable extends ExtendedBitmapDrawable {
+
+ public static final int CORNER_STYLE_SHARP = 0;
+ public static final int CORNER_STYLE_ROUND = 1;
+ public static final int CORNER_STYLE_FLAP = 2;
+
+ private static final int START_RIGHT = 0;
+ private static final int START_BOTTOM = 90;
+ private static final int START_LEFT = 180;
+ private static final int START_TOP = 270;
+ private static final int QUARTER_CIRCLE = 90;
+ private static final RectF sRectF = new RectF();
+
+ private final Paint mFlapPaint = new Paint();
+ private final Paint mBorderPaint = new Paint();
+ private final Path mClipPath = new Path();
+ private final float mCornerRoundRadius;
+ private final float mCornerFlapSide;
+
+ private int mTopLeftCornerStyle = CORNER_STYLE_SHARP;
+ private int mTopRightCornerStyle = CORNER_STYLE_SHARP;
+ private int mBottomRightCornerStyle = CORNER_STYLE_SHARP;
+ private int mBottomLeftCornerStyle = CORNER_STYLE_SHARP;
+ private int mScrimColor;
+ private float mBorderWidth;
+
+ /**
+ * Create a new StyledCornersBitmapDrawable.
+ */
+ public StyledCornersBitmapDrawable(Resources res, BitmapCache cache,
+ boolean limitDensity, ExtendedOptions opts, float cornerRoundRadius,
+ float cornerFlapSide) {
+ super(res, cache, limitDensity, opts);
+
+ mCornerRoundRadius = cornerRoundRadius;
+ mCornerFlapSide = cornerFlapSide;
+
+ mFlapPaint.setColor(Color.TRANSPARENT);
+ mFlapPaint.setStyle(Style.FILL);
+
+ mBorderPaint.setColor(Color.TRANSPARENT);
+ mBorderPaint.setStyle(Style.STROKE);
+ mBorderPaint.setStrokeWidth(mBorderWidth);
+ mBorderPaint.setAntiAlias(true);
+
+ mScrimColor = Color.TRANSPARENT;
+ }
+
+ /**
+ * Set the border stroke width of this drawable.
+ */
+ public void setBorderWidth(final float borderWidth) {
+ final boolean changed = mBorderPaint.getStrokeWidth() != borderWidth;
+ mBorderPaint.setStrokeWidth(borderWidth);
+ mBorderWidth = borderWidth;
+
+ if (changed) {
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Set the border stroke color of this drawable. Set to {@link Color#TRANSPARENT} to disable.
+ */
+ public void setBorderColor(final int color) {
+ final boolean changed = mBorderPaint.getColor() != color;
+ mBorderPaint.setColor(color);
+
+ if (changed) {
+ invalidateSelf();
+ }
+ }
+
+ /** Set the corner styles for all four corners */
+ public void setCornerStyles(int topLeft, int topRight, int bottomRight, int bottomLeft) {
+ boolean changed = mTopLeftCornerStyle != topLeft
+ || mTopRightCornerStyle != topRight
+ || mBottomRightCornerStyle != bottomRight
+ || mBottomLeftCornerStyle != bottomLeft;
+
+ mTopLeftCornerStyle = topLeft;
+ mTopRightCornerStyle = topRight;
+ mBottomRightCornerStyle = bottomRight;
+ mBottomLeftCornerStyle = bottomLeft;
+
+ if (changed) {
+ recalculatePath();
+ }
+ }
+
+ /**
+ * Set the flap color for all corners that have style {@link #CORNER_STYLE_SHARP}.
+ *
+ * Use {@link android.graphics.Color#TRANSPARENT} to disable flap colors.
+ */
+ public void setFlapColor(int flapColor) {
+ boolean changed = mFlapPaint.getColor() != flapColor;
+ mFlapPaint.setColor(flapColor);
+
+ if (changed) {
+ invalidateSelf();
+ }
+ }
+
+ /**
+ * Get the color of the scrim that is drawn over the contents, but under the flaps and borders.
+ */
+ public int getScrimColor() {
+ return mScrimColor;
+ }
+
+ /**
+ * Set the color of the scrim that is drawn over the contents, but under the flaps and borders.
+ *
+ * Use {@link android.graphics.Color#TRANSPARENT} to disable the scrim.
+ */
+ public void setScrimColor(int color) {
+ boolean changed = mScrimColor != color;
+ mScrimColor = color;
+
+ if (changed) {
+ invalidateSelf();
+ }
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ super.onBoundsChange(bounds);
+
+ recalculatePath();
+ }
+
+ /**
+ * Override draw(android.graphics.Canvas) instead of
+ * {@link #onDraw(android.graphics.Canvas)} to clip all the drawable layers.
+ */
+ @Override
+ public void draw(Canvas canvas) {
+ final Rect bounds = getBounds();
+ if (bounds.isEmpty()) {
+ return;
+ }
+
+ // Clip to path.
+ canvas.save();
+ canvas.clipPath(mClipPath);
+
+ // Draw parent within path.
+ super.draw(canvas);
+
+ // Draw scrim on top of parent.
+ canvas.drawColor(mScrimColor);
+
+ // Draw flap.
+ float left = bounds.left + mBorderWidth / 2;
+ float top = bounds.top + mBorderWidth / 2;
+ float right = bounds.right - mBorderWidth / 2;
+ float bottom = bounds.bottom - mBorderWidth / 2;
+ RectF flapCornerRectF = sRectF;
+ flapCornerRectF.set(0, 0, mCornerFlapSide + mCornerRoundRadius,
+ mCornerFlapSide + mCornerRoundRadius);
+
+ if (mTopLeftCornerStyle == CORNER_STYLE_FLAP) {
+ flapCornerRectF.offsetTo(left, top);
+ canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
+ mCornerRoundRadius, mFlapPaint);
+ }
+ if (mTopRightCornerStyle == CORNER_STYLE_FLAP) {
+ flapCornerRectF.offsetTo(right - mCornerFlapSide, top);
+ canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
+ mCornerRoundRadius, mFlapPaint);
+ }
+ if (mBottomRightCornerStyle == CORNER_STYLE_FLAP) {
+ flapCornerRectF.offsetTo(right - mCornerFlapSide, bottom - mCornerFlapSide);
+ canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
+ mCornerRoundRadius, mFlapPaint);
+ }
+ if (mBottomLeftCornerStyle == CORNER_STYLE_FLAP) {
+ flapCornerRectF.offsetTo(left, bottom - mCornerFlapSide);
+ canvas.drawRoundRect(flapCornerRectF, mCornerRoundRadius,
+ mCornerRoundRadius, mFlapPaint);
+ }
+
+ canvas.restore();
+
+ // Draw border around path.
+ canvas.drawPath(mClipPath, mBorderPaint);
+ }
+
+ protected Path getClipPath() {
+ return mClipPath;
+ }
+
+ private void recalculatePath() {
+ Rect bounds = getBounds();
+
+ if (bounds.isEmpty()) {
+ return;
+ }
+
+ // Setup.
+ float left = bounds.left + mBorderWidth / 2;
+ float top = bounds.top + mBorderWidth / 2;
+ float right = bounds.right - mBorderWidth / 2;
+ float bottom = bounds.bottom - mBorderWidth / 2;
+ RectF roundedCornerRectF = sRectF;
+ roundedCornerRectF.set(0, 0, 2 * mCornerRoundRadius, 2 * mCornerRoundRadius);
+ mClipPath.rewind();
+
+ switch (mTopLeftCornerStyle) {
+ case CORNER_STYLE_SHARP:
+ mClipPath.moveTo(left, top);
+ break;
+ case CORNER_STYLE_ROUND:
+ roundedCornerRectF.offsetTo(left, top);
+ mClipPath.arcTo(roundedCornerRectF, START_LEFT, QUARTER_CIRCLE);
+ break;
+ case CORNER_STYLE_FLAP:
+ mClipPath.moveTo(left, top - mCornerFlapSide);
+ mClipPath.lineTo(left + mCornerFlapSide, top);
+ break;
+ }
+
+ switch (mTopRightCornerStyle) {
+ case CORNER_STYLE_SHARP:
+ mClipPath.lineTo(right, top);
+ break;
+ case CORNER_STYLE_ROUND:
+ roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(), top);
+ mClipPath.arcTo(roundedCornerRectF, START_TOP, QUARTER_CIRCLE);
+ break;
+ case CORNER_STYLE_FLAP:
+ mClipPath.lineTo(right - mCornerFlapSide, top);
+ mClipPath.lineTo(right, top + mCornerFlapSide);
+ break;
+ }
+
+ switch (mBottomRightCornerStyle) {
+ case CORNER_STYLE_SHARP:
+ mClipPath.lineTo(right, bottom);
+ break;
+ case CORNER_STYLE_ROUND:
+ roundedCornerRectF.offsetTo(right - roundedCornerRectF.width(),
+ bottom - roundedCornerRectF.height());
+ mClipPath.arcTo(roundedCornerRectF, START_RIGHT, QUARTER_CIRCLE);
+ break;
+ case CORNER_STYLE_FLAP:
+ mClipPath.lineTo(right, bottom - mCornerFlapSide);
+ mClipPath.lineTo(right - mCornerFlapSide, bottom);
+ break;
+ }
+
+ switch (mBottomLeftCornerStyle) {
+ case CORNER_STYLE_SHARP:
+ mClipPath.lineTo(left, bottom);
+ break;
+ case CORNER_STYLE_ROUND:
+ roundedCornerRectF.offsetTo(left, bottom - roundedCornerRectF.height());
+ mClipPath.arcTo(roundedCornerRectF, START_BOTTOM, QUARTER_CIRCLE);
+ break;
+ case CORNER_STYLE_FLAP:
+ mClipPath.lineTo(left + mCornerFlapSide, bottom);
+ mClipPath.lineTo(left, bottom - mCornerFlapSide);
+ break;
+ }
+
+ // Finish.
+ mClipPath.close();
+ }
+}