什么是View
View是Android所有控件的基类。常见的View,如button,textview,imageview等等,用于展示数据和信息的。除了View还有ViewGroup,它是容纳View的容器,既然它是视图。说到view不得不说自己他的坐标系,这样才能更好的理解view和开发view 相关的功能。从下图可以看出,Android视图的坐标系,与我们在数学中学到的二位坐标有点不一样,左上角就是坐标的原点,向右为X轴的正向,向下为Y轴的正向。
坐标及相关属性
如何确定一个view的位置呢,既然是有坐标系,那么一个view的位置就好确定了。其实这么多获取位置,很容混淆的。
- 获取view的位置:
getTop():获取到的是view自身的顶边到其父布局顶边的距离
getLeft():获取到的是view自身的左边到其父布局左边的距离
getRight():获取到的是view自身的右边到其父布局左边的距离
getBottom():获取到的是view自身底边到其父布局顶边的距离
根据视图的关系,获取View的宽度和长度
height = getbottom()-getTop();
width = getRight()-getLeft();
- 获取手指位置
MotionEvent提供的方法:
getX(): 获取点击事件距离控件左边的距离,即视图坐标
getY(): 获取点击事件距离控件顶边的距离,即视图坐标
getRawX():获取到的是点击事件距离整个屏幕左边的距离,即绝对坐标
getRawY():获取到的是点击事件距离整个屏幕顶边的距离,即绝对坐标
自定义view
为了实现自定义view,必须继承View并重写方法,比如 onMeasure(int, int),onLayout(boolean, int, int, int, int), onDraw(android.graphics.Canvas) 等等,但是构建函数必须要重写的。详见View的api
下面是自定义view常用的重写方法
方法 | 描述 |
---|---|
构建函数 | 当从代码创建或者从布局文件中创建时调用 |
onMeasure(int, int) | 测量这个view以及它的所有子view(必须要实现的方法) |
onLayout(boolean, int, int, int, int) | 当view给子view分配大小和位置时会调用 |
onDraw(android.graphics.Canvas) | 当view渲染内容时会调用 |
onSizeChanged(int, int, int, int) | view大小发生变化时调用 |
onTouchEvent(MotionEvent) | 屏幕触摸事件发生时触发 |
-
重写View时,有四个构造函数可以重写,该如何选择,有什么区别?
- View(Context context) 在代码创建时,执行这个构造函数;
- View(Context context, @Nullable AttributeSet attrs) ,View(Context context, @Nullable AttributeSet attrs), View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) 从布局文件加载时会执行构造函数。
-
size,padding,margins
- View的大小时通过width,height来定义,但是有两套的width,height的值,第一套:getMeasuredWidth(),getMeasuredHeight(),获取的原始组件的宽高,在配置文件或者代码中设置的大小;第二套,width和height是在屏幕的实际的尺寸。这两个值大部分是相等的。比如说,在父布局的onLayout()方法或者该View的onDraw()方法里调用measure(0, 0),二者的结果可能会不同(measure中的参数可以自己定义)。
- 在测量他的大小时,还要考虑padding,通过getPadding(int ,int ,int ,int)或者getPaddingLeft()…等等,但是view是没有Margins,ViewGroup提供。
-
布局
布局有两个过程,Measure和layout两个过程- measure过程实现了 measure(int, int),自顶向下的遍历view树进行测量。
- onLayout(int ,int ,int,int) 自顶向下遍历的,父view负责放置每个子view位置,通过measure得到的测量数据。
-
在设置view大小时,一般有三种方式:具体大小、match_parent、wrap_content。View.MeasureSpec类告诉父view怎么测量和放置view。
- EXACTLY:具体大小,保证所有继承的view都符合这个尺寸,对应的是具体大小、match_parent。
- UNSPECIFIED:父view不对子view做限制,它想多大就多大,有特殊的测量需求。
- AT_MOST:对应于wrap_content。
-
绘制
To force a view to draw, call invalidate().
下面自定义view,实现可拖动功能,分别设置具体大小,match_parent,wrap_content三种模式,从日志中就能看出他们的对应关系。
public class CustomView extends View {
private static final String TAG = CustomView.class.getSimpleName();
private Paint mPaint = null;
private int dx;
private int dy;
private int startX;
private int startY;
private Canvas mCanvas;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLUE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i(TAG, "onMeasure");
setMeasuredDimension(getDefaultSize(widthMeasureSpec), getDefaultSize(heightMeasureSpec));
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
startY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
dx = (int) event.getX() - startX;
dy = (int) event.getY() - startY;
layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);
invalidate();//onDraw
// requestLayout();//onMeasure->onLayout
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.i(TAG, "onLayout");
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mCanvas = canvas;
canvas.drawCircle(300, 300, 300, mPaint);
Log.i(TAG, "onDraw");
}
private int getDefaultSize(int measureSpec) {
int defaultSize = 100;
int measureMode = MeasureSpec.getMode(measureSpec);
int measureSize = MeasureSpec.getSize(measureSpec);
switch (measureMode) {
case MeasureSpec.UNSPECIFIED:
Log.i(TAG, "MeasureSpec.UNSPECIFIED");
return defaultSize;
case MeasureSpec.AT_MOST:
Log.i(TAG, "MeasureSpec.AT_MOST");
return measureSize;
case MeasureSpec.EXACTLY:
Log.i(TAG, "MeasureSpec.EXACTLY");
return measureSize;
}
return -1;
}
}
- invalidate,postInvalidate,requestLayout区别
- invalidate,postInvalidate这个两个其实是一样的,只是使用场景不一样而已,invalidate必须在UI线程中使用,postInvalidate在非UI线程中使用。
用invalidate刷新view时,view只进行:onDraw - requestLayout :
刷新时进行散步操作:onMeasure->onLayout,在代码我注释掉了,解注释后,根据日志就能看出刷新的机制。
- invalidate,postInvalidate这个两个其实是一样的,只是使用场景不一样而已,invalidate必须在UI线程中使用,postInvalidate在非UI线程中使用。