ViewGroup与View的onDraw()异同
原本extends View的自定义控件改为extends ViewGroup后不正常显示,设置background后正常
原因:默认的ViewGroup不会调用onDraw()
画的其实是public void draw(@NonNull Canvas canvas) {},模板设计模式
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
}
// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
mPrivateFlags到底怎么赋值的 ,在View的构造函数中调用computeOpaqueFlags()
/**
* @hide
*/
@UnsupportedAppUsage
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为什么出不来-->initViewGroup();
private void initViewGroup() {
// ViewGroup doesn't draw by default
if (!isShowingLayoutBounds()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
}
导致mPrivateFlags会重新赋值
问题解决:思路:就是改变mPrivateFlags的状态
onDraw(canvas: Canvas)改为dispatchDraw(canvas: Canvas)- 设置透明背景
setWillNotDraw(false)
完整的自定义TextView代码如下 attrs.xml
<!-- 名字最好是自定义控件 的名字-->
<declare-styleable name="TextView">
<!--
name属性名称
format格式
string 文字
color 颜色
dimension 宽高,字体大小
integer数字
reference资源(drawable)
-->
<attr name="darrenText" format="string" />
<attr name="darrenTextColor" format="color" />
<attr name="darrenTextSize" format="dimension" />
<attr name="darrenMaxLength" format="integer" />
<!-- 枚举-->
<attr name="darrenInputType">
<enum name="number" value="1" />
<enum name="text" value="2" />
<enum name="password" value="3" />
</attr>
</declare-styleable>
TextView.kt
class TextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val text: String
private val textSize: Int
private val textColor: Int
private val paint: Paint
private val bounds by lazy { Rect() }
init {
//获取自定义属性
val array = context.obtainStyledAttributes(attrs, R.styleable.TextView)
text = array.getString(R.styleable.TextView_darrenText).toString()
textColor = array.getColor(R.styleable.TextView_darrenTextColor, Color.BLACK)
textSize = array.getDimensionPixelSize(R.styleable.TextView_darrenTextSize, context.sp2px(15))
//回收
array.recycle()
paint = with(Paint()) {
//抗锯齿
isAntiAlias = true
//设置字体大小,颜色
textSize = this@TextView.textSize.toFloat()
color = textColor
this
}
}
/**
* 自定义view的测量方法
* */
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//布局的宽高都是由这个方法指定
//指定控件的宽高,需要测量
//获取宽高的模式
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val widthSize = measureWidth(widthMeasureSpec, heightMeasureSpec, true)
val heightSize = measureWidth(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(widthSize, heightSize)
}
/**
* @param isWidth 是否计算宽度
* */
private fun measureWidth(
widthMeasureSpec: Int,
heightMeasureSpec: Int,
isWidth: Boolean = false
): Int {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
return if (widthMode == MeasureSpec.AT_MOST) {
//2.给的是 wrap_content 需要计算
//计算的宽度与字体的长度,大小有关,用画笔测量
paint.getTextBounds(text, 0, text.length, bounds)
if (isWidth) {
bounds.width() + paddingLeft + paddingRight
} else {
bounds.height() + paddingTop + paddingBottom
}
} else {
//1.确定的值,这个时候不需要计算,给的多少就是多少
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
if (isWidth) {
widthSize
} else {
heightSize
}
}
}
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
//x 开始的位置
//y 基线 baseLine
//dy 代表的是高度的一半到baseLine的距离
val dy = with(paint.fontMetricsInt) {
(bottom - top) / 2 - bottom
}
val baseLine = (height / 2).toFloat() + dy
val x = paddingLeft
canvas.drawText(text, x.toFloat(), baseLine, paint)
}
}