草庐IT

Android基础学习(二十四)—— View绘制

浅唱整个春天 2023-04-03 原文

1、Activity.setContentView

Activity.setContentView(layoutResID:int)
	PhoneWindow.setContentView(layoutResID:int)
    	PhoneWindow.installDecor
    	//mContentParent为DecorView
		LayoutInflater.inflate(layoutResID:int, mContentParent:ViewGroup)
    		//attachToRoot为 root != null
    		LayoutInflater.inflate(layoutResID:int, root:ViewGroup, attachToRoot:boolean) 
    			Resource res = getContext().getResources()
				XmlResourceParser parser = res.getLayout(layoutResID)
				LayoutInflater.inflate(parser:XmlResourceParser, root:ViewGroup, attachToRoot:boolean)
    				//获取layoutResID对应布局中的属性信息
					AttributeSet attrs = Xml.asAttributeSet(parser)
					String name = parser.getName()
    				//createViewFromTag方法根据tag标签属性来创建对应的View
					View temp = createViewFromTag(root, name, inflaterContext, attrs)
    				ViewGroup.LayoutParams params = root.generateLayoutParams(attrs)
    				//temp为xml布局文件的根View
    				//rInflateChildren,递归加载根View的所有子View
					LayoutInflater.rInflateChildren(parser, temp, attrs, true)
    					LayoutInflater.rInflate
    						View view = LayoutInflater.createViewFromTag
    						//这里的parent为temp
  							ViewGroup viewGroup = (ViewGroup)parent 
    						ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs)
    						//view会作为rInflateChildren方法的其中一个参数
    						LayoutInflater.rInflateChildren 
    							... ...
    						ViewGroup.addView
					root.addView(temp, params)	

(1)DecorView的创建时机:

Activity.setContentView->PhoneWindow.setContentView->PhoneWindow.installDecor -> PhoneWindow.generateDecor

(2)SetContentView主要做的事情:

①创建DecorView;②根据layoutResId创建View并添加到DecorView中

(3)LayoutParams

​ 在View绘制中,MeasureSpec封装了从父布局传递给子布局的布局要求。对于DecorView来说,其MeasureSpec由它自身的LayoutParams决定;对于除DecorView之外的普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams共同决定。

​ 每个View和ViewGroup都需要通过其父容器ViewGroup的generateLayoutParams方法来生成LayoutParams对象,且每个ViewGroup子类返回的LayoutParams一般来说都需要继承于MarginLayoutParams,这样才能具备解析layout_margin的能力,且还需要再根据自身ViewGroup提供的标签属性来进一步扩展MarginLayoutParams的功能。

2、View的绘制工作

ViewRoot负责执行View绘制的整个流程,每个应用程序窗口的decorView都有一个与之关联的ViewRoot对象,这种关联关系是由WindowManager来维护的,关联关系是在Activity启动时建立的:

ActivityThread.handleResumeActivity
	WindowManagerImpl.addView
		WindowManagerGlobal.addView  
			... ...   //将DecorView添加到Window中
			new ViewRootImpl(view.getContext(), display)  //创建ViewRootImpl对象
			ViewRootImpl.setView   //将ViewRootImpl对象与DecorView相互关联起来
				ViewRootImpl.requestLayout  //完成应用程序用户界面的初次布局
					 ViewRootImpl.scheduleTraversals
					 	ViewRootImpl.doTraversal
					 		ViewRootImpl.performTraversals
					 			ViewRootImpl.measureHierarchy
					 			ViewRootImpl.performLayout 
					 			ViewRootImpl.performDraw
				IWindowSession.addToDisplay  //跨进程调用,最终在系统进程使用WindowManagerService.addWindow()来实现更新window的逻辑

(1)measure 测量

