Skip to content

Instantly share code, notes, and snippets.

@miao1007
Last active June 26, 2018 09:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save miao1007/f6a486a746e6087ed8f8 to your computer and use it in GitHub Desktop.
Save miao1007/f6a486a746e6087ed8f8 to your computer and use it in GitHub Desktop.
BlurDrawable
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.github.miao1007.myapplication.MainActivity">
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:id="@+id/holder"
android:paddingTop="72dp"
android:clipToPadding="false"
android:layout_height="match_parent"/>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="#4f81ff"/>
</FrameLayout>
package com.github.miao1007.myapplication;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Build;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.ScriptIntrinsicBlur;
import android.support.annotation.ColorInt;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.view.View;
import android.view.Window;
/**
* Created by leon on 2/8/16.
* port Blur from @link https://github.com/500px/500px-android-blur
*
* Real time Blur
* API 17 and above: use blur
* API 17 lower: use {@link #setOverlayColor(int color)}
*/
public class BlurDrawable extends ColorDrawable {
private int mDownsampleFactor;
private View mBlurredBgView;
private int mBlurredViewWidth, mBlurredViewHeight;
private boolean mDownsampleFactorChanged;
private Bitmap mBitmapToBlur, mBlurredBitmap;
private Canvas mBlurringCanvas;
private RenderScript mRenderScript;
private ScriptIntrinsicBlur mBlurScript;
private Allocation mBlurInput, mBlurOutput;
private float offsetX;
private float offsetY;
private boolean enabled;
private int mOverlayColor = Color.argb(175, 0xff, 0xff, 0xff);
public BlurDrawable(@NonNull View mBlurredBgView) {
this.mBlurredBgView = mBlurredBgView;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
enabled = false;
} else {
enabled = true;
initializeRenderScript(mBlurredBgView.getContext());
}
}
/**
* used for dialog/fragment/popWindow
*
* @param activity the blurredView attached
* @see #setDrawOffset
*/
public BlurDrawable(Activity activity) {
this(activity.getWindow().getDecorView());
}
/**
* Set for window
*
* @param blurredWindow another window,void draw self(may throw stackoverflow)
*/
public BlurDrawable(Window blurredWindow) {
this(blurredWindow.getDecorView());
}
@TargetApi(17) public void setBlurRadius(@IntRange(from = 0, to = 25) int radius) {
if (!enabled) {
return;
}
mBlurScript.setRadius(radius);
}
@TargetApi(17)
public void setDownsampleFactor(int factor) {
if (factor <= 0) {
throw new IllegalArgumentException("Downsample factor must be greater than 0.");
}
if (!enabled) {
return;
}
if (mDownsampleFactor != factor) {
mDownsampleFactor = factor;
mDownsampleFactorChanged = true;
}
}
/**
* set both for blur and non-blur
*/
public void setOverlayColor(@ColorInt int color) {
mOverlayColor = color;
setColor(color);
}
@TargetApi(17) private void initializeRenderScript(Context context) {
mRenderScript = RenderScript.create(context);
mBlurScript = ScriptIntrinsicBlur.create(mRenderScript, Element.U8_4(mRenderScript));
//设置blur半径, iOS中默认为12px
setBlurRadius(16);
//图片缩放等级,缩放越大越节约性能,理论要在100px^2以内
setDownsampleFactor(16);
}
/**
* 相当于一个单例的初始化
*/
protected boolean prepare() {
//assume a 1080 x 1920 RecyclerView
final int width = mBlurredBgView.getWidth();
final int height = mBlurredBgView.getHeight();
if (mBlurringCanvas == null
|| mDownsampleFactorChanged
|| mBlurredViewWidth != width
|| mBlurredViewHeight != height) {
mDownsampleFactorChanged = false;
mBlurredViewWidth = width;
mBlurredViewHeight = height;
int scaledWidth = width / mDownsampleFactor;
int scaledHeight = height / mDownsampleFactor;
// The following manipulation is to avoid some RenderScript artifacts at the edge.
// 136 x 244
scaledWidth = scaledWidth - scaledWidth % 4 + 4;
scaledHeight = scaledHeight - scaledHeight % 4 + 4;
if (mBlurredBitmap == null
|| mBlurredBitmap.getWidth() != scaledWidth
|| mBlurredBitmap.getHeight() != scaledHeight) {
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
if (mBitmapToBlur == null) {
return false;
}
mBlurredBitmap = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
if (mBlurredBitmap == null) {
return false;
}
}
//创建了一个136 x 244的画板
//当画板调用draw是,将画到mBitmapToBlur上
mBlurringCanvas = new Canvas(mBitmapToBlur);
mBlurringCanvas.scale(1f / mDownsampleFactor, 1f / mDownsampleFactor);
mBlurInput = Allocation.createFromBitmap(mRenderScript, mBitmapToBlur,
Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
mBlurOutput = Allocation.createTyped(mRenderScript, mBlurInput.getType());
}
return true;
}
/**
* 渲染任务,可以在16ms完成,可以调用多核
* 将mBitmapToBlur渲染为mBlurredBitmap输出
*/
@TargetApi(17)
protected void blur(Bitmap mBitmapToBlur, Bitmap mBlurredBitmap) {
if (!enabled) {
return;
}
//类似于c中的alloc,这里是栈内存,这样就把bitmap放入了c的栈中
mBlurInput.copyFrom(mBitmapToBlur);
//滤镜加入输入源
mBlurScript.setInput(mBlurInput);
//滤镜进行渲染并输出到output,类似于DSP
mBlurScript.forEach(mBlurOutput);
//将栈内存复制到bitmap
mBlurOutput.copyTo(mBlurredBitmap);
}
/**
* force enable blur, however it will only works on API 17 or higher
* if your want to support more, use Support RenderScript Pack
*/
public void setEnabled(boolean enabled) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
enabled = false;
}
this.enabled = enabled;
}
@TargetApi(17)
public void onDestroy() {
if (!enabled) {
return;
}
if (mRenderScript != null) {
mRenderScript.destroy();
}
}
@Override public void draw(Canvas canvas) {
if (!enabled) {
//draw overlay color
super.draw(canvas);
} else {
drawBlur(canvas);
}
}
@TargetApi(17) private void drawBlur(Canvas canvas) {
if (prepare()) {
// If the background of the blurred view is a color drawable, we use it to clear
// the blurring canvas, which ensures that edges of the child views are blurred
// as well; otherwise we clear the blurring canvas with a transparent color.
if (mBlurredBgView.getBackground() != null
&& mBlurredBgView.getBackground() instanceof ColorDrawable) {
mBitmapToBlur.eraseColor(((ColorDrawable) mBlurredBgView.getBackground()).getColor());
} else {
mBitmapToBlur.eraseColor(Color.TRANSPARENT);
}
//在1920x1080中,只画一个大小为 136 x 244 的RecyclerView,这个View绘制了两次
//类似于开发者选项中的多显示输出
//将bitmaptoblur进行赋值
mBlurredBgView.draw(mBlurringCanvas);
//进行模糊渲染,生成mBlurredBitmap
blur(mBitmapToBlur, mBlurredBitmap);
//
canvas.save();
//这里的是dx,正好与坐标是相反的
canvas.translate(mBlurredBgView.getX() - offsetX, mBlurredBgView.getY() - offsetY);
//实际输出的只有 136 x 244的像素,缩放后就和当前view一样大了
canvas.scale(mDownsampleFactor, mDownsampleFactor);
canvas.drawBitmap(mBlurredBitmap, 0, 0, null);
canvas.restore();
}
canvas.drawColor(mOverlayColor);
}
/**
* set the offset between top view and blurred view
*/
@TargetApi(17)
public void setDrawOffset(float x, float y) {
this.offsetX = x;
this.offsetY = y;
}
}
package com.github.miao1007.myapplication;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
StatusbarUtils.from(this)
.setActionbarView(toolbar)
.setLightStatusBar(true)
.setTransparentStatusbar(true)
.process();
setSupportActionBar(toolbar);
RecyclerView recyclerView = ((RecyclerView) findViewById(R.id.holder));
BlurDrawable drawable = new BlurDrawable(recyclerView);
drawable.setBlurRadius(4);
drawable.setDownsampleFactor(4);
drawable.setOverlayColor(Color.parseColor("#5e4f81ff"));
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new RecyclerView.Adapter() {
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new BaseViewHolder(new ImageView(parent.getContext()));
}
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
((ImageView) holder.itemView).setImageResource(R.drawable.suzu);
}
@Override public int getItemCount() {
return 20;
}
class BaseViewHolder extends RecyclerView.ViewHolder {
public BaseViewHolder(View itemView) {
super(itemView);
}
}
});
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
toolbar.invalidate();
}
});
toolbar.setBackgroundDrawable(drawable);
}
@Override public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
}
package com.github.miao1007.myapplication;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Build;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created by leon on 10/31/15.
*/
public final class StatusbarUtils {
static final String TAG = "StatusbarUtils";
boolean lightStatusBar;
//透明且背景不占用控件的statusbar,这里估且叫做沉浸
boolean transparentStatusbar;
Window window;
View actionBarView;
private StatusbarUtils(Activity activity, boolean lightStatusBar, boolean transparentStatusbar,
View actionBarView) {
this.lightStatusBar = lightStatusBar;
this.transparentStatusbar = transparentStatusbar;
this.window = activity.getWindow();
this.actionBarView = actionBarView;
}
public static boolean isKitkat() {
return Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT;
}
public static boolean isLessKitkat() {
return Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT;
}
public static boolean isMoreLollipop() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
public static Builder from(Activity activity) {
return new StatusbarUtils.Builder().setActivity(activity);
}
/**
* Default status dp = 24 or 25
* mhdpi = dp * 1
* hdpi = dp * 1.5
* xhdpi = dp * 2
* xxhdpi = dp * 3
* eg : 1920x1080, xxhdpi, => status/all = 25/640(dp) = 75/1080(px)
*
* don't forget toolbar's dp = 48
*
* @return px
*/
@IntRange(from = 0, to = 75) public static int getStatusBarOffsetPx(Context context) {
if (isLessKitkat()) {
return 0;
}
Context appContext = context.getApplicationContext();
int result = 0;
int resourceId =
appContext.getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = appContext.getResources().getDimensionPixelSize(resourceId);
}
return result;
}
public void processActionBar(final View v) {
if (v == null || !transparentStatusbar || isLessKitkat()) {
return;
}
v.post(new Runnable() {
@Override public void run() {
v.setPadding(v.getPaddingLeft(), v.getPaddingTop() + getStatusBarOffsetPx(v.getContext()),
v.getPaddingRight(),
v.getPaddingBottom());
v.getLayoutParams().height += getStatusBarOffsetPx(v.getContext());
}
});
}
/**
* 调用私有API处理颜色
*/
public void processPrivateAPI() {
processFlyme(lightStatusBar);
processMIUI(lightStatusBar);
}
public void process() {
int current = Build.VERSION.SDK_INT;
//处理4.4沉浸
if (current == Build.VERSION_CODES.KITKAT) {
processKitkat();
}
//6.0处理沉浸与颜色,5.0只可以处理沉浸(不建议用白色背景)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
processLollipopAbove();
}
//调用私有API处理颜色
processPrivateAPI();
processActionBar(actionBarView);
}
/**
* 处理4.4沉浸
*/
@TargetApi(Build.VERSION_CODES.KITKAT) void processKitkat() {
//int current = activity.getWindow().gef
WindowManager.LayoutParams winParams = window.getAttributes();
final int bits = WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
if (transparentStatusbar) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
window.setAttributes(winParams);
}
/**
* 改变小米的状态栏字体颜色为黑色, 要求MIUI6以上
* Tested on: MIUIV7 5.0 Redmi-Note3
*/
void processMIUI(boolean lightStatusBar) {
Class<? extends Window> clazz = window.getClass();
try {
int darkModeFlag = 0;
Class<?> layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams");
Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE");
darkModeFlag = field.getInt(layoutParams);
Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class);
extraFlagField.invoke(window, lightStatusBar ? darkModeFlag : 0, darkModeFlag);
} catch (Exception ignored) {
}
}
/**
* 改变魅族的状态栏字体为黑色,要求FlyMe4以上
*/
private void processFlyme(boolean isLightStatusBar) {
WindowManager.LayoutParams lp = window.getAttributes();
try {
Class<?> instance = Class.forName("android.view.WindowManager$LayoutParams");
int value = instance.getDeclaredField("MEIZU_FLAG_DARK_STATUS_BAR_ICON").getInt(lp);
Field field = instance.getDeclaredField("meizuFlags");
field.setAccessible(true);
int origin = field.getInt(lp);
if (isLightStatusBar) {
field.set(lp, origin | value);
} else {
field.set(lp, (~value) & origin);
}
} catch (Exception ignored) {
//
}
}
/**
* 处理Lollipop以上
* Lollipop可以设置为沉浸,不能设置字体颜色
* M(API23)可以设定
*/
@TargetApi(Build.VERSION_CODES.LOLLIPOP) void processLollipopAbove() {
int flag = window.getDecorView().getSystemUiVisibility();
if (lightStatusBar) {
/**
* see {@link <a href="https://developer.android.com/reference/android/R.attr.html#windowLightStatusBar"></a>}
*/
flag |= (WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
if (transparentStatusbar) {
//改变字体颜色
flag |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
}
window.getDecorView().setSystemUiVisibility(flag);
window.setStatusBarColor(Color.TRANSPARENT);
}
final public static class Builder {
private Activity activity;
private boolean lightStatusBar = false;
private boolean transparentStatusbar = false;
private View actionBarView;
public Builder setActionbarView(@Nullable View actionbarView) {
this.actionBarView = actionbarView;
return this;
}
Builder setActivity(@NonNull Activity activity) {
this.activity = activity;
return this;
}
public Builder setLightStatusBar(boolean lightStatusBar) {
this.lightStatusBar = lightStatusBar;
return this;
}
public Builder setTransparentStatusbar(boolean transparentStatusbar) {
this.transparentStatusbar = transparentStatusbar;
return this;
}
public void process() {
new StatusbarUtils(activity, lightStatusBar, transparentStatusbar, actionBarView).process();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment