在开发过程中经常遇到自定义控件,打算自己一边写着博客一边总结一下自定义View 的过程,以便能更好的提高。
新建一个自定义View,继承View,实现父类的构造方法.
public classMyFirstView extendsView {
publicMyFirstView(Context context) {
this(context,null);
}
publicMyFirstView(Context context, AttributeSet attrs) {
this(context,attrs,0);
}
publicMyFirstView(Context context, AttributeSet attrs, intdefStyleAttr) {
super(context, attrs, defStyleAttr);
}
}
注意更改直接引用的构造方法,以下是三个构造函数使用时机。
①.在代码中直接new一个MyFirstView实例的时候,会调用第一个构造函数.这个没有任何争议.
②.在xml布局文件中调用MyFirstView的时候,会调用第二个构造函数.这个也没有争议.
③.在xml布局文件中调用MyFirstView,并且MyFirstView标签中还有自定义属性时,这里调用的还是第二个构造函数.
也就是说,系统默认只会调用Custom View的前两个构造函数,至于第三个构造函数的调用,通常是我们自己在构造函数中主动调用的(例如,在第二个构造函数中调用第三个构造函数).
自定义View的属性,首先在res/values/ 下找到attrs.xml (如果没有可以创建一个), 在里面定义我们的属性和声明我们的整个样式。
然后就是定义属性值了,通过 方式定义属性值,属性名字同样也要起的见名知意,format表示这个属性的值的类型,类型有以下几种:
reference:引用资源 string:字符串 Color:颜色 boolean:布尔值 dimension:尺寸值 float:浮点型 integer:整型 fraction:百分数 enum:枚举类型 flag:位或运算
然后在布局中声明我们的自定义View
其中 xmlns:first="http://schemas.android.com/apk/res-auto" 是引入自己的命名空间,也可以写成xmlns:first="http://schemas.android.com/apk/res/com.shsany.practice"的命名空间,后面的包路径指的是项目的package。
在上面的第三个构造方法里获取MyFirstView 的属性。
publicMyFirstView(Context context, AttributeSet attrs, intdefStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.MyFirstView, defStyleAttr, 0);
if(typedArray != null) {
mText= typedArray.getString(R.styleable.MyFirstView_text);
mTextColor= typedArray.getColor(R.styleable.MyFirstView_textColor, Color.RED);
//默认设置为16sp,intx = typedArray.getDimensionPixelSize(R.styleable.MyFirstView_textSize, 16);
mTextSize= DisplayUtil.sptopx(context, (float) x);
}
typedArray.recycle();
/**绘制文本的宽长*/mPaint= newPaint();
mPaint.setTextSize(mTextSize);
mBound= newRect();
mPaint.getTextBounds(mText, 0, mText.length(), mBound);
}
然后再重写onDraw()方法绘制控件。
@Overrideprotected voidonDraw(Canvas canvas) {
super.onDraw(canvas);
if(mPaint!= null) {
/**设置圆形的颜色为红色*/mPaint.setColor(Color.RED);
canvas.drawCircle(getWidth()/2f, getWidth()/2f, getWidth()/2f, mPaint);
/**设置文本的颜色 及 位置 这里的x y值是文本左下角点的x y值*/mPaint.setColor(mTextColor);
canvas.drawText(mText, getWidth() / 2- mBound.width() / 2, getHeight() / 2+ mBound.height() / 2, mPaint);
}
}
此时的效果是
系统帮我们测量的高度和宽度都是MATCH_PARNET,当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure()方法,
重写之前先了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
下面是我们重写onMeasure代码:
@Overrideprotected voidonMeasure(intwidthMeasureSpec, intheightMeasureSpec) {
intwidthMode = MeasureSpec.getMode(widthMeasureSpec);
intwidthSize = MeasureSpec.getSize(widthMeasureSpec);
intheightMode = MeasureSpec.getMode(heightMeasureSpec);
intheightSize = MeasureSpec.getSize(heightMeasureSpec);
intwidth;
intheight;
if(widthMode == MeasureSpec.EXACTLY) {
/**如果规定了具体的数值直接等于*/width = widthSize;
} else{
/**如果没规定具体的数值,就根据当前文字的宽度加上内边距 就是这个控件的宽度*/floattextWidth = mBound.width();
intdesired = (int) (getPaddingLeft() + textWidth + getPaddingRight());
width = desired;
}
if(heightMode == MeasureSpec.EXACTLY) {
/**如果规定了具体的数值直接等于*/height = heightSize;
} else{
/**如果没规定具体的数值,就根据当前文字的高度加上内边距 就是这个控件的高度*/floattextHeight = mBound.height();
intdesired = (int) (getPaddingTop() + textHeight + getPaddingBottom());
height = desired;
}
/**判断宽高那个大 那个大听那个*/if(width>height){
height=width;
}else{
width=height;
}
/***最后调用父类方法,把View的大小告诉父布局。*/setMeasuredDimension(width, height);
}
此时运行的结果是
这样,我们的第一个控件已经实现了,下面我们来总结一下自定义View 的步骤:
1.先自定义一个view,重写其构造方法。
2.自定义view 的属性,在垢找方法
3.重写onMeasure(int,int)方法。(该方法可重写可不重写,具体看需求)
4.重写onDraw(Canvas canvas)方法。