1. 如果让 自定义TextView继承系统的View,文字可以显示出来,继承 LinearLayout,问画的文字是否可以显示出来 class TextView extends LinearLayout ?
如果在activity_main 布局文件中设置background背景的话,那么直接继承LinearLayout是可以显示文字的;
如果不设置background的话,文字是不可以显示的
因为LinearLayout继承ViewGroup,而默认的ViewGroup不会调用 onDraw()方法,为什么呢?
LinearLayout --> 继承ViewGroup --> 继承View ,在View中有 public void draw(Canvas canvas) 方法
所以,我们在自定义View中写的onDraw(),画的方法其实是调用 View中的 draw(Canvas canvas) 方法, 这个是模板设计模式
if (!dirtyOpaque) onDraw(canvas);
dispatchDraw(canvas);
onDrawForeground
dirtyOpaque需要是false才行 其实是由 privateFlags = mPrivateFlags
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags到底是怎样赋值的 在View的构造方法中调用 computeOpaqueFlags
/**
* @hide
*/
protected void computeOpaqueFlags() {
// Opaque if:
// - Has a background
// - Background is opaque
// - Doesn't have scrollbars or scrollbars overlay
if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
} else {
mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
}
final int flags = mViewFlags;
if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
} else {
mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
}
}
ViewGroup为什么不能显示 , 因为ViewGroup中的 initViewGroup()方法
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
导致 mPrivateFlags 会重新赋值 ,
从而导致 if (!dirtyOpaque) onDraw(canvas);此方法不能进去,所以 ViewGroup不会显示
setFlags(WILL_NOT_DRAW, DRAW_MASK);此方法是View中的方法,意思就是默认的你不需要给我做任何画
而如果布局文件中设置了background的话, 那么此时你继承LinearLayout是可以显示出来你自定义的TextView的 , 代码如下
/**
* @deprecated use {@link #setBackground(Drawable)} instead
*/
@Deprecated
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
if (background == mBackground) {
return;
}
boolean requestLayout = false;
mBackgroundResource = 0;
/*
* Regardless of whether we're setting a new background or not, we want
* to clear the previous drawable.
*/
if (mBackground != null) {
mBackground.setCallback(null);
unscheduleDrawable(mBackground);
}
if (background != null) {
Rect padding = sThreadLocal.get();
if (padding == null) {
padding = new Rect();
sThreadLocal.set(padding);
}
resetResolvedDrawablesInternal();
background.setLayoutDirection(getLayoutDirection());
if (background.getPadding(padding)) {
resetResolvedPaddingInternal();
switch (background.getLayoutDirection()) {
case LAYOUT_DIRECTION_RTL:
mUserPaddingLeftInitial = padding.right;
mUserPaddingRightInitial = padding.left;
internalSetPadding(padding.right, padding.top, padding.left, padding.bottom);
break;
case LAYOUT_DIRECTION_LTR:
default:
mUserPaddingLeftInitial = padding.left;
mUserPaddingRightInitial = padding.right;
internalSetPadding(padding.left, padding.top, padding.right, padding.bottom);
}
mLeftPaddingDefined = false;
mRightPaddingDefined = false;
}
// Compare the minimum sizes of the old Drawable and the new. If there isn't an old or
// if it has a different minimum size, we should layout again
if (mBackground == null
|| mBackground.getMinimumHeight() != background.getMinimumHeight()
|| mBackground.getMinimumWidth() != background.getMinimumWidth()) {
requestLayout = true;
}
background.setCallback(this);
if (background.isStateful()) {
background.setState(getDrawableState());
}
background.setVisible(getVisibility() == VISIBLE, false);
mBackground = background;
applyBackgroundTint();
if ((mPrivateFlags & PFLAG_SKIP_DRAW) != 0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true;
}
} else {
/* Remove the background */
mBackground = null;
if ((mViewFlags & WILL_NOT_DRAW) != 0
&& (mForegroundInfo == null || mForegroundInfo.mDrawable == null)) {
mPrivateFlags |= PFLAG_SKIP_DRAW;
}
/*
* When the background is set, we try to apply its padding to this
* View. When the background is removed, we don't touch this View's
* padding. This is noted in the Javadocs. Hence, we don't need to
* requestLayout(), the invalidate() below is sufficient.
*/
// The old background's minimum size could have affected this
// View's layout, so let's requestLayout
requestLayout = true;
}
computeOpaqueFlags();
if (requestLayout) {
requestLayout();
}
mBackgroundSizeChanged = true;
invalidate(true);
}
在上边的setBackgroundDrawable()方法总的computeOpaqueFlags() ,会去重新计算
/**
* @hide
*/
protected void computeOpaqueFlags() {
// Opaque if:
// - Has a background
// - Background is opaque
// - Doesn't have scrollbars or scrollbars overlay
if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) {
mPrivateFlags |= PFLAG_OPAQUE_BACKGROUND;
} else {
mPrivateFlags &= ~PFLAG_OPAQUE_BACKGROUND;
}
final int flags = mViewFlags;
if (((flags & SCROLLBARS_VERTICAL) == 0 && (flags & SCROLLBARS_HORIZONTAL) == 0) ||
(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_INSIDE_OVERLAY ||
(flags & SCROLLBARS_STYLE_MASK) == SCROLLBARS_OUTSIDE_OVERLAY) {
mPrivateFlags |= PFLAG_OPAQUE_SCROLLBARS;
} else {
mPrivateFlags &= ~PFLAG_OPAQUE_SCROLLBARS;
}
}
总结:由以上分析可知:
1>. ViewGroup之所以不能显示 自定义的TextView,原因就是:
在ViewGroup源码中:在ViewGroup初始化时,会调用initViewGroup()方法,而此方法的setFlags(WILL_NOT_DRAW, DRAW_MASK) 中的WILL_NOT_DRAW意思就是默认不去画任何东西,所以就进不去View的draw(Canvas canvas)方法,所以就进不去 if (!dirtyOpaque) onDraw(canvas) 方法,就不能绘制,所以TextView文字不会显示;
2>:而在 activity_main.xml 布局文件中设置background可以显示,原因就是:
在View源码中:它会调用setBackgroundDrawable()方法时,会去重新计算computeOpaqueFlags();
3>:如果想实现:在布局文件中不设置 background,我也想让自定义的TextView显示出来,该如何实现?
思路:
目的就是改变 mPrivateFlags即可;
- 把其中的onDraw()方法改为 dispatchDraw()
- 在第三个构造方法中直接设置 透明背景即可,但是前提是人家在 布局文件中没有设置 background属性才可以这样去写,要不然就会把人家的背景覆盖的
- 在第三个构造方法中写setWillNotDraw(false); 即可
2. 综上所述:自定义TextView继承 View和继承ViewGroup的区别就是:
1>:继承自View:
在布局文件中,不管你设置不设置background,只要你重写onDraw()方法,那么你自定义的TextView文字显示的
2>:继承自ViewGroup: [ 此处是继承自LinearLayout ]
如果你在 activity_main.xml 文件中设置了 background的话,那么此时直接让自定义的TextView继承LinearLayout,文字直接可以出来;
如果你在 activity_main.xml 文件中没有设置 background的话,可以用如下3种方法实现即可:
目的就是改变 mPrivateFlags即可;
- 把其中的onDraw()方法改为 dispatchDraw();
- 在第三个构造方法中直接设置 透明背景即可,但是前提是人家在 布局文件中没有设置 background属性才可以这样去写,要不然就会把人家的背景覆盖的;
- 在第三个构造方法中写setWillNotDraw(false) 即可;