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 Window, View View PL and View of the three processes
  • Android custom View event distribution mechanism summary

When we customize a View, we have to use the invalidate method. This method is used to refresh the View and redraw it. The postInvalidate method, however, may not be as familiar because it is used relatively often in development. PostInvalidate: postInvalidate: postInvalidate: postInvalidate: postInvalidate: postInvalidate: postInvalidate: postInvalidate: postInvalidate: postInvalidate: postInvalidate

So what’s the difference between an invalidate method and a postInvalidate method?

The difference between the invalidate and postInvalidate methods

In fact, the answer is also very simple, in one sentence:

Both the invalidate method and the postInvalidate method are used to refresh the View. The invalidate method is used in the UI thread, and the postInvalidate method is used in the non-UI thread to switch the thread to the UI thread. The postInvalidate method finally calls the invalidate method.

Of course, empty words have no basis, we still come to see the source code

Invalidate method and postInvalidate method source analysis

Let’s first look at the postInvalidate method in the View

@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    
    ...
    
    public void postInvalidate() {
        postInvalidateDelayed(0);
    }
    
    public void postInvalidate(int left, int top, int right, int bottom) {
        postInvalidateDelayed(0, left, top, right, bottom);
    }
    
    public void postInvalidateDelayed(long delayMilliseconds) {
        final AttachInfo attachInfo = mAttachInfo;
        if(attachInfo ! = null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); }}... }Copy the code

We can see from the source, the last call postInvalidate method ViewRootImpl dispatchInvalidateDelayed method

//ViewRootImpl.class

final ViewRootHandler mHandler = new ViewRootHandler();

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
        Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
        mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
Copy the code

In ViewRootImpl dispatchInvalidateDelayed method is like ViewRootHandler sent a MSG_INVALIDATE message, ViewRootHandler is an internal Handler class in ViewRootImpl

final class ViewRootHandler extends Handler {
    @Override
    public String getMessageName(Message message) {
        switch (message.what) {
            case MSG_INVALIDATE:
                return "MSG_INVALIDATE"; . }returnsuper.getMessageName(message); }... @Override public void handleMessage(Message msg) { switch (msg.what) {case MSG_INVALIDATE:
            ((View) msg.obj).invalidate();
            break; . }}}Copy the code

We can see that the MSG_INVALIDATE message is handled in the ViewRootHandler by invoking the View’s invalidate method.

conclusion

We can draw the conclusion from the above source code:

(1) postInvalidate method calls in the ViewRootImpl dispatchInvalidateDelayed method is sending a message to the ViewRootHandler ViewRootImpl, Finally, the View’s invalidate method is called.

(2) Since ViewRootImpl is in the UI thread, the postInvalidate method is used to switch the refresh operation from the non-UI thread to the UI thread, so that the INvalidate method can be invoked in the UI thread to refresh the View. So, if the View is in the UI thread, and you don’t have to use multiple threads, you can refresh the View with invalidate. This is why we rarely touch the postInvalidate method

(3) In summary, postInvalidate implements the message mechanism that allows us to call the View refresh method on non-UI threads.


Invalidate method refresh View call process analysis

