diff --git a/cropimageview-samples/build.gradle b/cropimageview-samples/build.gradle index 4a907c4..c19ad5e 100644 --- a/cropimageview-samples/build.gradle +++ b/cropimageview-samples/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.jakewharton.hugo:hugo-plugin:1.2.0' + classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1' } } diff --git a/cropimageview/src/main/java/com/cesards/cropimageview/Corner.java b/cropimageview/src/main/java/com/cesards/cropimageview/Corner.java new file mode 100644 index 0000000..d75c05e --- /dev/null +++ b/cropimageview/src/main/java/com/cesards/cropimageview/Corner.java @@ -0,0 +1,15 @@ +package com.cesards.cropimageview; + +import android.support.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.SOURCE) +@IntDef({ + RoundedCornerDrawable.CORNER_TOP_LEFT, + RoundedCornerDrawable.CORNER_TOP_RIGHT, + RoundedCornerDrawable.CORNER_BOTTOM_LEFT, + RoundedCornerDrawable.CORNER_BOTTOM_RIGHT +}) +public @interface Corner { } \ No newline at end of file diff --git a/cropimageview/src/main/java/com/cesards/cropimageview/CropImageView.java b/cropimageview/src/main/java/com/cesards/cropimageview/CropImageView.java index c0a1cab..9b438b8 100644 --- a/cropimageview/src/main/java/com/cesards/cropimageview/CropImageView.java +++ b/cropimageview/src/main/java/com/cesards/cropimageview/CropImageView.java @@ -19,19 +19,27 @@ import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; +import android.graphics.Bitmap; import android.graphics.Matrix; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.os.Build; import android.util.AttributeSet; import android.widget.ImageView; + import java.util.HashMap; import java.util.Map; +import static com.cesards.cropimageview.RoundedCornerDrawable.*; + /** * @author cesards */ public class CropImageView extends ImageView { private CropType cropType = CropType.NONE; + private final int[] cornerRadius = new int[4]; + private boolean specificCornerRadiusSet = true; public CropImageView(Context context) { super(context); @@ -47,8 +55,8 @@ public CropImageView(Context context, AttributeSet attrs, int defStyle) { this.parseAttributes(attrs); } - @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CropImageView(Context context, AttributeSet attrs, int defStyleAttr, - int defStyleRes) { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public CropImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); this.parseAttributes(attrs); } @@ -82,6 +90,138 @@ public CropType getCropType() { return this.cropType; } + /** + * @return the largest corner radius. + */ + public float getCornerRadius() { + return getMaxCornerRadius(); + } + + /** + * @return the largest corner radius. + */ + public int getMaxCornerRadius() { + int maxRadius = DEFAULT_RADIUS; + for (int i = cornerRadius.length - 1; i >= 0; i--) { + maxRadius = Math.max(cornerRadius[i], maxRadius); + } + + return maxRadius; + } + + /** + * Get the corner radius of a specified corner. + * + * @param corner the corner. + * @return the radius. + */ + public int getCornerRadius(@Corner int corner) { + return cornerRadius[corner]; + } + + /** + * Set the corner radii of all corners in px. + * + * @param radius the radius to set. + */ + public void setCornerRadius(int radius) { + setCornerRadius(radius, radius, radius, radius); + } + + /** + * Set the corner radius of a specific corner from a dimension resource id. + * + * @param corner the corner to set. + * @param resId the dimension resource id of the corner radius. + */ + public void setCornerRadiusDimen(@Corner int corner, @DimenRes int resId) { + setCornerRadius(corner, getResources().getDimensionPixelSize(resId)); + } + + /** + * Set all the corner radii from a dimension resource id. + * + * @param resId dimension resource id of radii. + */ + public void setCornerRadiusDimen(@DimenRes int resId) { + final int radius = getResources().getDimensionPixelOffset(resId); + setCornerRadius(radius, radius, radius, radius); + } + + /** + * Set the corner radius of a specific corner in px. + * + * @param corner the corner to set. + * @param radius the corner radius to set in px. + */ + public void setCornerRadius(@Corner int corner, int radius) { + if (radius < DEFAULT_RADIUS) throw new IllegalArgumentException("Non negative radius supported"); + + if (cornerRadius[corner] == radius) return; + + cornerRadius[corner] = radius; + + setDrawableCorners(getDrawable()); + invalidate(); + } + + /** + * Set the corner radii of each corner individually. Currently only one unique nonzero value is + * supported. + * + * @param topLeft radius of the top left corner in px. + * @param topRight radius of the top right corner in px. + * @param bottomRight radius of the bottom right corner in px. + * @param bottomLeft radius of the bottom left corner in px. + */ + public void setCornerRadius(int topLeft, int topRight, int bottomLeft, int bottomRight) { + if (topLeft < DEFAULT_RADIUS || topRight < DEFAULT_RADIUS || bottomLeft < DEFAULT_RADIUS || bottomRight < DEFAULT_RADIUS) throw new IllegalArgumentException("Non negative radius supported"); + + if (cornerRadius[CORNER_TOP_LEFT] == topLeft && cornerRadius[CORNER_TOP_RIGHT] == topRight && cornerRadius[CORNER_BOTTOM_RIGHT] == bottomRight && cornerRadius[CORNER_BOTTOM_LEFT] == bottomLeft) return; + + cornerRadius[CORNER_TOP_LEFT] = topLeft; + cornerRadius[CORNER_TOP_RIGHT] = topRight; + cornerRadius[CORNER_BOTTOM_LEFT] = bottomLeft; + cornerRadius[CORNER_BOTTOM_RIGHT] = bottomRight; + + setDrawableCorners(getDrawable()); + invalidate(); + } + + @Override + public void setImageDrawable(Drawable drawable) { + final Drawable roundedDrawable = RoundedCornerDrawable.fromDrawable(drawable); + setDrawableCorners(roundedDrawable); + + super.setImageDrawable(roundedDrawable); + } + + @Override + public void setImageBitmap(Bitmap bm) { + final Drawable roundedDrawable = RoundedCornerDrawable.fromBitmap(bm); + setDrawableCorners(roundedDrawable); + + super.setImageDrawable(roundedDrawable); + } + + // TODO: Not sure if this is necessary + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + invalidate(); + } + + @Override + protected boolean setFrame(int l, int t, int r, int b) { + final boolean changed = super.setFrame(l, t, r, b); + if (!isInEditMode()) { + computeImageMatrix(); + } + + return changed; + } + private void parseAttributes(AttributeSet attrs) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CropImageView); @@ -90,28 +230,66 @@ private void parseAttributes(AttributeSet attrs) { setScaleType(ScaleType.MATRIX); this.cropType = CropType.get(crop); } + + final int cornerRadiusOverride = a.getDimensionPixelSize(R.styleable.CropImageView_cornerRadius, DEFAULT_RADIUS); + + cornerRadius[CORNER_TOP_LEFT] = a.getDimensionPixelSize(R.styleable.CropImageView_cornerRadiusTopLeft, DEFAULT_RADIUS); + cornerRadius[CORNER_TOP_RIGHT] = a.getDimensionPixelSize(R.styleable.CropImageView_cornerRadiusTopRight, DEFAULT_RADIUS); + cornerRadius[CORNER_BOTTOM_LEFT] = a.getDimensionPixelSize(R.styleable.CropImageView_cornerRadiusBottomLeft, DEFAULT_RADIUS); + cornerRadius[CORNER_BOTTOM_RIGHT] = a.getDimensionPixelSize(R.styleable.CropImageView_cornerRadiusBottomRight, DEFAULT_RADIUS); + + for (int i = 0; i < cornerRadius.length && specificCornerRadiusSet; i++) { + specificCornerRadiusSet = cornerRadius[i] > DEFAULT_RADIUS; + } + + if (cornerRadiusOverride > DEFAULT_RADIUS && !specificCornerRadiusSet) { + cornerRadius[CORNER_TOP_LEFT] = cornerRadiusOverride; + cornerRadius[CORNER_TOP_RIGHT] = cornerRadiusOverride; + cornerRadius[CORNER_BOTTOM_RIGHT] = cornerRadiusOverride; + cornerRadius[CORNER_BOTTOM_LEFT] = cornerRadiusOverride; + + specificCornerRadiusSet = true; + } + a.recycle(); } - @Override - protected boolean setFrame(int l, int t, int r, int b) { - final boolean changed = super.setFrame(l, t, r, b); - if (!isInEditMode()) { - this.computeImageMatrix(); + private void setDrawableCorners(Drawable drawable) { + if (drawable == null) return; + + if (drawable instanceof RoundedCornerDrawable && specificCornerRadiusSet) { + ((RoundedCornerDrawable) drawable).setCornerRadius(cornerRadius[CORNER_TOP_LEFT], cornerRadius[CORNER_TOP_RIGHT], cornerRadius[CORNER_BOTTOM_LEFT], cornerRadius[CORNER_BOTTOM_RIGHT]); + } else if (drawable instanceof LayerDrawable) { + // Loop through layers to and set drawable attrs + LayerDrawable ld = ((LayerDrawable) drawable); + for (int i = 0, layers = ld.getNumberOfLayers(); i < layers; i++) { + setDrawableCorners(ld.getDrawable(i)); + } } - return changed; } + + + + + private void computeImageMatrix() { final int viewWidth = getWidth() - getPaddingLeft() - getPaddingRight(); final int viewHeight = getHeight() - getPaddingTop() - getPaddingBottom(); if (cropType != CropType.NONE && viewHeight > 0 && viewWidth > 0) { - final Matrix matrix = getImageMatrix(); - - int drawableWidth = getDrawable().getIntrinsicWidth(); - int drawableHeight = getDrawable().getIntrinsicHeight(); + Matrix matrix = getImageMatrix(); + + int drawableWidth; + int drawableHeight; + if (specificCornerRadiusSet && getDrawable() instanceof RoundedCornerDrawable) { + drawableWidth = ((RoundedCornerDrawable) getDrawable()).getBitmapWidth(); + drawableHeight = ((RoundedCornerDrawable) getDrawable()).getBitmapHeight(); + } else { + drawableWidth = getDrawable().getIntrinsicWidth(); + drawableHeight = getDrawable().getIntrinsicHeight(); + } final float scaleY = (float) viewHeight / (float) drawableHeight; final float scaleX = (float) viewWidth / (float) drawableWidth; @@ -122,16 +300,21 @@ private void computeImageMatrix() { final float postDrawableWidth = drawableWidth * scale; final float xTranslation = getXTranslation(cropType, viewWidth, postDrawableWidth, verticalImageMode); - final float postDrawabeHeigth = drawableHeight * scale; - final float yTranslation = getYTranslation(cropType, viewHeight, postDrawabeHeigth, verticalImageMode); + final float postDrawableHeight = drawableHeight * scale; + final float yTranslation = getYTranslation(cropType, viewHeight, postDrawableHeight, verticalImageMode); matrix.postTranslate(xTranslation, yTranslation); - setImageMatrix(matrix); + + if (specificCornerRadiusSet && getDrawable() instanceof RoundedCornerDrawable) { + ((RoundedCornerDrawable) getDrawable()).setMatrix(matrix); + setImageMatrix(null); + } else { + setImageMatrix(matrix); + } } } - private float getYTranslation(CropType cropType, int viewHeight, float postDrawabeHeigth, - boolean verticalImageMode) { + private float getYTranslation(CropType cropType, int viewHeight, float postDrawabeHeigth, boolean verticalImageMode) { if (verticalImageMode) { switch (cropType) { case CENTER_BOTTOM: @@ -149,8 +332,7 @@ private float getYTranslation(CropType cropType, int viewHeight, float postDrawa return 0; } - private float getXTranslation(CropType cropType, int viewWidth, float postDrawableWidth, - boolean verticalImageMode) { + private float getXTranslation(CropType cropType, int viewWidth, float postDrawableWidth, boolean verticalImageMode) { if (!verticalImageMode) { switch (cropType) { case RIGHT_TOP: diff --git a/cropimageview/src/main/java/com/cesards/cropimageview/RoundedCornerDrawable.java b/cropimageview/src/main/java/com/cesards/cropimageview/RoundedCornerDrawable.java new file mode 100644 index 0000000..7c4162c --- /dev/null +++ b/cropimageview/src/main/java/com/cesards/cropimageview/RoundedCornerDrawable.java @@ -0,0 +1,235 @@ +package com.cesards.cropimageview; + +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +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 java.util.HashSet; +import java.util.Set; + +public class RoundedCornerDrawable extends Drawable { + + public static final int DEFAULT_RADIUS = 0; + public static final int CORNER_TOP_LEFT = 0; + public static final int CORNER_TOP_RIGHT = 1; + public static final int CORNER_BOTTOM_RIGHT = 2; + public static final int CORNER_BOTTOM_LEFT = 3; + + public static RoundedCornerDrawable fromBitmap(Bitmap bitmap) { + if (bitmap != null) { + return new RoundedCornerDrawable(bitmap); + } else { + return null; + } + } + + public static Drawable fromDrawable(Drawable drawable) { + if (drawable != null) { + if (drawable instanceof RoundedCornerDrawable) { + // Just return if it's already a RoundedCornerDrawable + return drawable; + } else if (drawable instanceof LayerDrawable) { + LayerDrawable ld = (LayerDrawable) drawable; + int num = ld.getNumberOfLayers(); + + // Loop through layers to and change to RounderCornerDrawable 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 + Bitmap bm = drawableToBitmap(drawable); + if (bm != null) { + return new RoundedCornerDrawable(bm); + } + } + return drawable; + } + + private final boolean[] cornersRounded = new boolean[] { true, true, true, true }; + private int cornerRadius = DEFAULT_RADIUS; + private Bitmap mBitmap; + private Paint mBitmapPaint; + + public RoundedCornerDrawable(Bitmap bitmap) { + mBitmap = bitmap; + + mBitmapPaint = new Paint(); + mBitmapPaint.setStyle(Paint.Style.FILL); + mBitmapPaint.setAntiAlias(true); + +// mShader = new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + } + + /** + * Sets the corner radius of all the corners. + * + * @param topLeft top left corner radius. + * @param topRight top right corner radius + * @param bottomRight bototm right corner radius. + * @param bottomLeft bottom left corner radius. + * @return the {@link RoundedCornerDrawable} for chaining. + */ + public RoundedCornerDrawable setCornerRadius(int topLeft, int topRight, int bottomRight, int bottomLeft) { + Set nonDefaultCorners = getNonDefaultCorners(topLeft, topRight, bottomRight, bottomLeft); + + if (nonDefaultCorners.size() > 1) { + throw new IllegalArgumentException("Multiple nonzero corner radius not yet supported."); + } + + if (!nonDefaultCorners.isEmpty()) { + int radius = nonDefaultCorners.iterator().next(); + if (Float.isInfinite(radius) || Float.isNaN(radius) || radius < 0) { + throw new IllegalArgumentException("Invalid radius value: " + radius); + } + + cornerRadius = radius; + } else { + cornerRadius = DEFAULT_RADIUS; + } + + cornersRounded[CORNER_TOP_LEFT] = topLeft > DEFAULT_RADIUS; + cornersRounded[CORNER_TOP_RIGHT] = topRight > DEFAULT_RADIUS; + cornersRounded[CORNER_BOTTOM_RIGHT] = bottomRight > DEFAULT_RADIUS; + cornersRounded[CORNER_BOTTOM_LEFT] = bottomLeft > DEFAULT_RADIUS; + + return this; + } + + private Set getNonDefaultCorners(int topLeft, int topRight, int bottomRight, int bottomLeft) { + final Set radiusSet = new HashSet<>(4); + radiusSet.add(topLeft); + radiusSet.add(topRight); + radiusSet.add(bottomRight); + radiusSet.add(bottomLeft); + + radiusSet.remove(DEFAULT_RADIUS); + + return radiusSet; + } + + + + + + + + + + + + + + + private RectF mDrawableRect = new RectF(); + + private BitmapShader mShader; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mDrawableRect.set(bounds); + } + + @Override + public void draw(Canvas canvas) { + mBitmapPaint.setShader(mShader); + canvas.drawRoundRect(mDrawableRect, cornerRadius, cornerRadius, mBitmapPaint); + } + + @Override + public void setAlpha(int alpha) { + mBitmapPaint.setAlpha(alpha); + invalidateSelf(); + } + + @Override + public void setColorFilter(ColorFilter cf) { + mBitmapPaint.setColorFilter(cf); + invalidateSelf(); + } + + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } + + public void setMatrix(Matrix matrix){ + mShader.setLocalMatrix(matrix); + invalidateSelf(); + } + + public int getBitmapWidth(){ + return mBitmap.getWidth(); + } + + public int getBitmapHeight(){ + return mBitmap.getHeight(); + } + + + + public static Bitmap drawableToBitmap(Drawable drawable) { + 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, Bitmap.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; + } +} \ No newline at end of file diff --git a/cropimageview/src/main/res/values/attrs.xml b/cropimageview/src/main/res/values/attrs.xml index bcdaad2..ab1a68e 100644 --- a/cropimageview/src/main/res/values/attrs.xml +++ b/cropimageview/src/main/res/values/attrs.xml @@ -3,15 +3,20 @@ - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file