自定义View

view的显示过程:

ActivityThread中,activity对象被创建后,会将DecorView添加到window中(控制DecorView的外观和行为策略,有唯一的实现类PhoneView)同时会创建ViewRoot对象将window和view关联起来,ViewRoot对象的performTraversal方法完成顶级View(decorview)的mesure,layout,draw方法,measure方法会调用onMeasure方法,onMeasure方法会对所有子元素进行measure过程

创建自定义View

  • 1.自定义View的属性
    attrs:我们要获取的属性的资源ID的一个数组,就是从一堆属性中我们希望查询什么属性的值,AttributeSet可以获得布局文件中定义的所有属性(attrs:定义属性类型;styles:定义属性值)
    如key和value:attrName = layout_width , attrVal = @2131165234
    TypedArray:其实是用来简化我们的工作的,比如上例,如果布局中的属性的值是引用类型(比如:@dimen/dp100),如果使用AttributeSet去获得最终的像素值,那么需要第一步拿到id,第二步再去解析id。而TypedArray正是帮我们简化了这个过程。
    declare-styleable:
    attr不依赖于styleable,styleable只是为了方便attr的使用。
    我们自己定义属性完全可以不放到styleable里面,比如直接在resources文件中定义一些属性:
    attr name=”custom_attr1” format=”string”/>
    attr name=”custom_attr2” format=”string”/>
    定义一个attr就会在R文件里面生成一个Id,那么我们去获取这个属性时,必须调用如下代码:
    int[] custom_attrs = {R.attr.custom_attr1,R.custom_attr2};
    TypedArray typedArray = context.obtainStyledAttributes(set,custom_attrs);
    而通过定义一个styleable,我们可以在R文件里自动生成一个int[],数组里面的int就是定义在styleable里面的attr的id。所以我们在获取属性的时候就可以直接使用styleable数组来获取一系列的属性。
    declare-styleable name=”custom_attrs”>
    attr name=”custom_attr1” format=”string” />
    attr name=”custom_attr2” format=”string” />
    declare-styleable/>
    获取:
    TypedArray typedArray = context.obtainStyledAttributes(set,R.styleable.custom_attrs);
  • 2.从View的构造方法中获得我们自定义的属性
  • 3.重写onMeasure,onLayout,onDraw方法(exactly,at_most,unspecified)
    view的measure方法会调用onMeasure方法,默认会调用getDefaultSize()方法来获取视图的大小,setMeasuredDimension()方法来设定测量出的大小;
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    接下来我们来看 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)源码
   public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

 。;       switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

首先会判断specMode,如果是AT_MOST和EXACTLY就是view测量后的大小,UNSPECIFIED我们要看getSuggestedMinimumHeight()源码

protected int getSuggestedMinimumHeight() {
        return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());

    }

UNSPECIFIED是用于系统内部的测量过程,这个取决于view是否设置背景
ViewGroup除了完成自己的measure过程以外,还会遍历调用子元素的measure方法。ViewGroup是一个抽象类,
这里写图片描述
onMeasure方法需要各个子类的具体实现
eg:linearlayout的onMeasure方法

layout的作用是ViewGroup用来确定子元素的位置,layout方法确定view本身的位置,onLayout确定子元素的位置,viewgroup在onlayout中遍历所有的子元素并调用layout方法,子元素又会调用onlayout,确定所有子元素的位置,onLayout方法需要具体实现(View和ViewGroup均没有具体实现),和实际布局相关。
draw方法会遍历所有子元素的draw方法,一层层传递下去

  • 4.视图状态:enabled(是否可用),selected(是否选中),pressed(是否按下),foucsed(是否获得焦点)
  • 5.调用视图的setVisibility()、setEnabled()、setSelected()等方法时都会通过调用invalidate()方法来导致视图重绘

  • 补充:
    pixs = dips * (density/160)
    关于layout_weight:
    android:layout_weight的真实含义是:一旦View设置了该属性(假设有效的情况下),那么该 View的宽度等于原有宽度(android:layout_width)加上剩余空间的占比!
    设屏幕宽度为L,在两个view的宽度都为match_parent的情况下,原有宽度为L,两个的View的宽度都为L,那么剩余宽度为L-(L+L) = -L, 左边的View占比三分之一,所以总宽度是L+(-L)*1/3 = (2/3)L.事实上默认的View的weight这个值为0,一旦设置了这个值,那么所在view在绘制的时候执行onMeasure两次的原因就在这。
    Google官方推荐,当使用weight属性时,将width设为0dip即可,效果跟设成wrap_content是一样的。这样weight就可以理解为占比了!

你可能感兴趣的:(layout,布局,weight,draw,measure)