文章目录

  • 前言
  • 一、权限申请
  • 二、自定义控件BlurBGImageView
  • 三、获取屏幕截图
  • 四、新加模糊效果的窗口
  • 五、动态更新背景
  • 六、在收到各手势后进行对应动作

前言

因项目需要实现动态毛玻璃遮盖效果,在此记录一下仿照一些手机厂商所做的通知中心下拉背景毛玻璃特效功能实现的一些步骤

一、权限申请

本demo涉及需要的权限如下:

<uses-permission android:name="android.permission.READ_FRAME_BUFFER"></uses-permission>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

二、自定义控件BlurBGImageView

参考这一篇文章自定义一个ImagView
核心方法使用RenderScript进行模糊计算使用的bitmap和radius由外部实时传入

private Bitmap blur(Context context, Bitmap image, float radius) {
        RenderScript rs = RenderScript.create(context);
        Bitmap outputBitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
        Allocation in = Allocation.createFromBitmap(rs, image);
        Allocation out = Allocation.createFromBitmap(rs, outputBitmap);

        ScriptIntrinsicBlur intrinsicBlur = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        intrinsicBlur.setRadius(radius);
        intrinsicBlur.setInput(in);
        intrinsicBlur.forEach(out);

        out.copyTo(outputBitmap);
        image.recycle();
        rs.destroy();
        return outputBitmap;
    }

传入需要进行模糊处理的View

/**
    *bgView 从外部传入的背景View
    **/
    public void refreshBG(View bgView){
        bgView.setDrawingCacheEnabled(true);
        bgView.buildDrawingCache();
        Bitmap bitmap1 = null;
        try {
            bitmap1 = getBitmap(bgView);
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (bitmap1 != null){
            blur(bitmap1,this,radius);//模糊处理
            bitmap1.recycle();
        }
        bgView.setDrawingCacheEnabled(false);//清除缓存
    }

在这里我做了一些改进,原方法传入的View缓冲区进行模糊处理会导致一些问题,比如VideoView传入时并不会有实际画面而是黑屏
所以这里再写一个refreshBG,之后在用其他方法获取屏幕截图的bitmap:

public void refreshBG(Bitmap bmp){
        if (bmp != null){
            blur(bmp,this,radius);
            bmp.recycle();
        }
    }

三、获取屏幕截图

上面有提到过,传入View作为背景发生如无法获取实际画面而是黑屏的问题,所以这里我们换用SurfaceControl的screenshot方法获取截图

public Bitmap takeScreenShot(){
        Bitmap mScreenBitmap;
        DisplayMetrics mDisplayMetrics;
        Display mDisplay;
        mDisplay = this.getDisplay();
        mDisplayMetrics = new DisplayMetrics();
        mDisplay.getRealMetrics(mDisplayMetrics);
        float[] dims = {mDisplayMetrics.widthPixels , mDisplayMetrics.heightPixels};
        mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
        return Bitmap.createBitmap(mScreenBitmap, 0, mNaturalBarHeight, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels - 48 - mNaturalBarHeight);
    }

四、新加模糊效果的窗口

新加的模糊遮盖通过WindowManager.addView加在原画面之上

private void createWindow() {
        WindowManager.LayoutParams testparams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.MATCH_PARENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                        | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.TRANSLUCENT);
        testparams.setTitle("BLUR window");
        testparams.x = 0;
        testparams.y = 12;
        testparams.width = 480;
        testparams.height = 800;
        ViewGroup vg =  (ViewGroup) mBlurBGImageView.getParent();
        if(vg!=null){
            vg.removeAllViews();
        }
        windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
        windowManager.addView(mBlurBGImageView, testparams);
    }

五、动态更新背景

起子线程以固定频率向Handler发送消息更新BlurBGImageView
Handler接受Message后调用refreshBG()更新控件

class MyThread extends Thread{
        private final Object lock = new Object();
        private boolean pause = false;
        void pauseThread() {
            pause = true;
        }
        void resumeThread() {
            pause = false;
            synchronized (lock) {
                lock.notifyAll();
            }
        }
        void onPause() {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        @Override
        public void run(){
            while (true) {
                while (pause) {
                    onPause();
                }
                Message msg = mHandler.obtainMessage();
                msg.what = UPDATE_BLUR_LAYOUT;
                mHandler.sendMessage(msg);
                try {
                    sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch(msg.what) {
                    case UPDATE_BLUR_LAYOUT:
                        mBlurBGImageView.refreshBG(takeScreenShot());
                        break;
                    default:
                        break;
                }
            }
        };

六、在收到各手势后进行对应动作

这一步是仿照一些定制安卓的第三方厂商所做的通知中心下拉手势:

ACTION_DOWN按下时:
将可拖动标志位设置为true、设置手势初始y坐标、将模糊窗口添加上来并resume子线程开始更新控件
ACTION_MOVE拖动时:
根据实时y坐标的值与初始值的差值计算控件模糊度和透明度并设置
ACTION_UP松手时:
根据当前窗口的展示状态和本次拖动距离判断是收起模糊窗口、展开窗口还是维持原状

mLayout.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                float y = event.getY();
                switch (event.getAction()){
                    case MotionEvent.ACTION_DOWN:
                        Log.d(TAG, "MotionEvent.ACTION_DOWN y: " + y);
                        if (!mCanDrag) {
                            mCanDrag = true;
                            initY = y;
                            Log.d(TAG, "MotionEvent.ACTION_DOWN initY: " + initY);
                            if (!mIsShown) {
                                coverBlurWindow();
                            }
                        }
                        break;
                    case MotionEvent.ACTION_MOVE:
                        if (mCanDrag) {
                            mOffsetY = y - initY;
                            mBlurRadius = calBlurRadius(mOffsetY);
                            mBlurAlpha = calBlurAlpha(mOffsetY);
                            Log.d(TAG, "MotionEvent.ACTION_MOVE mOffsetY: " + mOffsetY + " mBlurRadius: " + mBlurRadius
                             + " mBlurAlpha: " + mBlurAlpha + " mIsShown:" + mIsShown);
                            mBlurBGImageView.setRadius(mBlurRadius);
                            mBlurBGImageView.setAlpha(mBlurAlpha);
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        Log.d(TAG, "MotionEvent.ACTION_UP y: " + y);
                        if (mCanDrag) {
                            mCanDrag = false;
                            if (!mIsShown) {
                                if (mOffsetY > FLING_MIN_DISTANCE_WITH_PANEL_OPEN_AND_DISMISS) {
                                    mIsShown = true;
                                    mBlurBGImageView.setRadius(25f);
                                    mBlurBGImageView.setAlpha(0.9f);
                                } else {
                                    uncoverBlurWindow();
                                }
                            } else if (mIsShown) {
                                if (mOffsetY < -FLING_MIN_DISTANCE_WITH_PANEL_OPEN_AND_DISMISS) {
                                    mIsShown = false;
                                    uncoverBlurWindow();
                                } else {
                                    mBlurBGImageView.setRadius(25f);
                                    mBlurBGImageView.setAlpha(0.9f);
                                }
                            }
                        }
                        break;
                }
                return true;
            }
        });

    private void coverBlurWindow() {
        createWindow();
        mt.resumeThread();
    }

    private void uncoverBlurWindow() {
        mt.pauseThread();
        windowManager.removeView(mBlurBGImageView);
    }