Android custom View series

  • Android custom View Paint draws text and lines
  • Android custom View Precautions
  • Canvas for Android custom View
  • Android custom View image color processing
  • Android custom View image shape effects – easy to achieve rounded corners and round images
  • Android custom View dual buffering mechanism and SurfaceView
  • Android custom View invalidate method and postInvalidate method
  • Android custom View requestLayout method and invalidate method

The three processes of View refer to measure, layout and draw.

Let’s take a look at each of the three processes

Measure of a View

MeasureSpec

MeasureSpec is an internal static class of A View

//view.class public static 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; 100dp public static final int EXACTLY = 1 << MODE_SHIFT; Wrap_content */ public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, @MeasureSpecMode int mode) {if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return(size & ~MODE_MASK) | (mode & MODE_MASK); @measurespecMode public static int measureSpec (int measureSpec) {// noInspection ResourceTypereturn(measureSpec & MODE_MASK); Public static int measureSpec (measureSpec) {measureSpec (measureSpec) {return(measureSpec & ~MODE_MASK); }... }Copy the code

MeasureSpec sums it up as:

  • It consists of two parts of data, which respectively define the mode of View measurement and the size of View measurement
  • Where EXACTLY mode represents match_parent and the exact value; The AT_MOST maximum mode represents the case of WRAP_content

A View’s measure process

A View’s measure process is completed by its measure method, which calls the View’s onMeasure method

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code

The setMeasuredDimension method sets the measured width of the View, so getDefaultSize returns the measured width of the View. Let’s look at the getDefaultSize method

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break; // The corresponding is wrap_contentcaseMeasureSpec.AT_MOST: // match_parent = match_parentcase MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
Copy the code

From the getDefaultSize method, we can see that either the measurement mode, AT_MOST or EXACTLY, returns specSize, the measured size. When the View’s measurement mode is AT_MOST (wrAP_content), this specSize is actually the size available in the parent container, which is equivalent to match_parent. So when we customize a View by inheriting a View, we need to deal specifically with the wrAP_content case.

Measure procedure of ViewGroup

For a ViewGroup, in addition to completing its own measurement, it also needs to complete the measurement of its child elements. ViewGroup is an abstract class that provides a measureChildren method to measure subclasses:

//ViewGroup.class
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); } } } protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); ChildWidthMeasureSpec (parentWidthMeasureSpec) final int childWidthMeasureSpec (parentWidthMeasureSpec) mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }Copy the code

As you can see, the measureChildren method of the ViewGroup eventually loops through the measure method of the child element to measure the child element.

ViewGroup does not define its own measurement process, because its measurement process is done by subclasses such as LinearLayout and RelativeLayout, which are obviously different. Take a look at the onMearsure method of LinearLayout if you are interested.

A common way to get the width and height of a View in an Activity

The View’s measure process and Activity lifecycle methods are out of sync and require special methods to accurately measure the View’s width and height

(1) Obtained in onWindowFocusChanged method

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);

    if(hasFocus) { int width = myView.getMeasuredWidth(); int height = myView.getMeasuredHeight(); }}Copy the code

Note that the onWindowFocusChanged method is called multiple times

(2) the post (runnable)

The View post method posts a runnable to the end of the message queue, and when Looper calls the runnable, the view is already initialized

myView.post(new Runnable() {
    @Override
    public void run() { int width = myView.getMeasuredWidth(); int height = myView.getMeasuredHeight(); }});Copy the code

(3) the ViewTreeObserver

Use the OnGlobalLayoutListener callback interface of the ViewTreeObserver to call back when the state of the View tree changes

ViewTreeObserver viewTreeObserver = myView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() { myView.getViewTreeObserver().removeOnGlobalLayoutListener(this); int width = myView.getMeasuredWidth(); int height = myView.getMeasuredHeight(); }});Copy the code

View layout

The function of layout is ViewGroup used to determine the position of the child elements, the ViewGroup position is determined, the onLayout method will be called, traverse all the child elements and call its layout method, in the layout method will call onLayout method. The Layout method determines the position of the View itself, while the onLayout method determines the position of the child elements.

//View.class
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) {/ / determine the position of the child element onLayout (changed, l, t, r, b); . }... } protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }Copy the code

The layout method does two things. One is to determine the position of the setFrame method and the other is to determine the position of the child elements by calling the onLayout method.

We can see that the onLayout method is not implemented in the View, the same is not implemented in the ViewGroup onLayout method, this is because the specific implementation of onLayout is also related to the specific layout, so need to subclass according to the specific situation to implement. You can take a look at the onLayout implementation of the LinearLayout.

Note that by default the measured width is the same as the final width, i.e. getMeasuredWidth and getWidth are the same. The width and height obtained by measure process and layout process is obtained by layout process. So if the measure process needs to be performed multiple times or if the layout method is considered changed, the two may not be equal. But most of them are the same.


View draw

Draw simply means to Draw the contents of a View onto the screen

