说到绘制,其实就是如何把一个view的对象,变成手机上可视的图形。很多人总结3个过程:测量,布局,绘制。这也是所有的要显示图形的程序所应该抽象的3个步骤,测量就是测量出你view的大小,布局就是要显示在屏幕的哪个坐标位置,绘制就是把图形画到屏幕上。view和viewgroup的处理方法是不同的。
view:
1.measure:要测量应该考虑的就是view的大小,重点看view中的measure,首先是判断是否有必要测量view中有一个mprivateflags是一个标志位,标志了view的所有的状态表示。判断是否要FROCE_LAYOUT。然后判断现在的测量的大小和一个之前测量的大小是否一样。之前的测绘mOldWidthMeasurSpec和mOldHighMeasurSpec存储。初始值为Integer.MINVALUE.
tip:谈到标志的存储,这里有很大的学问,要知道并不是所有的标志都是boolean存储。还可以使用int,而且int要保存的标志位和他的位数是一致的,也就是int有32位,他就可以表示32个标志。怎么做到?在java中位的操作就是用| & 和&~。当我们增加一个标志位的时候使用|,判断一个标志使用&,消除一个标志使用&~。int的每一个bit都可以表示一个标志位,首先你要定义各种标志位使用0x0100 0000这样,然后假如有一个状态值为0x0100 0000 和默认值一样&之后就是非0的值,在java中Boolean必须使用 == 来判断,所以mPrivateFlags & FORCE_LAYOUT就是判断是否含有这个标志位。当你要添加一个标志位的使用就要使用| 。删除使用&~。这样的操作在android flag中比比皆是。这样做的好处是什么?不用定义多个标志位。仅仅使用一个int值就可以了。另外还相对于boolean减少了空间。(关于boolean占用多少个字节来说,有人说1byte,有人说1bit。虚拟机实现不同,也有一定关系。但是大量的标志位的时候还是使用int的这种位操作更加靠谱)
mOldHighMeasurSpec为什么将这两个旧的宽高设置为0?含义仅仅是0?判断完,当需要测量的时候,就调用了 onMeasure(widthMeasureSpec, heightMeasureSpec);细心一点就会想为什么不使用mWidth这样容易记的东西?而是使用冗长的名称?onmeasure方法里面就使用了一个setMeasuredDimension来设置宽高,在view中实际使用mMeasuredWidth和mMeasuredHeight来表示view的宽高。所以即使是我们继承了view也要使用setMeasuredDimension来设置测量好的宽高。带着所有的疑问,让我们重点关注一个view的内部静态类MeasureSpec。
MeasureSpec:
这是一个测量中的最关键的类,他把一个int表示了两个概念:模式,大小。模式这里面定义了3种模式:UNSPECIFIED,EXACTLY,AT_MOST用来分别表示未知,已测量,最大可用。大小就是测量的大小。当你传递进来一个数值的时候这个时候包含了两种信息:如果是未知,就需要你亲自测量。如果是已知,就使用测量好的,如果是最大可用,就用可使用最大值。
叙述完概念之后,看一下实际的数值UNSPECIFIED是0x0000 0000,EXACTLY是0x1000 0000,AT_MOST是0x2000 0000.知道为什么我这么写出来吗?对又是标志位的使用,这里为什么可以把int表示为mode和size两种概念,就是前两位表示模式,后30为表示大小。这里面还定义了两个辅助的常量就是MODE_SHIFT是30也就是说后30位表示大小,MODE_MASK表示“面具”就是说0x3000 0000为什么说是面具?其实是英语含义,我这么叫他是因为他可以除去模式只留下大小信息。
模式:首先来说如何获取一个模式?measureSpec & MODE_MASK,之前就说过使用&来判断有无标志位。大小:获取大小的方法一样measureSpec & ~MODE_MASK消除了标志位就是大小信息。那么怎么生成一个这样的measureSpec ?mode+size就可以了。
这样应该理解了MeasureSpec,在onmeasure里面有一个getDefaultSize的方法,这个方法就是默认的处理测量的,在UNSPECIFIED的时候使用传递来的值,在EXACTLY的时候使用测绘值,最大可用也是默认值(也就是measureSpec附带的size大小)。那传递值是?getSuggestedMinimumWidth这个里面有,也就是说默认我不测绘的时候会有一个默认的宽高。就是背景大小和mMinWidth比较。背景实际是一个bitmap也就是看一下这个背景的大小。然后和xml中的mMinwidth设置的值做一个比较。最小的就是默认值。也正是AT_MOST的含义,求出最大的可用宽高。
tip:那么什么时候会传递EXACTLY?什么时候传递AT_MOST,下面的仅仅是rootview的策略!就是decroview
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
也就是说既然view必须设置LayoutParams。这里就是他的用途,当你是一个填充窗体的时候,那么你的view所占据的大小就是确定的并且是父viewgroup大小windowSize(因为只有在viewgroup中才会有view)。在包裹内容的时候,view的大小就是可用的最大大小也是windowsize。这里面就是说无论是什么都要给rootview一个windowsize,真正的布局划分和测量在viewgroup中!
到处为止measure就算完了。有点乱。其实就是measure回调onMeasure,其实最关键的并不在这里,而在viewgroup中。
2layout:这个是布局,布局就是把位置表示出来,注意的是这里的位置view中的mLeft等变量表示的,但是却不是屏幕上的绝对位置,是基于父类的位置,这个概念很重要。关系到了layout的使用首先setFrame来看一下是否是布局变动了,如果变动了就设置到变量中,这里的onlayout就比较鸡肋了,仅仅是提供了一个回调。我们要改变布局的时候可以直接使用layout,而不用等待onlayout的回调,其实主要是setframe,但是这个是受保护的,我们不可以随便使用。
tip: 关于这个layout最关键的其实是我们如何使得view进行移动的一个方法,这里还有一个方法叫做scrollto和scrollby.这些概念都是基于view怎么布局的问题,一个view要想布局就要知道父布局,然后进行偏移,这个偏移是mScrollX和mScrollY通过scrollTo来移动的,表示把view移动到基于当前view的x和y处(坐标原点在view左上角),然后这个x,y就被设置为了mScrollX和mScrollY。scrollby是调用了scrollTo(mScrollX + x, mScrollY + y);也就是说是基于当前的位置再移动x,y。
了解了这个概念之后你就可以自定义移动view了不要以为移动只可以用scrollTo,还有就是layout也可以,但是layout实际上移动的是view在viewgroup中的位置,而scrollTo是移动的view在view本身的布局的位置。影响view在屏幕中的绝对位置的有3个:mLeft,mScrollX,paddingLeft也就是说mLeft是layout控制,mScrollX是由ScrollTo控制。主要就是利用这两个东西来控制view。所以一个view只能在父布局中移动。但是你要明白的是你的界面控制是你来控制的,所有移动都可以实现。只是布局设计的问题。
3draw:绘制,首先是绘制背景mBGDrawable,确定背景大小,然后执行canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);好好想一下这一个,你就会体会出view是无穷去界的,而我们的眼睛限制了我们。你要知道的所有的东西都在画布canvas上,这个是进行了画布的移动。画上了背景在把画布移动回去。也就是说canves是无穷*的。最后执行dispatchdraw其实就是画view的各个控件。这个方法在子类中扩展
viewgroup:
1。measureChildren:里面调用measureChild。首先还是检验模式,当为确定或者是最大值得时,无论包裹内容还是占据父窗体都是传递来的大小。当无法确定的时候,使用的是0.实际上在继承了viewgroup之后,你可以复写onmeasure方法,进行自定义的测量。
2.layout,这个viewgroup本身就没有实现,由于是交给子类实现的,所以根本就设为空方法。
3.draw viewgroup管理的是view的布局和大小,本身是没有太多的绘制,也没有实现父类。都交给了子类。
关于绘制view,更加关键的其实是实践,多布局和绘制一些view和viewgroup。