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

news/2025/1/13 7:38:12/

1、Activity.setContentView

Activity.setContentView(layoutResID:int)PhoneWindow.setContentView(layoutResID:int)PhoneWindow.installDecor//mContentParent为DecorViewLayoutInflater.inflate(layoutResID:int, mContentParent:ViewGroup)//attachToRoot为 root != nullLayoutInflater.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标签属性来创建对应的ViewView temp = createViewFromTag(root, name, inflaterContext, attrs)ViewGroup.LayoutParams params = root.generateLayoutParams(attrs)//temp为xml布局文件的根View//rInflateChildren,递归加载根View的所有子ViewLayoutInflater.rInflateChildren(parser, temp, attrs, true)LayoutInflater.rInflateView view = LayoutInflater.createViewFromTag//这里的parent为tempViewGroup viewGroup = (ViewGroup)parent ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs)//view会作为rInflateChildren方法的其中一个参数LayoutInflater.rInflateChildren ... ...ViewGroup.addViewroot.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.handleResumeActivityWindowManagerImpl.addViewWindowManagerGlobal.addView  ... ...   //将DecorView添加到Window中new ViewRootImpl(view.getContext(), display)  //创建ViewRootImpl对象ViewRootImpl.setView   //将ViewRootImpl对象与DecorView相互关联起来ViewRootImpl.requestLayout  //完成应用程序用户界面的初次布局ViewRootImpl.scheduleTraversalsViewRootImpl.doTraversalViewRootImpl.performTraversalsViewRootImpl.measureHierarchyViewRootImpl.performLayout ViewRootImpl.performDrawIWindowSession.addToDisplay  //跨进程调用,最终在系统进程使用WindowManagerService.addWindow()来实现更新window的逻辑

(1)measure 测量

//Ask host how big it wants to be
ViewRootImpl.measureHierarchy//获取根MeasureSpec,该值代表了对decorView的宽高的约束信息ViewRootImpl.getRootMeasureSpec  ViewRootImpl.performMeasureDecorView.measure//只有满足forceLayout或needsLayout为true这两种情况才会进行实际的测量工作View.measure DecorView.onMeasureFrameLayout.onMeasure//遍历DecorView的子View,对每个子View执行measureChildWithMargins(child) //目的是找到maxHeight和maxWidth,表示当前容器View用这个尺寸就能正常显示所有子View(同时考虑了padding和margin) ViewGroup.measureChildWithMargins ViewGroup.getChildMeasureSpecchild.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 uscase 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 uscase 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 ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

(2)layout 布局

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

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

(3)draw 绘制

ViewRootImpl.performDrawViewRootImpl.draw(boolean fullRedrawNeeded)  //视图重绘ViewRootImpl.drawSoftwareDecorView.draw(Canvas canvas)View.draw  //ViewGroup没有重写draw//绘制包含7步,2/5可略View.drawBackground //Step 1, draw the background, if neededView.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 绘制流程


http://www.ppmy.cn/news/6120.html

相关文章

【数据结构】详解队列和循环队列

目录一.队列1.队列的概念及结构2.队列的实现Queue.hQueue.c二.循环队列1.循环队列的实现2.设计循环队列解题思路代码一.队列 1.队列的概念及结构 队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出…

单商户商城系统功能拆解52—财务概况

单商户商城系统,也称为B2C自营电商模式单店商城系统。可以快速帮助个人、机构和企业搭建自己的私域交易线上商城。 单商户商城系统完美契合私域流量变现闭环交易使用。通常拥有丰富的营销玩法,例如拼团,秒杀,砍价,包邮…

10.1、Django框架简介、创建第一个应用

文章目录预备知识MVC模式和MTV模式MVC模式MTV 模式Django框架Django框架简介Django框架的应用启动后台admin站点管理数据库迁移创建管理员用户管理界面本地化创建并使用一个应用bookapp项目的数据库模型创建数据库模型生成数据库表数据库上的基本操作启用后台admin站点管理自定…

PID算法总结-从公式原理到参数整定解析

目录 一、控制系统 1.1控制系统的分类 1.2 性能指标 二、PID算法的起源及特点 三、PID应用 四、PID公式原理 五、PID源码 六、PID整定方法 6.1 经验法 6.2 衰减曲线法 6.3 响应曲线法 参考文献: 一、控制系统 1.1控制系统的分类 分为开环控制、闭环控制和复…

Web API节点操作

1、节点概述 网页中的所有内容都是节点(标签、属性、文本、注释等),在DOM 中,节点使用 node 来表示。HTML DOM 树中的所有节点均可通过 JavaScript 进行访问,所有 HTML 元素(节点)均可被修改&a…

TCManager——中药房管理系统大作业

简介 由于最近一个月世界变化有点大,所以一直在同步自己的大脑,没有写博客。 上个月花了5天(3天后端2天前端)写了个经典的springbootvue2的中药房管理系统大作业——TCManager。项目已在gitee上(校园网差,…

SpringBoot整合RocketMQ

SpringBoot整合RocketMQ 因为SpringBoot集成RocketMQ的starter依赖是由Spring社区提供的&#xff0c;目前正在快速迭代的过程当中&#xff0c;不同版本之间的差距非常大&#xff0c;甚至基础的底层对象都会经常有改动。 1.创建消息生产者 1.1生产者Pom <?xml version&q…

cgo对go的性能影响

cgo对go的性能影响 概述 做go封装给python和perl进行使用的时候&#xff0c;使用cgo->swig->python、perl流程&#xff0c;对整个流程的性能进行了粗略的性能比较。&#xff08;在deepin环境进行的测试&#xff09; cgo代码 package main //注意必须是main包// #inclu…