Android实现高斯模糊的效果网上能搜索到很多,但是关于多任务列表高斯模糊处理的文章缺比较少,这里简单记录一下我自己的实现过程,虽然有些瑕疵,但是思路最重要,记录一下。关于这个瑕疵,也会在接下来正文里有介绍。
先上一张MIUI11下的多任务预览图模糊的效果
,要实现这个效果,首先要确定具体的实现思路,我的思路分为以下四步:
1 监听多任务按键事件,Android中,返回键的监听可以通过重写onBackPressed或者拦截按键点击事件来实现。但是对于多任务键的监听比较特殊,无法通过按键拦截来实现监听(至少我的小米手机上是这样的),但是我们可以通过广播来监听多任务键。先贴一下代码吧
/**
* 参数
*/
private static final String SYSTEM_REASON = "reason";
private static final String SYSTEM_HOME_RECENT_APPS = "recentapps";
private Context mContext;
private BroadcastReceiver mDeviceKeyReceiver = null;
private OnKeyListener mListener;
public DeviceKeyMonitor (Context context, final OnKeyListener listener) {
mContext = context;
mListener = listener;
mDeviceKeyReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action=intent.getAction();
if(!TextUtils.isEmpty(action)){
if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
String reason = intent.getStringExtra(SYSTEM_REASON);
if (reason == null)
return;
// 最近任务列表键
if (reason.equals(SYSTEM_HOME_RECENT_APPS)) {
mListener.onRecentClick();
}
}
}
}
};
mContext.registerReceiver(mDeviceKeyReceiver, new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
public interface OnKeyListener{
void onRecentClick();//多任务键监听
}
public void unregister(){
if (mDeviceKeyReceiver != null){
mContext.unregisterReceiver(mDeviceKeyReceiver);
mDeviceKeyReceiver = null;
}
}
然后在activity中重写onRecentClick事件。
2 第二步就是对当前activity截图,核心代码如下
/***
* 截取当前activity
* @param activity
* @return
*/
public static Bitmap activityShot(Activity activity) {
/*获取windows中最顶层的view*/
View view = activity.getWindow().getDecorView();
//允许当前窗口保存缓存信息
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
//获取状态栏高度
Rect rect = new Rect();
view.getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rect.top;
WindowManager windowManager = activity.getWindowManager();
//获取屏幕宽和高
DisplayMetrics outMetrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(outMetrics);
int width = outMetrics.widthPixels;
int height = outMetrics.heightPixels;
//去掉状态栏
Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache(), 0, statusBarHeight, width,
height - statusBarHeight);
//销毁缓存信息
view.destroyDrawingCache();
view.setDrawingCacheEnabled(false);
return bitmap;
}
方法返回的bitmap就是我们截取activity之后的bitmap。
3 第三步,将截取的activity高斯模糊处理。关于高斯模糊,在Android中常用的高斯模糊技术有三种RenderScript 、fastBlur、对RenderScript和fastBlur的优化。由于本文使用的高斯模糊框架是RenderScript,所以这里简单介绍一下RenderScript,其他两种方式的详细介绍网上有大量的文章可以参考。
RenderScript是在Android上的高性能运行密集型运算的框架,RenderScript主要用于数据并行计算,尤其对图像处理、摄影分析和计算机视觉特别有用。RenderScript是在Android3.0(API 11)引入的。而Android图片高斯模糊处理,通常也是用这个库来完成。它提供了我们Java层调用的API,实际上是在c/c++ 层来处理的,所以它的效率和性能通常是最高的。要使用RenderScript完成图片高斯模糊只需要以下几步:
(1) 初始化一个RenderScript Context:RenderScript 上下文环境通过create(Context)方法来创建,它保证RenderScript的使用并且提供一个控制后续所有RenderScript对象(如:ScriptIntrinsicBlur、Allocation等)生命周期的对象。
(2)通过Script至少创建一个Allocation:一个Allocation是提供存储大量可变数据的RenderScript 对象。在内核中,Allocation作为输入和输出,在内核中通过rsGetElementAt_type ()和rsSetElementAt_type()方法来访问Allocation当script全局绑定的时候。使用createFromBitmap 和createTyped来创建Allocation。
(3)创建ScriptIntrinsic:它内置了RenderScript 的一些通用操作,如高斯模糊、扭曲变换、图像混合等等,更多的操作请看ScriptIntrinsic的子类,本文要用的高斯模糊处理就是用的它的子类ScriptIntrinsicBlur。
(4)填充数据到Allocations:除了使用方法createFromBitmap创建的Allocation外,其它的第一次创建时都是填充的空数据。
(5)** 设置模糊半径**:设置一个模糊的半径,其值为 0-25。
(6) 启动内核,调用方法处理:调用forEach 方法模糊处理。
(7) ** 从Allocation 中拷贝数据**:为了能在Java层访问Allocation的数据,用Allocation其中一个copy方法来拷贝数据。
(8) 销毁RenderScript对象:可以用destroy方法来销毁RenderScript对象或者让它可以被垃圾回收,destroy 之后,就能在用它控制的RenderScript对象了(比如在销毁了之后,再调用ScriptIntrinsic或者Allocation的方法是要抛异常的)。
对应的核心代码如下
public static Bitmap rsBlur(Context context, Bitmap source, int radius){
Bitmap inputBmp = source;
//(1)
//初始化一个RenderScript Context
RenderScript renderScript = RenderScript.create(context);
// Log.i(TAG,"setDrawingCacheEnabledle size:"+inputBmp.getWidth()+"*"+inputBmp.getHeight());
// Allocate memory for Renderscript to work with
//(2)
//创建输入输出的allocation
final Allocation input = Allocation.createFromBitmap(renderScript,inputBmp);
final Allocation output = Allocation.createTyped(renderScript,input.getType());
//(3)
// Load up an instance of the specific script that we want to use.
//创建ScriptIntrinsic
ScriptIntrinsicBlur scriptIntrinsicBlur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript));
//(4)
//填充数据
scriptIntrinsicBlur.setInput(input);
//(5)
// Set the blur radius
//设置模糊半径
scriptIntrinsicBlur.setRadius(radius);
//(6)
// Start the ScriptIntrinisicBlur
//启动内核
scriptIntrinsicBlur.forEach(output);
//(7)
// Copy the output to the blurred bitmap
//copy数据
output.copyTo(inputBmp);
//(8)
//销毁renderScript
renderScript.destroy();
return inputBmp;
}
4 第四步,将高斯模糊处理之后的bitmap填充到activity的根布局中,在这之前先简单介绍一下activity的布局结构图
这是从网上找的一张图,通过这张图可以看到,每个activity都包含一个Window对象,在Android中的window对象其实就是PhoneWindow。PhoneWindow内部又包含一个DecorView,这个DecorView就是整个activity的根布局。所以要将模糊处理之后的bitmap填充到整个activity中就需要在decoriew中做手脚。
因为的decorview本身就是一个FrameLayout,我们可以在decorview中创建一个imageview,然后将处理后的图片填充到imageview中去。核心代码如下
@Override
public void onRecentClick() {
// Toast.makeText(this,"按了多任务键",Toast.LENGTH_LONG).show();
// group = (ViewGroup) this.getWindow().getDecorView();
group.removeView(imageView);
// 创建imageview
imageView = new ImageView(this);
FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
imageView.setLayoutParams(params);
// 截图,并将图片高斯模糊处理
Bitmap bitmap=PicUtil.rsBlur(GaussianBlurActivity.this,PicUtil.activityShot(GaussianBlurActivity.this),20);
// 将处理后的图片填充到imageview
imageView.setImageBitmap(bitmap);
group.addView(imageView);
}
5 第五步,退出多任务模式时将界面的imageview删除
@Override
protected void onResume() {
super.onResume();
for(int i=0;i<group.getChildCount();i++){
group.removeView(imageView);
}
}
贴上最终效果:
。
前文说过,按照这个思路实现的效果是有瑕疵的,具体的表现就是,当按下多任务键的时候有时候会先出现模糊效果,然后模糊效果又消失了,猜测是和广播发送和接收的时机有关,具体原因有待进一步研究。