在Android上实现模糊视图

模糊效果

模糊效果可以生动地表现出内容的层次感,能帮助用户着重关注内容。即便在模糊表面下层发生视差效果或者动态改变,也能够保持当前专题内容。

在iOS上,我们首先构造一个UIVisualEffectView得到一类模糊层:

1
2
UIVisualEffect *blurEffect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *visualEffectView = [[UIVisualEffectView alloc] initWithEffect:blurEffect];

之后添加visualEffectView到视图层,在层上可以对其下层进行动态的模糊。

Android中的表现形式

即使在Android上实现这点并非易事,但我们的确在雅虎天气应用中看到了很好的模糊效果实例。然而,根据Nicholas Pomepuy的博客文章,这个应用是通过缓存一张预渲染的背景图片来实现图片虚化的。

虽然这种方法非常有效,但其实不符合我们的需求,在500px应用中,图像通常是获得焦点的内容,而不仅仅提供背景,这说明即便是在模糊层之下,图像的变化也可能很大而且变化迅速。在我们的Android应用中就有一个恰当的例子:当用户滑动至下一页时,整排图片会以相反方向淡出,为了组成所需的模糊效果,合理地管理多个预渲染图是很困难的。

2015-03-17-500px-android-tour-blurring

一种绘制模糊视图的方法

我们需要的效果是实时地模糊其下层的视图,最终得到的界面很简单,就像是模糊视图的一个blurred view引用。

1
blurringView.setBlurredView(blurredView);

之后当blurred view改变时,不管是因为内容改变(比如展示一张新的图片)、视图的变换还是加入动画过程,我们都要刷新模糊视图。

1
blurringView.invalidate();

为了实现模糊视图, 我们需要继承View类并重写onDraw()方法来渲染模糊效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected void onDraw(Canvas canvas) {
     super .onDraw(canvas);
 
     // Use the blurred view’s draw() method to draw on a private canvas.
     mBlurredView.draw(mBlurringCanvas);
 
     // Blur the bitmap backing the private canvas into mBlurredBitmap
     blur();
 
     // Draw mBlurredBitmap with transformations on the blurring view’s main canvas.
     canvas.save();
     canvas.translate(mBlurredView.getX() - getX(), mBlurredView.getY() - getY());
     canvas.scale(DOWNSAMPLE_FACTOR, DOWNSAMPLE_FACTOR);
     canvas.drawBitmap(mBlurredBitmap, 0 , 0 , null );
     canvas.restore();
}

这里的关键点在于,当模糊视图重绘时,它使用blurred view的draw()方法。模糊视图保持blurred view的引用,而绘制一个私有的、以bitmap为背景的画布。

1
mBlurredView.draw(mBlurringCanvas);

这种使用另一个视图的draw()方法,也适用于建立放大或者个性的UI界面。其中的内容是扩大的,而不是模糊的。

根据Nicholas Pomepuy的文章讨论出的想法,我们使用子采样和渲染脚本进行快速绘制,当初始化模糊视图的私有画布mBlurringCanvas时,子采样也随即完成了。

1
2
3
4
5
int scaledWidth = mBlurredView.getWidth() / DOWNSAMPLE_FACTOR;
int scaledHeight = mBlurredView.getHeight() / DOWNSAMPLE_FACTOR;
 
mBitmapToBlur = Bitmap.createBitmap(scaledWidth, scaledHeight, Bitmap.Config.ARGB_8888);
mBlurringCanvas = new Canvas(mBitmapToBlur);

通过mBlurringCanvas的 建立与恰当的渲染脚本初始化,重绘时的blur()方法如下:

1
2
3
4
mBlurInput.copyFrom(mBitmapToBlur);
mBlurScript.setInput(mBlurInput);
mBlurScript.forEach(mBlurOutput);
mBlurOutput.copyTo(mBlurredBitmap);

此时mBlurredBitmap已准备好,剩下的onDraw()方法要做的就是使用适当的变换和缩放,在模糊视图自己的画布上重绘视图。

实现细节

完整实现blurring view 时,我们需要注意几个技术点。第一,我们发现缩放因子8,模糊半径15能很好地满足我们的目标,但满足你需求的参数可能是不同的。

第二,在模糊的bitmap边缘会遇到一些渲染脚本效果,我们将缩放的宽度和高度进行了圆角化,直到最近的4的倍数。

1
2
3
// The rounding-off here is for suppressing RenderScript artifacts at the edge.
scaledWidth = scaledWidth - (scaledWidth % 4 ) + 4 ;
scaledHeight = scaledHeight - (scaledHeight % 4 ) + 4 ;

第三,为了保证更好的表现效果,我们新建两个bitmap对象,mBitmapToBlur和mBlurredBitmap,mBitmapToBlur位于私有画布mBlurringCanvas之下,mBlurredBitmap仅当blurred view的大小变化时才重新建立他们;同样地当blurred view的大小改变时,我们才创建渲染脚本对象mBlurInput和mBlurOutput。

第四,我们以PorterDuff.Mode.OVERLAY模式绘制一个白色半透明的层,它处在被模糊的的图片上层,用来处理设计需求中的淡化。

最后,由于RenderScript(渲染脚本)至少在API 17以上才可用,所以我们需要兼容Android的旧版本。糟糕的是,Nicholas Pomepuy的文章中提到的Java bitmap的模糊方案,当恰当地预渲染缓存副本时,对于实时渲染不够迅速,我们决定使用一个半透明的view来应对。(更新于2015年3月23日:通过使用RenderScript Support Library库,我的解决方法能在更低版本的API中运行。下面提及的库和Demo都更新了,非常感谢GitHub上的小伙伴panzerdev告诉我这点)

优点和缺点

我们喜欢这个视图的绘制方法,因为它可以实时模糊并且容易使用,使得blurred view的内容,也在blurring view和blurred view中间保证了灵活性,最重要的是它满足了我们的需求。

然而,这个方法确实使得blurring view与适当协同变换的blurred view保持了私有联系。相应地,模糊视图必须不能是blurred view的子类,否则会因为互相调用造成栈溢出。简单有效处理此限制的方法是要保证模糊视图与blurred view在同一级,并且Z轴上blurred view在blurring view之前。

另一点需要注意的限制是,由于与矢量图形和文本有关,我们使用默认的bitmap削减采样表现效果不是很好。

库文件和示例

你可以在我们的Android应用上看到解决方法,我们也把开源的库文件连同一个示例分享到了github上,它能够展示内容变换、动画和视图变换。

示例展示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值