当前位置:   article > 正文

View绘图原理总结_view ondraw

view ondraw

基本操作由三个函数完成:measure()、layout()、draw(),其内部又分别包含了onMeasure()、onLayout()、onDraw()三个子方法(回调函数)。

整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘(draw)。

View绘制流程图

其中measure、layout、draw可以看做动词,measure(测量大小)后才能进行layout(确定位置),然后才能draw(绘制),他们都是public的方法,前两个还是final的,根本没打算被子类继承。真正有变数的是on...回调函数,继承View(ViewGroup)时可以Override,on可以理解为当...时。

具体流程如下:

1、measure

用于计算View Tree的大小,即视图的宽度和长度,回调onMeasure函数。

  1. // View.java中measure的函数原型
  2. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  3. if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
  4. widthMeasureSpec != mOldWidthMeasureSpec ||
  5. heightMeasureSpec != mOldHeightMeasureSpec) {
  6. // first clears the measured dimension flag
  7. mPrivateFlags &= ~MEASURED_DIMENSION_SET;
  8. if (ViewDebug.TRACE_HIERARCHY) {
  9. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
  10. }
  11. // measure ourselves, this should set the measured dimension flag back
  12. onMeasure(widthMeasureSpec, heightMeasureSpec);
  13. // flag not set, setMeasuredDimension() was not invoked, we raise
  14. // an exception to warn the developer
  15. if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
  16. throw new IllegalStateException("onMeasure() did not set the"
  17. + " measured dimension by calling"
  18. + " setMeasuredDimension()");
  19. }
  20. mPrivateFlags |= LAYOUT_REQUIRED;
  21. }
  22. mOldWidthMeasureSpec = widthMeasureSpec;
  23. mOldHeightMeasureSpec = heightMeasureSpec;
  24. }

onMeasure函数需要两个参数:widthMeasureSpec和heightMeasureSpec(width and height measure specifications),这是两个int值,包含两部分:mode和size(高两位表示mode,低30位表示size,具体参见MeasureSpec类),在onMeasure计算出的widht和height应该尽量满足measure specifications,否则父容器可以选择如clipping,scrolling或者抛出异常,或者(也许是用新的specifications)再次调用onMeasure()。

一但width和height计算好了,就应调用View.setMeasuredDimension(int measuredWidth, int measuredHeight)方法对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,否则将导致抛出异常,而measure的主要目的就是对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值,一旦这两个变量被赋值,则意味着该View的测量工作结束。

  1. // onMeasure的函数原型
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  4. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  5. }
  6. // setMeasuredDimension的函数原型
  7. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
  8. mMeasuredWidth = measuredWidth;
  9. mMeasuredHeight = measuredHeight;
  10. mPrivateFlags |= MEASURED_DIMENSION_SET;
  11. }

View默认的onMeasure函数实现就只调用了一个setMeasureDimension函数,重载的话(ViewGroup必须重载实现onMeasure函数)大致流程应该如下所示:

  1. // 重载的onMeasure过程
  2. private void onMeasure(int widthMeasureSpec , int heightMeasureSpec) {
  3. // 设置该view的实际宽(mMeasuredWidth)高(mMeasuredHeight)
  4. // 1、该方法必须在onMeasure调用,否者报异常。
  5. setMeasuredDimension(w, h)
  6. // 2、如果该View是ViewGroup类型,则对它的每个子View进行measure()过程
  7. int childCount = getChildCount()
  8. for(int i=0; i < childCount; i++) {
  9. // 2.1、获得每个子View对象引用
  10. View child = getChildAt(i)
  11. // 整个measure()过程就是个递归过程
  12. // 该方法只是一个过滤器,最后会调用measure()过程 ;或者 measureChild(child , h, i)方法
  13. measureChildWithMargins(child, h, i)
  14. // 其实,对于我们自己写的应用来说,最简单的办法是去掉框架里的该方法,直接调用view.measure(),如下:
  15. // child.measure(h, l)
  16. }
  17. }
  18. // measureChildWithMargins具体实现在ViewGroup.java里。
  19. protected void measureChildWithMargins(View v, int height, int width) {
  20. v.measure(h, l)
  21. }

在自定义的View中需要重载onMeasure函数的话,关于MeasureSpec可能需要如下代码:

  1. if (specMode == MeasureSpec.EXACTLY) {
  2. // We were told how big to be
  3. result = specSize;
  4. } else { // MeasureSpec.UPSPECIFIED
  5. // Measure the text
  6. result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
  7. + getPaddingRight();
  8. if (specMode == MeasureSpec.AT_MOST) {
  9. // Respect AT_MOST value if that was what is called for by measureSpec
  10. result = Math.min(result, specSize);
  11. }
  12. }

2、layout

