首先我们思考一个问题:
如果我们按照如下的方式自定义TextView,会有显示吗?
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--name 自定义View的名字 TextView-->
<declare-styleable name="MyTextView">
<!-- name 属性名称
format 格式: string 文字 color 颜色
dimension 宽高 字体大小 integer 数字
reference 资源(drawable)
-->
<attr name="lzyText" format="string"/>
<attr name="lzyTextColor" format="color"/>
<attr name="lzyTextSize" format="dimension"/>
<attr name="lzyMaxLength" format="integer"/>
<!-- 枚举 -->
<attr name="lzyInputType">
<enum name="number" value="1"/>
<enum name="text" value="2"/>
<enum name="password" value="3"/>
</attr>
</declare-styleable>
</resources>
public class MyTextView extends LinearLayout {
private String mText;
private int mTextColor;
private int mTextSize;
private Paint mPaint;
//构造函数在代码new的时候调用
public MyTextView(Context context) {
this(context,null);
}
//在布局中使用
public MyTextView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
//在不居中调用,布局中有style的情况
public MyTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.MyTextView);
mText = a.getString(R.styleable.MyTextView_lzyText);
mTextColor = a.getColor(R.styleable.MyTextView_lzyTextColor, Color.BLACK);
mTextSize = a.getDimensionPixelSize(R.styleable.MyTextView_lzyTextSize,20);
a.recycle();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextSize(mTextSize);
mPaint.setColor(mTextColor);
}
//布局的宽高都是有这个方法指定
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//获取宽高的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST){
Rect rect = new Rect();
mPaint.getTextBounds(mText,0,mText.length(),rect);
width = rect.width();
}
int height = MeasureSpec.getSize(heightMeasureSpec);
if (heightMode == MeasureSpec.AT_MOST){
Rect r = new Rect();
mPaint.getTextBounds(mText,0,mText.length(),r);
height = r.height();
}
setMeasuredDimension(width,height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint.FontMetricsInt fontMetricsInt = mPaint.getFontMetricsInt();
int dy = 0;
dy = (fontMetricsInt.bottom - fontMetricsInt.top)/2 - fontMetricsInt.bottom;
int baseLine = getHeight()/2 + dy;
canvas.drawText(mText,0,baseLine,mPaint);
}
}
<com.example.michael.view_01.MyTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:lzyText="hello world"
app:lzyTextColor="@color/colorAccent"
app:lzyTextSize="20sp"
/>
当我们运行模拟器是,效果是这样的:
效果并没有出来,为什么我们自定义的TextView继承了一个ViewGroup效果就没有了呢?
draw(Canvas canvas)
我们再View的源码里面看draw()方法:
if (!dirtyOpaque) onDraw(canvas);
可见dirtyOpaque为false时,onDraw()方才会调用。
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
直接继承View会走onDraw()方法说明dirtyOpaque确实运算后会false。
下面我们来看ViewGroup的构造方法:
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
}
进到initViewGroup()里面:这样dirtyOpaque会被重新赋值。
setFlags(WILL_NOT_DRAW, DRAW_MASK);