//View.class
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {

    ...
    
    public void invalidate() {
        invalidate(true); } / / invalidateCache fortrueVoid invalidate(Boolean invalidateCache) {invalidateInternal(0, 0, mright-mleft, mbottom-mtop, Boolean invalidateCache) {invalidateInternal(0, 0, mright-mleft, mbottom-mtop, invalidateCache,true);
    }

    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        ...

        if((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) ! = PFLAG_INVALIDATED || (fullInvalidate && isOpaque() ! = mLastIsOpaque)) {if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            mPrivateFlags |= PFLAG_DIRTY;

            if(invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } // That's the point!! final AttachInfo ai = mAttachInfo; final ViewParent p = mParent;if(p ! = null && ai ! = null && l < r && t < b) { final Rect damage = ai.mTmpInvalRect; damage.set(l, t, r, b); p.invalidateChild(this, damage); }... }}}Copy the code

The View’s invalidate method finally calls the invalidateInternal method, and the key logic of the invalidateInternal method is highlighted above.

  • The damage variable represents the area that needs to be redrawn, which is adjusted to the parent layout during a series of calls.
  • The invalidateInternal method calls the View’s parent layout method, invalidateChild, to request a redraw. So who is the parent layout of the View? There are two cases: either ViewGroup or viewrotimPL class.

Let’s take a look at the invalidateChild method in ViewGroup

@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    @Override
    public final void invalidateChild(View child, final Rect dirty) {
        ViewParent parent = this;

        final AttachInfo attachInfo = mAttachInfo;
        if(attachInfo ! = null) { ... // This is a loop that iterates through the parent layout of the current layout View from the current layout View up to the ViewRootImpldo {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }

                if (drawAnimation) {
                    if(view ! = null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; }else if (parent instanceof ViewRootImpl) {
                        ((ViewRootImpl) parent).mIsAnimating = true; }}... / / call here is that the parent layout method of invalidateChildInParent parent. = the parent invalidateChildInParent (location, dirty); . }while(parent ! = null); } } @Override public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
                (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
            if((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) ! = FLAG_OPTIMIZE_INVALIDATE) { ... // Here is also something to calculate the drawing areareturn mParent;

            } else{ mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID; . // Here is also something to calculate the drawing areareturnmParent; }}returnnull; }}Copy the code

There is a loop inside the invalidateChild method of a ViewGroup that keeps calling the invalidateChildInParent method of the parent layout, and the final parent layout of both views and Viewgroups is ViewRootImpl

So the invalidateInternal method in the View and the invalidateChild method in the ViewGroup both end up calling the ViewRootImpl method

public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer. HardwareDrawCallbacks {/ / if there is no parent View layout, @Override public void invalidateChild(View child, Rect dirty) { invalidateChildInParent(null, dirty); } @override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); &emsp; &emsp; &emsp; &emsp; // If dirty is null, redraw the entire area indicated by the current ViewRootImplif (dirty == null) {
            invalidate();
            returnnull; // If dirty is empty, the calculated areas that need to be redrawn need not be drawn}else if(dirty.isEmpty() && ! mIsAnimating) {return null;
        }

        if(mCurScrollY ! = 0 || mTranslator ! = null) { mTempRect.set(dirty); dirty = mTempRect;if(mCurScrollY ! = 0) { dirty.offset(0, -mCurScrollY); }if(mTranslator ! = null) { mTranslator.translateRectInAppWindowToScreen(dirty); }if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }

        invalidateRectOnScreen(dirty);

        return null;
    }   
    
    private void invalidateRectOnScreen(Rect dirty) {
        final Rect localDirty = mDirty; .if(! MWillDrawSoon && (intersected | | mIsAnimating)) {/ / call scheduleTraversals method for drawing scheduleTraversals (); }} // Draw the entire ViewRootImpl area voidinvalidate() {
        mDirty.set(0, 0, mWidth, mHeight);
        if(! MWillDrawSoon) {// Call scheduleTraversals to draw scheduleTraversals(); }}}Copy the code

As you can see in ViewRootImpl the scheduleTraversals method is called at the end to draw. As we understand the rendering process of a View, it starts with the performTraversals method of ViewRootImpl. Let’s look at the scheduleTraversals method of ViewRootImpl.

//ViewRootImpl.class
void scheduleTraversals() {
    if(! mTraversalScheduled) { mTraversalScheduled =true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // This is the key!! mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}
Copy the code

There is no performTraversals method we are looking for. But we see the scheduleTraversals method called mChoreographer. PostCallback methods, there should be a key

The Choreoprapher class orchestrates the execution of input events, animation events, and draw events, and its postCallback method puts the event to be executed into an internal queue, and finally executes the passed Runnable, in this case mTraversalRunnable. So let’s look at mTraversalRunnable

//ViewRootImpl.class
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor"); } // Find our performTraversals method, and here is where to start rendering the View! performTraversals();if (mProfile) {
                Debug.stopMethodTracing();
                mProfile = false; }}}Copy the code

When we call the View’s invalidate method, the View will keep calling up the parent layout drawing method and counting the areas that need to be redrawn in the process, eventually going into the View wrotimpl, The view otimpl’s Perform the redraw traversals is called.

conclusion

  • When we customize a View, when we need to refresh the View in a UI thread, we call the Invalidate method directly, and in a non-UI thread, we call the postInvalidate method to refresh the View
  • The postInvalidate method implements the message mechanism, and the invalidate method is ultimately called to refresh the View
  • The invalidate method ends up calling performTraversals in View wrootimpl to redraw the View

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