您的位置:首页 > 其它

自定义TextView——解决ViewGroup不调用OnDraw方法

2018-02-08 08:37 369 查看
首先绘制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();

cfa7
//获取文本的矩形
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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: