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
*/
@NonNull
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
/**
* Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
* @hide
*/
@NonNull
public 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 backgrounds
if ((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 default
if (!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想画点别的东西又不想是背景的时候,得加一些骚操作,具体的自己研究去吧。