其中PhoneWindow是最基本的窗口系统,是activity和view系统的交互接口。
每个activity都有一个PhoneWindow.
DecorView是所有view的祖先。
Viewgroup也继承View.
每个view负责绘制自己,viewgroup还负责通知view绘制。
流程分为3步:测量(Measure),布局(Layout),绘制(Draw)
流程从根视图ViewRoot的performTraversals()方法开始:
在上图标记出,还有一种情况经常要重写onMeasure(),就是判断specMode,区分at_most和exactly模式,分不同情况setMeasureDimension。
接下来是如果viewgroup.measure(),则有
主要就是一个draw方法,例子中也会结合讲到。主要有6步:一般我们只需重写onDraw方法即可
public class RectView extends View {
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private int mcolor = Color.RED;
public RectView(Context context) {
super(context);
initdraw();
}
public RectView(Context context, AttributeSet attrs) {
super(context, attrs);
//如果有自定义的属性要用到,需加入下列代码:(自定义属性如何引用,下面会讲到)
TypedArray typearray = context.obtainStyledAttributes(attrs , R.styleable.RectView);
mcolor = typearray.getColor(R.styleable.RectView_rect_color , Color.RED);//没有设置该属性,默认为第2参数
typearray.recycle();//获取资源必须及时回收
initdraw();
}
public RectView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initdraw();
}
private void initdraw() {
paint.setColor(mcolor);
paint.setStrokeWidth(1.5f);
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas); //默认的不计算padding
/*//没有对padding做出改变,padding属性无效
int width = getWidth();
int height = getHeight();
canvas.drawRect(0 , 0 , width , height , paint);*/
//设置对layoutpadding的改变
int paddingleft = getPaddingLeft();
int paddingright = getPaddingRight();
int paddingtop = getPaddingTop();
int paddingbottom = getPaddingBottom();
int width = getWidth() - paddingleft - paddingright;
int height = getHeight() - paddingtop - paddingbottom;
canvas.drawRect(paddingleft , paddingtop , paddingleft+width , paddingtop+height , paint);
}
//如果不对specMode分别处理,则wrap_content和match_parent的效果是一样的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthspecmode = MeasureSpec.getMode(widthMeasureSpec);
int widthspecsize = MeasureSpec.getSize(widthMeasureSpec);
int heightspecmode = MeasureSpec.getMode(heightMeasureSpec);
int heightspecsize = MeasureSpec.getSize(heightMeasureSpec);
if (widthspecmode == MeasureSpec.AT_MOST && heightspecmode == MeasureSpec.AT_MOST){
//如果宽度和高度都是用了wrap_content
setMeasuredDimension(400 , 400);//默认的为setMeasureDimension(getDefaultSiza(getSuggestedMinimumWidth() , height一样写法)
} else if (widthspecmode == MeasureSpec.AT_MOST){
//只有宽度为wrap_content
setMeasuredDimension(400 , heightspecsize);
} else if (heightspecmode == MeasureSpec.AT_MOST){
setMeasuredDimension(widthspecsize , 400);
}
}
}
如果要用到Layout()方法,可以在自定义view中再写一个onTouchEvent(),如下:
public boolean onTouchEvent(MotionEvent event){
int xx = (int) event.getX(); //在控件中的x
int yy = (int) event.getY();
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
lastx = xx;
lasty = yy;
break;
case MotionEvent.ACTION_MOVE:
setText("" + new Random().nextInt(10));
int offsetx = xx - lastx; //得到偏移的距离
int offsety = yy - lasty;
/*//layout(),实现控件在viewgroup中的触摸滑动
layout(getLeft()+offsetx ,
getTop() + offsety,
getRight() + offsetx,
getBottom() + offsety); //在移动过程中,不断重绘位置*/
break;
}
return true;
}
又比如想在控件中画些线条,可以在onDraw方法中添加代码:如:
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int width = getWidth();
int height = getHeight();
canvas.drawLine(width/2 , 0 , width/2 , height , paint);
}
}
坐标系分:A. android坐标系 和 B. view坐标系
A.android坐标系
B.view坐标系
各种计算结合如下图:
getWidth() = getRight() - getLeft();
getHeight() = getBottom() - getTop();