自定义view,自认为不是一步登天,要循序渐进,所以从最简单的的TextView开始,小试牛刀,对自定义view有一个全面认识。
1.创建
继承自view
public class TextView extends View {
// 构造函数会在代码里面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);
}
2.自定义属性
在res-->valus下创建attrs.xml文件
在构造中获取属性 ,并初始化
private String mText;
private int mTextSize = 15;
private int mTextColor = Color.BLACK;
// 获取自定义属性
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.TextView);
mText = array.getString(R.styleable.TextView_mText);
mTextColor = array.getColor(R.styleable.TextView_mTextColor, mTextColor);
// 15 15px 15sp
mTextSize = array.getDimensionPixelSize(R.styleable.TextView_mTextSize,sp2px(mTextSize));
// 回收
array.recycle();
3.创建画笔
private Paint mPaint;
在构造中创建
//创建画笔
mPaint=new Paint();
//抗锯齿
mPaint.setAntiAlias(true);
//画笔大小
mPaint.setTextSize(mTextSize);
//画笔颜色
mPaint.setColor(mTextColor);
4.测量
/**
* 自定义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);
//2.给的wrap_content 需要计算
if (widthMode==MeasureSpec.AT_MOST){
//计算的宽度与字体长度有关 与字体的大小 用画笔来测量
Rect bounds= new Rect();
mPaint.getTextBounds(mText,0,mText.length(),bounds);
width=bounds.width()+getPaddingLeft()+getPaddingRight();
}
//1.确定的值,不需要计算
int height = MeasureSpec.getSize(heightMeasureSpec);
//2.给的wrap_content 需要计算
if (heightMode==MeasureSpec.AT_MOST){
//计算的宽度与字体长度有关 与字体的大小 用画笔来测量
Rect bounds= new Rect();
mPaint.getTextBounds(mText,0,mText.length(),bounds);
height= bounds.height()+getPaddingTop()+getPaddingBottom();
}
//设置控件的宽高
setMeasuredDimension(width,height);
}
5.绘制
/**
* 用于绘制
* @param canvas
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画文字 text ,x,y,paint
//求y值基线 bottom是个正值 top是个负值
Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
int dy=(fontMetrics.bottom-fontMetrics.top)/2-fontMetrics.bottom;
int baseLine=getHeight()/2+dy;
int x=getPaddingLeft();
canvas.drawText(mText,x,baseLine,mPaint);
}
6.问题讲解
自定义TextView能否继承LinearLayout,效果能否出来?
答案是可以出来的,但是不会走onDraw()方法
view中得知要中onDraw()方法,有下面代码
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&2. (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
if (!dirtyOpaque) onDraw(canvas);
也就是dirtyOpaque为false时会调用,mPrivateFlags 到底是怎么赋值的 在View的构造函数中调用 computeOpaqueFlags
/**
* @hide
*/
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 (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
mGroupFlags |= FLAG_CLIP_CHILDREN;
mGroupFlags |= FLAG_CLIP_TO_PADDING;
mGroupFlags |= FLAG_ANIMATION_DONE;
mGroupFlags |= FLAG_ANIMATION_CACHE;
mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;
if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
}
setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
mChildren = new View[ARRAY_INITIAL_CAPACITY];
mChildrenCount = 0;
mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
}
其中
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
导致 mPrivateFlags 会重新赋值
解决思路: 目的就是改变 mPrivateFlags
1.重写dispatchDraw(),不用重写onDraw();
2.可以背景透明的背景
3.setWillNotDraw()