First draw TextView inherited from View: paste code directly
public class TextView extends LinearLayout{
private String mText;
private int mTextSize = 18;
private int mTextColor = Color.BLACK;
private Paint mPaint;//A brush for writing
// Constructor will be called when new is in the code
// TextView tv = new TextView(this);
public TextView(Context context) {
this(context, null);
}
// Use in layout layout (call)
public TextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Use (call) in layout layout, but there will be style
public TextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// Get custom properties
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));
// Recycling s
array.recycle();
//Initialize brush
mPaint = new Paint();
mPaint.setAntiAlias(true);//Set anti aliasing
mPaint.setColor(mTextColor);//Set brush color
mPaint.setTextSize(mTextSize);
}
/**
* Measurement method of custom View
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// The width and height of the layout are specified by this method
// Specifies the width and height of the control, which needs to be measured
// Get the mode of width and height
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//1. The determined value does not need to be calculated at this time. What is the given value
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
//2. Uncertainty value is given
if (widthMode == MeasureSpec.AT_MOST) {
//The calculated width is related to the length of the font the size of the font is measured with a brush
Rect rect = new Rect();
//Get rectangle of text
mPaint.getTextBounds(mText, 0, mText.length(), rect);
width = rect.width();
}
//2. Uncertainty value is given
if (heightMode == MeasureSpec.AT_MOST) {
//The calculated width is related to the length of the font the size of the font is measured with a brush
Rect rect = new Rect();
//Get rectangle of text
mPaint.getTextBounds(mText, 0, mText.length(), rect);
height = rect.height();
}
setMeasuredDimension(width, height);
}
/**
* Used for rendering
*
* @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;
//Get Center (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());
}
}
Source code analysis why ViewGroup does not call onDraw, but after setting the background, it can display the customized TextView
- First look at the source code for calling onDraw in View.
// 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);
You can see that OnDraw is called when dirtyOpaque is false
- dirtyOpaque source code
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;
You can see that it is controlled by privateFlags, that is, mPrivateFlags
- view construction method
computeOpaqueFlags();
- computeOpaqueFlags(); check the source code
// 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;
}
- View the source code of ViewGroup construction method
super(context, attrs, defStyleAttr, defStyleRes);
initViewGroup();
initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
- View initViewGroup source code
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
You can see that the OnDraw in viewgrow is not visible because of setFlags.
Why: the background can be displayed after setting in viewgroup
- Source code of setBackDrawable in view
computeOpaqueFlags();
if (background == mBackground) {
return;
}
At this time, the discovery recalculated
I think there are three solutions
- ① Replace onDraw with dispatchDraw
- ② Set background transparency
- ③ You can see the source code and set the value of setFlags
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}