package com.beardedhen.androidbootstrap; import android.content.Context; import android.content.res.TypedArray; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.util.AttributeSet; import com.beardedhen.androidbootstrap.api.defaults.DefaultBootstrapBrand; import com.beardedhen.androidbootstrap.api.defaults.DefaultBootstrapSize; import com.beardedhen.androidbootstrap.utils.ColorUtils; import com.beardedhen.androidbootstrap.utils.DimenUtils; import com.beardedhen.androidbootstrap.utils.ViewUtils; /** * BootstrapCircleThumbnails display a circular image with an optional border, that can be themed * using BootstrapBrand colors. The view extends ImageView, and will automatically center crop and * scale images. */ public class BootstrapCircleThumbnail extends BootstrapBaseThumbnail { private static final String TAG = "com.beardedhen.androidbootstrap.BootstrapCircleThumbnail"; private final RectF imageRectF = new RectF(); private final Matrix matrix = new Matrix(); public BootstrapCircleThumbnail(Context context) { super(context); initialise(null); } public BootstrapCircleThumbnail(Context context, AttributeSet attrs) { super(context, attrs); initialise(attrs); } public BootstrapCircleThumbnail(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialise(attrs); } protected void initialise(AttributeSet attrs) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BootstrapCircleThumbnail); try { int typeOrdinal = a.getInt(R.styleable.BootstrapCircleThumbnail_bootstrapBrand, -1); int sizeOrdinal = a.getInt(R.styleable.BootstrapCircleThumbnail_bootstrapSize, -1); this.hasBorder = a.getBoolean(R.styleable.BootstrapCircleThumbnail_hasBorder, true); this.bootstrapSize = DefaultBootstrapSize.fromAttributeValue(sizeOrdinal).scaleFactor(); if (typeOrdinal == -1) { // override to use Primary for default border (looks nicer) this.bootstrapBrand = DefaultBootstrapBrand.PRIMARY; } else { this.bootstrapBrand = DefaultBootstrapBrand.fromAttributeValue(typeOrdinal); } } finally { a.recycle(); } baselineOuterBorderWidth = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bthumbnail_outer_stroke); super.initialise(attrs); } /** * This method is called when the Circle Image needs to be recreated due to changes in size etc. * A Paint object uses a BitmapShader to draw a center-cropped, circular image onto the View * Canvas. A Matrix on the BitmapShader scales the original Bitmap to match the current view * bounds, avoiding any inefficiencies in duplicating Bitmaps. * <a href="http://www.curious-creature.com/2012/12/11/android-recipe-1-image-with-rounded-corners"> * Further reading</a> */ protected void updateImageState() { float viewWidth = getWidth(); float viewHeight = getHeight(); if ((int) viewWidth <= 0 || (int) viewHeight <= 0) { return; } if (sourceBitmap != null) { BitmapShader imageShader = new BitmapShader(sourceBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); imagePaint.setShader(imageShader); // Scale the bitmap using a matrix, ensuring that it always matches the view bounds. float bitmapWidth = sourceBitmap.getWidth(); float bitmapHeight = sourceBitmap.getHeight(); float scaleFactor = (bitmapWidth < bitmapHeight) ? bitmapWidth : bitmapHeight; float xScale = viewWidth / scaleFactor; float yScale = viewHeight / scaleFactor; // Translate image to center crop (if it is not a perfect square bitmap) float dx = 0; float dy = 0; if (bitmapWidth > bitmapHeight) { dx = (viewWidth - bitmapWidth * xScale) * 0.5f; } else if (bitmapHeight > bitmapWidth) { dy = (viewHeight - bitmapHeight * yScale) * 0.5f; } matrix.set(null); matrix.setScale(xScale, yScale); matrix.postTranslate((dx + 0.5f), (dy + 0.5f)); imageShader.setLocalMatrix(matrix); imageRectF.set(0, 0, viewWidth, viewHeight); } updateBackground(); invalidate(); } @Override protected void onDraw(@NonNull Canvas canvas) { float viewWidth = getWidth(); float viewHeight = getHeight(); if ((int) viewWidth <= 0 || (int) viewHeight <= 0) { return; } boolean isPlaceholder = sourceBitmap == null; // draw the image paint first, then draw a border as a Stroke paint (if needed) float center = viewWidth / 2; float imageRadius = center; if (hasBorder) { imageRadius -= (baselineBorderWidth * bootstrapSize); } Paint paint = (isPlaceholder) ? placeholderPaint : imagePaint; canvas.drawCircle(center, center, imageRadius, paint); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int w = MeasureSpec.getSize(widthMeasureSpec); // AT_MOST/EXACTLY are used by default int h = MeasureSpec.getSize(heightMeasureSpec); if (sourceBitmap != null) { if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.UNSPECIFIED) { w = sourceBitmap.getWidth(); } if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { h = sourceBitmap.getHeight(); } } if (w > h) { // no ovals allowed w = h; } if (h > w) { h = w; } setMeasuredDimension(w, h); } private void updateBackground() { Drawable bg = null; if (hasBorder) { bg = BootstrapDrawableFactory.bootstrapCircleThumbnail( getContext(), bootstrapBrand, (int) (baselineOuterBorderWidth * bootstrapSize), ColorUtils.resolveColor(R.color.bootstrap_thumbnail_background, getContext())); } ViewUtils.setBackgroundDrawable(this, bg); } }