安卓自定义view(一)- 自定义view的基础知识

view的视图架构

安卓自定义view(一)- 自定义view的基础知识_第1张图片
image.png

每一个Activity都包含的一个window,这个window的实现类是Phone Window。后Phone Window是顶层的view,叫docor view。docor view中有一个叫content的FrameLayout,我们经常在Activity的onCreate中使用setContentView(R.layout.id)设置我们自定义的视图,就是添加到这个叫content的framelayout中。然后上面还有一个TitleActionBar,这个就是TitleBar。

在content这个view中,是我们在xml中自定义的view。从图中可以看出,我们自定义的view形成了一个树形结构。viewGroup中可以放置若干个view,而由于viewGroup本身也继承了view,所以ViewGroup的子view也可以是另一个Viewgroup,这样形成一个树形结构。

MeasureSpec

这是一个很重要的概念,一个view的MeasureSpec可以看作这个view暂定的大小。
MeasureSpec是一个32位的int值。前两位表示测量模式,后两位表示暂定的测量大小。
测量模式有三种,分别是:UNSPECIFIED,即父容器对子容器的大小不作任何要求;EXACTLY,父容器已经确定了自容器的精确大小;AT_MOST,父容器指定了一个最大的大小,view不能超过这个大小。

MeasureSpec由当前view的parentView调用getChildMeasureSpec生成,传入的三个参数分别是,parentView的MeasureSpec(父容器的暂定大小),parentView的左右上下内边距和当前view的LayoutParams(自容器的期望大小)。

/**
 * Does the hard part of measureChildren: figuring out the MeasureSpec to
 * pass to a particular child. This method figures out the right MeasureSpec
 * for one dimension (height or width) of one child view.
 *
 * 这个方法将根据父容器的MeasureSpec和子View LayoutParams中的宽/高
 * 为子View生成最合适的MeasureSpec
 *
 * @param spec 父容器的MeasureSpec
 * @param padding 父容器的内间距(padding)加上子View的外间距(margin)
 * @param childDimension 子View的LayoutParams中封装的width/height
 * @return 子View的MeasureSpec
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // ① 对父容器的MeasureSpec进行解包
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    // ② 减去间距,得到最大可用空间
    int size = Math.max(0, specSize - padding);

    // 记录子View最终的大小和测量模式
    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // ③ 父容器是精准测量模式
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {               //如果子view在LayoutParam中指定了大小,那么子view的resultSize 就是该大小,模式是EXACTLY
            resultSize = childDimension; 
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {       //如果子view中LayoutParams是MATCH_PARENT,则view的大小等于最大可用大小,测量模式是EXACTLY
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {   //如果子view中LayoutParams是WRAP_CONTENT,则view的大小等于最大可用大小,测量模式是AT_MOST

            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

     // ④ 父容器指定了一个最大可用的空间
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // ⑤ 父容器不对子View的大小作出限制
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    // ⑥ 将最终的size和mode打包为子View需要的MeasureSpec
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

在view中每个子view的MeasureSpec都是由父容器的MeasureSpec、间距和自容器的LayoutParam来生成,逐级往上,最顶层的view,也就是docor view的MeasureSpec是怎么生成的呢?docor view的MeasureSpec由其上层的window的大小和docor view的自身的LayoutParam来生成。

你可能感兴趣的:(安卓自定义view(一)- 自定义view的基础知识)