Last active
December 22, 2016 03:44
-
-
Save DanielGrech/4ff995879d27e8b50fa4 to your computer and use it in GitHub Desktop.
Blurred Image background with a rounded image bottom center. Image view which blurs its contents only supports API 17+. Older API's will just display the image as is
This file contains hidden or 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="CircularImageView"> | |
<!-- Specifies whether or not to draw a circular border around the image. --> | |
<attr name="civ_border" format="boolean"/> | |
<!-- Specifies the color of the border draw around the image. (if enabled) --> | |
<attr name="civ_border_color" format="color"/> | |
<!-- Makes the border this pixels wide. (if enabled) --> | |
<attr name="civ_border_width" format="dimension"/> | |
<!-- Specifies whether or not to draw a selector on this view upon touch events. --> | |
<attr name="civ_selector" format="boolean"/> | |
<!-- Specifies the color of the selector draw on top of the image upon touch events. (if enabled) --> | |
<attr name="civ_selector_color" format="color"/> | |
<!-- Specifies the color of the selector stroke drawn around the image upon touch events. Be sure to provide some opacity. (if enabled) --> | |
<attr name="civ_selector_stroke_color" format="color"/> | |
<!-- Makes the selector stroke drawn around the image upon touch events this pixels wide. (if enabled) --> | |
<attr name="civ_selector_stroke_width" format="dimension"/> | |
<!-- Specifies whether or not to draw a shadow around your circular image. --> | |
<attr name="civ_shadow" format="boolean"/> | |
</declare-styleable> | |
<declare-styleable name="CustomCircularImageViewTheme"> | |
<attr name="circularImageViewStyle" format="reference" /> | |
</declare-styleable> | |
</resources> |
This file contains hidden or 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.annotation.TargetApi; | |
import android.content.Context; | |
import android.graphics.Bitmap; | |
import android.graphics.BitmapFactory; | |
import android.os.Build; | |
import android.renderscript.Allocation; | |
import android.renderscript.Element; | |
import android.renderscript.RenderScript; | |
import android.renderscript.ScriptIntrinsicBlur; | |
import android.util.AttributeSet; | |
import android.widget.ImageView; | |
public class BlurredImageView extends ImageView { | |
private static final int BLUR_RADIUS = 25; | |
public BlurredImageView(Context context) { | |
super(context); | |
} | |
public BlurredImageView(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
} | |
public BlurredImageView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
} | |
@Override | |
public void setImageResource(int resId) { | |
setBlurredImage(resId); | |
} | |
@Override | |
public void setImageBitmap(Bitmap bm) { | |
setBlurredImage(bm); | |
} | |
public void setBlurredImage(int resId) { | |
setBlurredImage(BitmapFactory.decodeResource(getResources(), resId)); | |
} | |
public void setBlurredImage(Bitmap bitmap) { | |
Bitmap blurred = bitmap; | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { | |
blurred = blurBitmapRenderscript(bitmap); | |
} else { | |
// TODO: Support older apis? | |
} | |
super.setImageBitmap(blurred); | |
} | |
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) | |
private Bitmap blurBitmapRenderscript(Bitmap bitmap) { | |
Bitmap outBitmap = | |
Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888); | |
RenderScript rs = RenderScript.create(getContext().getApplicationContext()); | |
ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)); | |
Allocation allIn = Allocation.createFromBitmap(rs, bitmap); | |
Allocation allOut = Allocation.createFromBitmap(rs, outBitmap); | |
blurScript.setRadius(BLUR_RADIUS); | |
blurScript.setInput(allIn); | |
blurScript.forEach(allOut); | |
allOut.copyTo(outBitmap); | |
bitmap.recycle(); | |
rs.destroy(); | |
return outBitmap; | |
} | |
} |
This file contains hidden or 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.graphics.Bitmap; | |
import android.graphics.BitmapShader; | |
import android.graphics.Canvas; | |
import android.graphics.Color; | |
import android.graphics.ColorFilter; | |
import android.graphics.Paint; | |
import android.graphics.PorterDuff; | |
import android.graphics.PorterDuffColorFilter; | |
import android.graphics.Rect; | |
import android.graphics.Shader; | |
import android.graphics.drawable.BitmapDrawable; | |
import android.graphics.drawable.Drawable; | |
import android.util.AttributeSet; | |
import android.view.MotionEvent; | |
import android.widget.ImageView; | |
/** | |
* Kudos: https://github.com/Pkmmte/CircularImageView | |
*/ | |
public class CircularImageView extends ImageView { | |
// Border & Selector configuration variables | |
private boolean hasBorder; | |
private boolean hasSelector; | |
private boolean isSelected; | |
private int borderWidth; | |
private int canvasSize; | |
private int selectorStrokeWidth; | |
// Objects used for the actual drawing | |
private BitmapShader shader; | |
private Bitmap image; | |
private Paint paint; | |
private Paint paintBorder; | |
private Paint paintSelectorBorder; | |
private ColorFilter selectorFilter; | |
public CircularImageView(Context context) { | |
this(context, null); | |
} | |
public CircularImageView(Context context, AttributeSet attrs) { | |
this(context, attrs, R.attr.circularImageViewStyle); | |
} | |
public CircularImageView(Context context, AttributeSet attrs, int defStyle) { | |
super(context, attrs, defStyle); | |
init(context, attrs, defStyle); | |
} | |
/** | |
* Initializes paint objects and sets desired attributes. | |
* | |
* @param context | |
* @param attrs | |
* @param defStyle | |
*/ | |
private void init(Context context, AttributeSet attrs, int defStyle) { | |
// Initialize paint objects | |
paint = new Paint(); | |
paint.setAntiAlias(true); | |
paintBorder = new Paint(); | |
paintBorder.setAntiAlias(true); | |
paintSelectorBorder = new Paint(); | |
paintSelectorBorder.setAntiAlias(true); | |
// load the styled attributes and set their properties | |
TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.CircularImageView, defStyle, 0); | |
// Check if border and/or border is enabled | |
hasBorder = attributes.getBoolean(R.styleable.CircularImageView_civ_border, false); | |
hasSelector = attributes.getBoolean(R.styleable.CircularImageView_civ_selector, false); | |
// Set border properties if enabled | |
if (hasBorder) { | |
int defaultBorderSize = (int) (2 * context.getResources().getDisplayMetrics().density + 0.5f); | |
setBorderWidth(attributes.getDimensionPixelOffset(R.styleable.CircularImageView_civ_border_width, defaultBorderSize)); | |
setBorderColor(attributes.getColor(R.styleable.CircularImageView_civ_border_color, Color.WHITE)); | |
} | |
// Set selector properties if enabled | |
if (hasSelector) { | |
int defaultSelectorSize = (int) (2 * context.getResources().getDisplayMetrics().density + 0.5f); | |
setSelectorColor(attributes.getColor(R.styleable.CircularImageView_civ_selector_color, Color.TRANSPARENT)); | |
setSelectorStrokeWidth(attributes.getDimensionPixelOffset(R.styleable.CircularImageView_civ_selector_stroke_width, defaultSelectorSize)); | |
setSelectorStrokeColor(attributes.getColor(R.styleable.CircularImageView_civ_selector_stroke_color, Color.BLUE)); | |
} | |
// Add shadow if enabled | |
if (attributes.getBoolean(R.styleable.CircularImageView_civ_shadow, false)) | |
addShadow(); | |
// We no longer need our attributes TypedArray, give it back to cache | |
attributes.recycle(); | |
} | |
/** | |
* Sets the CircularImageView's border width in pixels. | |
* | |
* @param borderWidth | |
*/ | |
public void setBorderWidth(int borderWidth) { | |
this.borderWidth = borderWidth; | |
this.requestLayout(); | |
this.invalidate(); | |
} | |
/** | |
* Sets the CircularImageView's basic border color. | |
* | |
* @param borderColor | |
*/ | |
public void setBorderColor(int borderColor) { | |
if (paintBorder != null) | |
paintBorder.setColor(borderColor); | |
this.invalidate(); | |
} | |
/** | |
* Sets the color of the selector to be draw over the | |
* CircularImageView. Be sure to provide some opacity. | |
* | |
* @param selectorColor | |
*/ | |
public void setSelectorColor(int selectorColor) { | |
this.selectorFilter = new PorterDuffColorFilter(selectorColor, PorterDuff.Mode.SRC_ATOP); | |
this.invalidate(); | |
} | |
/** | |
* Sets the stroke width to be drawn around the CircularImageView | |
* during click events when the selector is enabled. | |
* | |
* @param selectorStrokeWidth | |
*/ | |
public void setSelectorStrokeWidth(int selectorStrokeWidth) { | |
this.selectorStrokeWidth = selectorStrokeWidth; | |
this.requestLayout(); | |
this.invalidate(); | |
} | |
/** | |
* Sets the stroke color to be drawn around the CircularImageView | |
* during click events when the selector is enabled. | |
* | |
* @param selectorStrokeColor | |
*/ | |
public void setSelectorStrokeColor(int selectorStrokeColor) { | |
if (paintSelectorBorder != null) | |
paintSelectorBorder.setColor(selectorStrokeColor); | |
this.invalidate(); | |
} | |
/** | |
* Adds a dark shadow to this CircularImageView. | |
*/ | |
public void addShadow() { | |
setLayerType(LAYER_TYPE_SOFTWARE, paintBorder); | |
paintBorder.setShadowLayer(4.0f, 0.0f, 2.0f, Color.BLACK); | |
} | |
@Override | |
public void onDraw(Canvas canvas) { | |
// Don't draw anything without an image | |
if (image == null) | |
return; | |
// Nothing to draw (Empty bounds) | |
if (image.getHeight() == 0 || image.getWidth() == 0) | |
return; | |
// Compare canvas sizes | |
int oldCanvasSize = canvasSize; | |
canvasSize = canvas.getWidth(); | |
if (canvas.getHeight() < canvasSize) | |
canvasSize = canvas.getHeight(); | |
// Reinitialize shader, if necessary | |
if (oldCanvasSize != canvasSize) | |
refreshBitmapShader(); | |
// Apply shader to paint | |
paint.setShader(shader); | |
// Keep track of selectorStroke/border width | |
int outerWidth = 0; | |
// Get the exact X/Y axis of the view | |
int center = canvasSize / 2; | |
if (hasSelector && isSelected) { // Draw the selector stroke & apply the selector filter, if applicable | |
outerWidth = selectorStrokeWidth; | |
center = (canvasSize - (outerWidth * 2)) / 2; | |
paint.setColorFilter(selectorFilter); | |
canvas.drawCircle(center + outerWidth, center + outerWidth, ((canvasSize - (outerWidth * 2)) / 2) + outerWidth - 4.0f, paintSelectorBorder); | |
} else if (hasBorder) { // If no selector was drawn, draw a border and clear the filter instead... if enabled | |
outerWidth = borderWidth; | |
center = (canvasSize - (outerWidth * 2)) / 2; | |
paint.setColorFilter(null); | |
canvas.drawCircle(center + outerWidth, center + outerWidth, ((canvasSize - (outerWidth * 2)) / 2) + outerWidth - 4.0f, paintBorder); | |
} else // Clear the color filter if no selector nor border were drawn | |
paint.setColorFilter(null); | |
// Draw the circular image itself | |
canvas.drawCircle(center + outerWidth, center + outerWidth, ((canvasSize - (outerWidth * 2)) / 2) - 4.0f, paint); | |
} | |
@Override | |
public boolean dispatchTouchEvent(MotionEvent event) { | |
// Check for clickable state and do nothing if disabled | |
if (!this.isClickable()) { | |
this.isSelected = false; | |
return super.onTouchEvent(event); | |
} | |
// Set selected state based on Motion Event | |
switch (event.getAction()) { | |
case MotionEvent.ACTION_DOWN: | |
this.isSelected = true; | |
break; | |
case MotionEvent.ACTION_UP: | |
case MotionEvent.ACTION_SCROLL: | |
case MotionEvent.ACTION_OUTSIDE: | |
case MotionEvent.ACTION_CANCEL: | |
this.isSelected = false; | |
break; | |
} | |
// Redraw image and return super type | |
this.invalidate(); | |
return super.dispatchTouchEvent(event); | |
} | |
public void invalidate(Rect dirty) { | |
super.invalidate(dirty); | |
image = drawableToBitmap(getDrawable()); | |
if (shader != null || canvasSize > 0) | |
refreshBitmapShader(); | |
} | |
public void invalidate(int l, int t, int r, int b) { | |
super.invalidate(l, t, r, b); | |
image = drawableToBitmap(getDrawable()); | |
if (shader != null || canvasSize > 0) | |
refreshBitmapShader(); | |
} | |
@Override | |
public void invalidate() { | |
super.invalidate(); | |
image = drawableToBitmap(getDrawable()); | |
if (shader != null || canvasSize > 0) | |
refreshBitmapShader(); | |
} | |
@Override | |
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
int width = measureWidth(widthMeasureSpec); | |
int height = measureHeight(heightMeasureSpec); | |
setMeasuredDimension(width, height); | |
} | |
private int measureWidth(int measureSpec) { | |
int result = 0; | |
int specMode = MeasureSpec.getMode(measureSpec); | |
int specSize = MeasureSpec.getSize(measureSpec); | |
if (specMode == MeasureSpec.EXACTLY) { | |
// The parent has determined an exact size for the child. | |
result = specSize; | |
} else if (specMode == MeasureSpec.AT_MOST) { | |
// The child can be as large as it wants up to the specified size. | |
result = specSize; | |
} else { | |
// The parent has not imposed any constraint on the child. | |
result = canvasSize; | |
} | |
return result; | |
} | |
private int measureHeight(int measureSpecHeight) { | |
int result = 0; | |
int specMode = MeasureSpec.getMode(measureSpecHeight); | |
int specSize = MeasureSpec.getSize(measureSpecHeight); | |
if (specMode == MeasureSpec.EXACTLY) { | |
// We were told how big to be | |
result = specSize; | |
} else if (specMode == MeasureSpec.AT_MOST) { | |
// The child can be as large as it wants up to the specified size. | |
result = specSize; | |
} else { | |
// Measure the text (beware: ascent is a negative number) | |
result = canvasSize; | |
} | |
return (result + 2); | |
} | |
/** | |
* Convert a drawable object into a Bitmap | |
* | |
* @param drawable | |
* @return | |
*/ | |
public Bitmap drawableToBitmap(Drawable drawable) { | |
if (drawable == null) { // Don't do anything without a proper drawable | |
return null; | |
} else if (drawable instanceof BitmapDrawable) { // Use the getBitmap() method instead if BitmapDrawable | |
return ((BitmapDrawable) drawable).getBitmap(); | |
} | |
// Create Bitmap object out of the drawable | |
Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); | |
Canvas canvas = new Canvas(bitmap); | |
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); | |
drawable.draw(canvas); | |
return bitmap; | |
} | |
/** | |
* Reinitializes the shader texture used to fill in | |
* the Circle upon drawing. | |
*/ | |
public void refreshBitmapShader() { | |
shader = new BitmapShader(Bitmap.createScaledBitmap(image, canvasSize, canvasSize, false), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); | |
} | |
/** | |
* Returns whether or not this view is currently | |
* in its selected state. | |
*/ | |
public boolean isSelected() { | |
return this.isSelected; | |
} | |
} |
This file contains hidden or 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"?> | |
<FrameLayout | |
xmlns:android="http://schemas.android.com/apk/res/android" | |
xmlns:app="http://schemas.android.com/apk/res-auto" | |
android:layout_width="wrap_content" | |
android:layout_height="wrap_content"> | |
<com.example.view.BlurredImageView | |
android:id="@+id/blurred" | |
android:layout_width="match_parent" | |
android:scaleType="centerCrop" | |
android:layout_height="200dp" | |
android:layout_marginBottom="36dp"/> | |
<com.example.view.CircularImageView | |
android:id="@+id/small_image" | |
android:layout_width="64dp" | |
android:layout_height="64dp" | |
android:scaleType="centerCrop" | |
app:civ_shadow="true" | |
app:civ_border="true" | |
app:civ_border_color="#666666" | |
app:civ_border_width="1px" | |
android:layout_gravity="bottom|center_horizontal"/> | |
</FrameLayout> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment