在Android中,我们可以实现很多很酷的处理图片的效果。在2014年某次会议的讲演《图像的魔力》中,我介绍了其中的一部分。其中的一项技术是如何模糊图像,示例代码是使用RenderScript实现的,因为在Android中没有内置的可使用的简单的API。在这个系列中,我们将着眼于RenderScript模糊技术和JAVA实现模糊功能。我们还将进行一些基准测试,以了解每种方案的运行情况,并探讨获取最佳性能的可行方法。 让我们先从实现一个简单的可以运行的例子开始,使用RenderScript!对于没有使用过RenderScript的开发者来说,这是一个让人心生恐惧的设想,因为RenderScript真的是很难,是不是?是的,的确是。不过它也有一些事情真的很简单,而模糊图像就是其中之一。 对于不熟悉RenderScript的人来说,他是在API11中引入的,并且有一个compat库,提供RenderScript给API8及以后的版本。它本质上是一个面向图形的本地计算框架。RenderScript引擎在运行期会选择最合适的处理器(CPU或者GPU核心,多核处理器间可以分解原子操作)来执行请求的操作。本地语法基于C99,与OpenCL, CUDA, and GLSL的API相似。 如果这听起来很可怕,请稍等一下,因为我们正要简化整个过程。因为它是一个框架,允许我们创建自定义的内核来实现过滤与处理,它内置了不少可以使用的内核,其中的一个允许我们模糊图像。 后面的实例代码基于API17及更高版本开发,我已经决定不使用compat库,因为在本文写作的时候,Android Studio还不支持。此外,我们使用的模糊核心在API17后才引入,所以有最小SDK版本为17的需求。 让我们深入到一个简单的例子,这里有一个非常简单的RelativeLayout,包含了一个ImageView,上层还有个TextView:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout
  3. xmlns:android="http://schemas.android.com/apk/res/android"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:gravity="center"
  7. android:orientation="vertical" >

  8. <ImageView
  9. android:id="@+id/image"
  10. android:src="@drawable/broadstairs"
  11. android:layout_width="match_parent"
  12. android:layout_height="match_parent"
  13. android:scaleType="matrix"
  14. android:layout_centerInParent="true"/>

  15. <TextView
  16. android:id="@+id/text"
  17. android:layout_width="wrap_content"
  18. android:layout_height="wrap_content"
  19. android:text="@string/hello"
  20. android:layout_centerHorizontal="true"
  21. android:textColor="<a href="http://www.jobbole.com/members/android/" rel="nofollow">@android</a>:color/white"
  22. android:layout_marginTop="300dp"
  23. android:textStyle="bold"
  24. android:textSize="48sp"/>
  25. </RelativeLayout>
复制代码


我们想要做的效果是模糊ImageView内位于TextView显示区域的图像,有效的模糊ImageView后的区域。我们最终使用的技术是拿到位于TextView区域的图像副本进行模糊,然后再将模糊后的副本设定为TextView的背景。 我刻意的设计了这种布局,使图像显示实际大小,并从显示屏的左上方开始。这样让随后的位置计算简单些,而且这里讨论的是模糊技术而不是图像定位的数学算法。尝试设定ImageView的属性android:scaleType=”center”,就会发现定位出现错乱。 此处有一个方法,它有3个参数,一个位图(已在ImageView里获得),一个视图(这是我们的TextView,但是该技术适用于任意的视图类型,所以我们将在方法中进行支持),以及一个半径用来控制模糊的程度。
  1. private void blur(Bitmap bkg, View view, float radius) {
  2. Bitmap overlay = Bitmap.createBitmap(
  3. view.getMeasuredWidth(),
  4. view.getMeasuredHeight(),
  5. Bitmap.Config.ARGB_8888);
  6. Canvas canvas = new Canvas(overlay);
  7. canvas.drawBitmap(bkg, -view.getLeft(),
  8. -view.getTop(), null);
  9. RenderScript rs = RenderScript.create(this);
  10. Allocation overlayAlloc = Allocation.createFromBitmap(
  11. rs, overlay);
  12. ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(
  13. rs, overlayAlloc.getElement());
  14. blur.setInput(overlayAlloc);
  15. blur.setRadius(radius);
  16. blur.forEach(overlayAlloc);
  17. overlayAlloc.copyTo(overlay);
  18. view.setBackground(new BitmapDrawable(
  19. getResources(), overlay));
  20. rs.destroy();
  21. }
复制代码
我们要做的第一件事是创建一个新的位图对象,用来保存想要模糊的图像区域的副本。它的大小将会是我们视图的尺寸(2-5行)。 现在我们将空的位图包装到画布中,我们可以在上面绘图(7行)。 接下来的步骤我称为”缓存剪切”,我们拷贝位图中的处于视图显示区域内的那部分图像(9-10行)。 接下来我们必须做的事情是构建一个RenderScript对象上下文,在其中我们执行模糊操作(12行)。 RenderScript是一个本地环境,它在自己的内存空间内运行,所以我们不能简单的传递位图引用,我们需要在JAVA和RenderScript内存区域进行序列化。这使用了一个分配实例来完成,这是在RenderScript内存区域中创建和引用对象的方式,为我们的位图创建一个分配会将位图的内容拷贝的分配区域中(14-15行)。 现在我们创建一个 ScriptIntrinsicBlur实例,它创建合适的RenderScript模糊脚本,并且是脚本对象的Java接口,它让我们能够控制它。(17-18行)。 脚本的输入定义了我们需要执行模糊的位图源(20行)。 现在我们设置半径来控制模糊强度(22行)。 forEach方法执行模糊操作,参数代表了结果的输出位置。我们将它写回源分配地址以减少创建的对象数量(24行)。 现在模糊完成了,但是我们必须把模糊后的图像拷贝回JAVA内存区域(26行)。 最后将模糊后的位图包装到BitmapDrawable内,并设置为视图的背景(28-29行)。 我们已经使用完RenderScript上下文,清理掉。这样做也将自动删除我们的分配(31行)。 这就是我们完整的基本模糊逻辑,但目前还不清楚什么地方和什么时候我们需要调用此方法。不幸的是关于这个问题不能简单的进行回答,所以我们将在接下来的文章中讨论。 平时我喜欢每篇文章都发布相关工作代码,但是在本例中,我们还没达到有一个可运行的完整的端到端的示例的程度。我保证在接下来的文章中进行调整。 via: 原文链接: stylingandroid 翻译: 伯乐在线 - fhdis
译文链接: http://blog.jobbole.com/65353/




