美文网首页
draw流程

draw流程

作者: 鹏鹏灬 | 来源:发表于2020-09-03 16:16 被阅读0次

    一、View的draw()流程

    • draw(入口)->
    • drawBackground(绘制背景)->
    • onDraw(绘制自己)->
    • dispatchDraw(没有子view,空实现)
    • onDrawForeground(绘制装饰如滚动条前景等)
    /**
      * 源码分析:draw()
      * 作用:根据给定的 Canvas 自动渲染 View(包括其所有子 View)。
      * 绘制过程:
      *   1. 绘制view背景
      *   2. 绘制view内容
      *   3. 绘制子View
      *   4. 绘制装饰(渐变框,滑动条等等)
      * 注:
      *    a. 在调用该方法之前必须要完成 layout 过程
      *    b. 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)
      *    c. 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制
      *    d. 若自定义的视图确实要复写该方法,那么需先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制
      */ 
      public void draw(Canvas canvas) {
    
        ...// 仅贴出关键代码
      
        int saveCount;
    
        // 步骤1: 绘制本身View背景
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    
        // 若有必要,则保存图层(还有一个复原图层)
        // 优化技巧:当不需绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
        // 因此在绘制时,节省 layer 可以提高绘制效率
        final int viewFlags = mViewFlags;
        if (!verticalEdges && !horizontalEdges) {
    
        // 步骤2:绘制本身View内容
            if (!dirtyOpaque) 
                onDraw(canvas);
            // View 中:默认为空实现,需复写
            // ViewGroup中:需复写
    
        // 步骤3:绘制子View
        // 由于单一View无子View,故View 中:默认为空实现
        // ViewGroup中:系统已经复写好对其子视图进行绘制我们不需要复写
            dispatchDraw(canvas);
            
        // 步骤4:绘制装饰,如滑动条、前景色等等
            onDrawScrollBars(canvas);
    
            return;
        }
        ...    
    }
    

    下面,我们继续分析在draw()中4个步骤调用的drawBackground()、 onDraw()、dispatchDraw()、onDrawScrollBars(canvas)

    /**
      * 步骤1:drawBackground(canvas)
      * 作用:绘制View本身的背景
      */
    private void drawBackground(Canvas canvas) {
        // 获取背景 drawable
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界
        setBackgroundBounds();
    
        .....
    
        // 获取 mScrollX 和 mScrollY值 
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            // 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移
            canvas.translate(scrollX, scrollY);
    
    
            // 调用 Drawable 的 draw 方法绘制背景
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    } 
    
    /**
      * 步骤2:onDraw(canvas)
      * 作用:绘制View本身的内容
      * 注:
      *   a. 由于 View 的内容各不相同,所以该方法是一个空实现
      *   b. 在自定义绘制过程中,需由子类去实现复写该方法,从而绘制自身的内容
      *   c. 谨记:自定义View中 必须 且 只需复写onDraw()
      */
      protected void onDraw(Canvas canvas) {
          
            ... // 复写从而实现绘制逻辑
    
      }
      
      
    /**
      * 步骤3: dispatchDraw(canvas)
      * 作用:绘制子View
      * 注:由于单一View中无子View,故为空实现
      */
      protected void dispatchDraw(Canvas canvas) {
    
            ... // 空实现
    
      }
    /**
      * 步骤4: onDrawScrollBars(canvas)
      * 作用:绘制装饰,如 滚动指示器、滚动条、和前景等
      */
      public void onDrawForeground(Canvas canvas) {
            onDrawScrollIndicators(canvas);
            onDrawScrollBars(canvas);
    
            final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
            if (foreground != null) {
                if (mForegroundInfo.mBoundsChanged) {
                    mForegroundInfo.mBoundsChanged = false;
                    final Rect selfBounds = mForegroundInfo.mSelfBounds;
                    final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
    
                    if (mForegroundInfo.mInsidePadding) {
                        selfBounds.set(0, 0, getWidth(), getHeight());
                    } else {
                        selfBounds.set(getPaddingLeft(), getPaddingTop(),
                                getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
                    }
    
                    final int ld = getLayoutDirection();
                    Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
                            foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
                    foreground.setBounds(overlayBounds);
                }
    
                foreground.draw(canvas);
            }
        }
    

    二、viewGroup的draw流程

    • draw(入口)->
    • drawBackground(绘制背景)->
    • onDraw(绘制自己)->
    • dispatchDraw(没有子view,空实现),在这里遍历子View,并且绘制,走view的draw流程
    • onDrawForeground(绘制装饰如滚动条前景等)
    /**
      * 源码分析:draw()
      * 与单一View的draw()流程类似
      * 作用:根据给定的 Canvas 自动渲染 View(包括其所有子 View)
      * 绘制过程:
      *   1. 绘制view背景
      *   2. 绘制view内容
      *   3. 绘制子View
      *   4. 绘制装饰(渐变框,滑动条等等)
      * 注:
      *    a. 在调用该方法之前必须要完成 layout 过程
      *    b. 所有的视图最终都是调用 View 的 draw ()绘制视图( ViewGroup 没有复写此方法)
      *    c. 在自定义View时,不应该复写该方法,而是复写 onDraw(Canvas) 方法进行绘制
      *    d. 若自定义的视图确实要复写该方法,那么需先调用 super.draw(canvas)完成系统的绘制,然后再进行自定义的绘制
      */ 
      public void draw(Canvas canvas) {
    
        ...// 仅贴出关键代码
      
        int saveCount;
    
        // 步骤1: 绘制本身View背景
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    
        // 若有必要,则保存图层(还有一个复原图层)
        // 优化技巧:当不需绘制 Layer 时,“保存图层“和“复原图层“这两步会跳过
        // 因此在绘制时,节省 layer 可以提高绘制效率
        final int viewFlags = mViewFlags;
        if (!verticalEdges && !horizontalEdges) {
    
        // 步骤2:绘制本身View内容
            if (!dirtyOpaque) 
                onDraw(canvas);
            // View 中:默认为空实现,需复写
            // ViewGroup中:需复写
    
        // 步骤3:绘制子View
        // ViewGroup中:系统已复写好对其子视图进行绘制,不需复写
            dispatchDraw(canvas);
            
        // 步骤4:绘制装饰,如滑动条、前景色等等
            onDrawScrollBars(canvas);
    
            return;
        }
        ...    
    }
    

    由于 步骤2:drawBackground()、步骤3:onDraw()、步骤5:onDrawForeground(),与单一View的draw过程类似,此处不作过多描述

    • 下面直接进入与单一View draw过程最大不同的步骤4:dispatchDraw()
    - /**
      * 源码分析:dispatchDraw()
      * 作用:遍历子View & 绘制子View
      * 注:
      *   a. ViewGroup中:由于系统为我们实现了该方法,故不需重写该方法
      *   b. View中默认为空实现(因为没有子View可以去绘制)
      */ 
        protected void dispatchDraw(Canvas canvas) {
            ......
    
             // 1. 遍历子View
            final int childrenCount = mChildrenCount;
            ......
    
            for (int i = 0; i < childrenCount; i++) {
                    ......
                    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                            transientChild.getAnimation() != null) {
                      // 2. 绘制子View视图 ->>分析1
                        more |= drawChild(canvas, transientChild, drawingTime);
                    }
                    ....
            }
        }
    
    /**
      * 分析1:drawChild()
      * 作用:绘制子View
      */
        protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
            // 最终还是调用了子 View 的 draw ()进行子View的绘制
            return child.draw(canvas, this, drawingTime);
        }
    - ```
    
    draw总结。先说一下view的draw()流程,本身draw里面分为几个步骤,首先画背景,然后ondraw画自身的内容,然后分发画子view(因为没有子view,所以是空实现),最后画装饰,如滚动和前景等。
    而viewGroup,主要是在onDraw方法中,实现自身的绘制逻辑,然后dispatchDraw,遍历子view进行绘制。
    

    相关文章

      网友评论

          本文标题:draw流程

          本文链接:https://www.haomeiwen.com/subject/erbvsktx.html