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

view的视图架构 安卓自定义view(一)-|安卓自定义view(一)- 自定义view的基础知识
文章图片
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(一)-|安卓自定义view(一)- 自定义view的基础知识】这是一个很重要的概念,一个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_MOSTresultSize = 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来生成。

    推荐阅读