package com.joooonho; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.content.res.TypedArray; 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.Path; 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.net.Uri; import android.util.AttributeSet; import android.util.Log; import android.widget.ImageView; import com.hoollyzhang.hlib.R; public class SelectableRoundedImageView extends ImageView { public static final String TAG = "SelectableRoundedImageView"; private int mResource = 0; private static final ScaleType[] sScaleTypeArray = { ScaleType.MATRIX, ScaleType.FIT_XY, ScaleType.FIT_START, ScaleType.FIT_CENTER, ScaleType.FIT_END, ScaleType.CENTER, ScaleType.CENTER_CROP, ScaleType.CENTER_INSIDE }; // Set default scale type to FIT_CENTER, which is default scale type of // original ImageView. private ScaleType mScaleType = ScaleType.FIT_CENTER; private float mLeftTopCornerRadius = 0.0f; private float mRightTopCornerRadius = 0.0f; private float mLeftBottomCornerRadius = 0.0f; private float mRightBottomCornerRadius = 0.0f; private float mBorderWidth = 0.0f; private static final int DEFAULT_BORDER_COLOR = Color.BLACK; private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); private boolean isOval = false; private Drawable mDrawable; private float[] mRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 }; public SelectableRoundedImageView(Context context) { super(context); } public SelectableRoundedImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SelectableRoundedImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SelectableRoundedImageView, defStyle, 0); final int index = a.getInt(R.styleable.SelectableRoundedImageView_android_scaleType, -1); if (index >= 0) { setScaleType(sScaleTypeArray[index]); } mLeftTopCornerRadius = a.getDimensionPixelSize( R.styleable.SelectableRoundedImageView_sriv_left_top_corner_radius, 0); mRightTopCornerRadius = a.getDimensionPixelSize( R.styleable.SelectableRoundedImageView_sriv_right_top_corner_radius, 0); mLeftBottomCornerRadius = a.getDimensionPixelSize( R.styleable.SelectableRoundedImageView_sriv_left_bottom_corner_radius, 0); mRightBottomCornerRadius = a.getDimensionPixelSize( R.styleable.SelectableRoundedImageView_sriv_right_bottom_corner_radius, 0); if (mLeftTopCornerRadius < 0.0f || mRightTopCornerRadius < 0.0f || mLeftBottomCornerRadius < 0.0f || mRightBottomCornerRadius < 0.0f) { throw new IllegalArgumentException("radius values cannot be negative."); } mRadii = new float[] { mLeftTopCornerRadius, mLeftTopCornerRadius, mRightTopCornerRadius, mRightTopCornerRadius, mRightBottomCornerRadius, mRightBottomCornerRadius, mLeftBottomCornerRadius, mLeftBottomCornerRadius }; mBorderWidth = a.getDimensionPixelSize( R.styleable.SelectableRoundedImageView_sriv_border_width, 0); if (mBorderWidth < 0) { throw new IllegalArgumentException("border width cannot be negative."); } mBorderColor = a .getColorStateList(R.styleable.SelectableRoundedImageView_sriv_border_color); if (mBorderColor == null) { mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); } isOval = a.getBoolean(R.styleable.SelectableRoundedImageView_sriv_oval, false); a.recycle(); updateDrawable(); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); invalidate(); } @Override public ScaleType getScaleType() { return mScaleType; } @Override public void setScaleType(ScaleType scaleType) { super.setScaleType(scaleType); mScaleType = scaleType; updateDrawable(); } @Override public void setImageDrawable(Drawable drawable) { mResource = 0; mDrawable = SelectableRoundedCornerDrawable.fromDrawable(drawable, getResources()); super.setImageDrawable(mDrawable); updateDrawable(); } @Override public void setImageBitmap(Bitmap bm) { mResource = 0; mDrawable = SelectableRoundedCornerDrawable.fromBitmap(bm, getResources()); super.setImageDrawable(mDrawable); updateDrawable(); } @Override public void setImageResource(int resId) { if (mResource != resId) { mResource = resId; mDrawable = resolveResource(); super.setImageDrawable(mDrawable); updateDrawable(); } } @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 (NotFoundException e) { Log.w(TAG, "Unable to find resource: " + mResource, e); // Don't try again. mResource = 0; } } return SelectableRoundedCornerDrawable.fromDrawable(d, getResources()); } private void updateDrawable() { if (mDrawable == null) { return; } ((SelectableRoundedCornerDrawable) mDrawable).setScaleType(mScaleType); ((SelectableRoundedCornerDrawable) mDrawable).setCornerRadii(mRadii); ((SelectableRoundedCornerDrawable) mDrawable).setBorderWidth(mBorderWidth); ((SelectableRoundedCornerDrawable) mDrawable).setBorderColor(mBorderColor); ((SelectableRoundedCornerDrawable) mDrawable).setOval(isOval); } public float getCornerRadius() { return mLeftTopCornerRadius; } /** * Set radii for each corner. * * @param leftTop The desired radius for left-top corner in dip. * @param rightTop The desired desired radius for right-top corner in dip. * @param leftBottom The desired radius for left-bottom corner in dip. * @param rightBottom The desired radius for right-bottom corner in dip. * */ public void setCornerRadiiDP(float leftTop, float rightTop, float leftBottom, float rightBottom) { final float density = getResources().getDisplayMetrics().density; final float lt = leftTop * density; final float rt = rightTop * density; final float lb = leftBottom * density; final float rb = rightBottom * density; mRadii = new float[] { lt, lt, rt, rt, rb, rb, lb, lb }; updateDrawable(); } public float getBorderWidth() { return mBorderWidth; } /** * Set border width. * * @param width * The desired width in dip. */ public void setBorderWidthDP(float width) { float scaledWidth = getResources().getDisplayMetrics().density * width; if (mBorderWidth == scaledWidth) { return; } mBorderWidth = scaledWidth; updateDrawable(); invalidate(); } public int getBorderColor() { return mBorderColor.getDefaultColor(); } public void setBorderColor(int color) { setBorderColor(ColorStateList.valueOf(color)); } public ColorStateList getBorderColors() { return mBorderColor; } public void setBorderColor(ColorStateList colors) { if (mBorderColor.equals(colors)) { return; } mBorderColor = (colors != null) ? colors : ColorStateList .valueOf(DEFAULT_BORDER_COLOR); updateDrawable(); if (mBorderWidth > 0) { invalidate(); } } public boolean isOval() { return isOval; } public void setOval(boolean oval) { isOval = oval; updateDrawable(); invalidate(); } static class SelectableRoundedCornerDrawable extends Drawable { private static final String TAG = "SelectableRoundedCornerDrawable"; private static final int DEFAULT_BORDER_COLOR = Color.BLACK; private RectF mBounds = new RectF(); private RectF mBorderBounds = new RectF(); private final RectF mBitmapRect = new RectF(); private final int mBitmapWidth; private final int mBitmapHeight; private final Paint mBitmapPaint; private final Paint mBorderPaint; private BitmapShader mBitmapShader; private float[] mRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 }; private float[] mBorderRadii = new float[] { 0, 0, 0, 0, 0, 0, 0, 0 }; private boolean mOval = false; private float mBorderWidth = 0; private ColorStateList mBorderColor = ColorStateList.valueOf(DEFAULT_BORDER_COLOR); // Set default scale type to FIT_CENTER, which is default scale type of // original ImageView. private ScaleType mScaleType = ScaleType.FIT_CENTER; private Path mPath = new Path(); private Bitmap mBitmap; private boolean mBoundsConfigured = false; public SelectableRoundedCornerDrawable(Bitmap bitmap, Resources r) { mBitmap = bitmap; mBitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); if (bitmap != null) { mBitmapWidth = bitmap.getScaledWidth(r.getDisplayMetrics()); mBitmapHeight = bitmap.getScaledHeight(r.getDisplayMetrics()); } else { mBitmapWidth = mBitmapHeight = -1; } mBitmapRect.set(0, 0, mBitmapWidth, mBitmapHeight); mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBitmapPaint.setStyle(Paint.Style.FILL); mBitmapPaint.setShader(mBitmapShader); mBorderPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mBorderPaint.setStyle(Paint.Style.STROKE); mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR)); mBorderPaint.setStrokeWidth(mBorderWidth); } public static SelectableRoundedCornerDrawable fromBitmap(Bitmap bitmap, Resources r) { if (bitmap != null) { return new SelectableRoundedCornerDrawable(bitmap, r); } else { return null; } } public static Drawable fromDrawable(Drawable drawable, Resources r) { if (drawable != null) { if (drawable instanceof SelectableRoundedCornerDrawable) { return drawable; } else if (drawable instanceof LayerDrawable) { LayerDrawable ld = (LayerDrawable) drawable; final int num = ld.getNumberOfLayers(); for (int i = 0; i < num; i++) { Drawable d = ld.getDrawable(i); ld.setDrawableByLayerId(ld.getId(i), fromDrawable(d, r)); } return ld; } Bitmap bm = drawableToBitmap(drawable); if (bm != null) { return new SelectableRoundedCornerDrawable(bm, r); } else { Log.w(TAG, "Failed to create bitmap from drawable!"); } } return drawable; } public static Bitmap drawableToBitmap(Drawable drawable) { if (drawable == null) { return null; } if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } Bitmap bitmap; int width = Math.max(drawable.getIntrinsicWidth(), 2); int height = Math.max(drawable.getIntrinsicHeight(), 2); 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 (IllegalArgumentException 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 configureBounds(Canvas canvas) { // I have discovered a truly marvelous explanation of this, // which this comment space is too narrow to contain. :) // If you want to understand what's going on here, // See http://www.joooooooooonhokim.com/?p=289 Rect clipBounds = canvas.getClipBounds(); Matrix canvasMatrix = canvas.getMatrix(); if (ScaleType.CENTER == mScaleType) { mBounds.set(clipBounds); } else if (ScaleType.CENTER_CROP == mScaleType) { applyScaleToRadii(canvasMatrix); mBounds.set(clipBounds); } else if (ScaleType.FIT_XY == mScaleType) { Matrix m = new Matrix(); m.setRectToRect(mBitmapRect, new RectF(clipBounds), Matrix.ScaleToFit.FILL); mBitmapShader.setLocalMatrix(m); mBounds.set(clipBounds); } else if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType || ScaleType.FIT_CENTER == mScaleType || ScaleType.CENTER_INSIDE == mScaleType) { applyScaleToRadii(canvasMatrix); mBounds.set(mBitmapRect); } else if (ScaleType.MATRIX == mScaleType) { applyScaleToRadii(canvasMatrix); mBounds.set(mBitmapRect); } } private void applyScaleToRadii(Matrix m) { float[] values = new float[9]; m.getValues(values); for (int i = 0; i < mRadii.length; i++) { mRadii[i] = mRadii[i] / values[0]; } } private void adjustCanvasForBorder(Canvas canvas) { Matrix canvasMatrix = canvas.getMatrix(); final float[] values = new float[9]; canvasMatrix.getValues(values); final float scaleFactorX = values[0]; final float scaleFactorY = values[4]; final float translateX = values[2]; final float translateY = values[5]; final float newScaleX = mBounds.width() / (mBounds.width() + mBorderWidth + mBorderWidth); final float newScaleY = mBounds.height() / (mBounds.height() + mBorderWidth + mBorderWidth); canvas.scale(newScaleX, newScaleY); if (ScaleType.FIT_START == mScaleType || ScaleType.FIT_END == mScaleType || ScaleType.FIT_XY == mScaleType || ScaleType.FIT_CENTER == mScaleType || ScaleType.CENTER_INSIDE == mScaleType || ScaleType.MATRIX == mScaleType) { canvas.translate(mBorderWidth, mBorderWidth); } else if (ScaleType.CENTER == mScaleType || ScaleType.CENTER_CROP == mScaleType) { // First, make translate values to 0 canvas.translate( -translateX / (newScaleX * scaleFactorX), -translateY / (newScaleY * scaleFactorY)); // Then, set the final translate values. canvas.translate(-(mBounds.left - mBorderWidth), -(mBounds.top - mBorderWidth)); } } private void adjustBorderWidthAndBorderBounds(Canvas canvas) { Matrix canvasMatrix = canvas.getMatrix(); final float[] values = new float[9]; canvasMatrix.getValues(values); final float scaleFactor = values[0]; float viewWidth = mBounds.width() * scaleFactor; mBorderWidth = (mBorderWidth * mBounds.width()) / (viewWidth - (2 * mBorderWidth)); mBorderPaint.setStrokeWidth(mBorderWidth); mBorderBounds.set(mBounds); mBorderBounds.inset(- mBorderWidth / 2, - mBorderWidth / 2); } private void setBorderRadii() { for (int i = 0; i < mRadii.length; i++) { if (mRadii[i] > 0) { mBorderRadii[i] = mRadii[i]; mRadii[i] = mRadii[i] - mBorderWidth; } } } @Override public void draw(Canvas canvas) { canvas.save(); if (!mBoundsConfigured) { configureBounds(canvas); if (mBorderWidth > 0) { adjustBorderWidthAndBorderBounds(canvas); setBorderRadii(); } mBoundsConfigured = true; } if (mOval) { if (mBorderWidth > 0) { adjustCanvasForBorder(canvas); mPath.addOval(mBounds, Path.Direction.CW); canvas.drawPath(mPath, mBitmapPaint); mPath.reset(); mPath.addOval(mBorderBounds, Path.Direction.CW); canvas.drawPath(mPath, mBorderPaint); } else { mPath.addOval(mBounds, Path.Direction.CW); canvas.drawPath(mPath, mBitmapPaint); } } else { if (mBorderWidth > 0) { adjustCanvasForBorder(canvas); mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW); canvas.drawPath(mPath, mBitmapPaint); mPath.reset(); mPath.addRoundRect(mBorderBounds, mBorderRadii, Path.Direction.CW); canvas.drawPath(mPath, mBorderPaint); } else { mPath.addRoundRect(mBounds, mRadii, Path.Direction.CW); canvas.drawPath(mPath, mBitmapPaint); } } canvas.restore(); } public void setCornerRadii(float[] radii) { if (radii == null) return; if (radii.length != 8) { throw new ArrayIndexOutOfBoundsException("radii[] needs 8 values"); } for (int i = 0; i < radii.length; i++) { mRadii[i] = radii[i]; } } @Override public int getOpacity() { return (mBitmap == null || mBitmap.hasAlpha() || mBitmapPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; } @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 getBorderWidth() { return mBorderWidth; } public void setBorderWidth(float width) { mBorderWidth = width; mBorderPaint.setStrokeWidth(width); } public int getBorderColor() { return mBorderColor.getDefaultColor(); } public void setBorderColor(int color) { setBorderColor(ColorStateList.valueOf(color)); } public ColorStateList getBorderColors() { return mBorderColor; } /** * Controls border color of this ImageView. * * @param colors * The desired border color. If it's null, no border will be * drawn. * */ public void setBorderColor(ColorStateList colors) { if (colors == null) { mBorderWidth = 0; mBorderColor = ColorStateList.valueOf(Color.TRANSPARENT); mBorderPaint.setColor(Color.TRANSPARENT); } else { mBorderColor = colors; mBorderPaint.setColor(mBorderColor.getColorForState(getState(), DEFAULT_BORDER_COLOR)); } } public boolean isOval() { return mOval; } public void setOval(boolean oval) { mOval = oval; } public ScaleType getScaleType() { return mScaleType; } public void setScaleType(ScaleType scaleType) { if (scaleType == null) { return; } mScaleType = scaleType; } } }