Android 自定义View
自定义View的最基本的三个方法分别是:onMeasure()、onLayout()、onDraw();
View 在activity中显示出来,要经历测量、布局和绘制三个步骤;
分别对应三个动作:measure、layout、和draw。
*测量:onMeasure()决定View的大小;
*布局:onLayout()决定View在ViewGroup中的位置;
*绘制:onDraw()决定绘制这个View。
自定义控件分类
*自定义View:只需要重写onMeasure()和onDraw()
*自定义ViewGroup:则只需要重写onMeasure()和onLayout()
自定义View基础
视图View的分类
*单一视图:即一个View,如TextView(不包含子View)
*视图组 :即多个View组成的ViewGroup,如LinearLayout(包含子View)
View视图结构
1.PhoneWindow是android系统中最基本的窗口系统,继承自Windows类,
负责管理界面显示以及事件响应,它是activity与View系统交互的接口
2.DecorView是PhoneWindow中的起始节点View,继承于View类,作为整个
视图容器来使用,用于设置窗口属性,它的本质是一个FrameLayout
3.ViewRoot在Activity启动时创建,负责管理、布局、渲染窗口UI等。
View的位置由4个顶点决定的
4个顶点的位置描述分别由4个值决定:
*Top:子View上边界到父view上边界的距离;
*Left:子view左边界到父View左边界的距离;
*Bottom:子view下边界到父View上边界的距离;
*Right:子View右边界到父View左边界的距离;Measure
1.系统为什么要有measure过程?
Android推荐以自适应的方式进行布局,在绘制UI过程过程中无法具体到控件的尺寸,所以需要measure过程进行测量。
2.measeure过程都做了什么事情?
计算自适应控件的具体尺寸,并映射到屏幕上。
3.对于自适应的尺寸机制,如何合理的测量一颗View树?
通过父布局尺寸来设置子View
4.那么ViewGroup是如何向子View传递限制信息的?
是通过Measurespec 来限制子View的;
MeasureSpec = mode + size ;
测量规格,封装了父容器对View的布局上的限制,内容提供了宽高的信息(SpacMode、SpaceSize),SpaceSize是指在某种SpaceMod下的参考尺寸
SpaceMode有以下三种:
*UNSPECIFIED
父控件不对子View有任何的限制,这种一般用于系统内部,表示一种测量状态(如ScrollView)
*EXACTLY
父控件已经知道你所需要的精确大小,
*AT_MOST
子View的大小不能大于父控件给予的指定尺寸,具体大小,需要自己实现。
5.ScrollView嵌套ListView的问题?onMeasure()方法中常用的方法:
1.getChildCount():获取子View的数量;
2.getChildAt(i):获取第i个子控件;
3.subView.getLayoutParams().width/height:设置获取子控件的宽或高;
4.measureChild(child,withMeasureSpec,heightMeasureSpec):测量子View的宽高;
5.child.getMeasureHeight/width():执行完measureChild()方法后就可以通过这种方式获取子View的宽高值;
6.getPaddingLeft/Right/Top/Bottom():获取控件的四周内边距;
7.setMeasuredDimension(width,height):重新设置控件的宽高。
实例: 自定义ViewGroup实现简单缩进;
//缩进的尺寸;
private static final int OFFSET = 100;
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//1.测量自身;
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//2.为每一个子View计算测量的限制信息;
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
//3.把上一步的限制信息,传递给每一个子View,然后子View开始
//measure自己的尺寸;
int childCount = getChildCount();
for (int i = 0;i < childCount;i++){
View child = getChildAt(i);
ViewGroup.LayoutParams lp = child.getLayoutParams();
int childWidthSpec = getChildMeasureSpec(widthMeasureSpec,0,lp.width);
int childHeightSpec = getChildMeasureSpec(heightMeasureSpec,0,lp.height);
child.measure(childWidthSpec,childHeightSpec);
}
int width = 0;
int height = 0;
//4.获取View测量完成后的尺寸;
//5.ViewGroup根据自身的情况,计算自己的尺寸;
switch (widthMode){
case MeasureSpec.EXACTLY:
width = widthSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i= 0; i < childCount;i++){
View child = getChildAt(i);
int widthAddOffset = i * OFFSET+child.getMeasuredWidth();
width = Math.max(width,widthAddOffset);
}
break;
default:
break;
}
switch (heightMode){
case MeasureSpec.EXACTLY:
height = heightSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
for (int i= 0; i < childCount;i++){
View child = getChildAt(i);
height += child.getMeasuredHeight();
}
break;
default:
break;
}
//6.保存自身的尺寸;
setMeasuredDimension(width,height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//1.遍历子View
//2.确定自己的规则
//3.子View的测量尺寸
//4.left,top,right,bottom,
//6.child,layout
int left = 0;
int top = 0;
int right = 0;
int bottom = 0;
int childCount = getChildCount();
for (int i = 0; i < childCount;i++){
View child = getChildAt(i);
left = i * OFFSET;
right = left +child.getMeasuredWidth();
bottom = top + child.getMeasuredHeight();
child.layout(left,top,right,bottom);
top += child.getMeasuredHeight();
}
}实现效果如图;
