View的工作原理

来源:互联网 发布:永琪与知画圆房视频 编辑:程序博客网 时间:2024/04/27 21:15

ViewRootDecorView

在正式了解View的三大流程(measurelayoutdraw)之前,我们先认识以下ViewRootDecorView

ViewRoot对应于ViewRootImpl类,它是连接WindowManagerDecorView是纽带,View的三大流程都是通过ViewRootImpl来完成的。在ActivityThread中,当Activity被创建的时,会将DecorView添加到Window中,同时创建一个ViewRootImpl与其关联

View的绘制流程是从ViewRootperformTraversals方法开始的,经过measure(测量宽高),layout(布局位置),draw(内容绘制)

 


PerformTraversals方法会依次调用performMeasureperformLayoutperformDraw方法,这三个方法分别完成顶级Viewmeasurelayoutdraw过程。其中performMeasure会调用measure方法完成顶级View自身的测量过程,紧接着调用onMeasure方法对所有子元素进行测量,接着子元素重新measure过程,如此反复完成整个view树的遍历。同理performLayoutperformDraw也是同样的过程,只不过performDraw的传递过程是在draw中调用dispatchDraw方法,但是本质上都是一样的。

 

Measure过程决定了View的测量宽高,完成后可以通过getMeasureWidth/Height获得测量宽高,测量宽高一般与最终宽高一致,但是也有例外情况

Layout过程决定了View的四个顶点的位置以及最终宽高,完成后可以通过getLeft/Right/Top/Bottom获取四个顶点坐标,可通过getWidth/Height获取View的最终宽高

Draw过程是对View内容的绘制,在draw完成后View的内容才会最终显示是屏幕上

 

DecorView是顶级View(继承自FrameLayout),一般情况下它内部包含一个LinearLayout里面分为上下两部分(具体与Android版本与主题有关),上部分为标题栏,下部分为内容栏(FrameLayout)。SetContentView就是将布局添加到内容栏中,其id就是android.R.id.content。那么我们可以通过如下代码获取ContentView

ViewGroup décor = getWindow().getDecorView(); // get décor

View content = décor.findViewById(android.R.id); // get content

 

 

理解MeasureSpec

MeasureSpecView进行测量过程的“测量规格”。它里面主要存储32位的int值,高2位代表测量模式,低30位代表测量大小。

View$MeasureSpec#makeMeasureSpec/getMode/getSize

MeasureSpec通过将SpecModeSpecSize打包成一个int值来避免过多的内存消耗,并且提供了打包与解包的方法

打包:makeMeasureSpec

解包:getModegetSize

SpecMode分为三类:

1. UNSPECIFIED

表示父容器不对View有任何限制

2. EXACTLY

精确值模式,表示View使用具体宽高

3. AT_MOST

最大模式,表示View可以根据自身需求设定宽高,但是不可超过当前的可用值

MeasureSpecLayoutParams的关系

MeasureSpec是用于定义对View的测量规范的,而View的宽高属性定义在LayoutParams中。那么在测量时系统会将LayoutParams中的相关属性在父容器的约束下转换成对应的MeasureSpec(即ViewMeasureSpec不是由LayoutParams单独决定的,是由LayoutParams与父容器的MeasureSpec共同决定)。

DecorViewMeasureSpec是由窗口尺寸与自身LayoutParams决定

一旦MeasureSpec确定后,在onMeasure就可以确定View的测量宽高

ViewRootImpl# measureHierarchy_1214

 

LayoutParams中宽高参数与SpecMode

MATCH_PARENT:精确模式,大小为父容器可用大小

WRAP_CONTENT:最大模式,不可大于父容器可用大小

固定大小:精确模式,大小为属性指定的value

 

源码分析:ViewGroupViewMeasure过程

控件的测量主要两个情况,如果只是一个原始View,那么直接measure过程完成测量,如果是ViewGroup除了自身测量外还会执行onMeasure遍历所有子Viewmeasure过程

Viewmeasure过程

Viewmeasure过程是由其measure方法完成,measurefinal方法,意味着子类无法重写,在measure方法中会调用ViewonMeasure方法

View#onMeasure

getDefaultSize方法获取默认大小

getSuggestedMinimumWidth方法获取建议的最小大小

 

ViewGroup并没有覆写ViewonMeasure方法,它只是抽象的规范,这需要在具体子类中根据自身规则完成ViewGroup自身的测量

ViewGroup#measureChildren

ViewGroup#measureChild

ViewGroup #getChildMeasureSpec

 

 

例如垂直的LinearLayout会在onMeasure中遍历所有元素,依次调用子元素的measure方法,并且通过mTotalLength存储在垂直方向的所有子元素占据的高度(子元素高度(包括padding) + margin

总高度 = 子元素总高度 + 自身padding

测量完子元素后根据自身lp与子元素总高度决定自身的测量

 

获取控件测量宽高的时机

由于View的测量过程和Activity的生命周期是不一致的,不是同步方式执行的。即我们无法在Activity中某个生命周期时获取View的测量宽高(因为此时View可能还没有测量结束)

方式1.Activity/View#WindowFocusChanged

当该方法触发时候证明View已经初始化完毕了,这个时候就可以去获取宽高

@Override
public void onWindowFocusChanged(booleanhasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if(hasFocus && mTextView!=null){
        Toast.makeText(this,"mTextView.getMeasuredHeight():"+mTextView.getMeasuredHeight(), Toast.LENGTH_SHORT).show();
    }
}

方式2.view.post(runnable)

通过post将一个runnable对象投递到消息队列的尾部,等待Looper执行(此时View已经完成初始化)

mTextView.post(newRunnable() {
    @Override
    public voidrun() {
        mWidth = mTextView.getMeasuredWidth(); }});

方式3.ViewTreeObserver

使用ViewTreeObserver的众多回调均可以实现该功能,比如使用OnGlobalLayoutListener接口,当View树的状态发生改变或者View树内部的View的可见性发现改变,onGlobalLayout会被回调(多次)

ViewTreeObserver observer = mTextView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public voidonGlobalLayout() {
        mTextView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
        mWidth = mTextView.getMeasuredWidth();
    }
});

方式4.view.measure(int widthSpec,heightSpec);

这种情况需要根据LayoutParams分情况处理

match_parent

无法使用该方式,因为此时无法知道父容器剩余空间

具体的数值

int widthSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
mTextView.measure(widthSpec,heightSpec);

wrap_content

int widthSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
mTextView.measure(widthSpec,heightSpec);

View的尺寸大小是由30位二进制表示,那么最大即为2^30-1

 

注意:ViewmeasureonMeasure以及layoutonLayout方法在ViewGroup中均没有被覆写,因为这四个方法只是定义了一个流程,而ViewGroup只是布局控件的统一父类,只有在具体的ViewGroup中才会去覆写onMeasureonLayout方法,比如LinearLayout

 

1 0
原创粉丝点击