View的作业原理
1.初识ViewRoot和DecorView
ViewRoot
对应ViewRootImpl,它是连接WindowManager和DecorView的纽带,View的三大流程均经过ViewRoot来完结的。
Activity创立后,把DecorView添加到Window中,一起创立ViewRootImpl目标,将ViewRootImpl和DecorView关联起来。
root = new ViewRootImpl(view.getContext(),display);
root.setView(view, wparams, panelParentView);
View的制作流程从ViewRoot#performTraversals开始,经过measure、layout、draw终究将一个View制作出来:
- measure进程决议了View的宽/高,完结后可经过getMeasuredWidth/getMeasureHeight办法来获取View丈量后的宽/高。
- Layout进程决议了View的四个顶点的坐标和实际View的宽高,完结后可经过getTop、getBotton、getLeft和getRight拿到View的四个定点坐标。
- Draw进程决议了View的显现,完结后View的内容才干呈现到屏幕上。
DecorView
作为顶层View,包括一个竖直LinearLayout,里边包括上面的标题栏,下面的内容栏 Activity中,setContentView便是拿到的内容栏。 DecorView其实是一个FrameLayout,View层的事情都先经过DecorView,然后才传递给咱们的View。
2.了解MeasureSpec
2.1MeasureSpec
- MeasureSpec代表一个32位的int值,高2位为SpecMode,低30位为SpecSize,SpecMode是指丈量形式,SpecSize是指在某种丈量形式下的规格巨细。 MpecMode有三类:
- UNSPECIFIED: 父容器不对View进行任何约束,要多大给多大,一般用于体系内部(parent不对child强加约束,child要多大给多大)
- EXACTLY:
父容器检测到View所需求的精确巨细,这时候View的终究巨细便是SpecSize所指定的值,对应LayoutParams中的match_parent和详细数值这两种形式。(parent现已决议child精确巨细,child要依据这个巨细)
- AT_MOST
父容器指定了一个可用巨细即SpecSize,View的巨细不能大于这个值,不同View完结不同,对应LayoutParams中的wrap_content。(child能够想要多大给多大,但是有一个上限,wrap_content)
2.2MeasureSpec和LayoutParams的对应联系
关于一个View,能够设置LayoutParams来指定宽高,体系会综合该LayoutParams和parent施加的MeasureSpec,得出终究应用于该View的MeasureSpec;
而关于DecorView,由于其没有parent,所以取而代之的是Window的size,结合自己的LayoutParams得出终究的MeasureSpec。
MeasureSpec一旦确认,onMeasure中就能够确认View的宽高。
2.2.1 DecorView的MeasureSpec核算进程:
在ViewRootImpl的measureHierarchy中,核算了DecorView的MeasureSpec。desiredWindow*为window的size:
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
getRootMeasureSpec中根据window size和DecorView的LayoutParams核算出MeasureSpec。规则很简单,假如是MATCH_PARENT或许固定的值,则spec mode为EXACTLY,一起size设置为相应的值;假如是WRAP_CONTENT,则spec mode为AT_MOST,size为window size:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
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;
}
return measureSpec;
}
2.2.2 普通View的MeasureSpec核算进程:
以ViewGroup的measureChildWithMargins为例,在该办法中会核算child的MeasureSpec。核算完结后,会直接对该view进行measure。核算时也会考虑parent的padding,child的margin:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
详细的核算进程在getChildMeasureSpec中进行:
- child指定确认的size,则遵从child的这个size设置。
- child指定match_parent,假如parent表示能够exactly,则其size为parent size;假如parent表示atmost,即其size也不确认,则其atmost为parent size。
- child指定wrap_content,则此时size由child自己决议,所以只约束其atmost为parent size。
3.View的作业流程
View三大流程:
-
measure
:确认View的丈量宽/高 -
layout
: 确认View的终究宽/高和四个顶点的方位 -
draw
:将View制作到屏幕上
3.1measure进程
3.1.1measure进程
3.1.1.1View的measure进程
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
- setMeasuredDimension办法会设置View的宽/高的丈量值
- getDefaultSize办法返回的巨细便是measureSpec中的specSize,也便是View丈量后的巨细,绝大部分状况和View的终究巨细(layout阶段确认)相同。
- getSuggestedMinimumWidth办法,作为getDefaultSize的第一个参数(主张宽度)
- 直接承继View的自界说控件,需求重写onMeasure办法并且设置 wrap_content时的自身巨细,否则在布局中运用了wrap_content相当于运用了match_parent。 解决办法:在onMeasure时,给View指定一个内部宽/高,并在wrap_content时设置即可,其他状况沿用体系的丈量值即可。
3.1.1.2ViewGroup的measure进程
- 关于ViewGroup来说,除了完结自己的measure进程之外,还会遍历去调用一切子元素的measure办法,个个子元素再递归去执行这个进程,和View不同的是,ViewGroup是一个抽象类,没有重写View的onMeasure办法,供给了measureChildren办法。
- measureChildren办法,遍历获取子元素,子元素调用measureChild办法
- measureChild办法,取出子元素的LayoutParams,再经过getChildMeasureSpec办法来创立子元素的MeasureSpec,接着将MeasureSpec传递给View的measure办法进行丈量。
- ViewGroup没有界说其丈量的详细进程,由于不同的ViewGroup子类有不同的布局特征,所以其丈量进程的onMeasure办法需求各个子类去详细完结。
- measure完结之后,经过getMeasureWidth/Height办法就能够获取View的丈量宽/高,需求留意的是,在某些极端状况下,体系可能要屡次measure才干确认终究的丈量宽/高,比较好的习惯是在onLayout办法中去获取丈量宽/高或许终究宽/高。 如何在Activity中获取View的宽/高信息 由于View的measure进程和Activity的生命周期不是同步进行,假如View还没有丈量完毕,那么获取到的宽/高便是0;所以在Activity的onCreate、onStart、onResume中均无法正确的获取到View的宽/高信息。下面给出4种解决办法。
- Activity/View#onWindowFocusChanged。 onWindowFocusChanged这个办法的含义是:VieW现已初始化完毕了,宽高现已准备好了,需求留意:它会被调用屡次,当Activity的窗口得到焦点和失掉焦点均会被调用。
- view.post(runnable)。 经过post将一个runnable投递到消息行列的尾部,当Looper调用此runnable的时候,View也初始化好了。
- ViewTreeObserver。 运用ViewTreeObserver的众多回调能够完结这个功用,比方OnGlobalLayoutListener这个接口,当View树的状况发送改动或View树内部的View的可见性发生改动时,onGlobalLayout办法会被回调。需求留意的是,伴随着View树状况的改动,onGlobalLayout会被回调屡次。
- view.measure(int widthMeasureSpec,int heightMeasureSpec)。 (1). match_parent: 无法measure出详细的宽高,由于不知道父容器的剩余空间,无法丈量出View的巨细 (2). 详细的数值(dp/px):
int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100,MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
(3). wrap_content:
int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1<<30)-1,MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
3.2layout进程
3.3draw进程
4.自界说View
4.1自界说View分类
4.1.1 承继View重写onDraw办法
首要用于完结一些不规则作用,不方便经过组合布局来完结,往往需求静态或许动态的现实一些不规则的图形。需求自己完结wrap_content,padding也需求自己处理
4.1.2 承继ViewGroup派生特殊的Layout
首要用于完结自界说的布局,咱们重新界说一种新布局,作用看起来很像几种View组合在一起。办法稍微杂乱一些,需求适宜的处理ViewGroup的丈量、布局这两个进程,并搭档处理子元素的丈量和布局进程。
4.1.2 承继特定View(比方TextView)
比较常见,一般用于扩展已有的View的功用。比方TextView,不需求自己支撑wrap_content,padding等
4.1.2 承继特定ViewGroup(比方LinearLayout)
当某种作用看起来像几种View组合在一起。采用这种办法不需求自己处理ViewGroup的丈量和布局这两个进程。办法2更接近View底层。
4.2自界说View须知
4.2.1 让View支撑wrap_content
直接承继View或许ViewGroup的控件,假如不在onMeasure中对wrap_content做特殊处理,那么运用时无法到达预期作用
4.2.2 假如有必要,让你的View支撑padding
直接承继View的控件,假如不在draw办法中处理padding,那么这个属性就无效。 直接承继ViewGroup的控件需求在onMeasure和onLayout中考虑padding和自元素的margin对其形成的影响,不然会导致失效。
4.2.3 尽量不要在View中运用Handler,没必要
View本身供给了post系列的办法
4.2.4 View中假如有线程或许动画,需求及时中止,参考View#onDetachedFromWindow
假如有线程或许动画要中止时,那么onDetachedFromWindow是个很好的时机。 包括此View的Activity推出或许当时View被remove时,View的onDetachedFromWindow办法会被调用(相对应办法onAttachedToWindow)。
4.2.5 View带有滑动嵌套景象时,需求处理好滑动抵触
假如有滑动抵触,会严重影响View的作用
4.3自界说View示例
4.4自界说View的思想
总结:
步骤 | 关键字 | 作用 |
---|---|---|
1 | 构造函数 | View初始化 |
2 | onMeasure | 丈量View巨细 |
3 | onSizeChanged | 确认View巨细 |
4 | onLayout | 确认子View布局(自界说View包括子View时有用) |
5 | onDraw | 实际制作内容 |
6 | 供给接口 | 控制View或监听View某些状况。 |