我们介绍了使用RenderScript使另一个视图范围内的图片部分模糊。但是实际上,我们并没有深入地调用这个方法来研究图像模糊行为。原因是我们需要在性能方面进行仔细考虑,这篇文章我们会进行更进一步地的探索。

调用这个方法最直白的方法是父布局的onDraw()。有经验的开发者读到这里可能会开始摇头,我们应该保持onDraw方法的实现尽可能有效。以前的文章中的代码包括创建对象、位图操作和切换到renderScript上下文。其中,OnDraw会降低帧速率。你可以不相信我的做法,但是可以通过测量并证明它是有效的。在后面的系列中,我们就会这样做。 如果布局是静态的(即我们的布局不包含任何动画),在布局时就不会改变待模糊的位置和范围。只有在布局改变时执行该操作才有意义,但前提是布局的所有视图大小和位置根据布局变化测量和计算过。这里有一个非常实用的技巧,可以注册一个OnGlobalLayoutListener监听函数。当布局发生改变的时候会调用onGlobalLayout()方法。当我们收到布局已经改变的通知时,注册的OnPreDrawListener监听函数的onPreDraw()方法会被调用每当执行onDraw方法。我们要做的第一件事情就是取消注册onPreDraw()方法,这样只有在布局改变的时候才会被调用,而不是每次onDraw方法触发时都调用。下面可以执行模糊方法,从这个方法的返回值很重要,使用它可以让我们放弃onDraw操作,重复之前的布局。这对在回调函数中修改布局非常有帮助,但是这里不需要这么做。所以返回true,继续绘制。 我们的Activity代码如下:
  1. public class MainActivity extends Activity {
  2. private ImageView mImage;
  3. private TextView mText;

  4. private OnPreDrawListener mPreDrawListener =
  5. new OnPreDrawListener() {

  6. @Override
  7. public boolean onPreDraw() {
  8. ViewTreeObserver observer =mText.getViewTreeObserver();
  9. if(observer != null) {
  10. observer.removeOnPreDrawListener(this);
  11. }
  12. Drawable drawable = mImage.getDrawable();
  13. if (drawable != null &&
  14. drawable instanceof BitmapDrawable) {
  15. Bitmap bitmap =
  16. ((BitmapDrawable) drawable).getBitmap();
  17. if (bitmap != null) {
  18. blur(bitmap, mText, 25);
  19. }
  20. }
  21. return true;
  22. }
  23. };

  24. private OnGlobalLayoutListener mLayoutListener =
  25. new OnGlobalLayoutListener() {

  26. @Override
  27. public void onGlobalLayout() {
  28. ViewTreeObserver observer = mText.getViewTreeObserver();
  29. if(observer != null) {
  30. observer.addOnPreDrawListener(
  31. mPreDrawListener);
  32. }
  33. }
  34. };

  35. /**
  36. * Called when the activity is first created.
  37. */
  38. @Override
  39. public void onCreate(Bundle savedInstanceState) {
  40. super.onCreate(savedInstanceState);
  41. setContentView(R.layout.main);
  42. mImage = (ImageView) findViewById(R.id.image);
  43. mText = (TextView)findViewById(R.id.text);
  44. if (mImage != null && mText != null) {
  45. ViewTreeObserver observer =
  46. mText.getViewTreeObserver();
  47. if (observer != null) {
  48. observer.addOnGlobalLayoutListener(
  49. mLayoutListener);
  50. }
  51. }
  52. }

  53. private void blur(Bitmap bkg, View view, float radius) {
  54. ....
  55. }
  56. }
复制代码 在父布局覆盖onDraw方法的优点是,可以在布局层次上任意附加OnPreDrawListener方法。因此需要自定义一个布局,这个布局是标准布局的子类,这样就可以覆盖onDraw方法。使用predrawlistener意味着可以非常容易在任意布局中添加。 最后,模糊图片实现如下:
在下一篇文章中,我会更深入地回答为什么要避免在onDraw中执行模糊操作,并且还会介绍有用的性能测量工具。 这篇文章的代码在这里
via: 原文链接: stylingandroid 翻译: 伯乐在线- fhdis 译文链接: http://blog.jobbole.com/65353/

更多相关文章

  1. Android获取三轴加速度和view的重绘
  2. android应酬资料
  3. 自定义UI框架
  4. Android自动更新:这里的更新静悄悄~
  5. Android原生与H5交互的实现
  6. Android学习系列(22)--App主界面比较
  7. Android(安卓)Studio代码混淆小结
  8. 深入浅析Android坐标系统
  9. Android调试方法大全

随机推荐

  1. android build kernel make menuconfig及
  2. android View.GONE 失效原因
  3. android 开发奇葩问题
  4. Android(安卓)串口数据处理
  5. Android(安卓)View之对现有控件进行拓展
  6. android 软键盘处理
  7. 自定义android循环拖动组件
  8. Android(安卓)以太网调用流程
  9. android显示和隐藏键盘
  10. android > Visualizer监听手机声波