某天某時(shí)某刻婉徘,腦內(nèi)突然發(fā)現(xiàn)一個(gè)疑問(wèn):RippleDrawable
是怎么把波紋繪制到所在 View
外面的绕娘?
稍微了解點(diǎn) Android
繪制知識(shí)的就知道委可,子 View
的 onDraw(canvas)
獲取到的畫(huà)布默認(rèn)是被父親裁剪掉的映皆,導(dǎo)致子 View
無(wú)法繪制到自身外面
那么問(wèn)題就來(lái)了访敌,為毛 RippleDrawable
可以繪制到外面凉敲,用了什么原理?莫非有特權(quán)寺旺?
帶著問(wèn)題先看了下 RippleDrawable
的源碼爷抓,恩!很好阻塑,完全沒(méi)看出啥東西
(╯°□°)╯︵┻━┻ 蓝撇。。陈莽。
網(wǎng)上搜了一波渤昌,恩!nice走搁,沒(méi)找到答案
(╯°□°)╯︵┻━┻
正當(dāng)我漏氣時(shí)独柑,突然想到會(huì)不會(huì)是硬件加速搞的鬼?寫了一個(gè)簡(jiǎn)單的 demo私植,關(guān)掉硬件加速的時(shí)候忌栅,波紋就畫(huà)不出去了,啟用硬件加速的時(shí)候曲稼,就畫(huà)出去了
沿著這個(gè)思路重新看了下 RippleDrawable
的源碼窍箍,發(fā)現(xiàn)一個(gè)沒(méi)看懂的 override
函數(shù)向臀,而且還是 @hide
的:
/**
* @hide
*/
@Override
public boolean isProjected() {
// If the layer is bounded, then we don't need to project.
if (isBounded()) {
return false;
}
...
return true;
}
這是啥?網(wǎng)上搜了一波這函數(shù),終于在老羅這篇文章中搜到了答案:
... 本來(lái)DisplayListRenderer類的成員函數(shù)addRenderNodeOp執(zhí)行到這里捌刮,就已經(jīng)完成任務(wù)了。但是在Android 5.0中玩徊,增加了一個(gè)新的API——RippleDrawable谍失。RippleDrawable有一個(gè)屬性,當(dāng)它沒(méi)有包含任何的Layer時(shí)嫡丙,它將被投影到當(dāng)前視圖的設(shè)置有Background的最近的一個(gè)父視圖的Background去拴袭。這一點(diǎn)可以參考官方文檔
為了達(dá)到上述目的,每一個(gè)Render Node都具有三個(gè)屬性:Projection Receive Index曙博、Projection Receiver和Projection Backwards拥刻。其中,Projection Receive Index是一個(gè)整型變量父泳,而Projection Receiver和Projection Backwards是兩個(gè)布爾變量般哼。注意吴汪,在一個(gè)應(yīng)用程序窗口的視圖結(jié)構(gòu)中,每一個(gè)View及其設(shè)置的Background都對(duì)應(yīng)一個(gè)Render Node蒸眠。上述三個(gè)屬性構(gòu)成了Render Node里面的一個(gè)Projection Nodes的概念漾橙,如圖3所示 ...
摘自老羅的文章
如果你想詳細(xì)了解硬件加速的原理的話,看老羅的文章楞卡,我下面就對(duì) RippleDrawable
做一下簡(jiǎn)單的解釋
硬件加速的情況下霜运,Android 5.0
以后的應(yīng)用程序 UI 繪制是分為兩步的
第一步構(gòu)建 DisplayList
,里面記錄了 View
的繪制命令集合蒋腮,發(fā)生在主進(jìn)程 MainThread
中
第二步是渲染 DisplayList
淘捡,把這些繪制命令轉(zhuǎn)為 Open GL 的命令,然后交給 GPU 執(zhí)行
Android
中的 View
都被抽象成一個(gè) RenderNode
(一個(gè) RenderNode
包含了自己和兒子的DisplayList
池摧,除了TextureView
和軟件渲染的子視圖不包含DisplayList
) 焦除,如果這個(gè) View
有背景的話,也會(huì)被抽象成一個(gè) RenderNode
也就是說(shuō)险绘,硬件加速最后繪制的東西全部都存在 DisplayList
中踢京,那么就來(lái)看看 View
是怎么更新背景的 DisplayList
的
先看 View
的 drawBackground(Canvas)
方法
/**
* Draws the background onto the specified canvas.
*
* @param canvas Canvas on which to draw the background
*/
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mHardwareRenderer != null) {
// 獲取一個(gè)背景的 render node
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
// ...
}
上面的代碼是 View
被調(diào)用 draw(Canvas)
的時(shí)候,繪制背景的函數(shù)宦棺,如果啟用了硬件加速瓣距,背景被轉(zhuǎn)換成一個(gè) RenderNode
,然后被繪制到 Canvas
上
看一下 DisplayListCanvas
內(nèi)部的 drawRenderNode
函數(shù)
/**
* Draws the specified display list onto this canvas. The display list can only
* be drawn if {@link android.view.RenderNode#isValid()} returns true.
*
* @param renderNode The RenderNode to draw.
*/
public void drawRenderNode(RenderNode renderNode) {
nDrawRenderNode(mNativeCanvasWrapper, renderNode.getNativeDisplayList());
}
調(diào)用了 native 方法代咸,需要查看 framework 方法才看得到了蹈丸,查找了一波后,找到了實(shí)現(xiàn)的地方
void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) {
LOG_ALWAYS_FATAL_IF(!renderNode, "missing rendernode");
DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(
renderNode,
*mState.currentTransform(),
mState.clipIsSimple());
addRenderNodeOp(op);
}
size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
int opIndex = addDrawOp(op);
#if !HWUI_NEW_OPS
int childIndex = mDisplayList->addChild(op);
// update the chunk's child indices
DisplayList::Chunk& chunk = mDisplayList->chunks.back();
chunk.endChildIndex = childIndex + 1;
if (op->renderNode->stagingProperties().isProjectionReceiver()) {
// use staging property, since recording on UI thread
mDisplayList->projectionReceiveIndex = opIndex;
}
#endif
return opIndex;
}
謝天謝地的終于找到了這個(gè)函數(shù)的用處 isProjectionReceiver()
呐芥,讓我稍微講解下吧
drawRenderNode()
函數(shù)是把 RenderNode
封裝成一個(gè) DrawRenderNodeOp
然后丟給 addRenderNodeOp()
函數(shù)
RenderNode
包含了繪制命令
addRenderNodeOp()
中首先把 op 加入到了 Canvas
的 mDisplayList 中逻杖,opIndex 就是當(dāng)前的 op 在 mDisplayList 中的位置。
然后在最后思瘟,那個(gè) if
判斷荸百,判斷當(dāng)前的 RenderNode
是否是isProjectionReceiver
的,如果是的滨攻,那么把當(dāng)前 op 的 index 賦值給 mDisplayList->projectReceiveIndex 這有什么用呢够话?這樣 mDisplayList 不就知道了我那個(gè)需要特殊待遇的 RenderNode
是誰(shuí)了嗎?
前面說(shuō)了 Backgound
自成一個(gè) RenderNode
光绕,所以 RippleDrawable
自成一個(gè) RenderNode
女嘲,既然 DisplayList
都知道了這個(gè)特殊的 RenderNode
,那么繪制的時(shí)候優(yōu)先繪制這個(gè) RenderNode
诞帐,就可以畫(huà)到外面了
硬件加速涉及的東西有點(diǎn)多欣尼,建議還是看下老羅的文章,雖然長(zhǎng)篇大論停蕉,不過(guò)源碼分析透徹