用于设置视图在屏幕中显示的位置,有两个基本操作(1)setFrame(l,t,r,b),l,t,r,b即子视图在父视图中的具体位置,该函数用于将这些参数保存起来;(2)onLayout(),在View中这个函数什么都不会做,提供该函数主要是为viewGroup类型布局子视图用的;

layout函数原型,位于View.java

  1. /* final 标识符 , 不能被重载 , 参数为每个视图位于父视图的坐标轴
  2. * @param l Left position, relative to parent
  3. * @param t Top position, relative to parent
  4. * @param r Right position, relative to parent
  5. * @param b Bottom position, relative to parent
  6. */
  7. public final void layout(int l, int t, int r, int b) {
  8. boolean changed = setFrame(l, t, r, b); //设置每个视图位于父视图的坐标轴
  9. if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
  10. if (ViewDebug.TRACE_HIERARCHY) {
  11. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
  12. }
  13. onLayout(changed, l, t, r, b);//回调onLayout函数 ,设置每个子视图的布局
  14. mPrivateFlags &= ~LAYOUT_REQUIRED;
  15. }
  16. mPrivateFlags &= ~FORCE_LAYOUT;
  17. }

如果该View是个ViewGroup类型,需要遍历每个子视图childView,调用该子视图的layout()方法去设置它的坐标值。

  1. //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
  2. private void onLayout(int left, int top, right, bottom) {
  3. //如果该View不是ViewGroup类型
  4. //调用setFrame()方法设置该控件的在父视图上的坐标轴
  5. setFrame(l ,t , r ,b) ;
  6. //--------------------------
  7. //如果该View是ViewGroup类型,则对它的每个子View进行layout()过程
  8. int childCount = getChildCount() ;
  9. for(int i=0 ;i<childCount ;i++){
  10. //2.1、获得每个子View对象引用
  11. View child = getChildAt(i) ;
  12. //整个layout()过程就是个递归过程
  13. child.layout(l, t, r, b) ;
  14. }
  15. }

3、draw

利用前两部得到的参数,将视图显示在屏幕上,到这里也就完成了整个的视图绘制工作。值得注意的是每次发起绘图时,并不会重新绘制每个View树的视图,而只会重新绘制那些"需要重绘”的视图,View类内部变量包含了一个标志位DRAWN,当该视图需要重绘时,就会为该View添加该标志位。有三个基本操作(1)绘制背景;(2)如果要视图显示渐变框,这里会做一些准备工作;(3)绘制视图本身,即调用onDraw()函数。在view中onDraw()是个空函数,每个View都需要重载该方法。而ViewGroup则不需要实现该函数,因为作为容器是“没有内容“的,其包含了多个子view,而子View已经实现了自己的绘制方法,因此只需要告诉子view绘制自己就可以了,也就是下面的dispatchDraw()方法。(4)绘制子视图,即dispatchDraw()函数。在view中不需要重载该方法,值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。(5)如果需要(应用程序调用了setVerticalFadingEdge或者setHorizontalFadingEdge),开始绘制渐变框;(6)绘制滚动条;

  1. //回调View视图里的onLayout过程 ,该方法只由ViewGroup类型实现
  2. private void draw(Canvas canvas){
  3. //该方法会做如下事情
  4. //1 、绘制该View的背景
  5. //2、为绘制渐变框做一些准备操作
  6. //3、调用onDraw()方法绘制视图本身
  7. //4、调用dispatchDraw()方法绘制每个子视图,dispatchDraw()已经在Android框架中实现了,在ViewGroup方法中。
  8. // 应用程序程序一般不需要重写该方法,但可以捕获该方法的发生,做一些特别的事情。
  9. //5、绘制渐变框
  10. }
  11. //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
  12. @Override
  13. protected void dispatchDraw(Canvas canvas) {
  14. //其实现方法类似如下:
  15. int childCount = getChildCount() ;
  16. for(int i=0 ;i<childCount ;i++){
  17. View child = getChildAt(i) ;
  18. //调用drawChild完成
  19. drawChild(child,canvas) ;
  20. }
  21. }
  22. //ViewGroup.java中的dispatchDraw()方法,应用程序一般不需要重写该方法
  23. protected void drawChild(View child,Canvas canvas) {
  24. // ....
  25. //简单的回调View对象的draw()方法,递归就这么产生了。
  26. child.draw(canvas) ;
  27. //.........
  28. }

从上面可以看出自定义View需要最少覆写onMeasure()和onDraw()两个方法,自定义viewGroup的时候需要最少覆写onMeasure()和onLayout()方法。

整个界面的更新依次执行measure、layout、draw操作,从ViewRoot按照从根到叶子的顺序绘制view。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/711348
推荐阅读
相关标签
  

闽ICP备14008679号