ezgif-2-683a6ac3cd.gif

从效果还是可以看出,当点击签到按钮的时候,会伴随签到页面和分享按钮的自下向上的弹出效果,这个动画直接使用属性动画就能实现没什么好说的,最主要的就是后面的高斯模糊效果,仔细看的话可以发现是有一个从清晰逐渐模糊的渐变效果,这个不是本文重点,这里想说的是在实现自定义高斯模糊效果的时候自己所遇到的一个坑。

高斯模糊一个隐藏的坑

实际上高斯模糊效果本身是没什么好说的,只要你去网上搜索一下相信能找到不少相关的实现,其中对于android开发来说最主要的实现方法就是使用renderscript方法去实现,自己实现模糊效果的时候也是参考这些代码来完成,写这篇文章主要不是来说高斯模糊是如何实现,而是想把自己在实现这个高斯模糊效果中遇到的一个大坑和大家分享下,这个坑大家不一定遇到到,尤其是从github直接找一个现成的高斯模糊控件,通过看源码几乎不可能发现这个大坑,只有真正去实现过这个效果的人才有可能发现这个问题,很明显自己就属于后者,还正巧踩到了这个坑上,这里就说下自己踩坑埋坑的心路历程。

高斯模糊实现思路

还是简单说下高斯模糊实现的简单思路,就是得到原图bitmap然后通过renderscript处理获取到模糊后的bitmap,自定义的view在ondraw中去draw该bitmap。思路就是这么简单,这里说下需要注意的几个点。
(1)如何得到模糊后的bitmap,这个就是通过renderscript来实现的,模板代码如下:

        //创建一个缩小后的bitmap
        Bitmap inputBitmap = Bitmap.createScaledBitmap(image, width, height, false);
        //创建将在ondraw中使用到的经过模糊处理后的bitmap
        Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
       //创建RenderScript,ScriptIntrinsicBlur固定写法
        RenderScript rs = RenderScript.create(context);
        ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
        //根据inputBitmap,outputBitmap分别分配内存
        Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
        Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
        //设置模糊半径取值0-25之间,不同半径得到的模糊效果不同
        blurScript.setRadius(15);
        blurScript.setInput(tmpIn);
        blurScript.forEach(tmpOut);
      //得到最终的模糊bitmap
        tmpOut.copyTo(outputBitmap);

高斯模糊的核心代码差不多都是这样大同小异,上述代码需要注意一下的有三个地方
1 blurScript.setRadius参数取值在0-25之前,超过这个范围程序会崩溃掉
2 RenderScript,ScriptIntrinsicBlur,Allocation这些类需要使用v8中的类,所以需要在你的build.gradle加入以下配置

defaultConfig {
        minSdkVersion 15
        targetSdkVersion 27
        versionCode 1
        versionName "1.0"
        renderscriptTargetApi 19
        renderscriptSupportModeEnabled true

3 巧妙得到“原图”,高斯模糊就是通过对原图处理得到一张模糊图,但是这里所说的原图并不是真正意义上的原图,听着有点拗口,举个简单的列子,比如你想对一个500x500大小的imageview进行高斯模糊处理,得到该bitmap后还需要对该原图进一步压缩得到一张例如200x200大小的图,为什么要这么做呢,其实最主要的原因还是因为高斯模糊处理过程本身就比较消耗性能,所以说理论上你处理的原图分辨率越小那么消耗时间也就越少,对原图进行压缩处理,主要就是考虑到性能上的问题,尤其是当你的app需要支持动态高斯模糊效果的时候,需要不停的去得到模糊的bitmap,如果每次的处理量都很大就会造成页面卡顿的问题,所以使用压缩后的原图优势显而易见,另一个原因就是高斯模糊得到的最终bitmap就是模糊的效果,原图压缩失真对最后的结果并没有太大的影响。明白了上面的道理那么高斯模糊的模板代码你也就掌握了。

处理imageview模糊效果

先来看下具体效果,如图原图如下

QQ截图20180830223438.png
看起来效果还是不错的吧,修改 blurScript.setRadius(15);的参数可以得到不同模糊效果的展示,关于原图的bitmap如何得到可以直接通过view.draw方法得到,简单粗暴,如果你的高斯模糊只是用来处理imageview就可以的话那么关于我说的那个坑你是不会遇到的。

处理整个屏幕的模糊效果

这才是可能会遇到坑的地方,如果你想模糊的是整个屏幕,就如我文章开头的那种效果,那么这个坑会弄得你晕头转向,至少我开始看到呈现出来的效果时真的就是一脸懵逼,先来看一下没有模糊时的布局demo

QQ截图20180830231248.png
,看起来似乎一样,但是仔细对比一下就能发现上面的那张图片似乎只是经过了半模糊的处理,边缘部分还是清晰可见,这显然不是我想要的效果

为什么会造成这种问题,当你模糊一个imageview的时候效果非常完美,而当你去模糊一个viewgroup的时候就出现上述诡异问题,起初是怀疑和图片有关,于是换了一张其他图片发现结果还是这样。然后排查代码检查代码逻辑是否有问题,这是一个比较漫长的过程把可能引起问题的代码注释重新运行,最后折腾了半天发现并没有任何卵用,图片没问题,代码看起来也似乎没有问题那到底是哪里出了问题,无奈只好网上搜相关问题,只能说网络这么大可惜没有我想要的答案,不过从网上的一些文章也算间接找到可能解决的方案,去github上找一个高斯模糊的控件,将该控件应用到我的布局上去,然后奇迹发生了,居然没有发生边缘清晰的异常,这只能说明是我自己的代码有问题才会导致这个问题,剩下的问题就是排查到底是什么代码引起的这个诡异问题,

这里说一句题外话为什么不直接使用github上的开源库,而是自己重新造轮子。第一github上的开源库不能完全满足需求即使使用也要进行二次开发,耗费的时间成本可能比自己写还要多。第二高斯模糊本身模板代码不复杂完全可以自己自定义。第三很多人都喜欢说不要重复造轮子,但我想说的是你也要有这个本事去造这个轮子,更多的时候给你这个时间你都不一定有本事造成这个轮子才是主要原因,很多人只不过是个代码的搬运工而已,github有个类似效果就直接拿到用也不去研究下如何实现。

回到问题本身,又是半天的摸索,经过大量对比终于将可疑点定位到了一个关键的地方,我自己代码中是通过得到你要模糊的view,然后调用该view的draw方法得到bitmap,而github上的高斯模糊控件是通过decorview的draw方法得到bitmap,当我将自己的高斯模糊控件对应逻辑改成decorview之后,整个世界都清净了,我得到了和github一样的高斯模糊效果,那一刻可以说困扰了我两天的问题终于得到了解决,这种成就感不是单单使用一个开源库所能得到的。

开心没有多久,一个巨大的疑问又让我感到郁闷,为什么使用decorview就没问题,而使用其他viewgroup就会出问题,可能猜测到的原因就是decorview内部做了什么操作,但是做了什么操作,面对这么多的代码可以说真的就是无从下手去找,猜测的第一个原因是不是和硬件加速有关,测试后发现并没有用处,继续懵逼中......,换几张图片看看能不能发现一点蛛丝马迹,以下效果引起了我注意

decorview什么时候设置背景色

最后一个困扰我的问题,为什么调用decorview的draw方法就没问题,经过上述我总结的原因,唯一一个解释就是decorview默认自带一个背景色!!,那么这个背景色是在什么时候设置的呢,只能在源码里面去找答案了,这次就不再是大海捞针的看源码了,在phonewindow--》installDecor--》generateLayout方法中最终找到了我想要的答案

            TypedArray a = getWindowStyle();
            if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            R.styleable.Window_windowBackground, 0);
            final Drawable background;
            if (mBackgroundResource != 0) {
                background = getContext().getDrawable(mBackgroundResource);
            } else {
                background = mBackgroundDrawable;
            mDecor.setWindowBackground(background);