原帖地址:http://segmentfault.com/a/1190000000448785

为什么学习这个模糊技术?

现在越来越多的开发者喜欢在自定义控件的时候加上各种模糊背景,看看RomanNurik开发的Muzei或者Yahoo的Weather应用app都非常不错。我非常喜欢他们的设计。

我从Mark Allison的帖子(帖子地址)得到启发,然后写了这篇文章。

这是我们需要完成下图展示的效果:

预备知识

首先描述一下我们需要的文件。我们需要一个主Activity,里面有一个含有多个FragmentViewPager,每个Fragment展示一种模糊技术。

这是主Activity的布局文件内容:

<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/pager"android:layout_width="match_parent"android:layout_height="match_parent"tools:context="com.paveldudka.MainActivity" />

这是Fragment的布局文件内容:

<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><ImageView
        android:id="@+id/picture"android:layout_width="match_parent"android:layout_height="match_parent"android:src="@drawable/picture"android:scaleType="centerCrop" /><TextView
        android:id="@+id/text"android:gravity="center_horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="My super text"android:textColor="@android:color/white"android:layout_gravity="center_vertical"android:textStyle="bold"android:textSize="48sp" /><LinearLayout
        android:id="@+id/controls"android:layout_width="match_parent"android:layout_height="wrap_content"android:background="#7f000000"android:orientation="vertical"android:layout_gravity="bottom"/>
</FrameLayout>

我们只是在布局上放了一个ImageView,然后在中间加一个TextView,还有一些作为效果显示和测试的控件(如 @+id/controls)。

最普遍的模糊技术是这样做的:

  • TextView的后一层背景中截取一部分;
  • 进行模糊处理;
  • 把模糊处理后的部分设置为TextView的背景。

Renderscript

那怎么在Android中实现模糊处理呢?最好的答案就是Renderscript。这是一个功能强大的图形处理“引擎”。RenderScript的底层原理就不做介绍了(因为我也不知道),而且这超过了我们这篇文章的范围。

先看下面的代码:

    public class RSBlurFragment extends Fragment {private ImageView image;private TextView text;private TextView statusText;@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_layout, container, false);image = (ImageView) view.findViewById(R.id.picture);text = (TextView) view.findViewById(R.id.text);statusText = addStatusText((ViewGroup) view.findViewById(R.id.controls));applyBlur();return view;}private void applyBlur() {image.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {@Overridepublic boolean onPreDraw() {image.getViewTreeObserver().removeOnPreDrawListener(this);image.buildDrawingCache();Bitmap bmp = image.getDrawingCache();blur(bmp, text);return true;}});}@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)private void blur(Bitmap bkg, View view) {long startMs = System.currentTimeMillis();float radius = 20;Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()),(int) (view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(overlay);canvas.translate(-view.getLeft(), -view.getTop());canvas.drawBitmap(bkg, 0, 0, null);RenderScript rs = RenderScript.create(getActivity());Allocation overlayAlloc = Allocation.createFromBitmap(rs, overlay);ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(rs, overlayAlloc.getElement());blur.setInput(overlayAlloc);blur.setRadius(radius);blur.forEach(overlayAlloc);overlayAlloc.copyTo(overlay);view.setBackground(new BitmapDrawable(getResources(), overlay));rs.destroy();statusText.setText(System.currentTimeMillis() - startMs + "ms");}@Overridepublic String toString() {return "RenderScript";}private TextView addStatusText(ViewGroup container) {TextView result = new TextView(getActivity());result.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));result.setTextColor(0xFFFFFFFF);container.addView(result);return result;}}
  • Fragment创建的时候,我先载入了布局文件,然后把TextView添加到我在布局文件中定义的LinearLayout中(用来显示模糊效果,进行效果测试),最后对图片做模糊处理。
  • applyBlur()函数中我注册了onPreDrawListener()。因为在applyBlur()方法调用的时候界面还没有开始布局,所以我需要实现这个监听器否则不能进行模糊处理。需要等到布局文件全都经过measured、laid out、displayed的时候,才能进行操作。
  • onPreDraw()回调函数中,我首先把返回值false改成true。这个很重要,如果返回false的话,刚开始出现的那一帧画面会被跳过,但是我们需要显示这第一帧,所以要返回true。
  • 接着我去掉了里面的回调方法,因为我们不需要监听它的preDraw事件。
  • 然后我们需要从ImageView中获取Bitmap,然后用getDrawingCache()函数创建drawing cache并保存。
  • 最后就是进行模糊处理了,我们接下来会详细讨论这个环节。

需要说明的是,我的代码中在两种情况下考虑不是很周全:

  • 在布局文件改变时不会再自动重新模糊处理。这个问题可以通过注册onGlobalLayoutListener监听器解决,在布局文件改变时重新进行模糊处理就可以了。
  • 这个模糊处理操作是在主线程中进行的。我们知道在实际开发中不会这么做,但是为了方便暂时先这么做了。

