自定义View

View的绘制流程一.自定义View分为measure、Layout、draw三大过程ViewRoot对应于ViewRootImpl类,他是链接WindowManager和DecorView的纽带,View的三大流程均是通过ViewRoot来完成的。在ActivityThread中,当Activity对象被创建完毕后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并将ViewRootImpl和DecorView产生关联root = new ViewRootImpl(view.getContent(),display);Root.setView(view,wparams,panelParentView);View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout、Draw三个过程Measure:用于测量View的宽高Layout:用于确定View在父容器的摆放位置Draw:用于将View绘制在屏幕上PerformTraversals会依次调用performMeasure、performLayout和performDraw方法,这三个方法分别完成顶级父类的measure、layout、Draw这三大流程。这其中performMeasure方法调用measure方法,在measure方法中调用OnMeasure方法,早OnMeasure方法中对所有的View进行测量。其他的两个过程也是同理可得。唯一不同的是performDraw的传递过程在draw方法中通过dispatchDraw来实现的。1. measure决定了View的宽高,在measure完成后可以通过getMeasureWidth和getMeasureHeight来获取测量的宽高2. Layout:决定了View的四个顶点和实际的宽高,完成后可以通过getTop、getBottomgetLeft和getRight拿到四个顶点的位置,并可以通过getWidth和getHeight拿到View的最终宽高3. draw:决定了View的显示,只有draw方法完成后,View才能显示在屏幕上DecorView作为顶级View,一般包括一个竖向的LinearLayout,在这个LinearLayout里面包括两部分,上面是标题栏,下面是内容栏。在Activity中通过setContentView就是把布局文件添加到内容栏中。DectorView其实是一个FrameLayout,View事件先经过DectorView,然后在传递给我们的ViewMeasureSpec(测量规格)MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecModel代表测量模式,SpecSize是指某种测量模式下的规格大小。MeasureSpec通过将SpecModel和SpecSize打包成一个int值来避免过多的对象内存分配,为了方便操作,其提供打包和解包的方法。SpecModel和SpecSize也是一个int值,一组SpecModel和SpecSize可以打包成MeasureSpec,而一个MeasureSpec又可以解包为SpecModel和SpecSizeSpecModel的三大类:1. Unspecified父容器对View没有限制,一般用于系统内部,表示一种测量的状态2. Exactly父容器已经检测出View所需要的精确大小,此刻View的最终大小就是SpecSize所指的值。他应用于LayoutParams中的match_parent和具体的数值这两种模式3. At_Most父容器指定一个可用的大小即SpecSize,View的大小不能大于这个值。它对应于LayoutParams中的wrap_contentMeasureSpec和LauoutParams的对应关系在View测量的时候,系统会将LayoutParams在父容器的约束下转换为对应的MeasureSpec,然后在根据这个Measure来确定View测量后的宽高。LayoutParams需要和父容器一起才能决定View的MeasureSpec,从而进一步决定View的宽高。对于DecorView来说,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定,从而得到View的宽高。对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LauoutParams来共同确定;对于普通的View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams确定的,MeasureSpec确定后,在OnMeasure方法中就可以确定View的宽高DectorView的MeasureSpec的创建过程width = getRootMeasureSpec(desiredWindowWidth,lp.width);height= getRootMeasureSpec(desiredWindowHeight,lp.height);开始测量  performMeasure(width,height);getRootMeasureSpec方法的实现MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);DecorView产生MeasureSpec遵循以下原则(根据LayoutParams中的宽高划分):1. LayoutParams.MATCH_PARENT:精确模式,指窗口的大小2. LayoutParams。WRAP_CONTENT:最大模式,大小不定,但是不能超过窗体的大小3. 固定大小:精确模式,大小为LayoutParams中指定的大小View的measure过程总结:View的宽高由specSize决定的。直接继承View的自定义控件需要重写OnMeasure方法并设置wrap_content时的自身大小,否则在布局中使用Wrap_content就相当于在使用match_parent,因为View在布局中使用wrap_content时,specSize是AT_MOST模式,在这种模式下,他的宽高就等于specSize,而这种情况下SpecSize又是ParentSize(父容器可以使用的大小)ViewGroup的measure过程对于ViewGroup来说,除了完成自己的measure过程之外,还会遍历去调用所有子元素的measure方法,各个元素在递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,没有重写OnMeasure方法,但是他提供了一个measureCildren的方法,而onMeasure方法需要子类去实现,因为不同的ViewGrop子类有不同的布局特性,这导致他们的测量细节不同。如何在Activity启动的时候就获取View的宽高,不能再OnCreate方法中直接测量View的宽高,因为View的measure和Activity的生命周期是不同步的,解决方法如下:1. OnWindowFouceChanged:View已经初始化完毕了,宽高已经准备就绪,这时候在获取宽高,不过该方法会在Activity的获得焦点和失去焦点的时候都会被执行2. view.post(runnable):通过post将一个runnable投递到消息队列的尾部,然后等待Looper调用Runnable的时候,view已经初始化完毕,在进行测量3. ViewTreeObserver:使用ViewTreeObserver的众多回调可以完成此功能,比如使用OnGlobalLayoutListener这个接口,当View树的状态发生改变或者View树内部的View的可见性发生改变的时候,OnGlobalLayout方法将被回调4. view.measure(int widthMeasureSpec,intheightMeasureSpec):通过手动对View进行measure来得到View的宽高Layout过程作用:ViewGroup的作用是确定子元素的位置,当ViewGroup的位置被确定后,它在OnLayout中会遍历所有的子元素并调用其layout方法,在Layout方法中调用OnLayout方法又会被调用。Layout的大致流程:首先通过setFrame方法设定View的四个顶点的位置,接着调用onLayout方法,用于确定子元素的位置Drow过程作用:将View绘制到屏幕上。View的绘制过程遵循以下几步:(1)绘制背景background.draw(canvas)(2)绘制自己(onDraw)(3)绘制Children(dispatchDraw)(4)绘制装饰(OnDrawScrollBars)View的绘制过程是通过dispatchDraw来实现的,diaspatchDraw会遍历所有子元素draw方法。View有一个特殊的方法setWillNotDraw。如果View不需要绘制任何内容,那么设置这个标记为true以后,系统会进行相应的优化。默认情况下,View没有启用这个优化标记位,但是ViewGroup会默认启用这个优化标记位。这个标记位对实际开发的意义是:当我们的自定义控件继承于ViewGroup并且本身不具有绘制功能时,就可以开启这个标记位从而便于系统进行后续的优化。当然,当明确知道一个ViewGroup需要通过onDraw来绘制内容时,我们需要显示地关闭WILL_NOT_DRAW这个标记位   使用案例:以前我写过关于titlebar和bottom按钮切换操作的自定义view,因为在开发每个项目的时候,都会用到这两个东西,所以以前对这两块也特意花时间进行过一些自定义。首先创建类继承viewGroup,然后重写他的方法,可以根据自己的需求,设置自定义属性,然后初始化布局,将自定义的属性设置到布局当中,这样在使用这个自定义view的时候,可以通过xml引用的时候直接添加需要的属性。另外,为方便后期调用,一些使用频率比较高的属性,我特意进行了封装,便于在代码当中动态设置。这些view封装好以后,不但在新项目进行过程中,大大提高了开发效率,同时优化了代码编写,提高了代码阅读性。

你可能感兴趣的:(自定义View)