某天某时某刻,脑内突然发现一个疑问:RippleDrawable
是怎么把波纹绘制到所在 View
外面的?
稍微了解点 Android
绘制知识的就知道,子 View
的 onDraw(canvas)
获取到的画布默认是被父亲裁剪掉的,导致子 View
无法绘制到自身外面
那么问题就来了,为毛 RippleDrawable
可以绘制到外面,用了什么原理?莫非有特权?
带着问题先看了下 RippleDrawable
的源码,恩!很好,完全没看出啥东西
(╯°□°)╯︵┻━┻ 。。。
网上搜了一波,恩!nice,没找到答案
(╯°□°)╯︵┻━┻
正当我漏气时,突然想到会不会是硬件加速搞的鬼?写了一个简单的 _demo_,关掉硬件加速的时候,波纹就画不出去了,启用硬件加速的时候,就画出去了
沿着这个思路重新看了下 RippleDrawable
的源码,发现一个没看懂的 override
函数,而且还是 @hide
的:
1 | /** |
这是啥?网上搜了一波这函数,终于在老罗这篇文章中搜到了答案:
… 本来DisplayListRenderer类的成员函数addRenderNodeOp执行到这里,就已经完成任务了。但是在Android 5.0中,增加了一个新的API——RippleDrawable。RippleDrawable有一个属性,当它没有包含任何的Layer时,它将被投影到当前视图的设置有Background的最近的一个父视图的Background去。这一点可以参考官方文档
为了达到上述目的,每一个Render Node都具有三个属性:Projection Receive Index、Projection Receiver和Projection Backwards。其中,Projection Receive Index是一个整型变量,而Projection Receiver和Projection Backwards是两个布尔变量。注意,在一个应用程序窗口的视图结构中,每一个View及其设置的Background都对应一个Render Node。上述三个属性构成了Render Node里面的一个Projection Nodes的概念,如图3所示 …
摘自老罗的文章
如果你想详细了解硬件加速的原理的话,看老罗的文章,我下面就对 RippleDrawable
做一下简单的解释
硬件加速的情况下,Android 5.0
以后的应用程序 UI 绘制是分为两步的
第一步构建 DisplayList
,里面记录了 View
的绘制命令集合,发生在主进程 MainThread
中
第二步是渲染 DisplayList
,把这些绘制命令转为 Open GL 的命令,然后交给 GPU 执行
Android
中的 View
都被抽象成一个 RenderNode
(一个 RenderNode
包含了自己和儿子的DisplayList
,除了TextureView
和软件渲染的子视图不包含DisplayList
) ,如果这个 View
有背景的话,也会被抽象成一个 RenderNode
也就是说,硬件加速最后绘制的东西全部都存在 DisplayList
中,那么就来看看 View
是怎么更新背景的 DisplayList
的
先看 View
的 drawBackground(Canvas)
方法
1 | /** |
上面的代码是 View
被调用 draw(Canvas)
的时候,绘制背景的函数,如果启用了硬件加速,背景被转换成一个 RenderNode
,然后被绘制到 Canvas
上
看一下 DisplayListCanvas
内部的 drawRenderNode
函数
1 | /** |
调用了 native 方法,需要查看 framework 方法才看得到了,查找了一波后,找到了实现的地方
1 | void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) { |
谢天谢地的终于找到了这个函数的用处 isProjectionReceiver()
,让我稍微讲解下吧
drawRenderNode()
函数是把 RenderNode
封装成一个 DrawRenderNodeOp
然后丢给 addRenderNodeOp()
函数
RenderNode
包含了绘制命令
addRenderNodeOp()
中首先把 op 加入到了 Canvas
的 mDisplayList 中,opIndex 就是当前的 op 在 mDisplayList 中的位置。
然后在最后,那个 if
判断,判断当前的 RenderNode
是否是isProjectionReceiver
的,如果是的,那么把当前 op 的 index 赋值给 mDisplayList->projectReceiveIndex 这有什么用呢?这样 mDisplayList 不就知道了我那个需要特殊待遇的 RenderNode
是谁了吗?
前面说了 Backgound
自成一个 RenderNode
,所以 RippleDrawable
自成一个 RenderNode
,既然 DisplayList
都知道了这个特殊的 RenderNode
,那么绘制的时候优先绘制这个 RenderNode
,就可以画到外面了
硬件加速涉及的东西有点多,建议还是看下老罗的文章,虽然长篇大论,不过源码分析透彻