/* * Copyright 2014 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * The original is from Google you can find here: * https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/BezelImageView.java */ package com.boardgamegeek.ui.widget; import android.annotation.SuppressLint; 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.boardgamegeek.R; /** * An {@link android.widget.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 final Paint blackPaint; private final Paint maskedPaint; private Rect bounds; private RectF boundsF; private final Drawable borderDrawable; private final Drawable maskDrawable; private ColorMatrixColorFilter desaturateColorFilter; private boolean shouldDesaturateOnPress = false; private boolean isCacheValid = false; private Bitmap cacheBitmap; private int cachedWidth; private int cachedHeight; 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); // Attribute initialization final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BezelImageView, defStyle, 0); maskDrawable = a.getDrawable(R.styleable.BezelImageView_maskDrawable); if (maskDrawable != null) { maskDrawable.setCallback(this); } borderDrawable = a.getDrawable(R.styleable.BezelImageView_borderDrawable); if (borderDrawable != null) { borderDrawable.setCallback(this); } shouldDesaturateOnPress = a.getBoolean(R.styleable.BezelImageView_desaturateOnPress, shouldDesaturateOnPress); a.recycle(); // Other initialization blackPaint = new Paint(); blackPaint.setColor(0xff000000); maskedPaint = new Paint(); maskedPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); // Always want a cache allocated. cacheBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); if (shouldDesaturateOnPress) { // Create a desaturate color filter for pressed state. ColorMatrix cm = new ColorMatrix(); cm.setSaturation(0); desaturateColorFilter = new ColorMatrixColorFilter(cm); } } @Override protected boolean setFrame(int l, int t, int r, int b) { final boolean changed = super.setFrame(l, t, r, b); bounds = new Rect(0, 0, r - l, b - t); boundsF = new RectF(bounds); if (borderDrawable != null) { borderDrawable.setBounds(bounds); } if (maskDrawable != null) { maskDrawable.setBounds(bounds); } if (changed) { isCacheValid = false; } return changed; } @Override protected void onDraw(Canvas canvas) { if (bounds == null) { return; } int width = bounds.width(); int height = bounds.height(); if (width == 0 || height == 0) { return; } if (!isCacheValid || width != cachedWidth || height != cachedHeight) { // Need to redraw the cache if (width == cachedWidth && height == cachedHeight) { // Have a correct-sized bitmap cache already allocated. Just erase it. cacheBitmap.eraseColor(0); } else { // Allocate a new bitmap with the correct dimensions. cacheBitmap.recycle(); //noinspection AndroidLintDrawAllocation cacheBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); cachedWidth = width; cachedHeight = height; } @SuppressLint("DrawAllocation") Canvas cacheCanvas = new Canvas(cacheBitmap); if (maskDrawable != null) { int sc = cacheCanvas.save(); maskDrawable.draw(cacheCanvas); maskedPaint.setColorFilter((shouldDesaturateOnPress && isPressed()) ? desaturateColorFilter : null); cacheCanvas.saveLayer(boundsF, maskedPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG); super.onDraw(cacheCanvas); cacheCanvas.restoreToCount(sc); } else if (shouldDesaturateOnPress && isPressed()) { int sc = cacheCanvas.save(); cacheCanvas.drawRect(0, 0, cachedWidth, cachedHeight, blackPaint); maskedPaint.setColorFilter(desaturateColorFilter); cacheCanvas.saveLayer(boundsF, maskedPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG | Canvas.FULL_COLOR_LAYER_SAVE_FLAG); super.onDraw(cacheCanvas); cacheCanvas.restoreToCount(sc); } else { super.onDraw(cacheCanvas); } if (borderDrawable != null) { borderDrawable.draw(cacheCanvas); } } // Draw from cache canvas.drawBitmap(cacheBitmap, bounds.left, bounds.top, null); } @Override protected void drawableStateChanged() { super.drawableStateChanged(); if (borderDrawable != null && borderDrawable.isStateful()) { borderDrawable.setState(getDrawableState()); } if (maskDrawable != null && maskDrawable.isStateful()) { maskDrawable.setState(getDrawableState()); } if (isDuplicateParentStateEnabled()) { ViewCompat.postInvalidateOnAnimation(this); } } @Override public void invalidateDrawable(@NonNull Drawable who) { if (who == borderDrawable || who == maskDrawable) { invalidate(); } else { super.invalidateDrawable(who); } } @Override protected boolean verifyDrawable(@NonNull Drawable who) { return who == borderDrawable || who == maskDrawable || super.verifyDrawable(who); } }