視圖樹形結(jié)構(gòu)皇钞,每一個節(jié)點均有Java層DisplayListCanvas和底層DisplayListCanvas冠息。在Java層则拷,調(diào)用Canvas#drawXxx方法江场,如drawPoint,drawPath嘲碱,drawRect金砍,DisplayListCanvas或Canvas中有JNI#方法,根據(jù)mNativeCanvasWrapper指針獲取底層DisplayListCanvas麦锯。底層DisplayListCanvas繼承底層Canvas恕稠。頭文件定義在/frameworks/base/libs/hwui/Canvas.h。
此外扶欣,除了繪制方法drawXxx鹅巍,還有變換方法,如translate料祠,scale等骆捧,以及save和restore方法,下面會通過一個繪圖實例具體分析這些方法實現(xiàn)的操作髓绽。
前面已經(jīng)介紹過凑懂,從頂層視圖DecorView#updateDisplayListIfDirty方法開始繪制樹形視圖結(jié)構(gòu),為了分析簡單梧宫,截取一小段樹分支(虛線框內(nèi))接谨,通過下面視圖結(jié)構(gòu),分析繪制時具體操作塘匣。
容器視圖繪制
虛線框的樹形結(jié)構(gòu)截取如圖。父視圖0是LinearLayout忌卤,三個子視圖分別是自定義CanvasView和兩個TextView扫夜。
當遍歷到達LinearLayout節(jié)點#updateDisplayListIfDirty方法時,再看一下此方法代碼驰徊,如下笤闯。
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
// ThreadedRenderer是空搬味,直接返回節(jié)點
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.isValid()//false燃领,還未記錄繪制
|| (mRecreateDisplayList)) {//重建Canvas
...//省略掉不需要重建Canvas的部分代碼
//第一次進來肯定需要建立Canvas,renderNode也還未記錄书蚪。
mRecreateDisplayList = true;//重建Canvas
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
//創(chuàng)建DisplayListCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
//判斷LayerType牺弹,以及獲取HardwareLayer的部分代碼省略掉浦马,LinearLayout不需要。
try {
// 一般視圖走硬件渲染都執(zhí)行下面程序
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
//LinearLayout視圖會跳過繪制张漂,則直接派發(fā)給子視圖
dispatchDraw(canvas);
} else {
draw(canvas);//繪制晶默,包括繪制自身,修飾航攒,以及派發(fā)磺陡,共六個步驟,此處不執(zhí)行漠畜。
}
} finally {
renderNode.end(canvas);//繪制結(jié)束币他,保存canvas記錄內(nèi)容
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
LinearLayout視圖創(chuàng)建畫布DisplayListCanvas后,因有PFLAG_SKIP_DRAW標志盆驹,選擇跳過繪制圆丹,它沒有設置Background,在View框架源碼中會設置此標志躯喇。跳過繪制辫封,即不會走LinearLayout的onDraw方法,另外四個步驟都不會觸發(fā)廉丽,僅僅觸發(fā)父類ViewGroup的dispatchDraw方法倦微。繪制直接向子視圖派發(fā)。
ViewGroup#dispatchDraw方法正压。
@Override
protected void dispatchDraw(Canvas canvas) {
...
boolean more = false;
final long drawingTime = getDrawingTime();
//LinearLayout的畫布寫入Reorder柵欄
if (usingRenderNodeProperties) canvas.insertReorderBarrier();
...
//繪制子視圖
for (int i = 0; i < childrenCount; i++) {
...
int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
//LinearLayout的畫布寫入Inorder柵欄
if (usingRenderNodeProperties) canvas.insertInorderBarrier();
...
}
//drawChild方法
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
dispatchDraw源碼比較多欣福,主要功能是派發(fā)子視圖繪制。觸發(fā)每個子視圖三個參數(shù)的draw重載方法焦履。在遍歷子視圖前后拓劝,有兩個方法雏逾,LinearLayout畫布的insertReorderBarrier和insertInorderBarrier方法。
他們在觸發(fā)底層DisplayListCanvas的方法一樣郑临,只是enableReorder標志不同栖博,insertReorderBarrier支持重排。
void DisplayListCanvas::insertReorderBarrier(bool enableReorder) {
flushRestoreToCount();
flushTranslate();
mDeferredBarrierType = enableReorder ? kBarrier_OutOfOrder : kBarrier_InOrder;
}
柵欄設置成kBarrier_OutOfOrder或kBarrier_InOrder類型厢洞,kBarrier_OutOfOrder用于標記Chunk的一個變量值reorderChildren仇让。DisplayListCanvas的prepareDirty方法初始化值默認是kBarrier_InOrder,這個值后續(xù)addOpAndUpdateChunk方法會用到躺翻,在繪制子視圖之前丧叽,已經(jīng)創(chuàng)建過第一個Chunk。
在繪制子視圖前公你,插入一個kBarrier_OutOfOrder柵欄踊淳,LinearLayout畫布drawXxx方法繪制會創(chuàng)建一個新Chunk,該Chunk索引LinearLayout畫布繪制的子視圖節(jié)點省店。繪制子視圖結(jié)束后嚣崭,再次插入一個kBarrier_InOrder柵欄,再次新建一個Chunk塊懦傍,索引LinearLayout畫布后續(xù)繪制的內(nèi)容雹舀。
LinearLayout子視圖是葉子節(jié)點,子視圖繪制粗俱,三個參數(shù)的draw重載方法说榆。
View#draw方法。
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
boolean drawingWithRenderNode = mAttachInfo != null
&& mAttachInfo.mHardwareAccelerated
&& hardwareAcceleratedCanvas;
boolean more = false;
...
RenderNode renderNode = null;
Bitmap cache = null;
...
//硬件渲染
if (drawingWithRenderNode) {
//在這里觸發(fā)子視圖渲染方法寸认。
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
renderNode = null;
drawingWithRenderNode = false;
}
}
...
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//父視圖畫布繪制子視圖RenderNode節(jié)點
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
...
}
} else if (cache != null) {
...
}
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
..
mRecreateDisplayList = false;
return more;
}
draw方法很長签财,這里指摘取硬件渲染相關的。關注兩個點偏塞。
1:子視圖#updateDisplayListIfDirty方法唱蒸,重建Canvas,傳遞給一個參數(shù)的draw重載方法灸叼,繪制神汹,返回子視圖RenderNode節(jié)點,該節(jié)點相關畫布已經(jīng)完成繪制內(nèi)容記錄古今。
2:LinearLayout畫布drawRenderNode繪制子視圖RenderNode節(jié)點屁魏。
void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) {
DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(
renderNode,
*mState.currentTransform(),
mState.clipIsSimple());
addRenderNodeOp(op);
}
創(chuàng)建一個DrawRenderNodeOp,DrawRenderNodeOp繼承DrawBoundedOp捉腥,DrawBoundedOp繼承DrawOp氓拼,基類是DisplayListOp。
size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
//增加一個繪制操作到mDisplayListData的displayListOps數(shù)組。
int opIndex = addDrawOp(op);
//mDisplayListData是LinearLayout底層存儲繪制數(shù)據(jù)的對象
//增加操作到DisplayListData的mChildren數(shù)組桃漾,代表子視圖
int childIndex = mDisplayListData->addChild(op);
//寫入了一個繪制子節(jié)點的操作坏匪,告訴Chunk的endChildIndex自增。
DisplayListData::Chunk& chunk = mDisplayListData->chunks.editTop();
chunk.endChildIndex = childIndex + 1;
if (op->renderNode()->stagingProperties().isProjectionReceiver()) {
mDisplayListData->projectionReceiveIndex = opIndex;
}
return opIndex;
}
addDrawOp方法增加一個繪制Op呈队,后面會詳細介紹剥槐,總之,將DrawRenderNodeOp加入到DisplayListData的displayListOps數(shù)組中宪摧。
addChild方法將操作加入到DisplayListData的mChildren數(shù)組中。
注意颅崩,因前期插入一個kBarrier_OutOfOrder柵欄几于,因此在addDrawOp觸發(fā)的addOpAndUpdateChunk中,會創(chuàng)建一個新Chunk塊沿后,寫入DrawRenderNodeOp后沿彭,更新該Chunk中指向mChildren數(shù)組的endChildIndex 索引。
三個葉子RenderNode節(jié)點操作寫入LinearLayout底層數(shù)據(jù)數(shù)組中尖滚,他們在一個Chunk塊中喉刘。子視圖繪制完畢,再次插入一個kBarrier_InOrder柵欄漆弄,創(chuàng)建一個新Chunk睦裳。
葉子節(jié)點視圖繪制
葉子節(jié)點繪制主要關注onDraw方法,CanvasView的onDraw方法撼唾,繪制三個矩形區(qū)域廉邑。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.parseColor("#ffffcc")); //畫布顏色
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);//設置非填充風格
Rect rect1 = new Rect(20, 20, 180, 180);
canvas.drawRect(rect1, paint);//繪制一個正方形
/************************第一層*****************************/
canvas.save();//當前狀態(tài)保存
/***********************第二層*****************************/
canvas.translate(180, 180);
Rect rect2 = new Rect(20, 20, 180, 180);
canvas.drawRect(rect2, paint);//再次繪制一個正方形
/************************第二層***************************/
canvas.restore();//恢復到第一層狀態(tài)
/************************第一層**************************/
Rect rect3 = new Rect(20, 200, 180, 360);
canvas.drawRect(rect3, paint);//再次繪制一個正方形
}
四個Canvas方法
drawRect,繪制一個矩形區(qū)域倒谷。
save蛛蒙,保存當前圖層狀態(tài)。
restore渤愁,恢復上一個圖層狀態(tài)牵祟。
translate,兩個方向x和y抖格,偏移一定距離诺苹。
先繪制一個矩形,然后保存狀態(tài)他挎,進行偏移筝尾,繪制第二個矩形,第二個矩形受到偏移影響办桨,恢復狀態(tài)筹淫,繪制第三個矩形,第三個矩形不受偏移影響。
詳細分析
執(zhí)行順序
drawRect(1)
save
translate
drawRect(2)
restore
drawRect(3)
1损姜,drawRect:觸發(fā)底層DisplayListCanvas的drawRect方法饰剥。該方法是
在DisplayListCanvas的頭文件中定義虛函數(shù),在Canvas的頭文件中也有定義摧阅。
底層 DisplayListCanvas#drawRect方法汰蓉。
void DisplayListCanvas::drawRect(float left, float top, float right, float bottom,
const SkPaint& paint) {
addDrawOp(new (alloc()) DrawRectOp(left, top, right, bottom, refPaint(&paint)));
}
addDrawOp增加一個繪制Op,DrawRectOp類型棒卷,不同的繪制Op不同顾孽,如其drawPoints繪制增加的Op是DrawPointsOp類型,總之比规,他們都繼承DrawOp類若厚。
addDrawOp增加一個繪制操作DrawOp。DrawOp繼承DisplayListOp類蜒什。
底層DisplayListCanvas#addDrawOp方法测秸。
size_t DisplayListCanvas::addDrawOp(DrawOp* op) {
Rect localBounds;
...
mDisplayListData->hasDrawOps = true;
return flushAndAddOp(op);
}
觸發(fā)flushAndAddOp方法。
2灾常,save:觸發(fā)底層DisplayListCanvas的save方法霎冯。注意:save和restore一般是成對出現(xiàn)的。
底層DisplayListCanvas#save方法钞瀑。
int DisplayListCanvas::save(SkCanvas::SaveFlags flags) {
addStateOp(new (alloc()) SaveOp((int) flags));
return mState.save((int) flags);
}
addStateOp增加狀態(tài)操作StateOp沈撞,前面的是增加DrawOp,而現(xiàn)在是StateOp
和DrawOp一樣仔戈,狀態(tài)StateOp也繼承DisplayListOp類关串。
底層DisplayListCanvas#addStateOp方法。
size_t DisplayListCanvas::addStateOp(StateOp* op) {
return flushAndAddOp(op);
}
觸發(fā)flushAndAddOp方法监徘。
除了flushAndAddOp保存晋修,還會觸發(fā)CanvasState保存。
總結(jié):
DisplayListCanvas的save方法凰盔,addStateOp增加一個SaveOp墓卦,addStateOp方法和addDrawOp類似,都觸發(fā)flushAndAddOp方法户敬,入?yún)⑹峭粋€基類DisplayListOp落剪。
CanvasState#save方法。
int CanvasState::save(int flags) {
return saveSnapshot(flags);
}
int CanvasState::saveSnapshot(int flags) {
mSnapshot = new Snapshot(mSnapshot, flags);
return mSaveCount++;
}
在CanvasState類save操作中尿庐,創(chuàng)建一個Snapshot對象忠怖,封裝當前對象,新建的Snapshot放到鏈表最前面抄瑟,mSaveCount自增凡泣,mSaveCount代表當前操作的是位于第幾層次,若上層調(diào)用了restore,則觸發(fā)CanvasState的restore方法鞋拟,回退到上一層骂维。
可以看出,每一個層次狀態(tài)的save都保存在Snapshot中贺纲。save之后的改變在新建的Snapshot保存航闺,restore后回到之前的Snapshot。
3猴誊,translate:觸發(fā)底層DisplayListCanvas的translate方法潦刃,x和y軸方向上的偏移。
底層DisplayListCanvas#translate方法
void DisplayListCanvas::translate(float dx, float dy) {
if (dx == 0.0f && dy == 0.0f) return;
mHasDeferredTranslate = true;
mTranslateX += dx;
mTranslateY += dy;
flushRestoreToCount();
mState.translate(dx, dy, 0.0f);
}
增加偏移量dx和dy稠肘。
flushRestoreToCount方法福铅,在mRestoreSaveCount>=0時才起作用,即執(zhí)行過restore或restoreToCount项阴。
寫入一個RestoreToCountOp操作。
CanvasState的translate方法笆包,當前mSnapshot的transform觸發(fā)translate偏移环揽。因前面觸發(fā)過一個save方法,因此有兩個Snapshot庵佣,當前mSnapshot是表頭第一個歉胶。
4,restore:觸發(fā)底層DisplayListCanvas的restore方法巴粪,恢復到上一個繪制狀態(tài)通今。
底層DisplayListCanvas#restore方法。
void DisplayListCanvas::restore() {
if (mRestoreSaveCount < 0) {
restoreToCount(getSaveCount() - 1);
return;
}
mRestoreSaveCount--;
flushTranslate();
mState.restore();
}
若mRestoreSaveCount已經(jīng)>=0肛根,則直接自減辫塌。每次restore只能回退一層。同時CanvasState回退派哲,內(nèi)部mSaveCount保存層級臼氨。
若mRestoreSaveCount小于0,即有可能是初始值-1芭届,則觸發(fā)restoreToCount方法储矩,getSaveCount獲取當前層級。若執(zhí)行過一個save褂乍,restore時持隧,getSaveCount得到2,那么restoreToCount恢復的值就是1逃片,mRestoreSaveCount設置成1屡拨。
總結(jié):restore回退一層。
上層直接調(diào)用restoreToCount方法時,直接回退saveCount層洁仗,這個方法中會設置mRestoreSaveCount值层皱。
底層DisplayListCanvas#restoreToCount方法。
void DisplayListCanvas::restoreToCount(int saveCount) {
mRestoreSaveCount = saveCount;
flushTranslate();
mState.restoreToCount(saveCount);
}
save和restore成對出現(xiàn)赠潦,多個save后叫胖,第一次restore時,mRestoreSaveCount將會是初始值-1她奥,restoreToCount方法會第一次設置其值瓮增,設置成restore回退后的層級。
即mRestoreSaveCount存儲當前層級哩俭。
例如绷跑,3個save后,第一次restore后凡资,則mRestoreSaveCount變?yōu)?砸捏。
總結(jié):
在上層一次save,將當前的狀態(tài)保存下來隙赁,包括偏移垦藏,縮放等狀態(tài),后續(xù)的改變?nèi)匀辉诖嘶A上伞访,偏移掂骏,繪制等,若執(zhí)行restore厚掷,則回到上一個save保存的狀態(tài)中弟灼,中間的改變忽略掉。
1:drawRect冒黑,繪制正方形田绑,第一層,寫入DrawRectOp薛闪,mRestoreSaveCount是-1辛馆,flushRestoreCount無作用,無偏移豁延,flushTranslate無作用昙篙。
addOpAndUpdateChunk寫入繪制。
2:save诱咏,保存當前層級狀態(tài)苔可,mRestoreSaveCount是-1,flushRestoreCount無作用袋狞,無偏移焚辅,flushTranslate無作用映屋。
addOpAndUpdateChunk寫入狀態(tài)。
3:translate同蜻,偏移棚点,第二層,mRestoreSaveCount是-1湾蔓,flushRestoreCount無作用瘫析,設置偏移值mTranslateX與mTranslateY,當前Snapshot(第二層)偏移默责。
4:drawRect贬循,繪制正方形,第二層桃序,寫入DrawRectOp杖虾,mRestoreSaveCount是-1,flushRestoreCount無作用媒熊,有偏移奇适,
addOpAndUpdateChunk寫入TranslateOp,并恢復偏移值芦鳍。
addOpAndUpdateChunk寫入繪制滤愕。
5:restore,恢復上一個層級怜校,前面僅有一個save,因此mRestoreSaveCount是-1注竿,觸發(fā)restoreToCount方法茄茁,設置mRestoreSaveCount=1。無偏移巩割,flushTranslate無作用裙顽。
6:drawRect,繪制正方形宣谈,第一層愈犹,寫入DrawRectOp,mRestoreSaveCount是1闻丑,無偏移漩怎,flushTranslate無作用,
addOpAndUpdateChunk寫入RestoreToCountOp嗦嗡。并恢復mRestoreSaveCount為-1勋锤。
addOpAndUpdateChunk寫入繪制。
flushAndAddOp方法在上層寫入DrawOp或StateOp時觸發(fā)侥祭。
最終目的是addOpAndUpdateChunk叁执。
若當前存在mRestoreSaveCount>=0或偏移茄厘,首先寫入相應Op,即RestoreToCountOp與TranslateOp谈宛。
底層DisplayListCanvas#flushAndAddOp方法次哈。
size_t DisplayListCanvas::flushAndAddOp(DisplayListOp* op) { //
flushRestoreToCount();
flushTranslate();
return addOpAndUpdateChunk(op);
}
三個方法。
flushRestoreToCount:寫入RestoreToCountOp吆录。
flushTranslate:寫入TranslateOp窑滞。
addOpAndUpdateChunk:執(zhí)行寫入。
DisplayListCanvas#flushRestoreToCount方法径筏。
void DisplayListCanvas::flushRestoreToCount() {
if (mRestoreSaveCount >= 0) {
addOpAndUpdateChunk(new (alloc()) RestoreToCountOp(mRestoreSaveCount));
mRestoreSaveCount = -1;
}
}
若mRestoreSaveCount>=0葛假,增加一個RestoreToCountOp操作。mRestoreSaveCount在剛初始化時prepareDirty滋恬,設置成-1聊训。
DisplayListCanvas#flushTranslate方法。
void DisplayListCanvas::flushTranslate() {
if (mHasDeferredTranslate) {
if (mTranslateX != 0.0f || mTranslateY != 0.0f) {
addOpAndUpdateChunk(new (alloc()) TranslateOp(mTranslateX, mTranslateY));
mTranslateX = mTranslateY = 0.0f;
}
mHasDeferredTranslate = false;
}
}
mTranslateX或mTranslateY不是0時寫入TranslateOp恢氯。
addOpAndUpdateChunk方法負責增加Op带斑,不僅包括RestoreToCountOp和TranslateOp。
DisplayListCanvas#addOpAndUpdateChunk方法勋拟。
size_t DisplayListCanvas::addOpAndUpdateChunk(DisplayListOp* op) {//增加op,更新chunk
int insertIndex = mDisplayListData->displayListOps.add(op);
if (mDeferredBarrierType != kBarrier_None) {
// op is first in new chunk
mDisplayListData->chunks.push();
DisplayListData::Chunk& newChunk = mDisplayListData->chunks.editTop();//新Chunk
newChunk.beginOpIndex = insertIndex;
newChunk.endOpIndex = insertIndex + 1;
newChunk.reorderChildren = (mDeferredBarrierType == kBarrier_OutOfOrder);
int nextChildIndex = mDisplayListData->children().size();
newChunk.beginChildIndex = newChunk.endChildIndex = nextChildIndex;
mDeferredBarrierType = kBarrier_None;
} else {
// standard case - append to existing chunk
mDisplayListData->chunks.editTop().endOpIndex = insertIndex + 1;//最上面Chunk修改
}
return insertIndex;
}
將操作DisplayListOp加入DisplayListData的內(nèi)部數(shù)組勋磕,類型全部都是DisplayListOp,返回插入的索引敢靡。
DisplayListData在底層Canvas的prepareDirty時創(chuàng)建挂滓,不管是繪制操作還是狀態(tài)操作,都屬于DisplayListOp類型啸胧,按照順序放到相同數(shù)組赶站。
依次是DrawRectOp纺念,SaveOp贝椿,TranslateOp,DrawRectOp陷谱,RestoreToCountOp烙博,DrawRectOp。
任重而道遠