IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android基础学习(二十四)—— View绘制 -> 正文阅读

[移动开发]Android基础学习(二十四)—— View绘制

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 绘制流程

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 11:21:32  更:2022-12-25 11:25:53 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/22 18:39:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码