-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
733 additions
and
0 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
308 changes: 308 additions & 0 deletions
308
iosalert/src/main/java/com/maliotis/iosalert/blurview/BlockingBlurController.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,308 @@ | ||
package com.maliotis.iosalert.blurview; | ||
|
||
import android.graphics.Bitmap; | ||
import android.graphics.Canvas; | ||
import android.graphics.Color; | ||
import android.graphics.Paint; | ||
import android.graphics.drawable.Drawable; | ||
import android.os.Build; | ||
import android.support.annotation.ColorInt; | ||
import android.support.annotation.NonNull; | ||
import android.support.annotation.Nullable; | ||
import android.view.View; | ||
import android.view.ViewGroup; | ||
import android.view.ViewTreeObserver; | ||
|
||
/** | ||
* Blur Controller that handles all blur logic for the attached View. | ||
* It honors View size changes, View animation and Visibility changes. | ||
* <p> | ||
* The basic idea is to draw the view hierarchy on a bitmap, excluding the attached View, | ||
* then blur and draw it on the system Canvas. | ||
* <p> | ||
* It uses {@link ViewTreeObserver.OnPreDrawListener} to detect when | ||
* blur should be updated. | ||
* <p> | ||
* Blur is done on the main thread. | ||
*/ | ||
final class BlockingBlurController implements BlurController { | ||
|
||
// Bitmap size should be divisible by ROUNDING_VALUE to meet stride requirement. | ||
// This will help avoiding an extra bitmap allocation when passing the bitmap to RenderScript for blur. | ||
// Usually it's 16, but on Samsung devices it's 64 for some reason. | ||
private static final int ROUNDING_VALUE = 64; | ||
@ColorInt | ||
static final int TRANSPARENT = 0; | ||
|
||
private final float scaleFactor = DEFAULT_SCALE_FACTOR; | ||
private float blurRadius = DEFAULT_BLUR_RADIUS; | ||
private float roundingWidthScaleFactor = 1f; | ||
private float roundingHeightScaleFactor = 1f; | ||
|
||
private BlurAlgorithm blurAlgorithm; | ||
private Canvas internalCanvas; | ||
private Bitmap internalBitmap; | ||
|
||
@SuppressWarnings("WeakerAccess") | ||
final View blurView; | ||
private int overlayColor; | ||
private final ViewGroup rootView; | ||
private final int[] rootLocation = new int[2]; | ||
private final int[] blurViewLocation = new int[2]; | ||
|
||
private final ViewTreeObserver.OnPreDrawListener drawListener = new ViewTreeObserver.OnPreDrawListener() { | ||
@Override | ||
public boolean onPreDraw() { | ||
// Not invalidating a View here, just updating the Bitmap. | ||
// This relies on the HW accelerated bitmap drawing behavior in Android | ||
// If the bitmap was drawn on HW accelerated canvas, it holds a reference to it and on next | ||
// drawing pass the updated content of the bitmap will be rendered on the screen | ||
|
||
updateBlur(); | ||
return true; | ||
} | ||
}; | ||
|
||
private boolean blurEnabled = true; | ||
private boolean initialized; | ||
|
||
@Nullable | ||
private Drawable frameClearDrawable; | ||
private boolean hasFixedTransformationMatrix; | ||
private final Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG); | ||
|
||
/** | ||
* @param blurView View which will draw it's blurred underlying content | ||
* @param rootView Root View where blurView's underlying content starts drawing. | ||
* Can be Activity's root content layout (android.R.id.content) | ||
* or some of your custom root layouts. | ||
*/ | ||
BlockingBlurController(@NonNull View blurView, @NonNull ViewGroup rootView, @ColorInt int overlayColor) { | ||
this.rootView = rootView; | ||
this.blurView = blurView; | ||
this.overlayColor = overlayColor; | ||
this.blurAlgorithm = new NoOpBlurAlgorithm(); | ||
|
||
int measuredWidth = blurView.getMeasuredWidth(); | ||
int measuredHeight = blurView.getMeasuredHeight(); | ||
|
||
if (isZeroSized(measuredWidth, measuredHeight)) { | ||
deferBitmapCreation(); | ||
return; | ||
} | ||
|
||
init(measuredWidth, measuredHeight); | ||
} | ||
|
||
private int downScaleSize(float value) { | ||
return (int) Math.ceil(value / scaleFactor); | ||
} | ||
|
||
/** | ||
* Rounds a value to the nearest divisible by {@link #ROUNDING_VALUE} to meet stride requirement | ||
*/ | ||
private int roundSize(int value) { | ||
if (value % ROUNDING_VALUE == 0) { | ||
return value; | ||
} | ||
return value - (value % ROUNDING_VALUE) + ROUNDING_VALUE; | ||
} | ||
|
||
@SuppressWarnings("WeakerAccess") | ||
void init(int measuredWidth, int measuredHeight) { | ||
if (isZeroSized(measuredWidth, measuredHeight)) { | ||
blurView.setWillNotDraw(true); | ||
return; | ||
} | ||
|
||
blurView.setWillNotDraw(false); | ||
allocateBitmap(measuredWidth, measuredHeight); | ||
internalCanvas = new Canvas(internalBitmap); | ||
initialized = true; | ||
|
||
if (hasFixedTransformationMatrix) { | ||
setupInternalCanvasMatrix(); | ||
} | ||
} | ||
|
||
private boolean isZeroSized(int measuredWidth, int measuredHeight) { | ||
return downScaleSize(measuredHeight) == 0 || downScaleSize(measuredWidth) == 0; | ||
} | ||
|
||
@SuppressWarnings("WeakerAccess") | ||
void updateBlur() { | ||
if (!blurEnabled || !initialized) { | ||
return; | ||
} | ||
|
||
if (frameClearDrawable == null) { | ||
internalBitmap.eraseColor(Color.TRANSPARENT); | ||
} else { | ||
frameClearDrawable.draw(internalCanvas); | ||
} | ||
|
||
if (hasFixedTransformationMatrix) { | ||
rootView.draw(internalCanvas); | ||
} else { | ||
internalCanvas.save(); | ||
setupInternalCanvasMatrix(); | ||
rootView.draw(internalCanvas); | ||
internalCanvas.restore(); | ||
} | ||
|
||
blurAndSave(); | ||
} | ||
|
||
/** | ||
* Deferring initialization until view is laid out | ||
*/ | ||
private void deferBitmapCreation() { | ||
blurView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { | ||
@Override | ||
public void onGlobalLayout() { | ||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | ||
blurView.getViewTreeObserver().removeOnGlobalLayoutListener(this); | ||
} else { | ||
legacyRemoveOnGlobalLayoutListener(); | ||
} | ||
|
||
int measuredWidth = blurView.getMeasuredWidth(); | ||
int measuredHeight = blurView.getMeasuredHeight(); | ||
|
||
init(measuredWidth, measuredHeight); | ||
} | ||
|
||
@SuppressWarnings("deprecation") | ||
void legacyRemoveOnGlobalLayoutListener() { | ||
blurView.getViewTreeObserver().removeGlobalOnLayoutListener(this); | ||
} | ||
}); | ||
} | ||
|
||
private void allocateBitmap(int measuredWidth, int measuredHeight) { | ||
int nonRoundedScaledWidth = downScaleSize(measuredWidth); | ||
int nonRoundedScaledHeight = downScaleSize(measuredHeight); | ||
|
||
int scaledWidth = roundSize(nonRoundedScaledWidth); | ||
int scaledHeight = roundSize(nonRoundedScaledHeight); | ||
|
||
roundingHeightScaleFactor = (float) nonRoundedScaledHeight / scaledHeight; | ||
roundingWidthScaleFactor = (float) nonRoundedScaledWidth / scaledWidth; | ||
|
||
internalBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, blurAlgorithm.getSupportedBitmapConfig()); | ||
} | ||
|
||
/** | ||
* Set up matrix to draw starting from blurView's position | ||
*/ | ||
private void setupInternalCanvasMatrix() { | ||
rootView.getLocationOnScreen(rootLocation); | ||
blurView.getLocationOnScreen(blurViewLocation); | ||
|
||
int left = blurViewLocation[0] - rootLocation[0]; | ||
int top = blurViewLocation[1] - rootLocation[1]; | ||
|
||
float scaleFactorX = scaleFactor * roundingWidthScaleFactor; | ||
float scaleFactorY = scaleFactor * roundingHeightScaleFactor; | ||
|
||
float scaledLeftPosition = -left / scaleFactorX; | ||
float scaledTopPosition = -top / scaleFactorY; | ||
|
||
internalCanvas.translate(scaledLeftPosition, scaledTopPosition); | ||
internalCanvas.scale(1 / scaleFactorX, 1 / scaleFactorY); | ||
} | ||
|
||
@Override | ||
public boolean draw(Canvas canvas) { | ||
if (!blurEnabled || !initialized) { | ||
return true; | ||
} | ||
// Not blurring own children | ||
if (canvas == internalCanvas) { | ||
return false; | ||
} | ||
|
||
updateBlur(); | ||
|
||
canvas.save(); | ||
canvas.scale(scaleFactor * roundingWidthScaleFactor, scaleFactor * roundingHeightScaleFactor); | ||
canvas.drawBitmap(internalBitmap, 0, 0, paint); | ||
canvas.restore(); | ||
|
||
if (overlayColor != TRANSPARENT) { | ||
canvas.drawColor(overlayColor); | ||
} | ||
return true; | ||
} | ||
|
||
private void blurAndSave() { | ||
internalBitmap = blurAlgorithm.blur(internalBitmap, blurRadius); | ||
if (!blurAlgorithm.canModifyBitmap()) { | ||
internalCanvas.setBitmap(internalBitmap); | ||
} | ||
} | ||
|
||
@Override | ||
public void updateBlurViewSize() { | ||
int measuredWidth = blurView.getMeasuredWidth(); | ||
int measuredHeight = blurView.getMeasuredHeight(); | ||
|
||
init(measuredWidth, measuredHeight); | ||
} | ||
|
||
@Override | ||
public void destroy() { | ||
setBlurAutoUpdate(false); | ||
blurAlgorithm.destroy(); | ||
initialized = false; | ||
} | ||
|
||
@Override | ||
public BlurViewFacade setBlurRadius(float radius) { | ||
this.blurRadius = radius; | ||
return this; | ||
} | ||
|
||
@Override | ||
public BlurViewFacade setBlurAlgorithm(BlurAlgorithm algorithm) { | ||
this.blurAlgorithm = algorithm; | ||
return this; | ||
} | ||
|
||
@Override | ||
public BlurViewFacade setFrameClearDrawable(@Nullable Drawable frameClearDrawable) { | ||
this.frameClearDrawable = frameClearDrawable; | ||
return this; | ||
} | ||
|
||
@Override | ||
public BlurViewFacade setBlurEnabled(boolean enabled) { | ||
this.blurEnabled = enabled; | ||
setBlurAutoUpdate(enabled); | ||
blurView.invalidate(); | ||
return this; | ||
} | ||
|
||
public BlurViewFacade setBlurAutoUpdate(final boolean enabled) { | ||
blurView.getViewTreeObserver().removeOnPreDrawListener(drawListener); | ||
if (enabled) { | ||
blurView.getViewTreeObserver().addOnPreDrawListener(drawListener); | ||
} | ||
return this; | ||
} | ||
|
||
@Override | ||
public BlurViewFacade setHasFixedTransformationMatrix(boolean hasFixedTransformationMatrix) { | ||
this.hasFixedTransformationMatrix = hasFixedTransformationMatrix; | ||
return this; | ||
} | ||
|
||
@Override | ||
public BlurViewFacade setOverlayColor(int overlayColor) { | ||
if (this.overlayColor != overlayColor) { | ||
this.overlayColor = overlayColor; | ||
blurView.invalidate(); | ||
} | ||
return this; | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
iosalert/src/main/java/com/maliotis/iosalert/blurview/BlurAlgorithm.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.maliotis.iosalert.blurview; | ||
|
||
import android.graphics.Bitmap; | ||
import android.support.annotation.NonNull; | ||
|
||
public interface BlurAlgorithm { | ||
/** | ||
* @param bitmap bitmap to be blurred | ||
* @param blurRadius blur radius | ||
* @return blurred bitmap | ||
*/ | ||
Bitmap blur(Bitmap bitmap, float blurRadius); | ||
|
||
/** | ||
* Frees allocated resources | ||
*/ | ||
void destroy(); | ||
|
||
/** | ||
* @return true if this algorithm returns the same instance of bitmap as it accepted | ||
* false if it creates a new instance. | ||
* <p> | ||
* If you return false from this method, you'll be responsible to swap bitmaps in your | ||
* {@link BlurAlgorithm#blur(Bitmap, float)} implementation | ||
* (assign input bitmap to your field and return the instance algorithm just blurred). | ||
*/ | ||
boolean canModifyBitmap(); | ||
|
||
/** | ||
* Retrieve the {@link Bitmap.Config} on which the {@link BlurAlgorithm} | ||
* can actually work. | ||
* | ||
* @return bitmap config supported by the given blur algorithm. | ||
*/ | ||
@NonNull | ||
Bitmap.Config getSupportedBitmapConfig(); | ||
} |
26 changes: 26 additions & 0 deletions
26
iosalert/src/main/java/com/maliotis/iosalert/blurview/BlurController.java
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.maliotis.iosalert.blurview; | ||
|
||
import android.graphics.Canvas; | ||
|
||
interface BlurController extends BlurViewFacade { | ||
|
||
float DEFAULT_SCALE_FACTOR = 8f; | ||
float DEFAULT_BLUR_RADIUS = 16f; | ||
|
||
/** | ||
* Draws blurred content on given canvas | ||
* | ||
* @return true if BlurView should proceed with drawing itself and its children | ||
*/ | ||
boolean draw(Canvas canvas); | ||
|
||
/** | ||
* Must be used to notify Controller when BlurView's size has changed | ||
*/ | ||
void updateBlurViewSize(); | ||
|
||
/** | ||
* Frees allocated resources | ||
*/ | ||
void destroy(); | ||
} |
Oops, something went wrong.