关闭

Android GUI系统-ViewTree遍历过程(五)

标签: ViewTree遍历过程performMeasureperformLayoutperformDraw
32人阅读 评论(0) 收藏 举报
分类:

ViewTree的遍历过程performTraversals

遍历过程主要对应三个函数performMeasure(计算大小),perforLayout(计算位置),performLayout(绘制内容)。具体看下这三个步骤执行的条件,及怎么执行的?


1performMeasure

private void performTraversals() @ViewRootImpl.java{
//mView是ViewTree的根。
	final View host = mView;

// mWinFrame是WMS计算后的结果,通过mWindowSession.relayout(...mWinFrame...)传递回来的。
	Rect frame = mWinFrame;
//mFirst是不是第一次执行performTraversal。
	if (mFirst) {
		mFullRedrawNeeded = true;
		mLayoutRequested = true;
	}else{
		desiredWindowWidth = frame.width();
		desiredWindowHeight = frame.height();
//当前的宽高跟期望值不一致。
		if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
              	mFullRedrawNeeded = true;
              	mLayoutRequested = true;
              	windowSizeMayChange = true;
		}
	}
// measureHierarchy是第一次可能会调用performMeasure的地方。mStopped表示窗口的拥有者Activity处于stopped状态,所以这个窗口就不再是active的。
	boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
	if (layoutRequested) {
		windowSizeMayChange |= measureHierarchy(host, lp, res,
			desiredWindowWidth, desiredWindowHeight);
	}
}

//是否执行performMeasure,前面的layoutRequested是其中一部分条件,measureHierarchy方法中还有一些判断。


private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,final 	Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
	boolean goodMeasure = false;
//View请求的宽是 WRAP_CONTENT。
	if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// mTmpValue是期望的对话框的最大宽度,
		res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
		if (baseSize != 0 && desiredWindowWidth > baseSize) {
			performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
		}
	}

//如果View请求的宽度不是 WRAP_CONTENT, goodMeasure为false,会执行 performMeasure。
	if (!goodMeasure) {
		childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
		childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
		performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
	}
}


如果上面的条件没能满足performMeasure的执行条件,performTraversals往下走,还有判断是不是执行performMeasure


private void performTraversals() @ViewRootImpl.java{
// mStopped当前activity不处于stoped状态; mReportNextDraw通常在点亮屏幕扥带第一次绘制时,用于加快systemUI跟窗口管理之间的交互时,设置为true。
	if (!mStopped || mReportNextDraw) {
//触摸模式是否引起焦点变化。
		boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
		(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
//WMS计算后得到的宽高跟View经过onMeasure计算的值不一致。
		if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
			|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
			updatedConfiguration) {
			performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
		}
	}
}

以上条件满足,开始执行performMeasure


private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
//直接调用ViewTree的根元素mView的 measure,View中measure和onMeasure子类都可以重载,通常建议重载onMeasure,测量工作也是onMeasure中进行的。
	mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}	

measure方法会调用onMeasure执行实际的测量工作。


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) @View.java{
	setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
		getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

其中的参数widthMeasureSpecheightMeasureSpec是上一级父View要求的宽高,依次往下传递到viewTree的各个元素。在ViewRootImpl中,这两个值是通过getRootMeasureSpec计算得出的,然后传给ViewTree的根元素DecorView

子类在重写onMeasure方法时,必须调用setMeasuredDimension来存储该view测量后的宽高。


看一个具体View的子类,如何重写onMeasure


protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) @FrameLayout.java{
	int count = getChildCount();
//循环处理所有的子View。
	for (int i = 0; i < count; i++) {
		final View child = getChildAt(i);
//测量每个子对象,条件是 mMeasureAllChildren为true,相关的属性是:android.R.styleable#FrameLayout_measureAllChildren;并且View是可见的。
		if ( mMeasureAllChildren || child.getVisibility() != GONE) {
//测量子对象,考虑padding,margins,如果子view是个ViewGroup,则会调用其child.measure函数继续递归,直到叶节点。
			measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
//得到view(Child)的计算结果,child.getMeasuredWidth()对应了View的MeasuredWidth变量,这个值通常是在onMeasure结束时通过setMeasuredDimension来设置的。
                	maxWidth = Math.max(maxWidth,
                        child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
                	maxHeight = Math.max(maxHeight,
                        child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
		}
	}

//考虑其他因素,如padding,对于View类来说,它只有padding,padding指的是内容区域与外围边框的距离,有left、top、right、bottom四个值;对于ViewGroup来说,除了padding,还有margin,margin指的是内容中各个元素之间的距离。
        maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
        maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

//对前面测量的宽高的最大值做调整,
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

//保存测量的结果。
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
}

2performLayout计算位置。

private void performTraversals() @ViewRootImpl.java{
	final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
//这个条件跟前面performMeasure类似, mWidth, mHeight是由WMS计算后的值,表示WMS期望的宽高。
	if (didLayout) {
		performLayout(lp, mWidth, mHeight);
	}
}


private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) @ViewRootImpl.java{
	final View host = mView;
//调用的ViewTree根元素的方法 layout,传入的参数也是DecorView的测量后的宽高。
	host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}

子类不应该重载layout方法,而是应该重载onLayout方法,然后在onLayout方法中,调用每个子viewlayout方法。四个参数表示的是这个view相对于它的父view四个方向的位置。


public void layout(int l, int t, int r, int b) {
//通过setFrame将这4个边距记录到成员变量中mLeft;mTop;mBottom;mRight;
	boolean changed = isLayoutModeOptical(mParent) ?
		setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

//view.java中的onLayout是null的,调用具体子类的onLayout。
	if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == 			
		PFLAG_LAYOUT_REQUIRED) {
		onLayout(changed, l, t, r, b);
	}
//将flag恢复。
	mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}

protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
	@FrameLayout.java{
	layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) 
	@FrameLayout.java{
	final int count = getChildCount();
        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();
        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top – getPaddingBottomWithForeground();

	for (int i = 0; i < count; i++) {
		final View child = getChildAt(i);
		if (child.getVisibility() != GONE) {
//子view设置的layout属性。
			final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//在onMeasure中计算的宽高,这里在计算出left,top就可以确定一个子view在父View中具体位置。
			final int width = child.getMeasuredWidth();
			final int height = child.getMeasuredHeight();
			int childLeft;
			int childTop;
// gravity属性,是在不改变子View大小的情况下,控制其在父View中的位置,比如放在父对象的顶部、左部、垂直居中、水平居中等。
			int gravity = lp.gravity;
			final int layoutDirection = getLayoutDirection();
			final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
			final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
//根据具体的布局属性,计算left。
			switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    	case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
			}
//根据具体的布局属性,计算top。
			 switch (verticalGravity) {
                    		case Gravity.CENTER_VERTICAL:
                       	 childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        	 lp.topMargin - lp.bottomMargin;
                      	  break;
			}
//调用子View的layout方法,依次递归下去。
			child.layout(childLeft, childTop, childLeft + width, childTop + height);
		}
	}
}


3)performDraw。遍历流程的最后一步,

private void performTraversals() @ViewRootImpl.java{
	boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
// cancelDraw表示当前draw被取消,需要重新遍历; 在前面relayoutWindow过程中,如果WMS没有成功申请到surface,newSurface会置为true,这是也需要重新遍历。
	if (!cancelDraw && !newSurface) {
// performDraw重点调用了draw方法。
		performDraw();
	}
}

图形绘制的方式分软件和硬件两种,什么时候会使用硬件方式绘制呢?

当执行ActivityonCreate方法时,调用了PhoneWindow.javasetContentView,接着调用了PhoneWindow.javainstallDecor


private void installDecor() @PhoneWindow.java{
// mDecor为null时,调用 generateDecor传入的featureId是-1,这个id将会影响后期的绘制方式。
	if (mDecor == null) {
		mDecor = generateDecor(-1);
	}else{…...}
}

生成DecorView对象时,把这个featureId保存在DecorView的变量mFeatureId中。除了生成应用程序的DecorViewfeatureId-1,其他类型窗口的featureId都是大于0的值,如:


public abstract class Window @Window.java{
    /** Flag for the "options panel" feature.  This is enabled by default. */
    public static final int FEATURE_OPTIONS_PANEL = 0;
    /** Flag for the context menu.  This is enabled by default. */
    public static final int FEATURE_CONTEXT_MENU = 6;
    /**
     * Flag for enabling the Action Bar.
     * This is enabled by default for some devices. The Action Bar
     * replaces the title bar and provides an alternate location
     * for an on-screen menu button on some devices.
     */
    public static final int FEATURE_ACTION_BAR = 8;
…...	
}

只有featureid小于0时,willYouTakeTheSurface方法的返回值才可能不为null


public android.view.SurfaceHolder.Callback2 willYouTakeTheSurface() @DecorView.java{
	return mFeatureId < 0 ? mWindow.mTakeSurfaceCallback : null;
}

ViewRootImpl.java中,willYouTakeTheSurface方法的返回值,会影响到会不会生成对象mAttachInfo.mHardwareRenderermAttachInfo.mHardwareRenderer= ThreadedRenderer.create(mContext, translucent);)而这个对象判断绘制方式时会用到。


在回头看下performDraw调用的draw方法。


