android 自定义View(一)

796 阅读4分钟

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();
   }

}

实现效果如图;