summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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();
+ }
+}