在继承View类时,需要重写两个方法,分别是onMeasure和onLayout。
1,在方法onMeasure中调用setMeasuredDimension方法
void android.view.View.setMeasuredDimension(int measuredWidth, int measuredHeight)
public class MyTestView extends View {
private Bitmap backGround;
private Context mContext;
public MyTestView(Context context) {
super(context);
init(context);
// TODO Auto-generated constructor stub
}
public MyTestView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
// TODO Auto-generated constructor stub
}
public MyTestView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
// TODO Auto-generated constructor stub
}
private void init(Context mContext){
mContext = mContext;
backGround = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.jiaoyu);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
System.out.println("widthMeasureSpec"+widthMeasureSpec+" heightMeasureSpec"+heightMeasureSpec);
setMeasuredDimension(measureWidth
(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int model = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
if (model == MeasureSpec.EXACTLY) {
result = size;
} else {
if (backGround != null) {
result = backGround.getWidth() + getPaddingLeft()
+ getPaddingRight();
}
if (model == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
private int measureHeight(int heightMeasureSpec){
int result = 0;
int model = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
if (model == MeasureSpec.EXACTLY) {
result = size;
} else {
if (backGround != null) {
result = backGround.getHeight() + getPaddingTop()
+ getPaddingBottom();
}
if (model == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO Auto-generated method stub
super.onLayout(changed, left, top, right, bottom);
}
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
this.setBackgroundResource(R.color.red);
canvas.save();
canvas.translate(15, 0);
canvas.drawBitmap(backGround, 0, 0, new Paint());
canvas.restore();
}
}
解析View.MeasureSpec类
android.view.View.MeasureSpec
MeasureSpec对象,封装了layout规格说明,并且从父view传递给子view。每个MeasureSpec对象代表了width或height的规格。
MeasureSpec对象包含一个size和一个mode,其中mode可以取以下三个数值之一:
UNSPECIFIED,1073741824 [0x40000000],未加规定的,表示没有给子view添加任何规定。
EXACTLY,0 [0x0],精确的,表示父view为子view确定精确的尺寸。
AT_MOST,-2147483648 [0x80000000],子view可以在指定的尺寸内尽量大。
view在测量显示的时候是受到父View的限制的,如果父View的宽高设置为wrap_content,那么就对子View的显示没有什么限制,可以让子View按照最大的尺寸显示,如果父View的尺寸限定了,那么子View的显示的大小一定不会超过父view,如果超过的话会被截掉。
MeasureSpec,是从父view传给子View的,所以MeasureSpec中规定了子View显示的最大区域。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<com.fy.communityapp.view.MyTestView
android:layout_width="fill_parent"
android:layout_centerInParent="true"
android:layout_height="fill_parent"
android:paddingLeft="10dp"
android:visibility="gone"
android:paddingRight="20dp"
>
</com.fy.communityapp.view.MyTestView>
</RelativeLayout>
如果我们给RelativeLayout设定宽高为 match-parent或者wrap_content,那么MyTestView的宽显示的最大区域都为整个屏幕的宽度(高度同理),如过 宽度设定为一个固定的值的话,那么子View MyTestView 显示的最大宽度也为这个值。
int model = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
而我们在计算的时候 这个size就是RelativeLayout测量出的MyTestView显示的最大宽度,这个最大宽度是不会超过父View的宽度的
例如:
if (model == MeasureSpec.EXACTLY) {
result = size;
} else {
if (backGround != null) {
result = backGround.getHeight() + getPaddingTop()
+ getPaddingBottom();
}
if (model == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
如果你给MyTestView设定的固定的值比父view设定的固定的值要大,那么MyTestView最多显示父View宽度的空间,如果设定的固定的值比父view设定的固定的值要小,那么显示的是MyTestView设定的值。
如果 MyTestView设定的宽为match-parent的话,那么显示的也是屏幕的宽度,如果设定的是wrap_content,那么MyTestView所能显示的最大宽度为屏幕宽,但是显示的是MyTestView的所能显示的宽度就是以下计算结果
if (backGround != null) {
result = backGround.getHeight() + getPaddingTop()
+ getPaddingBottom();
}
if (model == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
得到的值,肯定是比size要小的。
那么这个MyTestView的所能显示的宽度,是如何计算的呢?
包括你显示的Text或者图片的宽度+paddingLeft+paddingRight
就是MyTestView所能显示的宽度了。
2、onDraw()
如果我们自定义的控件中画一个图
如果我们给MyTestView设定了属性Padding的话,就像我们代码中一样,paddingleft是10,paddingRight是20,那么我们如果直接用
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
this.setBackgroundResource(R.color.red);
canvas.drawBitmap(backGround, 0, 0, new Paint());
}
那么显示的时候宽度虽然算上paddingleft和paddingRight了但是,图像是在画的时候,是在最左边画起的如图
右边红色的宽度就是paddingLeft+paddingRight
如果想要我们的左填充起到应有的效果的话,那么我们就移动canvas 15是10dp转换后的px值
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
this.setBackgroundResource(R.color.red);
canvas.save();
canvas.translate(15, 0);
canvas.drawBitmap(backGround, 0, 0, new Paint());
canvas.restore();
}
这样就得到我们想要的结果了,左边padding10dp,右边padding20dp