现在回到blur()方法:

  • 首先我创建了一个空的bitmap,把背景的一部分复制进去,之后我会对这个bitmap进行模糊处理并设置为TextView的背景。
  • 通过这个bitmap保存Canvas的状态;
  • 在父布局文件中把Canvas移动到TextView的位置;
  • ImageView的内容绘到bitmap中;
  • 此时,我们就有了一个和TextView一样大小的bitmap,它包含了ImageView的一部分内容,也就是TextView背后一层布局的内容;
  • 创建一个Renderscript的实例;
  • 把bitmap复制一份到Renderscript需要的数据片中;
  • 创建Renderscript模糊处理的实例;
  • 设置输入,半径范围然后进行模糊处理;
  • 把处理后的结果复制回之前的bitmap中;
  • 好了,我们已经把bitmap惊醒模糊处理了,可以将它设置为TextView背景了;

这是我们处理后的效果:

可以看到效果还不错,但是它用了57ms的时间。我们知道在Android中渲染一帧的时间应该不超过16ms(60fps),但如果在UI线程中做模糊处理就会让帧率降到了17fps。显然这是不可接受的,我们需要把这个操作移到AsyncTask上或者使用别的机制实现。

而且有必要说明的是,ScriptIntrinsicBlur只支持API17以上,当然也可以用Renderscript的support lib降低一些API版本的要求。

可是我们还是需要支持一些老一些的API版本,它们不支持Renderscript,现在我们看看该怎么办吧。

FastBlur

因为我们知道,这种模糊处理的过程也就是像素处理而已,所以我们可以尝试着手动进行模糊操作。幸运的是,Java上已经有了很多实现模糊处理方案的例子。我们唯一要做的就是找到一个相对快速的实现方案。

感谢在StackOverFlow上的一篇帖子,我找到了一个能快速实现模糊处理的方案。先看看它是怎么样的。

因为很多代码都是一样的,所以这里只讨论关于模糊处理的函数:

    private void blur(Bitmap bkg, View view) {long startMs = System.currentTimeMillis();float radius = 20;Bitmap overlay = Bitmap.createBitmap((int) (view.getMeasuredWidth()),(int) (view.getMeasuredHeight()), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(overlay);canvas.translate(-view.getLeft(), -view.getTop());canvas.drawBitmap(bkg, 0, 0, null);overlay = FastBlur.doBlur(overlay, (int)radius, true);view.setBackground(new BitmapDrawable(getResources(), overlay));statusText.setText(System.currentTimeMillis() - startMs + "ms");}

实现的效果如下:

可以看到,模糊处理的效果也相当不错。使用FastBlur的好处是,我们可以去掉对Renderscript的依赖(还有最低API版本的限制)。但是可恶的是,模糊处理操作竟然花费了147ms!这还不是最慢的SW模糊算法,我都不敢用高斯模糊了…

继续深入

现在我们要想想该怎么做。模糊处理的过程都会有精度损失,你知道什么是精度损失吗?对,要降低尺寸。

既然这样,为什么不把bitmap的尺寸先降低然后进行模糊处理,然后再放大尺寸呢?我试着实现了一下这个想法,这是处理后得到的效果:

看到了吗!Renderscript只用了13ms,FastBlur只用了2ms!还不错。

再看看代码。这里只讨论FastBlur版本,因为Renderscript也是一样的,全部代码都可以从GitHub仓库中检出。

    private void blur(Bitmap bkg, View view) {long startMs = System.currentTimeMillis();float scaleFactor = 1;float radius = 20;if (downScale.isChecked()) {scaleFactor = 8;radius = 2;}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);overlay = FastBlur.doBlur(overlay, (int)radius, true);view.setBackground(new BitmapDrawable(getResources(), overlay));statusText.setText(System.currentTimeMillis() - startMs + "ms");}

我们过一遍这个代码:

  • scaleFactor提供了需要缩小的等级,在代码中我把bitmap的尺寸缩小到原图的1/8。因为这个bitmap在模糊处理时会先被缩小然后再放大,所以在我的模糊算法中就不用radius这个参数了,所以把它设成2。
  • 接着需要创建bitmap,这个bitmap比最后需要的小八倍。
  • 请注意我给Paint提供了FILTER_BITMAP_FLAG标示,这样的话在处理bitmap缩放的时候,就可以达到双缓冲的效果,模糊处理的过程就更加顺畅了。
  • 接下来和之前一样进行模糊处理操作,这次的图片小了很多,幅度也降低了很多,所以模糊过程非常快。
  • 把模糊处理后的图片作为背景,它会自动进行放大操作的。

之前说到FastBlur进行模糊操作比Renderscript还要快,这是因为FastBlur在进行bitmap复制操作时还会同时进行其他操作,节省了时间。经过了这些处理后,我们应该已经掌握了相对快速的模糊处理方案和原理,并去掉了对Renderscript的依赖。

