How View works (3): layout and draw

1. layout process

  1. Let's take a look at the layoutsource code of the View method:

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
    
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
    
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
    
            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }
    
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                            (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }
    
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    
        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

    setFrame(l, t, r, b): Set the 4 vertex positions of the View.

    onLayout(changed, l, t, r, b): The ViewGroup determines the position of its child elements. Neither View nor ViewGroup implement this method, and ordinary View does not need to implement it, while the implementation of ViewGroup's onLayout is related to the layout type.

  2. onLayoutExample: method implementation of linear layout :

    //LinearLayout
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical(l, t, r, b);
        } else {
            layoutHorizontal(l, t, r, b);
        }
    }

    We select VERTICALthe type to continue to view the source code:

    void layoutVertical(int left, int top, int right, int bottom) {
        ...
        ...
        final int count = getVirtualChildCount();
        for (int i = 0; i < count; i++) {
            final View child = getVirtualChildAt(i);
            if (child == null) {
                childTop += measureNullChild(i);
            } else if (child.getVisibility() != GONE) {
                final int childWidth = child.getMeasuredWidth();
                final int childHeight = child.getMeasuredHeight();
    
                final LinearLayout.LayoutParams lp =
                        (LinearLayout.LayoutParams) child.getLayoutParams();
    
                int gravity = lp.gravity;
                if (gravity < 0) {
                    gravity = minorGravity;
                }
                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                ...
                ...
                if (hasDividerBeforeChildAt(i)) {
                    childTop += mDividerHeight;
                }
    
                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
    
                i += getChildrenSkipCount(child, i);
            }
        }
    }
    • As you can see, the child elements are traversed and setChildFrame()the method is called to specify the position of the child element.
    • setChildFrame()The layout method of the child element will be called:

      private void setChildFrame(View child, int left, 
              int top, int width, int height) {
          child.layout(left, top, left + width, top + height);
      }

      The layout of the child element will call its onLayout, so as to achieve the process of recursively traversing the entire View tree.

    • setChildFrame()The parameters:

      final int childWidth = child.getMeasuredWidth();
      final int childHeight = child.getMeasuredHeight();

      Obviously, it is the measured width and height of the child View.

    • childTopThe primary key will increase with the traversal of the child elements. Obviously, the added child View will appear at a lower position, which is also the feature of the vertical 'LinearLayout'.

  3. On View's measureWidth/ measureHeightand width/ height: basically no difference

    1. First look at getWidthand getHieght:

      public final int getWidth() {
          return mRight - mLeft;
      }
      public final int getHeight() {
          return mBottom - mTop;
      }

      Obviously, getWidthand getHieghtmust wait until the layout is complete to get the correct value.

    2. As summarized above, the measurement of width and height is formed in the measure process of View, and the final width and height are formed in the layout process. Therefore, in daily development, it can be considered that there is no difference between them.
    3. There are always exceptions, such as overriding the layoutmethod of View (happy skin):

      public void layout(int l, int t, int r, int b){
          super.layout(l,t,r + 10, b + 10);
      }

      So the final width and height will be 10 pixels more than the measured width and height.

2. draw process

  1. The source code is too long, here are the key parts:

    //View的draw方法
    /*
     * Draw traversal performs several drawing steps which must be executed
     * in the appropriate order:
     *
     *      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)
     */
     ...
     ...
    
    // skip step 2 & 5 if possible (common case)
    final int viewFlags = mViewFlags;
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);
    
        // Step 4, draw the children
        dispatchDraw(canvas);
    
        drawAutofilledHighlight(canvas);
    
        // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
    
        // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
    
        // Step 7, draw the default focus highlight
        drawDefaultFocusHighlight(canvas);
    
        if (debugDraw()) {
            debugDrawFocus(canvas);
        }
    
        // we're done...
        return;
    }
    ...
    ...
  2. The comments in the source code above are very clear. The basic drawing process is as follows:

    1. draw background
    2. If necessary, save canvasthe layersprepared gradient.
    3. Draw the content of the View
    4. draw children
    5. If necessary, draw gradient edges and restorelayers
    6. Draw decorations, such as scrollbars.
  3. The transfer of the drawing process is realized by dispatchDraw:

    // Step 4, draw the children
    dispatchDraw(canvas);

    This method in View is an empty implementation (ordinary View has no children), while ViewGroup provides implementation. In this method of ViewGroup, the draw method of all child Views will be traversed and called.

  4. Special method: setWillNotDraw:

    /**
     * If this view doesn't do any drawing on its own, set this flag to
     * allow further optimizations. By default, this flag is not set on
     * View, but could be set on some View subclasses such as ViewGroup.
     *
     * Typically, if you override {@link #onDraw(android.graphics.Canvas)}
     * you should clear this flag.
     *
     * @param willNotDraw whether or not this View draw on its own
     */
    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }
    • If the View does not intend to draw itself, set this flag to notify the system to do further optimization work.
    • This flag is turned off by default for ordinary View, but this flag is enabled by default for ViewGroup.
    • When we customize a container View of type ViewGroup, if we need onDrawto draw ourselves, we need to manually turn off this flag: setWillNotDraw(false).

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325611433&siteId=291194637