package com.jdroid.android.images; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.util.AttributeSet; import android.widget.ImageView; import com.jdroid.android.R; /** * An {@link ImageView} that draws its contents inside a mask and draws a border * drawable on top. This is useful for applying a beveled look to image contents, but is also * flexible enough for use with other desired aesthetics. */ public class BezelImageView extends ImageView { private Paint mBlackPaint; private Paint mMaskedPaint; private Rect mBounds; private RectF mBoundsF; private Drawable mBorderDrawable; protected Drawable mMaskDrawable; private ColorMatrixColorFilter mDesaturateColorFilter; private boolean mDesaturateOnPress = false; private boolean mCacheValid = false; private Bitmap mCacheBitmap; private int mCachedWidth; private int mCachedHeight; public BezelImageView(Context context) { this(context, null); } public BezelImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public BezelImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); attributeInitialization(context, attrs, defStyle); // Other initialization mBlackPaint = new Paint(); mBlackPaint.setColor(0xff000000); mMaskedPaint = new Paint(); mMaskedPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); // Always want a cache allocated. mCacheBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); if (mDesaturateOnPress) { // Create a desaturate color filter for pressed state. ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); mDesaturateColorFilter = new ColorMatrixColorFilter(cm); } } protected void attributeInitialization(Context context, AttributeSet attrs, int defStyle) { final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.jdroid_bezelImageView, defStyle, 0); if (mMaskDrawable == null) { mMaskDrawable = a.getDrawable(R.styleable.jdroid_bezelImageView_maskDrawable); if (mMaskDrawable != null) { mMaskDrawable.setCallback(this); } } mBorderDrawable = a.getDrawable(R.styleable.jdroid_bezelImageView_borderDrawable); if (mBorderDrawable != null) { mBorderDrawable.setCallback(this); } mDesaturateOnPress = a.getBoolean(R.styleable.jdroid_bezelImageView_desaturateOnPress, mDesaturateOnPress); a.recycle(); } @Override protected boolean setFrame(int l, int t, int r, int b) { final boolean changed = super.setFrame(l, t, r, b); mBounds = new Rect(0, 0, r - l, b - t); mBoundsF = new RectF(mBounds); if (mBorderDrawable != null) { mBorderDrawable.setBounds(mBounds); } if (mMaskDrawable != null) { mMaskDrawable.setBounds(mBounds); } if (changed) { mCacheValid = false; } return changed; } @Override protected void onDraw(@NonNull Canvas canvas) { if (mBounds == null) { return; } int width = mBounds.width(); int height = mBounds.height(); if (width == 0 || height == 0) { return; } if (!mCacheValid || width != mCachedWidth || height != mCachedHeight) { // Need to redraw the cache if (width == mCachedWidth && height == mCachedHeight) { // Have a correct-sized bitmap cache already allocated. Just erase it. mCacheBitmap.eraseColor(0); } else { // Allocate a new bitmap with the correct dimensions. mCacheBitmap.recycle(); //noinspection AndroidLintDrawAllocation mCacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); mCachedWidth = width; mCachedHeight = height; } Canvas cacheCanvas = new Canvas(mCacheBitmap); if (mMaskDrawable != null) { int sc = cacheCanvas.save(); mMaskDrawable.draw(cacheCanvas); mMaskedPaint.setColorFilter((mDesaturateOnPress && isPressed()) ? mDesaturateColorFilter : null); cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG); super.onDraw(cacheCanvas); cacheCanvas.restoreToCount(sc); } else if (mDesaturateOnPress && isPressed()) { int sc = cacheCanvas.save(); cacheCanvas.drawRect(0, 0, mCachedWidth, mCachedHeight, mBlackPaint); mMaskedPaint.setColorFilter(mDesaturateColorFilter); cacheCanvas.saveLayer(mBoundsF, mMaskedPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG); super.onDraw(cacheCanvas); cacheCanvas.restoreToCount(sc); } else { super.onDraw(cacheCanvas); } if (mBorderDrawable != null) { mBorderDrawable.draw(cacheCanvas); } } // Draw from cache canvas.drawBitmap(mCacheBitmap, mBounds.left, mBounds.top, null); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (mBorderDrawable != null && mBorderDrawable.isStateful()) { mBorderDrawable.setState(getDrawableState()); } if (mMaskDrawable != null && mMaskDrawable.isStateful()) { mMaskDrawable.setState(getDrawableState()); } if (isDuplicateParentStateEnabled()) { ViewCompat.postInvalidateOnAnimation(this); } } @Override public void invalidateDrawable(@NonNull Drawable who) { if (who == mBorderDrawable || who == mMaskDrawable) { invalidate(); } else { super.invalidateDrawable(who); } } @Override protected boolean verifyDrawable(@NonNull Drawable who) { return who == mBorderDrawable || who == mMaskDrawable || super.verifyDrawable(who); } }