9.view 作图过程,讲讲draw/onDraw和drawChild

2023-11-05 12:18

本文主要是介绍9.view 作图过程,讲讲draw/onDraw和drawChild,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

9.view 绘制过程,讲讲draw/onDraw和drawChild
转载请标明出处: 

http://blog.csdn.net/yujun411522/article/details/46226135
本文出自:【yujun411522的博客】


 9.1 view的绘制过程
view的绘制过程在UI中还是非常重要的,view的绘制是从根节点,自上而下的一个过程,主要经历三个过程:
     
先看performTraversals函数代码,这段代码很长,主要工作可以归结为三个函数:   
 ViewRootImpl.java中performTraversals()private void perfromTraversals(){final View host = mView;....//1.measure过程host.measure(childWidthMeasureSpec,childHeightMeasureSpec);....//2.layout过程host.layout(0,0,host.getMeasureWidth(),host.getMeasureHeight);        .....//3 draw过程draw(fullRedrawNeeded);}
下面分别来介绍着三个过程:

9.1.1 measure
measure中主要涉及以下几个方法:
public final void measure(int widthMeasureSpec,int heightMeasureSpec)
protected voidonMeasure(int widthMeasureSpec,int heightMeasureSpec)
protected final void setMeasureDimension(int measuredWidht,int measureHeight)
这里先还要介绍一个类:MeasureSpec。引入这个类的主要目的是在android中子view的尺寸还要受到父view的限制
一个MeasureSpec是一个int 类型,共32b,有两部分组成:高两位是mode,剩下的30位是size
mode中有三种模式:
UNSPECIFIED:不受到父view的限制,可以设置为任意值。基本上不用到。
EXACTLY:父view中指定子view的大小,不管子view如果设置。比如:match_parent、或者具体的大小
AT_MOST:父view中指定子view最多只能这么多。比如:wrap_content
在ViewRootImpl中已经说明了对应关系    
  private int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT</strong>:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT</strong>:// 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;}
可以看出match_parent具体尺寸对应的都是EXACTLYwrap_content对应的是AT_MOST
接下来看一下view.measure函数的处理过程    
 public final void measure (int widthMeasureSpec, int heightMeasureSpec) {....// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec);....    mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;}
调用了onMeasure方法:   
  protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimensiongetDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
里面涉及了三个方法: getSuggestedMinimumWidth,getDefaultSize,setMeasuredDimension
先看view.getSuggestedMinimumWidth()   
  protected int getSuggestedMinimumWidth() {int suggestedMinWidth = mMinWidth ;if (mBGDrawable != null) {final int bgMinWidth = mBGDrawable.getMinimumWidth();if (suggestedMinWidth < bgMinWidth) {suggestedMinWidth = bgMinWidth;}}return suggestedMinWidth</strong>;//返回背景图最小值和view最小值中的较大者}
getSuggestedMinimumHeight()方法类似,不做介绍。
再看view.getDefaultSize()方法      
  public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;//UNSPECIFIED,返回值为size值break;case MeasureSpec.AT_MOST://AT_MOST,返回值为size值case MeasureSpec.EXACTLY:result = specSize;//EXACTLY,返回值为specSize值break;}return result;}
再看setMeasuredDimension方法:       
 protected final void setMeasuredDimension (int measuredWidth, int measuredHeight) {mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= MEASURED_DIMENSION_SET ;}
我们在override完onMeasure方法之后一定要执行这个setMeasuredDimension方法,这个方法的目的就是保存measure之后的值。

以上介绍的是view的measure过程,如果是viewGroup时流程会有区别:viewGroup循环measure所有子view。先看一看ViewGroup中的measureChildren方法   
  /*** Ask all of the children of this view to measure themselves, taking into account both the MeasureSpec requirements for this view and its padding.* We skip children that are in the GONE state The heavy lifting is done in getChildMeasureSpec.     * @param widthMeasureSpec The width requirements for this view* @param heightMeasureSpec The height requirements for this view*/protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {final int size = mChildrenCount;final View[] children = mChildren;for (int i = 0; i < size; ++i) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {//过滤掉GONE的viewmeasureChild(child, widthMeasureSpec, heightMeasureSpec);//让所有的子view measure自己。调用measureChild方法。}}}
调用ViewGroup.measureChild(view,int,int)。类似measureChild函数还有measureChildWithMargins(view,int,int))
  /*** Ask one of the children of this view to measure itself, taking into account both the MeasureSpec requirements for this view and its padding.* The heavy lifting is done in getChildMeasureSpec.* @param child The child to measure* @param parentWidthMeasureSpec The width requirements for this view* @param parentHeightMeasureSpec The height requirements for this view*/protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {final LayoutParams lp = child.getLayoutParams();//主要工作还是在getChildMeasureSpec函数中进行final int childWidthMeasureSpec = getChildMeasureSpec</strong>(parentWidthMeasureSpec,mPaddingLeft+mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec</strong>(parentHeightMeasureSpec, mPaddingTop+ mPaddingBottom, lp.height);child.measure</strong>(childWidthMeasureSpchildHeightMeasureSpec ec, childHeightMeasureSpec);//计算完childWidthMeasureSpec 、childHeightMeasureSpec 调用子view的measure方法}
其中调用了ViewGroup.getChildMeasureSpec方法,这个方法比较复杂: 此方法的目的是根据父view对子view的MeasureSpec和子view的layout参数产生一个最合适的MeasureSpec  
  / * Does the hard part of measureChildren: figuring out the MeasureSpec to pass to a particular child. This method figures out the right MeasureSpec for one dimension (height or width) of one child view. The goal is to combine information from ourMeasureSpec with the LayoutParams of the child to get the best possible results. For example,if the this view knows its size (because its MeasureSpec has a mode of EXACTLY), and the child has indicated in its LayoutParamsthat it wants to be the same size as the parent, the parent should ask the child to layout given an exact size.* @param spec The requirements for this view* @param padding The padding of this view for the current dimension and  margins, if applicable* @param childDimension How big the child wants to be in the current    dimension* @return a MeasureSpec integer for the child*/public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);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) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.resultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size, but our size is not fixed.// Constrain child to not be bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent asked to see how big we want to becase MeasureSpec.UNSPECIFIED:if (childDimension >= 0) {// Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size... find out how big it should// beresultSize = 0;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size.... find out how// big it should beresultSize = 0;resultMode = MeasureSpec.UNSPECIFIED;}break;}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);}
可以看出上面的处理过程还是挺复杂的,该方法返回父view对子view的一个32b的measureSpec。
measureChild方法最后调用view的measure方法,再按照view的measure流程绘制。
具体的可以参看不同view控件,以及measure的实现。

9.1.2 layout
measure的过程就是确定view的大小而layout的过程则是确定view位置
看一下view.layout()方法:   
  public void layout(int l, int t, int r, int b) {int oldL = mLeft ;int oldT = mTop ;int oldB = mBottom ;int oldR = mRight ;boolean changed = setFrame(l, t, r, b);if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED ) {if (ViewDebug. TRACE_HIERARCHY) {ViewDebug. trace( this, ViewDebug.HierarchyTraceType.ON_LAYOUT );}onLayout(changed, l, t, r, b);//调用onLayout方法mPrivateFlags &= ~LAYOUT_REQUIRED ;if (mOnLayoutChangeListeners != null) {ArrayList<OnLayoutChangeListener> listenersCopy =(ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners .clone();int numListeners = listenersCopy.size();for (int i = 0; i < numListeners; ++i) {listenersCopy.get(i).onLayoutChange(this , l, t, r, b, oldL, oldT, oldR, oldB);//通知对象的listener调用onLayoutChange}}}mPrivateFlags &= ~FORCE_LAYOUT ;}     
调用onLayout方法,再看onLayout方法是一个空实现:     
    protected void onLayout( boolean changed, int left, int top, int right, int bottom) {    }
也就是我们需要自己实现onLayout方法。
这是view的layout过程,再看viewGroup过程,先看viewGroup.layout方法:   
 @Overridepublic final void layout(int l, int t, int r, int b) {if (mTransition == null || !mTransition.isChangingLayout()) {super.layout</strong>(l, t, r, b);//调用父类view的layout方法</strong>} else {// record the fact that we noop'd it; request layout when transition finishesmLayoutSuppressed = true;}}
调用父类view的layout方法,再看onLayout方法   
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
viewGroup.onLayout是抽象函数。
ViewGroup对应的就是一个个具体的布局,看具体布局是如何实现的。
LinearLayout的layoutVertical方法 
  void layoutVertical() {// ....     for (int i = 0; i < count; i++) {final View child = getVirtualChildAt(i);if (child == null) {childTop += measureNullChild(i);} else if (child.getVisibility() != GONE) {childTop += lp. topMargin ;setChildFrame</strong>(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child);i += getChildrenSkipCount(child, i);}}}
其中出现了LinearLayout.setChildFrame方法:     
    private void setChildFrame(View child, int left, int top, int width, int height) {       child.layout(left, top, left + width, top + height);}
再看FrameLayout的onLayout方法: 
   protected void onLayout( boolean changed, int left, int top, int right, int bottom) {final int count = getChildCount();        for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (child.getVisibility() != GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();final int width = child.getMeasuredWidth();final int height = child.getMeasuredHeight();                    ..switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK ) {..}switch (verticalGravity) {..}child.layout</strong>(childLeft, childTop, childLeft + width, childTop + height);}}}   
可以看出都是调用子view中的layout方法。所以在自定义view的时候需要自己重写view的onLayout方法。

9.1.3 draw
得到位置之后,最后就是绘制view了。
view做的事情主要有以下几个部分:
1. Draw the background
2. If necessary, save the canvas' layers to prepare for fading
3. Draw view's content
4. Draw children
5. If necessary, draw the fading edges and restore layers
6. Draw decorations (scrollbars for instance)
比较重要的是step3和step4:       
   // Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);其中onDraw实现:空实现。因为view怎样展示是由具体的view自行实现。protected void onDraw(Canvas canvas) {}// Step 4, draw the childrendispatchDraw(canvas);
里面涉及了两个函数,onDraw和dispatchDraw函数,先看onDraw函数:
protected void onDraw(Canvas canvas) { }//空实现,因为view到底怎么展示是view自己决定的,所以要空实现
再看dispatchDraw方法:
protected void dispatchDraw(Canvas canvas) {  }也是空实现,因为view中没有子view,也就不需要向子view派发draw请求了,所以是空实现。
 
viewGroup中没有重写draw函数,所以是继承view的draw函数; 
viewGroup中也没有重写onDraw函数,所以是继承view的onDraw函数,也就是空实现
再看viewGroup中的viewGroup.dispatchDraw(Canvas)方法:      
   protected void dispatchDraw(Canvas canvas) {final int count = mChildrenCount;final View[] children = mChildren ;int flags = mGroupFlags ;       ..          if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {for (int i = 0; i < count; i++) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {more |= drawChild</strong>(canvas, child, drawingTime);}}} else {for (int i = 0; i < count; i++) {final View child = children[getChildDrawingOrder(count, i)];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {more |=drawChild</strong>(canvas, child, drawingTime);}}}// Draw any disappearing views that have animationsif (mDisappearingChildren != null) {final ArrayList<View> disappearingChildren = mDisappearingChildren ;final int disappearingCount = disappearingChildren.size() - 1;// Go backwards -- we may delete as animations finishfor (int i = disappearingCount; i >= 0; i--) {final View child = disappearingChildren.get(i);more |= drawChild</strong>(canvas, child, drawingTime);}}..}
又调用了drawChild(Canvas,View,Long)方法,此方法主要作用是draw viewGroup中的某一个view,代码太长就不贴出现了。

这篇关于9.view 作图过程,讲讲draw/onDraw和drawChild的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/349801

相关文章

SpringBoot 整合 Grizzly的过程

《SpringBoot整合Grizzly的过程》Grizzly是一个高性能的、异步的、非阻塞的HTTP服务器框架,它可以与SpringBoot一起提供比传统的Tomcat或Jet... 目录为什么选择 Grizzly?Spring Boot + Grizzly 整合的优势添加依赖自定义 Grizzly 作为

mysql-8.0.30压缩包版安装和配置MySQL环境过程

《mysql-8.0.30压缩包版安装和配置MySQL环境过程》该文章介绍了如何在Windows系统中下载、安装和配置MySQL数据库,包括下载地址、解压文件、创建和配置my.ini文件、设置环境变量... 目录压缩包安装配置下载配置环境变量下载和初始化总结压缩包安装配置下载下载地址:https://d

springboot整合gateway的详细过程

《springboot整合gateway的详细过程》本文介绍了如何配置和使用SpringCloudGateway构建一个API网关,通过实例代码介绍了springboot整合gateway的过程,需要... 目录1. 添加依赖2. 配置网关路由3. 启用Eureka客户端(可选)4. 创建主应用类5. 自定

最新版IDEA配置 Tomcat的详细过程

《最新版IDEA配置Tomcat的详细过程》本文介绍如何在IDEA中配置Tomcat服务器,并创建Web项目,首先检查Tomcat是否安装完成,然后在IDEA中创建Web项目并添加Web结构,接着,... 目录配置tomcat第一步,先给项目添加Web结构查看端口号配置tomcat    先检查自己的to

SpringBoot集成SOL链的详细过程

《SpringBoot集成SOL链的详细过程》Solanaj是一个用于与Solana区块链交互的Java库,它为Java开发者提供了一套功能丰富的API,使得在Java环境中可以轻松构建与Solana... 目录一、什么是solanaj?二、Pom依赖三、主要类3.1 RpcClient3.2 Public

Android数据库Room的实际使用过程总结

《Android数据库Room的实际使用过程总结》这篇文章主要给大家介绍了关于Android数据库Room的实际使用过程,详细介绍了如何创建实体类、数据访问对象(DAO)和数据库抽象类,需要的朋友可以... 目录前言一、Room的基本使用1.项目配置2.创建实体类(Entity)3.创建数据访问对象(DAO

SpringBoot整合kaptcha验证码过程(复制粘贴即可用)

《SpringBoot整合kaptcha验证码过程(复制粘贴即可用)》本文介绍了如何在SpringBoot项目中整合Kaptcha验证码实现,通过配置和编写相应的Controller、工具类以及前端页... 目录SpringBoot整合kaptcha验证码程序目录参考有两种方式在springboot中使用k

SpringBoot整合InfluxDB的详细过程

《SpringBoot整合InfluxDB的详细过程》InfluxDB是一个开源的时间序列数据库,由Go语言编写,适用于存储和查询按时间顺序产生的数据,它具有高效的数据存储和查询机制,支持高并发写入和... 目录一、简单介绍InfluxDB是什么?1、主要特点2、应用场景二、使用步骤1、集成原生的Influ

SpringBoot实现websocket服务端及客户端的详细过程

《SpringBoot实现websocket服务端及客户端的详细过程》文章介绍了WebSocket通信过程、服务端和客户端的实现,以及可能遇到的问题及解决方案,感兴趣的朋友一起看看吧... 目录一、WebSocket通信过程二、服务端实现1.pom文件添加依赖2.启用Springboot对WebSocket

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti