上图展示了一种很典型的视觉效果——文字的背景不再是固定的,而是将底层的相应区域模糊化,好似盖了一层毛玻璃。
其原理也很简单,分为三步走:
获取底层的view的Bitmap——b1
将b1模糊化,保存为b2
把b2设为文字的背景。
里面涉及到的技术点有两个:
何时访问能获取到底层view的完整Bitmap?
如何模糊化,该采用什么算法?
技术点一:何时访问能获取到底层view的完整Bitmap?
ViewTreeObserver里面有一个监听器为OnPreDrawListener
// 当一个视图树将要绘制时,所要调用的回调函数的接口类
public interface OnPreDrawListener {
boolean onPreDraw();
}
当它执行时,布局文件经过了measured、laid out、displayed,即将被绘制到屏幕,此时调用它的getDrawingCache()方法可以获得其Bitmap。完整方法如下:
lowerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
lowerView.getViewTreeObserver().removeOnPreDrawListener(this);
lowerView.buildDrawingCache();
Bitmap bmp = lowerView.getDrawingCache();
return true;
}
});
技术点二:如何模糊化,该采用什么算法?
方案有两种:
借助Renderscript做高斯模糊,本质上是做卷积计算。
采用StackBlur算法对图片进行模糊处理,相较于高斯模糊,计算量小了很多。
两种方案都可以进行对Bitmap对象的模糊处理,但当模糊半径增大时,StackBlur能够保持较好的性能,且不受Renderscript半径25px的限制。
在GitHub项目有一个项目blurring,其实现了StackBlur算法的Java实现版FastBlur,并给出两种方案效率对比demo。经过测试,
在Mac上Genymotion模拟器下运行Android4.4.4,Renderscript耗时42ms,FastBlur耗时82ms
在小米4真机上运行Android6.0.1,Renderscript耗时124ms,FastBlur耗时271ms
看起来,Renderscript的性能更好,应该是Android上对Renderscript做了优化。尽管如此,考虑到Android中渲染一帧的时间应该不超过16ms(60fps),这样的性能并不友好。blurring作者想出了另外一种思路:
把bitmap的尺寸先降低然后进行模糊处理,然后再放大尺寸
这时候,效率提升非常明显:
在Mac上Genymotion模拟器下运行Android4.4.4,Renderscript耗时5ms,FastBlur耗时1ms
在小米4真机上运行Android6.0.1,Renderscript耗时16ms,FastBlur耗时7ms。
生成的模糊图片当然有所不同,但是都是模糊背景,所以对用户而言没有太大差别。
好了,至此Android上制作毛玻璃背景模糊效果的技术都确定了。
源码
我在blurring基础上做了封装,接口如下:
/**
*
* @param context 上下文
* @param lowerView 底层view控件
* @param upperView 需要模糊背景的控件
*/
public static void doBlur(final Context context, final View lowerView, final View upperView) {
lowerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
lowerView.getViewTreeObserver().removeOnPreDrawListener(this);
lowerView.buildDrawingCache();
Bitmap bmp = lowerView.getDrawingCache();
doBlur(context, bmp, upperView, true);
return true;
}
});
}
相对应类有两个:
RSBlur
package com.paveldudka.util;
import android.annotation.TargetApi;
import android.c