Last active
June 26, 2018 09:57
-
-
Save miao1007/f6a486a746e6087ed8f8 to your computer and use it in GitHub Desktop.
BlurDrawable
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
<?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> |
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
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; | |
} | |
} |
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
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); | |
} | |
} |
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
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