private void draw(boolean fullRedrawNeeded) @ViewRootImpl.java{
//surface是画板,如果没有合法的surface,UI数据就无法正常存储和显示。
	Surface surface = mSurface;
	if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
//这个条件下,调用硬件绘制的方式。
		if (mAttachInfo.mHardwareRenderer != null && 			
			mAttachInfo.mHardwareRenderer.isEnabled()) {
			mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
		}else{
//否则,调用软件绘制的方式。
			if (!drawSoftware(surface, mAttachInfo, 
				xOffset, yOffset, scalingRequired, dirty)) {
				return;
			}
		}
	}
}

看下软件渲染是怎么绘制的。


private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) @ViewRootImpl.java{
//canvas是作画的工具集。
	final Canvas canvas;
//锁定一个canvas对象,将使用这个canvas把数据画到surface中。
	canvas = mSurface.lockCanvas(dirty);

	mView.mPrivateFlags |= View.PFLAG_DRAWN;
//做必要的坐标变换。
	canvas.translate(-xoff, -yoff);
	mTranslator.translateCanvas(canvas);

//从Viewtree的根开始绘制。
	mView.draw(canvas);
//绘制结束,解锁canvas,提交结果,canvas的绘制结果通过surface提交给surfacefligner,最后合成到framebuffer中,才能显示到屏幕上。
	surface.unlockCanvasAndPost(canvas);
}


0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

Android View原理(View树遍历,View重绘,View动画)

一、屏幕绘图基础 Android中的GUI系统是客户端和服务端配合的窗口系统,即后台运行了一个绘制服务,每个应用程序都是该服务端的一个客户端,当客户端需要绘制时,首先请求服务端创建一个窗口,然后...
  • wtyvhreal
  • wtyvhreal
  • 2015-04-01 10:36
  • 2847

Windows编程 - 遍历所有进程(exe) 代码(C++)

遍历所有进程(exe) 代码(C++)本文地址: http://blog.csdn.net/caroline_wendy/article/details/29381987 遍历所有进程, 即任务管理器...
  • u012515223
  • u012515223
  • 2014-06-08 18:18
  • 4712

HashMap、LinkedHashMap元素遍历机制探讨

转载原文地址:http://blog.csdn.net/luanlouis/article/details/43017071 Map作为键值对Entry的的容器,对其内部 键值对Entry 的遍...
  • feixiaohuijava
  • feixiaohuijava
  • 2016-04-29 17:34
  • 1604

Android GUI系统-ViewTree的绘图遍历(六)

ViewTree的绘图遍历 绘图遍历也就是ViewTree遍历过程的最后一步。这个过程有3个核心的步骤: 1)canvas= mSurface.lockCanvas(dirty); 2)mVie...
  • lin20044140410
  • lin20044140410
  • 2018-01-07 20:48
  • 23

Android GUI系统-ViewTree的遍历(四)

那些情况会引起ViewTree的遍历 1)应用程序刚启动时,会在构造出整棵ViewTree后,执行第一次遍历。 public void setView(View view, WindowMan...
  • lin20044140410
  • lin20044140410
  • 2017-12-26 07:09
  • 29

Android GUI系统-ViewTree的创建(二)

View树的创建过程 当AMS通知应用进程来启动一个Activity任务时,最终这个请求会转化为ActivityThread中的一个消息LAUNCH_ACTIVITY,同类型的消息还是RESUME_...
  • lin20044140410
  • lin20044140410
  • 2017-12-21 22:38
  • 43

Android GUI系统-ViewTree的管理者(三)

作为ViewTree的管理者ViewRoot,是怎么工作的。 一,ViewRootImpl跟WMS间的通信 ViewRootImpl.java在构造的时候,需要建立跟WMS的通信的双向...
  • lin20044140410
  • lin20044140410
  • 2017-12-24 18:16
  • 41

App的启动过程(5)ViewTree遍历中最后一步的Draw

以上是WMS端窗口的添加,下面接着ViewTree遍历中最后一步的Draw的分析。 /* ViewRootImpl.java */ private boolean drawSoftware()à ca...
  • lin20044140410
  • lin20044140410
  • 2017-06-01 17:15
  • 254

五个条件的专家系统,包括初始化,以及遍历过程

  • 2011-09-04 18:39
  • 4KB
  • 下载

Android 7.1 GUI系统-窗口管理WMS-窗口大小计算(五)

窗口大小的计算 一个应用窗口,除了应用程序本身的内容外,还有状态栏,可能还有输入法窗口,状态栏的大小是固定的,输入法窗口可以在AndroidManifest.xml中配置,相关属性如下: 以sta...
  • lin20044140410
  • lin20044140410
  • 2017-12-16 22:44
  • 67
    个人资料
    • 访问:35523次
    • 积分:1686
    • 等级:
    • 排名:千里之外
    • 原创:130篇
    • 转载:38篇
    • 译文:0篇
    • 评论:9条
    最新评论