视图树形结构,每一个节点均有Java层DisplayListCanvas和底层DisplayListCanvas。在Java层,调用Canvas#drawXxx方法,如drawPoint,drawPath,drawRect,DisplayListCanvas或Canvas中有JNI#方法,根据mNativeCanvasWrapper指针获取底层DisplayListCanvas。底层DisplayListCanvas继承底层Canvas。头文件定义在/frameworks/base/libs/hwui/Canvas.h。
此外,除了绘制方法drawXxx,还有变换方法,如translate,scale等,以及save和restore方法,下面会通过一个绘图实例具体分析这些方法实现的操作。
前面已经介绍过,从顶层视图DecorView#updateDisplayListIfDirty方法开始绘制树形视图结构,为了分析简单,截取一小段树分支(虚线框内),通过下面视图结构,分析绘制时具体操作。
容器视图绘制
虚线框的树形结构截取如图。父视图0是LinearLayout,三个子视图分别是自定义CanvasView和两个TextView。
当遍历到达LinearLayout节点#updateDisplayListIfDirty方法时,再看一下此方法代码,如下。
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
// ThreadedRenderer是空,直接返回节点
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();
//创建DisplayListCanvas
final DisplayListCanvas canvas = renderNode.start(width, height);
//判断LayerType,以及获取HardwareLayer的部分代码省略掉,LinearLayout不需要。
try {
// 一般视图走硬件渲染都执行下面程序
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视图会跳过绘制,则直接派发给子视图
dispatchDraw(canvas);
} else {
draw(canvas);//绘制,包括绘制自身,修饰,以及派发,共六个步骤,此处不执行。
}
} finally {
renderNode.end(canvas);//绘制结束,保存canvas记录内容
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
LinearLayout视图创建画布DisplayListCanvas后,因有PFLAG_SKIP_DRAW标志,选择跳过绘制,它没有设置Background,在View框架源码中会设置此标志。跳过绘制,即不会走LinearLayout的onDraw方法,另外四个步骤都不会触发,仅仅触发父类ViewGroup的dispatchDraw方法。绘制直接向子视图派发。
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源码比较多,主要功能是派发子视图绘制。触发每个子视图三个参数的draw重载方法。在遍历子视图前后,有两个方法,LinearLayout画布的insertReorderBarrier和insertInorderBarrier方法。
他们在触发底层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,这个值后续addOpAndUpdateChunk方法会用到,在绘制子视图之前,已经创建过第一个Chunk。
在绘制子视图前,插入一个kBarrier_OutOfOrder栅栏,LinearLayout画布drawXxx方法绘制会创建一个新Chunk,该Chunk索引LinearLayout画布绘制的子视图节点。绘制子视图结束后,再次插入一个kBarrier_InOrder栅栏,再次新建一个Chunk块,索引LinearLayout画布后续绘制的内容。
LinearLayout子视图是叶子节点,子视图绘制,三个参数的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) {
//在这里触发子视图渲染方法。
renderNode = updateDisplayListIfDirty();
if (!renderNode.isValid()) {
renderNode = null;
drawingWithRenderNode = false;
}
}
...
if (!drawingWithDrawingCache) {
if (drawingWithRenderNode) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
//父视图画布绘制子视图RenderNode节点
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
} else {
...
}
} else if (cache != null) {
...
}
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
..
mRecreateDisplayList = false;
return more;
}
draw方法很长,这里指摘取硬件渲染相关的。关注两个点。
1:子视图#updateDisplayListIfDirty方法,重建Canvas,传递给一个参数的draw重载方法,绘制,返回子视图RenderNode节点,该节点相关画布已经完成绘制内容记录。
2:LinearLayout画布drawRenderNode绘制子视图RenderNode节点。
void DisplayListCanvas::drawRenderNode(RenderNode* renderNode) {
DrawRenderNodeOp* op = new (alloc()) DrawRenderNodeOp(
renderNode,
*mState.currentTransform(),
mState.clipIsSimple());
addRenderNodeOp(op);
}
创建一个DrawRenderNodeOp,DrawRenderNodeOp继承DrawBoundedOp,DrawBoundedOp继承DrawOp,基类是DisplayListOp。
size_t DisplayListCanvas::addRenderNodeOp(DrawRenderNodeOp* op) {
//增加一个绘制操作到mDisplayListData的displayListOps数组。
int opIndex = addDrawOp(op);
//mDisplayListData是LinearLayout底层存储绘制数据的对象
//增加操作到DisplayListData的mChildren数组,代表子视图
int childIndex = mDisplayListData->addChild(op);
//写入了一个绘制子节点的操作,告诉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数组中。
addChild方法将操作加入到DisplayListData的mChildren数组中。
注意,因前期插入一个kBarrier_OutOfOrder栅栏,因此在addDrawOp触发的addOpAndUpdateChunk中,会创建一个新Chunk块,写入DrawRenderNodeOp后,更新该Chunk中指向mChildren数组的endChildIndex 索引。
三个叶子RenderNode节点操作写入LinearLayout底层数据数组中,他们在一个Chunk块中。子视图绘制完毕,再次插入一个kBarrier_InOrder栅栏,创建一个新Chunk。
叶子节点视图绘制
叶子节点绘制主要关注onDraw方法,CanvasView的onDraw方法,绘制三个矩形区域。
@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();//当前状态保存
/***********************第二层*****************************/
canvas.translate(180, 180);
Rect rect2 = new Rect(20, 20, 180, 180);
canvas.drawRect(rect2, paint);//再次绘制一个正方形
/************************第二层***************************/
canvas.restore();//恢复到第一层状态
/************************第一层**************************/
Rect rect3 = new Rect(20, 200, 180, 360);
canvas.drawRect(rect3, paint);//再次绘制一个正方形
}
四个Canvas方法
drawRect,绘制一个矩形区域。
save,保存当前图层状态。
restore,恢复上一个图层状态。
translate,两个方向x和y,偏移一定距离。
先绘制一个矩形,然后保存状态,进行偏移,绘制第二个矩形,第二个矩形受到偏移影响,恢复状态,绘制第三个矩形,第三个矩形不受偏移影响。
详细分析
执行顺序
drawRect(1)
save
translate
drawRect(2)
restore
drawRect(3)
1,drawRect:触发底层DisplayListCanvas的drawRect方法。该方法是
在DisplayListCanvas的头文件中定义虚函数,在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);
}
触发flushAndAddOp方法。
2,save:触发底层DisplayListCanvas的save方法。注意:save和restore一般是成对出现的。
底层DisplayListCanvas#save方法。
int DisplayListCanvas::save(SkCanvas::SaveFlags flags) {
addStateOp(new (alloc()) SaveOp((int) flags));
return mState.save((int) flags);
}
addStateOp增加状态操作StateOp,前面的是增加DrawOp,而现在是StateOp
和DrawOp一样,状态StateOp也继承DisplayListOp类。
底层DisplayListCanvas#addStateOp方法。
size_t DisplayListCanvas::addStateOp(StateOp* op) {
return flushAndAddOp(op);
}
触发flushAndAddOp方法。
除了flushAndAddOp保存,还会触发CanvasState保存。
总结:
DisplayListCanvas的save方法,addStateOp增加一个SaveOp,addStateOp方法和addDrawOp类似,都触发flushAndAddOp方法,入参是同一个基类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操作中,创建一个Snapshot对象,封装当前对象,新建的Snapshot放到链表最前面,mSaveCount自增,mSaveCount代表当前操作的是位于第几层次,若上层调用了restore,则触发CanvasState的restore方法,回退到上一层。
可以看出,每一个层次状态的save都保存在Snapshot中。save之后的改变在新建的Snapshot保存,restore后回到之前的Snapshot。
3,translate:触发底层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时才起作用,即执行过restore或restoreToCount。
写入一个RestoreToCountOp操作。
CanvasState的translate方法,当前mSnapshot的transform触发translate偏移。因前面触发过一个save方法,因此有两个Snapshot,当前mSnapshot是表头第一个。
4,restore:触发底层DisplayListCanvas的restore方法,恢复到上一个绘制状态。
底层DisplayListCanvas#restore方法。
void DisplayListCanvas::restore() {
if (mRestoreSaveCount < 0) {
restoreToCount(getSaveCount() - 1);
return;
}
mRestoreSaveCount--;
flushTranslate();
mState.restore();
}
若mRestoreSaveCount已经>=0,则直接自减。每次restore只能回退一层。同时CanvasState回退,内部mSaveCount保存层级。
若mRestoreSaveCount小于0,即有可能是初始值-1,则触发restoreToCount方法,getSaveCount获取当前层级。若执行过一个save,restore时,getSaveCount得到2,那么restoreToCount恢复的值就是1,mRestoreSaveCount设置成1。
总结:restore回退一层。
上层直接调用restoreToCount方法时,直接回退saveCount层,这个方法中会设置mRestoreSaveCount值。
底层DisplayListCanvas#restoreToCount方法。
void DisplayListCanvas::restoreToCount(int saveCount) {
mRestoreSaveCount = saveCount;
flushTranslate();
mState.restoreToCount(saveCount);
}
save和restore成对出现,多个save后,第一次restore时,mRestoreSaveCount将会是初始值-1,restoreToCount方法会第一次设置其值,设置成restore回退后的层级。
即mRestoreSaveCount存储当前层级。
例如,3个save后,第一次restore后,则mRestoreSaveCount变为3。
总结:
在上层一次save,将当前的状态保存下来,包括偏移,缩放等状态,后续的改变仍然在此基础上,偏移,绘制等,若执行restore,则回到上一个save保存的状态中,中间的改变忽略掉。
1:drawRect,绘制正方形,第一层,写入DrawRectOp,mRestoreSaveCount是-1,flushRestoreCount无作用,无偏移,flushTranslate无作用。
addOpAndUpdateChunk写入绘制。
2:save,保存当前层级状态,mRestoreSaveCount是-1,flushRestoreCount无作用,无偏移,flushTranslate无作用。
addOpAndUpdateChunk写入状态。
3:translate,偏移,第二层,mRestoreSaveCount是-1,flushRestoreCount无作用,设置偏移值mTranslateX与mTranslateY,当前Snapshot(第二层)偏移。
4:drawRect,绘制正方形,第二层,写入DrawRectOp,mRestoreSaveCount是-1,flushRestoreCount无作用,有偏移,
addOpAndUpdateChunk写入TranslateOp,并恢复偏移值。
addOpAndUpdateChunk写入绘制。
5:restore,恢复上一个层级,前面仅有一个save,因此mRestoreSaveCount是-1,触发restoreToCount方法,设置mRestoreSaveCount=1。无偏移,flushTranslate无作用。
6:drawRect,绘制正方形,第一层,写入DrawRectOp,mRestoreSaveCount是1,无偏移,flushTranslate无作用,
addOpAndUpdateChunk写入RestoreToCountOp。并恢复mRestoreSaveCount为-1。
addOpAndUpdateChunk写入绘制。
flushAndAddOp方法在上层写入DrawOp或StateOp时触发。
最终目的是addOpAndUpdateChunk。
若当前存在mRestoreSaveCount>=0或偏移,首先写入相应Op,即RestoreToCountOp与TranslateOp。
底层DisplayListCanvas#flushAndAddOp方法。
size_t DisplayListCanvas::flushAndAddOp(DisplayListOp* op) { //
flushRestoreToCount();
flushTranslate();
return addOpAndUpdateChunk(op);
}
三个方法。
flushRestoreToCount:写入RestoreToCountOp。
flushTranslate:写入TranslateOp。
addOpAndUpdateChunk:执行写入。
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的内部数组,类型全部都是DisplayListOp,返回插入的索引。
DisplayListData在底层Canvas的prepareDirty时创建,不管是绘制操作还是状态操作,都属于DisplayListOp类型,按照顺序放到相同数组。
依次是DrawRectOp,SaveOp,TranslateOp,DrawRectOp,RestoreToCountOp,DrawRectOp。
任重而道远