@CallSuper
public void draw(Canvas canvas) {
    ...

    /*
     * 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)
     */

    // Step 1, draw the background, ifneeded int saveCount; // Draw the backgroundif (!dirtyOpaque) {
        drawBackground(canvas);
    }

    ...
    if(! verticalEdges && ! HorizontalEdges) {// Step 3, draw the content // Call onDraw to draw the contentif(! dirtyOpaque) onDraw(canvas); // Call dispatchDraw to draw the child element dispatchDraw(canvas); // Overlay is part of the content and draws beneath Foregroundif(mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // come down (foreground, scrollbars); // we're done... return; }... }Copy the code

From the draw method above, we can see that the drawing process follows the following steps:

(1) drawBackground: drawBackground(Canvas Canvas)

(2) Draw its own content: onDraw(Canvas)

(3) Draw child element: dispatchDraw(canvas)

(4) Paint decoration: onDrawForeground(canvas)


View is the place where the three main processes begin

The above picture is a very classic picture, a good description of the View drawing process. The performTraversals method in ViewRootImpl calls the performMeasure, performLayout, and performDraw methods to begin the View’s measurement, layout, and drawing process. When is the performTraversals method called in view wroompl? This requires understanding the concept of a Window, a Window.

The Window of the Android

Window is an abstract concept. Each Window corresponds to a View and a ViewRootImpl. A Window is connected to a View through ViewRootImpl. All views in Android are rendered through Windows. Whether it’s an Activity, Dialog, or Toast, their views are attached to the Window, so the Window is actually the direct manager of the View. For example, the event that we touch the screen is passed to the DecorView through the Window, and then from the DecorView to our View. The setContentView method we use to set the view content in an Activity or Dialog is also done at the bottom through the Window

Window adding process

When we start an Activity or Dialog, the system creates a Window for us and registers the Window with the system’s WindowManagerService.

The Window is added through the addView method of WindowManager’s implementation class, WindowManagerImpl. Only after the View is added to the Window through the addView method can our View be associated with the Window and receive input information passed through the Window

//WindowManagerImpl.class
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Copy the code

The WindowManagerImpl source code shows that WindowManagerImpl leaves the job of adding views to the WindowManagerGlobal class. Let’s take a quick look at the Windows ManagerGlobal class

/ / WindowManagerGlobal. The class / / all of the Window View private final ArrayList < View > mViews = new ArrayList < View > (); Private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); / / there are Window corresponding to the private final layout parameters ArrayList < WindowManager. LayoutParams > mParams = new ArrayList<WindowManager.LayoutParams>(); Private final ArraySet<View> mDyingViews = new ArraySet<View>(); public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... // Create ViewRootImpl root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); // Add a list of Window objects to the corresponding list mviews.add (view); mRoots.add(root); mParams.add(wparams); //doThis last because it fires off messages to start doing things try {// call ViewRootImplsetRoot. setView(View, wparams, panelParentView); } catch (RuntimeException e) { // BadTokenException or InvalidDisplayException, clean up.if (index >= 0) {
            removeViewLocked(index, true); } throw e; }}Copy the code

The source code has been annotated accordingly. Here we see that Windows Global finally calls the setView method of the ViewRootImpl after creating it in the addView method. Now let’s take a look at the setView method for view rule PL

//ViewRootImpl.class
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if(mView == null) { ... RequestLayout (); . try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes =true; collectViewAttributes(); / / here call WindowSession addToDisplay method to register Windows res = mWindowSession. AddToDisplay (mWindow mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); } catch (RemoteException e) { mAdded =false;
                mView = null;
                mAttachInfo.mRootView = null;
                mInputChannel = null;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                throw new RuntimeException("Adding window failed", e);
            } finally {
                if(restore) { attrs.restore(); }}... } } } @Override public voidrequestLayout() {
    if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code

The setView method in ViewRootImpl mainly does two things, one is to call requestLayout method to start the View drawing process; The other is a call to WindowSession’s addToDisplay method asking WindowManagerService to add the Window, which is an IPC call.

At this point, we have analyzed the Window add process, summarized as follows:

(1) The display of View and the handling of touch and click events cannot be separated from Windows, and the two are related through ViewRootImpl

(2) We create a Window whenever we start an Activity, create a Dialog, or pop up a Toast. Then we create a ViewRootImpl class using the addView method of the WindowManagerGlobal class. And associate the Window, View, and ViewrotimPL,

(3) After ViewRootImpl is created, the setView method of ViewRootImpl will be called. In setView method, requestLayout method will be finally called to performTraversals method to open the three processes of View; The Window is added by making a remote IPC call to WindowManagerService through the addToDisplay method of WindowSession.

conclusion

Wrap_content (match_parent, match_parent, match_parent, match_parent, match_parent, match_parent, match_parent, match_parent)

(2) Obtaining the View width and height in the Activity requires special methods: onWindowFocusChanged, View. post(runnable), and OnGlobalLayoutListener in ViewTreeObserver

(3) The display of our View cannot be separated from the Window, no matter it is an Activity, Dialog or Toast, it corresponds to a Window. The View and Window are associated with each other via the View wrotimpl. We show, update, and hide interfaces, such as the show and dismiss of the Dialog, which are essentially the process of adding, updating, and removing views from the Window.

(4) We add View through setContentView method, which is actually the process of adding View corresponding to Window. Window will create ViewRootImpl to perform the operation of registering Window and opening the drawing process of View.

(5) To sum up, the process of displaying an interface is: Create Window–> Create ViewRootImpl–> Add View–> Draw View and register Window


Welcome to follow my wechat public number, and make progress with me every day!Copy the code