//Ask host how big it wants to be
ViewRootImpl.measureHierarchy
    //获取根MeasureSpec,该值代表了对decorView的宽高的约束信息
	ViewRootImpl.getRootMeasureSpec  
	ViewRootImpl.performMeasure
		DecorView.measure
    		//只有满足forceLayout或needsLayout为true这两种情况才会进行实际的测量工作
			View.measure 
				DecorView.onMeasure
					FrameLayout.onMeasure
						//遍历DecorView的子View,对每个子View执行measureChildWithMargins(child) 
    					//目的是找到maxHeight和maxWidth,表示当前容器View用这个尺寸就能正常显示所有子View(同时考虑了padding和margin) 
						ViewGroup.measureChildWithMargins 
							ViewGroup.getChildMeasureSpec
							child.measure   //child:View  若此时的子View为ViewGroup的子类,便会调用相应容器类的onMeasure()方法,其他容器View的onMeasure()方法与FrameLayout的onMeasure()方法执行过程相似
								...   //递归执行所有子View的测量工作
						View.resolveSizeAndState  //根据之前的测量结果确定最终对FrameLayout的测量结果并存储起来
						ViewGroup.setMeasuredDimension

① ViewGroup没有像View一样对onMeasure方法做统一实现,ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,比如LinearLayout、RelativeLayout等。

② MeasureSpec并不是指View的测量宽高,而是根据MeasureSpec测出测量宽高。MeasureSpec是一个32位整数,由SpecMode和SpecSize两部分组成,其中,高2位为SpecMode,低30位为SpecSize。SpecMode为测量模式,SpecSize为相应测量模式下的测量尺寸。View(包括普通View和ViewGroup)的SpecMode由本View的LayoutParams结合父View的MeasureSpec生成

SpecMode的取值可为以下三种:

  • EXACTLY: 对子View提出了一个确切的建议尺寸(SpecSize);

  • AT_MOST: 子View的大小不得超过SpecSize;

  • UNSPECIFIED: 对子View的尺寸不作限制,通常用于系统内部。

    //ViewRootImpl.getRootMeasureSpec
    private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    	int measureSpec;
    	switch (rootDimension) {
    		case ViewGroup.LayoutParams.MATCH_PARENT:
    			// Window can't resize. Force root view to be windowSize.
    			measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
    			break;
    		case ViewGroup.LayoutParams.WRAP_CONTENT:
    			// Window can resize. Set max size for root view.
    			measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
    			break;
    		default:
    			// Window wants to be an exact size. Force root view to be that size.
    			measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, 		MeasureSpec.EXACTLY);
    			break;
    	}
    	return measureSpec;
    }
    
//ViewGroup.measureChildWithMargins
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
	final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
	final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed, lp.height);
	
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);  //参数是父View用于约束其测量的
}
//ViewGroup.getChildMeasureSpec
//展现了根据父View的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec的过程
  //参数:
  // spec为父View的MeasureSpec
  // padding为父View在相应方向的已用尺寸加上父View的padding和子View的margin
  // childDimension为子View的LayoutParams的值
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  int specMode = MeasureSpec.getMode(spec);
  int specSize = MeasureSpec.getSize(spec);

  // 现在size的值为父View相应方向上的可用大小
  int size = Math.max(0, specSize - padding);

  int resultSize = 0;
  int resultMode = 0;

  switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
      if (childDimension >= 0) {
        // 表示子View的LayoutParams指定了具体大小值(xx dp)
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // 子View想和父View一样大
        resultSize = size;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // 子View想自己决定其尺寸,但不能比父View大 
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      }
      break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
      if (childDimension >= 0) {
        // 子View指定了具体大小
        resultSize = childDimension;
        resultMode = MeasureSpec.EXACTLY;
      } else if (childDimension == LayoutParams.MATCH_PARENT) {
        // 子View想跟父View一样大,但是父View的大小未固定下来
        // 所以指定约束子View不能比父View大
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      } else if (childDimension == LayoutParams.WRAP_CONTENT) {
        // 子View想要自己决定尺寸,但不能比父View大
        resultSize = size;
        resultMode = MeasureSpec.AT_MOST;
      }
      break;

      . . .
  }

  //noinspection ResourceType
  return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

(2)layout 布局

ViewRootImpl.performLayout
	DecorView.layout
		View.layout    //layout方法确定View本身的位置
			View.setFrame  //设定View的四个顶点的位置,即初始化l、r、t、b四个值
			View.onLayout	//父容器确定子元素的位置   View和ViewGroup都没有真正实现此方法
			FrameLayout.onLayout
            	FrameLayout.layoutChildren
					child.layout  //child:View
						View.setFrame
    					View.onLayout
					    LinearLayout.onLayout  //以LinearLayout为例
					    	LinearLayout.layoutVertical
					    		LinearLayout.setChildFrame //调用子元素的layout方法
					    			child.layout
    									... //重复上述调用

