您的位置:首页 > 产品设计 > UI/UE

Android View requestLayout 与 onDraw跟onMeasure的关系(一)

2017-09-21 11:53 846 查看

requestLayout

如果一个View的大小、位置等View的形状发生变化后,我们可以调用这个函数请求重新布局,将变化后的View更新到屏幕上。如果子View调用了该方法,那么会从View树重新进行一次测量、布局、绘制这三个流程。

简介

调用requestLayout申请重新布局后一般是会调用该View的onDraw()跟ViewGroup的dispatchDraw()

只有在如下几种情况下不调用绘制draw函数:

1、View 不用显示的时候,包含两种情况:

view的Visibility属性不为View.VISIBLE

view设置了OnPreDrawListener,并在onPreDraw里返回了false

2、app还没启动完成正在创建Surface的时候,即app显示的Surface还没有创建完成之前调用了

3、Window窗口正在跑动画的时候不会调用,但是requestLayout的请求在窗口动画第一帧之前会调用一次,即窗口上没有任何显示内容的时候会调用一次将内容更新到窗口上。

调用requestLayout申请重新布局后,肯定是会调用该View的onMeasure方法,以及该View的所有父View的onMeasure方法。对于该View的子View,如果布局发生变化则也会调用onMeasure方法,否则是不会调的,所以你调用了父View的requestLayout方法,如果子View的布局没有发生变化,是不会调用它的onMeasure方法的。

源码分析

源码本版:android-7.0.0_r1

首先查看frameworks/base/core/java/android/view/View.java 文件里的requestLayout()函数

public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();

if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
//如果当前View在请求布局的时候,View树正在进行布局流程的话
//该请求会延迟到布局流程完成后或者绘制流程完成且下一次布局发现的时候再执行
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//设置该View的标志位flag
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;

if (mParent != null && !mParent.isLayoutRequested()) {
//将布局的请求递交父View
//就这样一级一级的向上传递,最终传递到ViewRootImpl
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}


代码中可以看出,这里主要做了三件事情:一判断View树是否正处于布局流程,是则暂缓处理请求,二设置该View的标志位,三将请求递交给父view.

所以重新布局的请求会从下面一级一级往上传递,最终到达ViewRootImpl来处理

查看frameworks/base/core/java/android/view/ViewRootImpl.java文件

public void requestLayout() {
//假如当前正在处理布局变动,则不处理这次的请求
if (!mHandlingLayoutInLayoutRequest) {
//检查是否是Main线程调用的
checkThread();
//设置重新布局的请求为true,处理过这个请求后把它置false
mLayoutRequested = true;
//开启处理任务
scheduleTraversals();
}
}


void scheduleTraversals() {
//已经开启任务则不再开启
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//异步调用真正的工作函数mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}


final class TraversalRunnable implements Runnable {
@Override
public void run() {

​    ​    ​//处理布局请求的函数

doTraversal();
}
}


void doTraversal() {
//mTraversalScheduled前面设置的true
if (mTraversalScheduled) {
mTraversalScheduled = false;
//任务移出队列
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//处理任务
performTraversals();

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


从上面代码可以看出,重新布局的请求最终会在一个异步队列里处理,performTraversals函数过长,下面就只列出关键部位代码

//没有处于停止状态,或者已经上报了强制绘制的属性
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//各种布局发生变化的状态判断
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || framesChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " framesChanged=" + framesChanged);

// Ask host how big it wants to be
//调度Measure
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;

if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}

if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

//设置重新布局的请求为true
//在判断performLayout的时候会用到
layoutRequested = true;
}
}
} else {


//是否需要处理布局
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
//如果处理了performMeasure,就会处理performLayout
if (didLayout) {
performLayout(lp, mWidth, mHeight);


//View是否不需要绘制,两种情况下不需要绘制
//view设置了OnPreDrawListener并在onPreDraw里返回了false,或者view的Visibility属性不为View.VISIBLE
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

//View不需要绘制跟Surface没有创建的时候,不需要调度绘制函数
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}

performDraw();
} else {


而ViewRootImpl最终通过​performMeasure,performLayout,performDraw来调用View的mersure,layout,draw函数来将这些事件在一级一级的分发给子View,直到调用的View
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 源码 布局