Android实现高斯模糊的效果网上能搜索到很多,但是关于多任务列表高斯模糊处理的文章缺比较少,这里简单记录一下我自己的实现过程,虽然有些瑕疵,但是思路最重要,记录一下。关于这个瑕疵,也会在接下来正文里有介绍。

先上一张MIUI11下的多任务预览图模糊的效果

android 高斯模糊 高斯模糊 miui_Android

,要实现这个效果,首先要确定具体的实现思路,我的思路分为以下四步:

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的布局结构图

android 高斯模糊 高斯模糊 miui_Android_02

这是从网上找的一张图,通过这张图可以看到,每个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);
        }


    }

贴上最终效果:

android 高斯模糊 高斯模糊 miui_数据_03


前文说过,按照这个思路实现的效果是有瑕疵的,具体的表现就是,当按下多任务键的时候有时候会先出现模糊效果,然后模糊效果又消失了,猜测是和广播发送和接收的时机有关,具体原因有待进一步研究。