layout方法又会调用自身的onLayout方法。onLayout方法在View类中是空实现,大部分情况下View都无需重写该方法;onLayout方法在ViewGroup中为抽象方法,即每个ViewGroup子类都需要通过实现该方法来管理自己的所有childView的摆放位置。

(3)draw 绘制

ViewRootImpl.performDraw
	ViewRootImpl.draw(boolean fullRedrawNeeded)  //视图重绘
		ViewRootImpl.drawSoftware
			DecorView.draw(Canvas canvas)
				View.draw  //ViewGroup没有重写draw
    				//绘制包含7步,2/5可略
					View.drawBackground //Step 1, draw the background, if needed
					View.onDraw  //Step 3, draw the content 不同的View有不同的实现
					View.dispatchDraw  //Step 4, draw the children  ViewGroup重写了此方法
					View.onDrawForeground  //Step 6, draw decorations (foreground, scrollbars)
					View.drawDefaultFocusHighlight  // Step 7, draw the default focus highlight

draw是绘制视图的过程,在这个过程中View需要通过操作Canvas来实现自己的UI效果。





参考文章:
深入理解Android之View的绘制流程
一文读懂 View 的 Measure、Layout、Draw 流程
探索 Android View 绘制流程

有关Android基础学习(二十四)—— View绘制的更多相关文章

  1. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  2. ruby-on-rails - 渲染另一个 Controller 的 View - 2

    我想要做的是有2个不同的Controller,client和test_client。客户端Controller已经构建,我想创建一个test_clientController,我可以使用它来玩弄客户端的UI并根据需要进行调整。我主要是想绕过我在客户端中内置的验证及其对加载数据的管理Controller的依赖。所以我希望test_clientController加载示例数据集,然后呈现客户端Controller的索引View,以便我可以调整客户端UI。就是这样。我在test_clients索引方法中试过这个:classTestClientdefindexrender:template=>

  3. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  4. ruby-on-rails - 如何在 Rails View 上显示错误消息? - 2

    我是rails的新手,想在form字段上应用验证。myviewsnew.html.erb.....模拟.rbclassSimulation{:in=>1..25,:message=>'Therowmustbebetween1and25'}end模拟Controller.rbclassSimulationsController我想检查模型类中row字段的整数范围,如果不在范围内则返回错误信息。我可以检查上面代码的范围,但无法返回错误消息提前致谢 最佳答案 关键是您使用的是模型表单,一种显示ActiveRecord模型实例属性的表单。c

  5. ruby-on-rails - 复数 for fields_for has_many 关联未显示在 View 中 - 2

    目前,Itembelongs_toCompany和has_manyItemVariants。我正在尝试使用嵌套的fields_for通过Item表单添加ItemVariant字段,但是使用:item_variants不显示该表单。只有当我使用单数时才会显示。我检查了我的关联,它们似乎是正确的,这可能与嵌套在公司下的项目有关,还是我遗漏了其他东西?提前致谢。注意:下面的代码片段中省略了不相关的代码。编辑:不知道这是否相关,但我正在使用CanCan进行身份验证。routes.rbresources:companiesdoresources:itemsenditem.rbclassItemi

  6. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  7. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  8. LC滤波器设计学习笔记(一)滤波电路入门 - 2

    目录前言滤波电路科普主要分类实际情况单位的概念常用评价参数函数型滤波器简单分析滤波电路构成低通滤波器RC低通滤波器RL低通滤波器高通滤波器RC高通滤波器RL高通滤波器部分摘自《LC滤波器设计与制作》,侵权删。前言最近需要学习放大电路和滤波电路,但是由于只在之前做音乐频谱分析仪的时候简单了解过一点点运放,所以也是相当从零开始学习了。滤波电路科普主要分类滤波器:主要是从不同频率的成分中提取出特定频率的信号。有源滤波器:由RC元件与运算放大器组成的滤波器。可滤除某一次或多次谐波,最普通易于采用的无源滤波器结构是将电感与电容串联,可对主要次谐波(3、5、7)构成低阻抗旁路。无源滤波器:无源滤波器,又称

  9. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  10. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

随机推荐