概述
看到好多app用模糊效果来制作启动界面,或是模糊图片、弹出菜单背景模糊等等效果,觉得这种效果很诱人总是有一种朦胧美在里面,于是乎查资料。发现这种效果可由UI设计者用ps做模糊图片,说到这里我觉得大家也不用再看了,找你们的美工人员给你们P一张图就好了。。。
Oh no.等等这怎么可能啊,那我要是让模糊效果渐变该怎么办呢?这个问的好,那就让美工多P几张图然后不断轮播就好了嘛(ps:累死美工哈哈)
以上纯属恶搞,在此进入正题我们要的当然不是上面所说的效果,而是另一个概念:高斯模糊
说实在的这个概念我以前也没有听过,当看完之后瞬间有一种高大上的感觉,至于他的解释没有比官方概念更直接了。但是我还是稍加润色及比喻简单的说一下吗:大家应该知道数码图像都是由一个一个的像素点组成的,而每个像素点其实就是对应的数字;模糊图像中心点的像素取周围像素的平均值;也就是说如果图像是单一色彩那么模糊之后也是看不出来的,因为平均值还是一样;那么也就是说如果两种颜色以中间线分隔模糊之后也只有分隔线模糊了因为只有在分割线那里的像素点相互交织所以产生了模糊;当然图像都是色彩缤纷的所以模糊之后就会很明显了。
以上是高斯模糊后和高斯模糊前的对比大家看到只有黄色和蓝色交接的边产生了模糊,所以我们现在就很明白图像模糊的原理。
当然接下来我也并不是去实现这样的算法,因为这个已经早有人写出来了。
RenderScript来实现高斯模糊
先来看看效果图:
上图使用genymotion模拟器做的gif,确实快了不少;也推荐广大Android开发者用
实现步骤:
- 获取需要模糊的图片(及得到Bitmap)
- 通过RenderScript中的ScriptIntrinsicBlur类来对图像进行渲染,也就是进行高斯模糊
- 将模糊后的图片设置到ImageView的上层View上实现模糊效果
好了接下里我们就按部就班的来完成任务
1.获取需要模糊的图片(及得到Bitmap)
先来看布局文件activity_main.xml
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.blurview.MainActivity" >
<ImageView
android:id="@+id/imageView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/girl" />
<View
android:id="@+id/blurView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0" />
<TextView
android:id="@+id/blurTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="96dp"
android:alpha="0"
android:gravity="center"
android:text="@string/hello_world"
android:textSize="48sp"
android:textStyle="bold" />
</FrameLayout>
获取ImageView中的Bitmap:
mImageView.getViewTreeObserver().addOnPreDrawListener(
new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mImageView.getViewTreeObserver()
.removeOnPreDrawListener(this);
// 加载背景图构建Bitmap
mImageView.buildDrawingCache();
// 获取ImageView缓存的Bitmap
Bitmap bmp = mImageView.getDrawingCache();
// 在异步任务中执行模糊
new BlurTask().execute(bmp);
return true;
}
});
上面需要说明几点:
在ImageView加载背景图时,取出Bitmap,然后交给异步任务去执行模糊。为什么要用异步任务?
1.高斯模糊还是比较耗时的操作
2.我们模糊的是整张图片
3.Google之前发布了Android性能优化课程中指出要达到60fps顺畅的效果就要求UI必须在16ms内渲染完,这样才不会在造成卡顿的现象(ps:16ms怎么算出来的呢?60fps指每秒屏幕刷新的频率 1秒/60≈16ms)。Google《Android性能优化》学习笔记
所以为了达到比较流畅的效果我们放到了异步线程中。
2.
通过RenderScript中的ScriptIntrinsicBlur类来对图像进行渲染,也就是进行高斯模糊
private Bitmap blur(Bitmap bkg, View view) {
float radius = 2;
float scaleFactor = 8;
// 创建需要模糊的Bitmap
Bitmap overlay = Bitmap.createBitmap(
(int) (view.getMeasuredWidth() / scaleFactor),
(int) (view.getMeasuredHeight() / scaleFactor),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(overlay);
canvas.translate(-view.getLeft() / scaleFactor, -view.getTop()
/ scaleFactor);
canvas.scale(1 / scaleFactor, 1 / scaleFactor);
Paint paint = new Paint();
paint.setFlags(Paint.FILTER_BITMAP_FLAG);
canvas.drawBitmap(bkg, 0, 0, paint);
/**
* 用RenderScript来实现模糊效果
*/
// 初始化RenderScript对象
RenderScript rs = RenderScript.create(MainActivity.this);
// 为要模糊Bitmap分配内存
Allocation overlayAlloc = Allocation.createFromBitmap(rs, overlay);
// 创建系统提供的模糊类
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs,
Element.U8_4(rs));
// 设置模糊半径
blur.setRadius(radius);
// 执行渲染
/**
* 这里可以创建两个Bitmap一个用于输入一个用于输出,
* 现在合成一个既是输入也是输出的Bitmap也算是节省的内存
*/
blur.setInput(overlayAlloc);
blur.forEach(overlayAlloc);
// 将Bitmap复制给overlay
overlayAlloc.copyTo(overlay);
// 销毁RenderScript
rs.destroy();
bkg.recycle();
return overlay;
}
3.
将模糊后的图片设置到ImageView的上层View上实现模糊效果
private class BlurTask extends AsyncTask<Bitmap, Void, Bitmap> {
@Override
protected Bitmap doInBackground(Bitmap... params) {
return blur(params[0], mBlurView);
}
@Override
protected void onPostExecute(Bitmap result) {
super.onPostExecute(result);
// 将模糊的Bitmap设置到View上
mBlurView.setBackground(new BitmapDrawable(getResources(), result));
}
}
制作启动界面
其实上面说了那么多关于模糊的东西实则是为了我们进入app的一种效果的实现;有了模糊的图片就很好实现渐变了,因为
Radius这个变量控制了模糊的程度。模糊半径越大模糊效果越明显,我们可以通过设置不同模糊程度的Bitmap来实现渐变的效果;
但是上面的方式进过实际测试变得很糟糕,模糊一张图片已经很耗时了;如今我们要模糊N张图片,想想性能就可想而知了;那么有没有更好的办法呢?答案是肯定的了,我们只需换个思路就能解决模糊渐变的过程;我们只需生成最终要模糊的一张图片然后设置他的透明度一样可以实现模糊渐变的过程,因为我的效果图就是这么做的:);
我们知道模糊图片是要对图片的每个像素点做出计算求得权值,那么也就是说2*2和4*4的图片计算时间实现相差4倍;也就是说要模糊的图片越大模糊时间成指数级增长;为了减少模糊的时间,我们想了一个好办法那就是先缩放图片,然后去模糊设置背景,这样模糊效果反而会更好,并且大大提高了性能;(
ps:我上面设置的scaleFactor参数就是缩放的比例)
启动界面的代码其实就是加了一些动画的效果,代码在此我就不贴了。
模糊效果的开源框架
Github上早有牛人已经实现了高斯模糊的效果, stackblur;用法也就3行代码,大家可以去自行查看;
它用三种方式实现了模糊的效果,本地方法、java实现、RenderScript;
源代码中没有编译so库,所以本地方法不能用;自己编译玩后经过对比:
- java实现确实最慢,比其他两个慢了2-5倍吧,也许是机器不同的原因
- native和RS差不多,因为都编译为了本地代码吗
最后附上源码地址
https://github.com/xiaozhi003/BlurView