preface

I believe that Android developers with a bit of experience are familiar with custom View, almost everyone knows through measurre, layout, draw three processes, but when these three processes are executed, and how to customize? This article will explain when to draw a View and the process of customizing a View. After reading this article, you will have a new understanding of how a View works.

1. Drawing time of View

1.1. Knowledge reserve

  • Each Window:ActivityWill create aWindowUsed to host the display of the View,WindowAn abstract class has a unique implementation classPhoneWindow
  • DecorView: The top-level View, is aFrameLayoutSubclasses, which will eventually be loaded into the Window, have only one vertical insideLinearLayoutDivided into two parts:
    • TitleBar: Status bar at the top of the screen
    • ContentView:ActivityCorresponding XML layout, throughsetContentViewSet it toDecorViewIn the.

1.2. The relationship between Activity, Window, and DecorView

Activity setContentView source code:

Public void setContentView(@layoutres int layoutResID) {// Pass XML layout to Window getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }Copy the code

The Activity’s setContentView essentially passes the View to the Window’s setContentView() method. Windows’ setContenView creates a DecorView internally by calling the installDecor() method. Take a look at some of the source code:

Public void setContentView(int layoutResID) {if (mContentParent == null) {// Initialize DecorView and its internal Content installDecor(); } else if (! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { ............... } else {// Load contentView into DecorVoew mLayOutInflater.inflate (layoutResID, mContentParent); }... } private void installDecor() { ............... If (mDecor == null) {// Instantiate DecorView mDecor = generateDecor(-1); . } } else { mDecor.setWindow(this); } if (mContentParent == null) {// Get Content mContentParent = generateLayout(mDecor); }... } protected DecorView generateDecor(int featureId) { ............... return new DecorView(context, featureId, this, getAttributes()); }Copy the code

New a DecorView by generateDecor(), then call generateLayout() to get the content in the DecorView, and finally add the Activity view to the content in the DecorView through the inflate view. But the DecorView has not yet been added to the Window. The add operation requires ViewRootImpl.

The ViewRootImpl interface is used to connect the WindowManager to the DecorView. After the Activity is created, the DecorView is added to the PhoneWindow through the WindowManager and the ViewRootImpl instance is created. We then associate the DecorView with the ViewRootImpl, and finally open the entire View tree by performing performTraversals() of the ViewRootImpl.

2. Drawing process

From the first section, we can see that View rendering is performed from the performTraversals() method of ViewRootImpl, and from the top-level View(ViewGroup), each View is plotted layer by layer. Here is the source code for this method:

private void performTraversals() { ............... // Measur procedure performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . // Layout procedure performLayout(LP, desiredWindowWidth, desiredWindowHeight); . / / the draw process performDraw (); }Copy the code

There are hundreds of lines in this method, and the clever author has extracted three to present them

  • Measure: In order to measure the width and height, if it is a ViewGroup, measure operations should be performed on all sub-views in onMeasure.
  • Layout: Used to position a View in a ViewGroup. If it is a ViewGroup, perform layout operations on all child views in the onLayout method.
  • Draw: Draw an image to a View.

2.1 Measure

PerformMeasure () the source code

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { if (mView == null) { return; } try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

It can be seen that the measurement operation starts from mView(topmost ViewGroup), and then traverses the View layer by layer and performs measure operation.

MeasureSpac

Measure is the first step in the three processes of drawing a View. Measure is a 32-bit int value, with the high two bits of SpacMode representing the measurement mode and the low 30 bits of SpacSize representing the measurement size. It is an internal class of View, source code is as follows:

public class MeasureSpec {
        private static final int MODE_SHIFT = 30;  
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;  
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;  
        public static final int EXACTLY = 1 << MODE_SHIFT;  
        public static final int AT_MOST = 2 << MODE_SHIFT;  
  }
Copy the code

The interior also contains three measurement modes:

  • UNSPECIFIED: The parent layout does not impose any restrictions on the child views, such as the UNSPECIFIED parent layoutScrollViewThat’s the measurement model.
  • -Dan: EXACTLY. -Dan: EXACTLYmatch_parentOr XXXDP, indicating that the parent layout already determines the childrenViewThe size is usually in this caseViewThe size ofSpacSize
  • AT_MOST: adaptivewrap_contentThe child View can set its own size based on the content, but only if it is not larger than the parentViewGroupThe width of high.

Note:

This method receives two parameters from the parent layout, widthMeasureSpac and heightMeasureSpac, so the width and height of the child layout is limited by the parent layout.

A custom ViewGroup must also pass MeasurSpac to its child View. Android also gives us a way to evaluate and get values from MeasurSpac, both within MeasurSpac, as follows:

public static class MeasureSpec {
     public static int makeMeasureSpec( int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
     }

     public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
     }

     public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK)
     }
}
Copy the code

Size and mode are encapsulated and unpacked from ViewGroup to View in order to reduce object creation and avoid unnecessary memory waste.

LayoutParams

In the first contact with Android often have a question, why View set its own width and height, but also create a XXx. LayoutParams? You can’t set the width and height of a child View by setWidth or setHeight. LayoutParams can do more than that. For example, if the parent layout of a View is RelativeLayout, Can by setting RelativeLayout. LayoutParams above and below the positions of the attributes such as to adjust the parent layout.

Custom View width and height measurement demo

Create a class that inherits the View and overrides its onMeasure() method

Protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// defaultWidth int defaultWidth = 0; // default high int defaultHeight = 0; setMeasuredDimension( getDefaultSize(defaultWidth, widthMeasureSpec), getDefaultSize(defaultHeight, heightMeasureSpec));  }Copy the code

General custom View, if there is no special need for width and height can be directly obtained by getDefaultSize() method, the method is located in the View source code as follows:

Public static int resultSize (int size, int measureSpec) {// static int resultSize = measureSpec; Int specMode = MeasureSpec. GetMode (MeasureSpec); Int specSize = MeasureSpec. GetSize (MeasureSpec); switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }Copy the code

According to the code analysis, mode and Size are determined with UNSPECIFIED default dimensions, while AT_MOST and EXACTLY use the measurement dimensions given by the parent layout. Set the final width and height using setMeasuredDimension(Width,height) after the measurements are calculated.

2.2 Layout

PerformLayout ()

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { ......... final View host = mView; if (host == null) { return; } host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); . }Copy the code

Similar to measure, the layout operation starts from mView(topmost ViewGroup) and then traverses layer by layer. Layout (L, T, R, B) four parameters correspond to the upper left and lower right positions respectively, so as to determine the position of the View in the ViewGroup. Layout () :

public void layout(int l, int t, int r, int b) { ....... Boolean changed = isLayoutModeOptical(mParent)? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); . OnLayout (changed, l, t, r, b); onLayout(changed, l, t, r, b); . }Copy the code

Layout () passes four positional arguments to setOpticalFrame() or setFrame(). SetOpticalFrame () calls setFrame(). So finally setFrame() determines the position of the View in the ViewGroup. After the location is determined, onLayout(L, T,r, B) will be called to place the sub-view.

onLayout()

OnLayout () is called after setFrame() is executed on both views and viewgroups. This method is used to position sub-views, so there is no need to override this method for a single View. And the ViewGroup will arrange its child views according to its own characteristics.

2.3 the Draw

I believe that many students learning custom View are running towards the day of their own to achieve those dazzling effects, at least I am. The colorful pictures and animations we see on our mobile phones are drawn in this way.

Compared with measure and layout stage, the View and ViewGroup in draw stage become less close. ViewGroup does not need to be considered during the drawing process of View, and ViewGroup only needs to trigger the drawing method of sub-view.

PerformDraw () execution will also start from the root layout of each View layer by layer draw operation, draw operations in View through draw(), take a look at its main source:

public void draw(Canvas canvas) { ........ DrawBackground (canvas); // Draw content onDraw(canvas); // Draw sub-view dispatchDraw(canvas); // Draw decoration, such as scrollBar onDrawForeground(canvas)........ }Copy the code

The draw() method consists of four main parts, of which all we developers need to worry about is onDraw(Canvas), which is drawing our own content.

Description of drawing Content

There is a lot of knowledge available in this part of drawing content, so much that you could write a book, so it is obviously unrealistic to rely only on the full description of this article. Below I list a part of common content for your reference:

  • Canvas: no matter the text, graphics or pictures are drawn on Canvas
  • Paint: you can set the color, thickness, size, shadow, etc. It is usually used with the canvas
  • Path: A Path used to form irregular shapes.
  • Matrix: Matrix, which can realize the geometric transformation of the canvas.

conclusion

Article summarizes the View from four aspects of drawing process: draw time, wide, high measurement location, image rendering, because will focus on the process is the essence of the four parts to carry out to share with everyone, play a role, the author wants to thoroughly understand the startup process, how to play the custom View also need to various parts of the knowledge system of learning.