首先绘制TextView继承于View:直接贴代码
public class TextView extends LinearLayout{
private String mText;
private int mTextSize = 18;
private int mTextColor = Color.BLACK;
private Paint mPaint;//文字的画笔
// 构造函数会在代码里面new的时候调用
// TextView tv = new TextView(this);
public TextView(Context context) {
this(context, null);
}
// 在布局layout中使用(调用)
public TextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// 在布局layout中使用(调用),但是会有style
public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
mText = array.getString(R.styleable.TextView_yijiaText);
mTextColor = array.getColor(R.styleable.TextView_yijiaTextColor, mTextColor);
// 18 18px 18sp
mTextSize = array.getDimensionPixelSize(R.styleable.TextView_yijiaTextSize,
sp2px(mTextSize));
// 回收s
array.recycle();
//初始化画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);//设置抗锯齿
mPaint.setColor(mTextColor);//设置画笔颜色
mPaint.setTextSize(mTextSize);
}
/**
* 自定义View的测量方法
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 布局的宽高都是由这个方法指定
// 指定控件的宽高,需要测量
// 获取宽高的模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//1.确定的值,这个时候不需要计算,给多少是多少
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//2.给的是不确定值
if (widthMode == MeasureSpec.AT_MOST) {
//计算的宽度 与字体的长度有关 与字体的大小 用画笔来测量
Rect rect = new Rect();
//获取文本的矩形
mPaint.getTextBounds(mText, 0, mText.length(), rect);
width = rect.width();
}
//2.给的是不确定值
if (heightMode == MeasureSpec.AT_MOST) {
//计算的宽度 与字体的长度有关 与字体的大小 用画笔来测量
Rect rect = new Rect();
//获取文本的矩形
mPaint.getTextBounds(mText, 0, mText.length(), rect);
height = rect.height();
}
setMeasuredDimension(width, height);
}
/**
* 用于绘制
*
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Rect rect = new Rect();
mPaint.getTextBounds(mText, 0, mText.length(), rect);
int dx = getWidth() / 2 - rect.width() / 2;
//获取中心(fontMetrics.bottom - fontMetrics.top) / 2
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
int dy = (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
int baseLine = getHeight() / 2 + dy;
canvas.drawText(mText, dx, baseLine, mPaint);
Log.e("TAG", "dy==" + dy + ",centerY==" + getHeight() / 2+",baseLine=="+baseLine);
}
private int sp2px(int spValue) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue,
getResources().getDisplayMetrics());
}
}
源码分析为什么ViewGroup不调用onDraw,而设置背景后又可以显示自定义的TextView
- 首先看View中调用onDraw的源码
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
可以看到当dirtyOpaque为false时调用OnDraw
- dirtyOpaque源码
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;
可以看到是由privateFlags控制,即mPrivateFlags
- 看view的构造方法
computeOpaqueFlags();
- 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构造方法源码
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
- 查看initViewGroup源码
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
可以看到最后因为setFlags的原因导致viewGrop中的OnDraw不可见。
为什么:viewgroup中设置背景后又可以显示
- view中setBackDrawable的源码
computeOpaqueFlags();
if (background == mBackground) {
return;
}
此时发现又重新计算了
个人觉得解决方法可以有三
- ①onDraw换成dispatchDraw
- ②设置背景透明
- ③看源码可以知道设置setFlags的值即可
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}