本人菜鸟一枚,自己没那个水平研究出view的绘制流程,不过经过各种查阅前辈大牛的资料或者博客知道了view的绘制过程分为onMeasure,onLayout,onDraw三个重要的过程,姑且拿来作为结论来指引自己对Android绘图的的学习,少走了很多的弯路。下面就贴上自己的心得和体会,不对之处欢迎批评指正,共同学习。
开篇之前先说说View和ViewGroup之间的关系,ViewGroup是View的子类,但同样的ViewGroup里面也封装了许多个View的引用包括View集合的引用,这点看源码就可以看出来,从设计模式上来说是典型的组合模式,所以他们之间的关系可以用如下图所示:
当然也可以有其他的树形结构。
View类:
1) view的onMeasure在measure方法里调用,在onMeasure会调用setMeasureDimension(intmeasureWidth,int measureHeight)来完成measure过程.onMeasure方法来完成具体的测量逻辑,需要注意的是measure是final方法,当你要实现自己的测量逻辑的时候在子类中是不能重写measure方法的,只能重写onMeasure方法来完成自己的测量逻辑
2)onLayout方法在View类里面是个空方法,由子类去完成
3)同样onDraw方法为空方法,由子类去时间自己的绘图逻辑
4)View类还提供了供绘制View的其他的相关方法:
4.1)layout(int l,int t,int r,int b),该方法的作用是绘制某个view相对于父View的位置,各个参数用图可以表示如下:红、黑、绿、蓝的箭头分别为l,t,r,b
4.2)dispatchDraw方法,也是个空方法,由子类ViewGroup实现,主要用来绘制当前view的子view,程序员也可以自己实现这个方法来自定义相关view的绘制,github上关于分组gridView的绘制就是重写了这方方法来进行headerview的绘制。
4.3) draw(Canvas canvas, ViewGroup parent, long drawingTime):正如注释所说,这个方法被View的子类ViewGroup的drawChild方法调用,该方法的作用是让子view绘制自己本身,这方方法设计出来的本身意图不希望被子类重写,也不会在drawChild方法之外的任何地方调用。
4.4)View类还提供了draw的重载方法draw(Canvas canvas),这个方法实现了绘制view的逻辑,注意当用户自定义view的时候最好重写onDraw()方法而不是重写这个方法,当然如果你确实要重写这个方法的话,记得super.draw();绘制view的步骤在方法的注释上写的很清晰,如下:
1.Draw the background
2.If necessary, save the canvas‘ layers to prepare for fading
3.Draw view‘s content
4.Draw children
5.If necessary, draw the fading edges and restore layers
6.Draw decorations (scrollbars for instance)
注意在步骤三的时候调用了onDraw方法进行绘制,在步骤4的时候调用dispatchDraw方法。
介绍到此地方基本上说完了view的绘制流程,不过在onDraw方法里面还有对不经常见情况的处理,在此个人水平有限,就不误人了。
ViewGroup类:
1)viewGroup没有重写父类的onMeasure方法,就说明所有的测量工作交给父类View或者viewGroup的子类来动态绑定实现。但是提供了measureChild、measureChildWithMargins、measureChildren方法。根据方法名字不难测出measureChildren方法里面循环遍历了每一个view然后调用measeChild方法:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
measureChild方法最终会执行View类的measure方法
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
2) 在该类里面把父类的onLayout方法改成的abstract方法,交给ViewGroup的子类去实现。并重写了layout方法。
3)正如上面4.3说的,该类提供了一个drawChild方法,调用父类的方法。
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); },
该方法在dispatchDraw方法让每一个子view去调用。
4)该类实现了dispatchDraw方法,循环遍历每一个子view,在调用drawChild方绘制每一个view.所以调用dispatchDraw绘制调用方法的顺序为:dispatchDraw-->drawChild-->draw
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) { for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } } else { for (int i = 0; i < count; i++) { final View child = children[getChildDrawingOrder(count, i)]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) { more |= drawChild(canvas, child, drawingTime); } } }
所以从以上的分析来说,当你自定义View来实现自己的绘制逻辑的时候可以根据需要来决定你的视图类是继承ViewGroup还是继承View,实际上在Android源码中大部分View都是直接或者间接的继承自ViewGroup,比如LinearLayout,FrameLayout,RelativeLayout等等,也有直接继承自View的组件,比如TextView,ImageView等。所以涉及到的的绘制流程涉及到的方法的uml图简单的总结如下
写完了再回顾看看,除了几张图片之外,基本上都是在解释方法的作用或者说是给相关方法写注释,但是在写博客的过程中通过自己浏览和分析源码,确实收获了不小,希望对阅读者有所帮助,另外不当之处还请不吝赐教