diff options
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(); + } +} |