Android View related - common methods of View and differences in use

After the exploration in the previous chapter, we have already understood that the drawing process of View in Android is measure, layout and draw. If you have some understanding of Android, you must know that there are several methods invalidate, postInvalidate and requestLayout in View. We know that After these methods are called, the view will be redrawn (not necessarily correct), so what are their usages, what are the differences, and what are the precautions when using them, this is what we try to understand in this article, okay Now, without further ado, let's start today's analysis.

invalidate

We trace the source from the View source code, and we can see that the method invocation in the View is like this:

/**
 * 使当前View失效,若View是可见的那么onDraw方法会在未来的某个时间点被调用
 * 该方法必须在UI线程被调用,若需要在非UI线程调用,调用postInvalidate
 */
public void invalidate() {
    invalidate(true);
}
/**
 * 这个方法是invalidate整整产生作用的地方,一个invalidate请求会将绘制缓存设为失效
 * 不过这个方式可以设置参数为false来禁止绘制缓存失效
 */
void invalidate(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)) {
        // Propagate the damage rectangle to the parent view.
        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);
            //调用父控件的invalidateChild方法
            p.invalidateChild(this, damage);
        }
        ...
    }
}

It can be seen from this that the function of the invalidate method is to invalidate the drawing cache of the current View, and the onDraw method will be called, but we did not see onDraw in the above code (note that the invalidate method can only be called in the UI thread, and the worker thread calls it This method will report an error), we know that the method finally calls the invalidateChild method of the parent control, then let's check the invalidateChild in the ViewGroup:

/**
 * 不要主动调用该方法
 */
public final void invalidateChild(View child, final Rect dirty) {
    ViewParent parent = this;
    ...
        do {
            ...
            if (view != null) {
                if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                    view.getSolidColor() == 0) {
                    opaqueFlag = PFLAG_DIRTY;
                }
                if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                    view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                }
            }

            parent = parent.invalidateChildInParent(location, dirty);
        } while (parent != null);
    }
}

This method executes a loop of code, where the invalidateChildInParent method of the parent control is called layer by layer. Knowing that the return value is empty, we know that the starting point of drawing is in ViewRootImpl, then open ViewRootImpl and check its invalidateChildInParent code:

@Override
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
    invalidateRectOnScreen(dirty);
    return null;
}
private void invalidateRectOnScreen(Rect dirty) {
    if (!mWillDrawSoon && (intersected || mIsAnimating)) {
        scheduleTraversals();
    }
}

Here, the above loop is terminated after returning null. We notice that ViewRootImpl calls the scheduleTraversals method before the loop terminates. This method is very similar to the performTraversals method we talked about before. Let's take a look at what's in it:

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        //这行代码在Choreographer类内部使用Handler机制最终调用了doTraversal方法
        mChoreographer.postCallback(
            Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

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

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }
        //调用此方法判断是否需要重新测量放置以及绘制
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}

It can be seen that after a circle, ViewRootImpl will eventually call the performTraversals method we mentioned in the previous article to repeat the judgment process. At this point, the execution process of View's invalidate method is completed.

postInvalidate

We can see in the previous comments that the invalidate method cannot be executed on a non-UI thread, but postInvalidate can. Combined with the Handler knowledge we learned before, we guess that the handler mechanism may be used, and invalidate is still called in the end. , then we verify our conjecture in the source code:

public void postInvalidate() {
    postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
    // We try only with the AttachInfo because there's no point in invalidating
    // if we are not attached to our window
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
    }
}
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
    Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
    mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
@Override
public void handleMessage(Message msg) {
    switch (msg.what) {
        case MSG_INVALIDATE:
            ((View) msg.obj).invalidate();
            break;
    }
}

It can be seen that, as we guessed, after a series of column calls, and finally using the Handler mechanism, the invalidate method is called, which means that the difference between the two methods is basically only between threads.

requestLayout

requestLayout is also a very important method in our View. We also saw this method when we talked about the drawing process before, so let's explore what it does:

public void requestLayout() {
    ...
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
    ...
}

This method is similar to the previous invalidateInternal method. It also calls the requestLayout method of the parent control layer by layer until ViewRootImpl (ViewGroup does not overwrite this method, so I will not pay attention here):

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}

I saw the familiar scheduleTraversals method again. The difference between this and the previous invalidate is that the flag bit may change, and the subsequent process is the same.

summary

Generally, the scenarios that cause invalidate calls are as follows:

  • Call the invalidate() method, requesting a redraw(), but only the caller itself will be drawn.
  • Calling the setSelection() method: Requests a redraw(), but only the caller itself is drawn.
  • Call the setVisibility() method: When the View's visual state is converted from INVISIBLE to VISIBLE, the invalidate method will be called indirectly, and then the View will be drawn. When the visual state of the View is converted to the GONE state in the INVISIBLE\VISIBLE state, the requestLayout and invalidate methods will be indirectly called. At the same time, because the size of the View tree has changed, the measure process and the draw process will be requested, and only the "redraw" needs to be drawn. view.
  • Calling the setEnabled() method: Requests a redraw(), but does not redraw any views including the caller itself.
  • Call the requestFocus method. Request the draw process of the View tree, and only draw the Views that "need to be redrawn".
  • The invalidate method can only be called on the UI thread, and postInvalidate can be called on the worker thread, but invalidate is more efficient (obviously)
  • The requestLayout() method will call the measure process and layout process, will not call the draw process, and will not redraw any View including the caller itself

Well, this is the whole content of this article, there are still more theoretical things here, just master the context. In the next article, we will talk about the event distribution mechanism in Android, so stay tuned~
If you think the article is good, you can come to my personal blog to see more content~

Guess you like

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