探索RippleDrawable作為背景是如何繪制到View外的

某天某時(shí)某刻婉徘,腦內(nèi)突然發(fā)現(xiàn)一個(gè)疑問(wèn):RippleDrawable 是怎么把波紋繪制到所在 View 外面的绕娘?

稍微了解點(diǎn) Android 繪制知識(shí)的就知道委可,子 ViewonDraw(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

先看 ViewdrawBackground(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 加入到了 CanvasmDisplayList 中逻杖,opIndex 就是當(dāng)前的 opmDisplayList 中的位置。

然后在最后思瘟,那個(gè) if 判斷荸百,判斷當(dāng)前的 RenderNode 是否是isProjectionReceiver 的,如果是的滨攻,那么把當(dāng)前 opindex 賦值給 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ò)源碼分析透徹

原文鏈接

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末愕鼓,一起剝皮案震驚了整個(gè)濱河市钙态,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拒啰,老刑警劉巖驯绎,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異谋旦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)屈尼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門册着,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人脾歧,你說(shuō)我怎么就攤上這事甲捏。” “怎么了鞭执?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵司顿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我兄纺,道長(zhǎng)大溜,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任估脆,我火速辦了婚禮钦奋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疙赠。我一直安慰自己付材,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布圃阳。 她就那樣靜靜地躺著厌衔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪捍岳。 梳的紋絲不亂的頭發(fā)上富寿,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音祟同,去河邊找鬼作喘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛晕城,可吹牛的內(nèi)容都是我干的泞坦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼砖顷,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贰锁!你這毒婦竟也來(lái)了赃梧?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤豌熄,失蹤者是張志新(化名)和其女友劉穎授嘀,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體锣险,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蹄皱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了芯肤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片巷折。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖崖咨,靈堂內(nèi)的尸體忽然破棺而出锻拘,到底是詐尸還是另有隱情,我是刑警寧澤击蹲,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布署拟,位于F島的核電站,受9級(jí)特大地震影響歌豺,放射性物質(zhì)發(fā)生泄漏推穷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一世曾、第九天 我趴在偏房一處隱蔽的房頂上張望缨恒。 院中可真熱鬧,春花似錦轮听、人聲如沸骗露。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)萧锉。三九已至,卻和暖如春述寡,著一層夾襖步出監(jiān)牢的瞬間柿隙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工鲫凶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禀崖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓螟炫,卻偏偏與公主長(zhǎng)得像波附,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容