Canvas的故事
來自一個群友的問題:
使用Canvas繪制的時候坐標(biāo)系是什么落午?是屏幕坐標(biāo)系還是view坐標(biāo)系穿撮?
Canvas是單例嗎沈矿?
樂于助(shui)人(qun)的我說了一句… “翻看源碼看看onDraw
是怎么被調(diào)用的”移斩,然后我也沒有管住我這個手…
其實我們做的事情很簡單 - 就是:分析onDraw的方法調(diào)用棧
代碼環(huán)節(jié)
onDraw -> draw
然后我們看draw
這幾個方法我們挨個瞅瞅(這就是靜態(tài)代碼分析的蛋疼之處肚医,一個函數(shù)被多處調(diào)用的時候,要挨個檢查向瓷,一會我會說一個更方便的方法)
最后我們追查到View
的updateDisplayListIfDirty
方法
省略掉其他代碼肠套,我們可以看到Canvas被創(chuàng)建
/**
* Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
* @hide
*/
@NonNull
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
// 省略大量代碼
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);
try {
// 省略大量代碼
draw(canvas);
}
} finally {
renderNode.end(canvas);
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
final DisplayListCanvas canvas = renderNode.start(width, height);
canvas.setHighContrastText(mAttachInfo.mHighContrastText);
然后我們看renderNode.start
這個方法
public DisplayListCanvas start(int width, int height) {
return DisplayListCanvas.obtain(this, width, height);
}
然后到這個obtain
方法
//DisplayListCanvas類的方法
static DisplayListCanvas obtain(@NonNull RenderNode node, int width, int height) {
if (node == null) throw new IllegalArgumentException("node cannot be null");
DisplayListCanvas canvas = sPool.acquire();
if (canvas == null) {
canvas = new DisplayListCanvas(node, width, height);
} else {
nResetDisplayListCanvas(canvas.mNativeCanvasWrapper, node.mNativeRenderNode,
width, height);
}
canvas.mNode = node;
canvas.mWidth = width;
canvas.mHeight = height;
return canvas;
}
在這里我們就可以看到,代碼會先嘗試從sPool取得一個Canvas猖任,如果無法取得你稚,那就自己new一個。如果成功取得朱躺,那就調(diào)用一個方法來把這個canvas給reset掉刁赖。
最后我們來看看這個sPool
到底是啥東西
//DisplayListCanvas類的變量
private static final int POOL_LIMIT = 25;
private static final SynchronizedPool<DisplayListCanvas> sPool =
new SynchronizedPool<>(POOL_LIMIT);
//DisplayListCanvas類的方法
void recycle() {
mNode = null;
sPool.release(this);
}
這里就很清楚了,在需要Canvas的時候室琢,從這個容量25的池子里面取一個來用乾闰,取不出就只好自己new一個,在canvas完成工作后盈滴,回收到這個池子里面涯肩。
所以Canvas不是全局單例,而是在一個池里緩存著巢钓。
碎碎念
另外一種分析代碼的方法… 我叫做“動態(tài)分析法”病苗,就是在代碼運行的時候打斷點,然后查看斷點時的函數(shù)調(diào)用棧症汹,通過Debug信息里面的調(diào)用棧來查看onDraw里面的Canvas是從哪里來的硫朦。
可以看另一篇博客,它就是動態(tài)分析來看這個Canvas背镇,寫的也很nice
Canvas坐標(biāo)系
onDraw里面的坐標(biāo)系是View坐標(biāo)系而不是屏幕坐標(biāo)系咬展。
為什么泽裳?canvas在創(chuàng)建的時候并不知道view在哪里也不懂view坐標(biāo)系,它只是一個畫布破婆。是Android系統(tǒng)幫你悄悄地做了Translate操作涮总。
(這部分代碼解析最近會更)