Created
August 4, 2014 10:27
-
-
Save neilbantoc/d4724653b4761827e1bd to your computer and use it in GitHub Desktop.
RoundedSquareImageViews for Rounded image views locked in a square aspect ratio
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8"?> | |
<resources> | |
<declare-styleable name="SquareImageView"> | |
<attr name="useWidthToScale" format="boolean"/> | |
</declare-styleable> | |
</resources> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.res.ColorStateList; | |
import android.graphics.Bitmap; | |
import android.graphics.Bitmap.Config; | |
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.PixelFormat; | |
import android.graphics.Rect; | |
import android.graphics.RectF; | |
import android.graphics.Shader; | |
import android.graphics.drawable.BitmapDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.graphics.drawable.LayerDrawable; | |
import android.util.Log; | |
import android.widget.ImageView.ScaleType; | |
public class RoundedDrawable extends Drawable { | |
public static final String TAG = "RoundedDrawable"; | |
public static final int DEFAULT_BORDER_COLOR = Color.BLACK; | |
private final RectF mBounds = new RectF(); | |
private final RectF mDrawableRect = new RectF(); | |
private final RectF mBitmapRect = new RectF(); | |
private final BitmapShader mBitmapShader; | |
private final Paint mBitmapPaint; | |
private final int mBitmapWidth; | |
private final int mBitmapHeight; | |
private final RectF mBorderRect = new RectF(); | |
private final Paint mBorderPaint; | |
private final Matrix mShaderMatrix = new Matrix(); | |
private float mCornerRadius = 0; | |
private boolean mOval = false; | |
private float mBorderWidth = 0; | |
private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); | |
private ScaleType mScaleType = ScaleType.FIT_CENTER; | |
public RoundedDrawable(Bitmap bitmap) { | |
mBitmapWidth = bitmap.getWidth(); | |
mBitmapHeight = bitmap.getHeight(); | |
mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight); | |
mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); | |
mBitmapShader.setLocalMatrix(mShaderMatrix); | |
mBitmapPaint = new Paint(); | |
mBitmapPaint.setStyle(Paint.Style.FILL); | |
mBitmapPaint.setAntiAlias(true); | |
mBitmapPaint.setShader(mBitmapShader); | |
mBorderPaint = new Paint(); | |
mBorderPaint.setStyle(Paint.Style.STROKE); | |
mBorderPaint.setAntiAlias(true); | |
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR)); | |
mBorderPaint.setStrokeWidth(mBorderWidth); | |
} | |
public static RoundedDrawable fromBitmap(Bitmap bitmap) { | |
if (bitmap != null) { | |
return new RoundedDrawable(bitmap); | |
} else { | |
return null; | |
} | |
} | |
public static Drawable fromDrawable(Drawable drawable) { | |
if (drawable != null) { | |
if (drawable instanceof RoundedDrawable) { | |
// just return if it's already a RoundedDrawable | |
return drawable; | |
} else if (drawable instanceof LayerDrawable) { | |
LayerDrawable ld = (LayerDrawable) drawable; | |
int num = ld.getNumberOfLayers(); | |
// loop through layers to and change to RoundedDrawables if possible | |
for (int i = 0; i < num; i++) { | |
Drawable d = ld.getDrawable(i); | |
ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d)); | |
} | |
return ld; | |
} | |
// try to get a bitmap from the drawable and | |
Bitmap bm = drawableToBitmap(drawable); | |
if (bm != null) { | |
return new RoundedDrawable(bm); | |
} else { | |
Log.w(TAG, "Failed to create bitmap from drawable!"); | |
} | |
} | |
return drawable; | |
} | |
public static Bitmap drawableToBitmap(Drawable drawable) { | |
if (drawable instanceof BitmapDrawable) { | |
return ((BitmapDrawable) drawable).getBitmap(); | |
} | |
Bitmap bitmap; | |
int width = Math.max(drawable.getIntrinsicWidth(), 1); | |
int height = Math.max(drawable.getIntrinsicHeight(), 1); | |
try { | |
bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888); | |
Canvas canvas = new Canvas(bitmap); | |
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); | |
drawable.draw(canvas); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
bitmap = null; | |
} | |
return bitmap; | |
} | |
@Override | |
public boolean isStateful() { | |
return mBorderColor.isStateful(); | |
} | |
@Override | |
protected boolean onStateChange(int[] state) { | |
int newColor = mBorderColor.getColorForState(state, 0); | |
if (mBorderPaint.getColor() != newColor) { | |
mBorderPaint.setColor(newColor); | |
return true; | |
} else { | |
return super.onStateChange(state); | |
} | |
} | |
private void updateShaderMatrix() { | |
float scale; | |
float dx; | |
float dy; | |
switch (mScaleType) { | |
case CENTER: | |
mBorderRect.set(mBounds); | |
mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2); | |
mShaderMatrix.set(null); | |
mShaderMatrix.setTranslate((int) ((mBorderRect.width() - mBitmapWidth) * 0.5f + 0.5f), | |
(int) ((mBorderRect.height() - mBitmapHeight) * 0.5f + 0.5f)); | |
break; | |
case CENTER_CROP: | |
mBorderRect.set(mBounds); | |
mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2); | |
mShaderMatrix.set(null); | |
dx = 0; | |
dy = 0; | |
if (mBitmapWidth * mBorderRect.height() > mBorderRect.width() * mBitmapHeight) { | |
scale = mBorderRect.height() / (float) mBitmapHeight; | |
dx = (mBorderRect.width() - mBitmapWidth * scale) * 0.5f; | |
} else { | |
scale = mBorderRect.width() / (float) mBitmapWidth; | |
dy = (mBorderRect.height() - mBitmapHeight * scale) * 0.5f; | |
} | |
mShaderMatrix.setScale(scale, scale); | |
mShaderMatrix.postTranslate((int) (dx + 0.5f) + mBorderWidth, | |
(int) (dy + 0.5f) + mBorderWidth); | |
break; | |
case CENTER_INSIDE: | |
mShaderMatrix.set(null); | |
if (mBitmapWidth <= mBounds.width() && mBitmapHeight <= mBounds.height()) { | |
scale = 1.0f; | |
} else { | |
scale = Math.min(mBounds.width() / (float) mBitmapWidth, | |
mBounds.height() / (float) mBitmapHeight); | |
} | |
dx = (int) ((mBounds.width() - mBitmapWidth * scale) * 0.5f + 0.5f); | |
dy = (int) ((mBounds.height() - mBitmapHeight * scale) * 0.5f + 0.5f); | |
mShaderMatrix.setScale(scale, scale); | |
mShaderMatrix.postTranslate(dx, dy); | |
mBorderRect.set(mBitmapRect); | |
mShaderMatrix.mapRect(mBorderRect); | |
mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL); | |
break; | |
default: | |
case FIT_CENTER: | |
mBorderRect.set(mBitmapRect); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.CENTER); | |
mShaderMatrix.mapRect(mBorderRect); | |
mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL); | |
break; | |
case FIT_END: | |
mBorderRect.set(mBitmapRect); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.END); | |
mShaderMatrix.mapRect(mBorderRect); | |
mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL); | |
break; | |
case FIT_START: | |
mBorderRect.set(mBitmapRect); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBounds, Matrix.ScaleToFit.START); | |
mShaderMatrix.mapRect(mBorderRect); | |
mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL); | |
break; | |
case FIT_XY: | |
mBorderRect.set(mBounds); | |
mBorderRect.inset((mBorderWidth) / 2, (mBorderWidth) / 2); | |
mShaderMatrix.set(null); | |
mShaderMatrix.setRectToRect(mBitmapRect, mBorderRect, Matrix.ScaleToFit.FILL); | |
break; | |
} | |
mDrawableRect.set(mBorderRect); | |
mBitmapShader.setLocalMatrix(mShaderMatrix); | |
} | |
@Override | |
protected void onBoundsChange(Rect bounds) { | |
super.onBoundsChange(bounds); | |
mBounds.set(bounds); | |
updateShaderMatrix(); | |
} | |
@Override | |
public void draw(Canvas canvas) { | |
if (mOval) { | |
if (mBorderWidth > 0) { | |
canvas.drawOval(mDrawableRect, mBitmapPaint); | |
canvas.drawOval(mBorderRect, mBorderPaint); | |
} else { | |
canvas.drawOval(mDrawableRect, mBitmapPaint); | |
} | |
} else { | |
if (mBorderWidth > 0) { | |
canvas.drawRoundRect(mDrawableRect, Math.max(mCornerRadius, 0), Math.max(mCornerRadius, 0), mBitmapPaint); | |
canvas.drawRoundRect(mBorderRect, mCornerRadius, mCornerRadius, mBorderPaint); | |
} else { | |
canvas.drawRoundRect(mDrawableRect, mCornerRadius, mCornerRadius, mBitmapPaint); | |
} | |
} | |
} | |
@Override | |
public int getOpacity() { | |
return PixelFormat.TRANSLUCENT; | |
} | |
@Override | |
public void setAlpha(int alpha) { | |
mBitmapPaint.setAlpha(alpha); | |
invalidateSelf(); | |
} | |
@Override | |
public void setColorFilter(ColorFilter cf) { | |
mBitmapPaint.setColorFilter(cf); | |
invalidateSelf(); | |
} | |
@Override public void setDither(boolean dither) { | |
mBitmapPaint.setDither(dither); | |
invalidateSelf(); | |
} | |
@Override public void setFilterBitmap(boolean filter) { | |
mBitmapPaint.setFilterBitmap(filter); | |
invalidateSelf(); | |
} | |
@Override | |
public int getIntrinsicWidth() { | |
return mBitmapWidth; | |
} | |
@Override | |
public int getIntrinsicHeight() { | |
return mBitmapHeight; | |
} | |
public float getCornerRadius() { | |
return mCornerRadius; | |
} | |
public RoundedDrawable setCornerRadius(float radius) { | |
mCornerRadius = radius; | |
return this; | |
} | |
public float getBorderWidth() { | |
return mBorderWidth; | |
} | |
public RoundedDrawable setBorderWidth(float width) { | |
mBorderWidth = width; | |
mBorderPaint.setStrokeWidth(mBorderWidth); | |
return this; | |
} | |
public int getBorderColor() { | |
return mBorderColor.getDefaultColor(); | |
} | |
public RoundedDrawable setBorderColor(int color) { | |
return setBorderColor(ColorStateList.valueOf(color)); | |
} | |
public ColorStateList getBorderColors() { | |
return mBorderColor; | |
} | |
public RoundedDrawable setBorderColor(ColorStateList colors) { | |
mBorderColor = colors != null ? colors : ColorStateList.valueOf(0); | |
mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR)); | |
return this; | |
} | |
public boolean isOval() { | |
return mOval; | |
} | |
public RoundedDrawable setOval(boolean oval) { | |
mOval = oval; | |
return this; | |
} | |
public ScaleType getScaleType() { | |
return mScaleType; | |
} | |
public RoundedDrawable setScaleType(ScaleType scaleType) { | |
if (scaleType == null) { | |
scaleType = ScaleType.FIT_CENTER; | |
} | |
if (mScaleType != scaleType) { | |
mScaleType = scaleType; | |
updateShaderMatrix(); | |
} | |
return this; | |
} | |
public Bitmap toBitmap() { | |
return drawableToBitmap(this); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.content.res.ColorStateList; | |
import android.content.res.Resources; | |
import android.graphics.Bitmap; | |
import android.graphics.drawable.Drawable; | |
import android.graphics.drawable.LayerDrawable; | |
import android.net.Uri; | |
import android.util.AttributeSet; | |
import android.util.Log; | |
public class RoundedSquareImageView extends SquareImageView { | |
public static final String TAG = "RoundedImageView"; | |
public static final float DEFAULT_RADIUS = 0f; | |
public static final float DEFAULT_BORDER_WIDTH = 0f; | |
private float cornerRadius = DEFAULT_RADIUS; | |
private float borderWidth = DEFAULT_BORDER_WIDTH; | |
private ColorStateList borderColor = | |
ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR); | |
private boolean isOval = false; | |
private boolean mutateBackground = false; | |
private int mResource; | |
private Drawable mDrawable; | |
private Drawable mBackgroundDrawable; | |
private ScaleType mScaleType; | |
public RoundedSquareImageView(Context context) { | |
super(context); | |
} | |
public RoundedSquareImageView(Context context, AttributeSet attrs) { | |
this(context, attrs, 0); | |
} | |
@Override | |
protected void onSizeChanged(int w, int h, int oldw, int oldh) { | |
super.onSizeChanged(w, h, oldw, oldh); | |
float halfWidth = (float) w / 2; | |
setCornerRadius(halfWidth); | |
} | |
public RoundedSquareImageView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
cornerRadius = DEFAULT_RADIUS; | |
borderWidth = DEFAULT_BORDER_WIDTH; | |
borderColor = ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR); | |
mutateBackground = false; | |
isOval = false; | |
updateDrawableAttrs(); | |
updateBackgroundDrawableAttrs(true); | |
} | |
@Override | |
protected void drawableStateChanged() { | |
super.drawableStateChanged(); | |
invalidate(); | |
} | |
/** | |
* Return the current scale type in use by this ImageView. | |
* | |
* @attr ref android.R.styleable#ImageView_scaleType | |
* @see android.widget.ImageView.ScaleType | |
*/ | |
@Override | |
public ScaleType getScaleType() { | |
return mScaleType; | |
} | |
/** | |
* Controls how the image should be resized or moved to match the size | |
* of this ImageView. | |
* | |
* @param scaleType The desired scaling mode. | |
* @attr ref android.R.styleable#ImageView_scaleType | |
*/ | |
@Override | |
public void setScaleType(ScaleType scaleType) { | |
assert scaleType != null; | |
if (mScaleType != scaleType) { | |
mScaleType = scaleType; | |
switch (scaleType) { | |
case CENTER: | |
case CENTER_CROP: | |
case CENTER_INSIDE: | |
case FIT_CENTER: | |
case FIT_START: | |
case FIT_END: | |
case FIT_XY: | |
super.setScaleType(ScaleType.FIT_XY); | |
break; | |
default: | |
super.setScaleType(scaleType); | |
break; | |
} | |
updateDrawableAttrs(); | |
updateBackgroundDrawableAttrs(false); | |
invalidate(); | |
} | |
} | |
@Override | |
public void setImageDrawable(Drawable drawable) { | |
mResource = 0; | |
mDrawable = RoundedDrawable.fromDrawable(drawable); | |
updateDrawableAttrs(); | |
super.setImageDrawable(mDrawable); | |
} | |
@Override | |
public void setImageBitmap(Bitmap bm) { | |
mResource = 0; | |
mDrawable = RoundedDrawable.fromBitmap(bm); | |
updateDrawableAttrs(); | |
super.setImageDrawable(mDrawable); | |
} | |
@Override | |
public void setImageResource(int resId) { | |
if (mResource != resId) { | |
mResource = resId; | |
mDrawable = resolveResource(); | |
updateDrawableAttrs(); | |
super.setImageDrawable(mDrawable); | |
} | |
} | |
@Override public void setImageURI(Uri uri) { | |
super.setImageURI(uri); | |
setImageDrawable(getDrawable()); | |
} | |
private Drawable resolveResource() { | |
Resources rsrc = getResources(); | |
if (rsrc == null) { return null; } | |
Drawable d = null; | |
if (mResource != 0) { | |
try { | |
d = rsrc.getDrawable(mResource); | |
} catch (Exception e) { | |
Log.w(TAG, "Unable to find resource: " + mResource, e); | |
// Don't try again. | |
mResource = 0; | |
} | |
} | |
return RoundedDrawable.fromDrawable(d); | |
} | |
@Override | |
public void setBackground(Drawable background) { | |
setBackgroundDrawable(background); | |
} | |
private void updateDrawableAttrs() { | |
updateAttrs(mDrawable); | |
} | |
private void updateBackgroundDrawableAttrs(boolean convert) { | |
if (mutateBackground) { | |
if (convert) { | |
mBackgroundDrawable = RoundedDrawable.fromDrawable(mBackgroundDrawable); | |
} | |
updateAttrs(mBackgroundDrawable); | |
} | |
} | |
private void updateAttrs(Drawable drawable) { | |
if (drawable == null) { return; } | |
if (drawable instanceof RoundedDrawable) { | |
((RoundedDrawable) drawable) | |
.setScaleType(mScaleType) | |
.setCornerRadius(cornerRadius) | |
.setBorderWidth(borderWidth) | |
.setBorderColor(borderColor) | |
.setOval(isOval); | |
} else if (drawable instanceof LayerDrawable) { | |
LayerDrawable ld = ((LayerDrawable) drawable); | |
for (int i = 0, layers = ld.getNumberOfLayers(); i < layers; i++) { | |
updateAttrs(ld.getDrawable(i)); | |
} | |
} | |
} | |
@Override | |
@Deprecated | |
public void setBackgroundDrawable(Drawable background) { | |
mBackgroundDrawable = background; | |
updateBackgroundDrawableAttrs(true); | |
super.setBackgroundDrawable(mBackgroundDrawable); | |
} | |
public float getCornerRadius() { | |
return cornerRadius; | |
} | |
public void setCornerRadius(int resId) { | |
setCornerRadius(getResources().getDimension(resId)); | |
} | |
public void setCornerRadius(float radius) { | |
if (cornerRadius == radius) { return; } | |
cornerRadius = radius; | |
updateDrawableAttrs(); | |
updateBackgroundDrawableAttrs(false); | |
} | |
public float getBorderWidth() { | |
return borderWidth; | |
} | |
public void setBorderWidth(int resId) { | |
setBorderWidth(getResources().getDimension(resId)); | |
} | |
public void setBorderWidth(float width) { | |
if (borderWidth == width) { return; } | |
borderWidth = width; | |
updateDrawableAttrs(); | |
updateBackgroundDrawableAttrs(false); | |
invalidate(); | |
} | |
public int getBorderColor() { | |
return borderColor.getDefaultColor(); | |
} | |
public void setBorderColor(int color) { | |
setBorderColor(ColorStateList.valueOf(color)); | |
} | |
public ColorStateList getBorderColors() { | |
return borderColor; | |
} | |
public void setBorderColor(ColorStateList colors) { | |
if (borderColor.equals(colors)) { return; } | |
borderColor = (colors != null) ? colors : ColorStateList.valueOf(RoundedDrawable.DEFAULT_BORDER_COLOR); | |
updateDrawableAttrs(); | |
updateBackgroundDrawableAttrs(false); | |
if (borderWidth > 0) { | |
invalidate(); | |
} | |
} | |
public boolean isOval() { | |
return isOval; | |
} | |
public void setOval(boolean oval) { | |
isOval = oval; | |
updateDrawableAttrs(); | |
updateBackgroundDrawableAttrs(false); | |
invalidate(); | |
} | |
public boolean isMutateBackground() { | |
return mutateBackground; | |
} | |
public void setMutateBackground(boolean mutate) { | |
if (mutateBackground == mutate) { return; } | |
mutateBackground = mutate; | |
updateBackgroundDrawableAttrs(true); | |
invalidate(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.content.res.TypedArray; | |
import android.util.AttributeSet; | |
import android.widget.ImageView; | |
import com.myspace.ilike.R; | |
public class SquareImageView extends ImageView { | |
private boolean useWidthToScale; | |
public SquareImageView(Context context) { | |
this(context, null); | |
} | |
public SquareImageView(Context context, AttributeSet attrs) { | |
this(context, attrs, 0); | |
} | |
public SquareImageView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
useWidthToScale = true; | |
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SquareImageView, 0, 0); | |
for (int x = 0; x < typedArray.length(); x++) { | |
int index = typedArray.getIndex(x); | |
switch (index) { | |
case R.styleable.SquareImageView_useWidthToScale: | |
useWidthToScale = typedArray.getBoolean(index, true); | |
break; | |
} | |
} | |
typedArray.recycle(); | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
int scaleBy = useWidthToScale ? MeasureSpec.getSize(widthMeasureSpec) : MeasureSpec.getSize(heightMeasureSpec); | |
setMeasuredDimension(scaleBy, scaleBy); | |
} | |
public void setUseWidthToScale(boolean useWidth){ | |
useWidthToScale = useWidth; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment