当前位置: 代码迷 >> 综合 >> ViewGroup onDraw调用和不调用
  详细解决方案

ViewGroup onDraw调用和不调用

热度:34   发布时间:2023-12-13 04:59:56.0

ViewGroup onDraw调用和不调用

view 的绘制一般都是,测量(onMeasure),布局(onLayout)和绘制(onDraw)。自定义View一般是复写上述三个方法。但是自定义View如果是继承ViewGroup,会发现onDraw不会调用。
android View的绘制一般是从 draw或者dispatchDraw开始的。

绘制跟踪

view绘制调用,一般是parent的dispatchDraw开始.具体View的整个绘制请参考其他文章,本文只是找到为什么不调用onDraw。

class ViewGroup{
.....protected void dispatchDraw(Canvas canvas) {...more |= drawChild(canvas, transientChild, drawingTime);//主要是这句,这句在这个方法有很多调用,主要是涉及到绘制顺序相关的东西,本文不描述。.....}...protected boolean drawChild(Canvas canvas, View child, long drawingTime) {return child.draw(canvas, this, drawingTime);//注意这个方法,调用view的draw}}

上面可以看到,parent调用了view的draw方法,AS直接调整会过去,然后发现这里调用了draw,为什么viewGroup不会调用呢????这个地方困惑我好久,然后我发现细心才是最重要的,细心。
注意上面drawChild调用的子view的draw方法,是3个参数的,而我们一般说的draw方法其实是:

class View{........public void draw(Canvas canvas) {//注意这个是单参数的,单纯的绘制.......}....../*** This method is called by ViewGroup.drawChild() to have each child view draw itself.** This is where the View specializes rendering behavior based on layer type,* and hardware acceleration.*/boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {//方法明确注释了,这个才是viewGroup调用子类的draw,这个是复写不了的。这个方法没有加修饰就是default的......//前面是判断硬件加速和layer type的逻辑。关于硬件加速和layer type我会在后续文章说明.....renderNode = updateDisplayListIfDirty();//关键方法来了........}....../*** Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)* @hide*/@NonNullpublic RenderNode updateDisplayListIfDirty() {final RenderNode renderNode = mRenderNode;
/*** Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)* @hide*/@NonNullpublic RenderNode updateDisplayListIfDirty() {........try {if (layerType == LAYER_TYPE_SOFTWARE) {buildDrawingCache(true);Bitmap cache = getDrawingCache(true);if (cache != null) {canvas.drawBitmap(cache, 0, 0, mLayerPaint);}} else {computeScroll();canvas.translate(-mScrollX, -mScrollY);mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;// Fast path for layouts with no backgroundsif ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {//看方法注释dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}if (debugDraw()) {debugDrawFocus(canvas);}} else {draw(canvas);}}} finally {renderNode.end(canvas);setDisplayListProperties(renderNode);}} else {mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;mPrivateFlags &= ~PFLAG_DIRTY_MASK;}return renderNode;}........}.........}
}

所以就是这句:mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW
mPrivateFlags 这个属性,绘制和显示都是用它来来的,上面说的layer type会影响到这个。
然后哪里设置的呢?ViewGroup初始化的时候

 private void initViewGroup() {// ViewGroup doesn't draw by defaultif (!debugDraw()) {setFlags(WILL_NOT_DRAW, DRAW_MASK);}........}

默认会调用这个设置不调用,但是为啥有backgroud的时候会调用draw呢?这个时候是必须得画自己的,继续看 setFlags方法。这个方法在View里面

/*** Set flags controlling behavior of this view.** @param flags Constant indicating the value which should be set* @param mask Constant indicating the bit range that should be changed*/void setFlags(int flags, int mask) {.........if ((changed & DRAW_MASK) != 0) {if ((mViewFlags & WILL_NOT_DRAW) != 0) {if (mBackground != null //看这个判断条件,或,有一个成立就行|| mDefaultFocusHighlight != null|| (mForegroundInfo != null && mForegroundInfo.mDrawable != null)) {mPrivateFlags &= ~PFLAG_SKIP_DRAW;} else {mPrivateFlags |= PFLAG_SKIP_DRAW;}} else {mPrivateFlags &= ~PFLAG_SKIP_DRAW;}requestLayout();invalidate(true);}..........}

所以原因明确了:ViewGroup的初始化的时候,只设置成PFLAG_SKIP_DRAW,如果有背景那么这个设置不会成立,但是没有背景的时候这个属性就会设置成功。
然后在绘制流程中调用View.draw(Canvas canvas, ViewGroup parent, long drawingTime) 里面会根据这个标志来决定是走 draw还是dispatchDraw。
draw最后也会调用dispatchDraw。所以复写view的时候,draw不一定走,但是dispatchDraw一定会走。
所以自定义viewGroup想画点别的东西又不想是背景的时候,得加一些骚操作,具体的自己研究去吧。