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制作出来:

Android开发这么多年,你真的懂得了UIView原理吗,搞定它能给你工作带来不少便利,提高效率

  • 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中进行:

Android开发这么多年,你真的懂得了UIView原理吗,搞定它能给你工作带来不少便利,提高效率

  • 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));}

Android开发这么多年,你真的懂得了UIView原理吗,搞定它能给你工作带来不少便利,提高效率

  • 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进程

Android开发这么多年,你真的懂得了UIView原理吗,搞定它能给你工作带来不少便利,提高效率

  • 关于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种解决办法。
  1. Activity/View#onWindowFocusChanged。 onWindowFocusChanged这个办法的含义是:VieW现已初始化完毕了,宽高现已准备好了,需求留意:它会被调用屡次,当Activity的窗口得到焦点和失掉焦点均会被调用。
  2. view.post(runnable)。 经过post将一个runnable投递到消息行列的尾部,当Looper调用此runnable的时候,View也初始化好了。
  3. ViewTreeObserver。 运用ViewTreeObserver的众多回调能够完结这个功用,比方OnGlobalLayoutListener这个接口,当View树的状况发送改动或View树内部的View的可见性发生改动时,onGlobalLayout办法会被回调。需求留意的是,伴随着View树状况的改动,onGlobalLayout会被回调屡次。
  4. 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某些状况。

谢谢我们