自定义View知识体系

ViewRoot和DecorView

在正式了解View的三大流程(measure,layout,draw)之前,我们先认识以下ViewRoot和DecorView

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

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

自定义View知识体系_第1张图片

PerformTraversals方法会依次调用performMeasure,performLayout与performDraw方法,这三个方法分别完成顶级View的measure,layout,draw过程。其中performMeasure会调用measure方法完成顶级View自身的测量过程,紧接着调用onMeasure方法对所有子元素进行测量,接着子元素重新measure过程,如此反复完成整个view树的遍历。同理performLayout与performDraw也是同样的过程,只不过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

自定义View知识体系_第2张图片


理解MeasureSpec

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

View$MeasureSpec#makeMeasureSpec/getMode/getSize

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

打包:makeMeasureSpec

解包:getMode与getSize

SpecMode分为三类:

1. UNSPECIFIED

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

2. EXACTLY

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

3. AT_MOST

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

MeasureSpec与LayoutParams的关系

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

而DecorView的MeasureSpec是由窗口尺寸与自身LayoutParams决定

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

ViewRootImpl# measureHierarchy_1214


LayoutParams中宽高参数与SpecMode

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

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

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


源码分析:ViewGroup到View的Measure过程

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

View的measure过程

View的measure过程是由其measure方法完成,measure是final方法,意味着子类无法重写,在measure方法中会调用View的onMeasure方法

View#onMeasure

getDefaultSize方法获取默认大小

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


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

ViewGroup#measureChildren

ViewGroup#measureChild

ViewGroup #getChildMeasureSpec

自定义View知识体系_第3张图片


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

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

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


获取控件测量宽高的时机

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

方式1.Activity/View#WindowFocusChanged

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

@Overridepublic 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


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

你可能感兴趣的:(自定义View知识体系)