本文的示例代码: GitHub – blurring 。

这里有其他的一下资料

1)Android RenderScript 学习笔记

http://blog.csdn.net/droidpioneer/article/details/6639068

2)Android RenderScript 的使用基础篇

http://blog.csdn.net/droidpioneer/article/details/6639068

3)关于Android RenderScript 的详细说明和一些实用文档

http://www.cnblogs.com/TerryBlog/archive/2012/03/02/2377251.html

Android高级模糊技术RenderScript和FastBlur相关推荐

  1. Android高级模糊技术

    Android高级模糊技术 这里只是备注一下 这是网上的一个地址:http://blog.jobbole.com/63894/重点内容 下面这个代码修复了github上有些手机崩溃的问题 并增加了一个 ...

  2. Android高级混淆和代码保护技术

    本文讲的是 Android高级混淆和代码保护技术, 这是一篇关于 Android 代码保护的文章,旨在介绍代码混淆.防止逆向工程的各种高级技巧.大家都很忙,我也赶着回去继续开发我的新应用,因此话不多说 ...

  3. Android 高级混淆和代码保护技术

    转载自@drakeet Android大牛的博客 http://drakeet.me/android-advanced-proguard-and-security/ Android 高级混淆和代码保护 ...

  4. Android 高级面试

    一. Android 性能优化 1. 如何对 Android 应用进行性能分析 2. 什么情况下会导致内存泄露 3. 如何避免 OOM 异常 4. Android 中如何捕获未捕获的异常 5. ANR ...

  5. 你连《Android高级UI与FrameWork源码》都搞不懂学什么Android?还敢面试阿里P7!

    Android高级UI与FrameWork源码 重要性? 这块知识是现今使用者最多的,我们称之为Android2013~2016年的技术,但是,即使是这样的技术,Android开发者也往往因为网上Co ...

  6. 【建议收藏】2020年中高级Android大厂面试秘籍,为你保驾护航金三银四,直通大厂(Android高级篇下)...

    前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~. A awesome android expert interview questions a ...

  7. Android开发,技术实力的本质是什么?,面向Android开发者的复习指南

    技术实力的 <Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义> 开源分享完整内容戳这里 本质究竟是什么? ======================== ...

  8. Android实时模糊

    关于Android 获取模糊背景的一些记录 Android常见的模糊应用场景 分为两种:1,静态模糊 2,动态模糊 静态模糊就是直接模糊一张图片然后设置进View显示,动画模糊也就是实时模糊一般就是指 ...

  9. android高级编程-android高级应用

    android高级应用>>> 第一阶段 程序员基本素质养成 程序员所需要具备的12条职业素质 让学员初步了解和审视自己所应该具备的职业素质.并且我们会在授课中随时训练和贯彻这样的素质 ...

最新文章

  1. Centos下通过SMTP发送邮件失败解决
  2. 怎么用贝塞尔工具画圆_Win10恶意软件删除工具怎么用?这个方法都舍不得分享...
  3. 文件共享服务器第二部,第二章-构建Samba文件共享服务器.docx
  4. linux屏幕怎么放大_02|初始Linux——Windows与Linux区别
  5. php遍历文件夹下文件内容_php实现的遍历文件夹下所有文件,编辑删除
  6. ASP.NET MVC3 快速入门-第四节 添加一个模型(转)
  7. 一文读懂腾讯云“AI即服务”战略新品“智能云”
  8. 生成M3U8播放地址用于手机HLS Player(M3U8Player)观看电视频道直播方案
  9. Linux下G++编译
  10. python两个下划线怎么打_Python中单下划线与双下划线用法总结
  11. UINO优锘:EMV,让IT告警实现至繁归于至简
  12. 阿里云ECS的1M带宽能干嘛?
  13. Huawei RH2288 V3 风扇噪音大的解决方案
  14. 东华大学计算机考研大纲,2019年东华大学854计算机及软件工程专业基础综合考研初试大纲...
  15. huan shen总结
  16. 校招进腾讯,二本没戏了?
  17. 微信公众号和同步助手使用
  18. 工作就是不让自己闲着
  19. 多项式事件模型(multinomial event model)
  20. JAVA的类来实现银行账户基本操作

热门文章

  1. Android 从ImageView中获取Bitmap对象方法
  2. java 图片阴影_Java如何为 PPT 中的图形添加阴影效果
  3. MySQL探秘(四):InnoDB的磁盘文件及落盘机制(持久性)
  4. c modern approach‘s forteenth chapter‘s study
  5. 迭代器和反向迭代器,常量迭代器和非常量迭代器
  6. c++ primer 5th p289页练习题自己的解答程序
  7. c++ setstate() 和 clear() 函数
  8. [C++]constexpr函数
  9. php substr的用法,php substr(函数用法)
  10. python实现画图哆啦A梦