Android绘制链图:

网上很多讲Android  view的绘制流程往往只讲到了Measure - Layout - Draw。

但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。

  1. public void setContentView(@LayoutRes int layoutResID) {
  2. getWindow().setContentView(layoutResID);
  3. initWindowDecorActionBar();
  4. }

而最终调用了initWindowDecorActionBar这个方法,我们看下这个方法里面都实现了什么

  1. <span style="font-size: 16px;"> </span><span style="font-size:14px;"> private void initWindowDecorActionBar() {
  2. Window window = getWindow();
  3. // Initializing the window decor can change window feature flags.
  4. // Make sure that we have the correct set before performing the test below.
  5. window.getDecorView();
  6.  
  7. if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
  8. return;
  9. }
  10.  
  11. mActionBar = new WindowDecorActionBar(this);
  12. mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
  13.  
  14. mWindow.setDefaultIcon(mActivityInfo.getIconResource());
  15. mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
  16. }</span>

根据人家给我们的注释,这段代码是创建一个actionbar,初始化这个view和actionbar。这里面有一段很重要的代码:

  1. window.getDecorView();

正式这段代码告知系统可以从view的根节点开始绘制了,通过DecorView方法,decorview调用了performTraversals方法,我们来看下performTraversals源码:

  1. <span style="font-size:14px;">private void performTraversals() {
  2. final View host = mView;
  3. ...
  4. host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  5. ...
  6. host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  7. ...
  8. draw(fullRedrawNeeded);
  9. } </span>

调用然后系统再调用Measure
- Layout - Draw
实现了View的绘制。

我们看一下完整的绘制流程,直接上一张图,或许更能说明这个意思:

到这里,系统会调用我们之前的比较熟悉的几个方法:Measure - Layout
- Draw

Measure



Measure过程是计算视图大小,View中视图measure过程相关的方法主要有三个

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec)
  2. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
  3. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

measure调用onMeasure,onMeasure测量完成后setMeasureDimension,setMeasureDimension是final类型,view的子类不需要重写。

measure
源码:

  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  2. if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
  3. widthMeasureSpec != mOldWidthMeasureSpec ||
  4. heightMeasureSpec != mOldHeightMeasureSpec) {
  5.  
  6. // first clears the measured dimension flag
  7. mPrivateFlags &= ~MEASURED_DIMENSION_SET;
  8.  
  9. if (ViewDebug.TRACE_HIERARCHY) {
  10. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
  11. }
  12.  
  13. // measure ourselves, this should set the measured dimension flag back
  14. onMeasure(widthMeasureSpec, heightMeasureSpec);
  15.  
  16. // flag not set, setMeasuredDimension() was not invoked, we raise
  17. // an exception to warn the developer
  18. if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
  19. throw new IllegalStateException("onMeasure() did not set the"
  20. + " measured dimension by calling"
  21. + " setMeasuredDimension()");
  22. }
  23.  
  24. mPrivateFlags |= LAYOUT_REQUIRED;
  25. }
  26.  
  27. mOldWidthMeasureSpec = widthMeasureSpec;
  28. mOldHeightMeasureSpec = heightMeasureSpec;
  29. }

我们看一下OnMearsure方法:

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  4. }

这个方法主要是实现setMeasuredDimension,这个方法是测量view的大小:

  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
  2. boolean optical = isLayoutModeOptical(this);
  3. if (optical != isLayoutModeOptical(mParent)) {
  4. Insets insets = getOpticalInsets();
  5. int opticalWidth = insets.left + insets.right;
  6. int opticalHeight = insets.top + insets.bottom;
  7.  
  8. measuredWidth += optical ? opticalWidth : -opticalWidth;
  9. measuredHeight += optical ? opticalHeight : -opticalHeight;
  10. }
  11. setMeasuredDimensionRaw(measuredWidth, measuredHeight);
  12. }

而对于这个measuredWidth和measuredHeight参数,系统却调了一个getDefaultSize();

  1. public static int getDefaultSize(int size, int measureSpec) {
  2. int result = size;
  3. int specMode = MeasureSpec.getMode(measureSpec);
  4. int specSize = MeasureSpec.getSize(measureSpec);
  5.  
  6. switch (specMode) {
  7. case MeasureSpec.UNSPECIFIED:
  8. result = size;
  9. break;
  10. case MeasureSpec.AT_MOST:
  11. case MeasureSpec.EXACTLY:
  12. result = specSize;
  13. break;
  14. }
  15. return result;
  16. }

widthMeasureSpec和heightMeasureSpec决定了Mode和Size的值,widthMeasureSpec和heightMeasureSpec来自父视图,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了.



关于视图的measure过程可以阅读以下LinearLayout源码。

Layout

measure过程确定视图的大小,而layout过程确定视图的位置。
  1. public void layout(int l, int t, int r, int b) {
  2. if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
  3. onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
  4. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  5. }
  6.  
  7. int oldL = mLeft;
  8. int oldT = mTop;
  9. int oldB = mBottom;
  10. int oldR = mRight;
  11.  
  12. boolean changed = isLayoutModeOptical(mParent) ?
  13. setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  14.  
  15. if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
  16. onLayout(changed, l, t, r, b);
  17. mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
  18.  
  19. ListenerInfo li = mListenerInfo;
  20. if (li != null && li.mOnLayoutChangeListeners != null) {
  21. ArrayList<OnLayoutChangeListener> listenersCopy =
  22. (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
  23. int numListeners = listenersCopy.size();
  24. for (int i = 0; i < numListeners; ++i) {
  25. listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
  26. }
  27. }
  28. }
  29.  
  30. mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
  31. mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
  32. }

函数中参数l、t、r、b是指view的左、上、右、底的位置,通过这几个参数来确定view在Windows的位置。


在layout函数中,重载了一个空函数

  1. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  2. }

这个需要子类去实现的。

比如Linearlayout:

  1. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  2. if (mOrientation == VERTICAL) {
  3. layoutVertical();
  4. } else {
  5. layoutHorizontal();
  6. }
  7. }

具体实现请自行看源码。

而在最后无论是layoutVertical还是layoutHorizontal都会掉一个setChildFrame方法来控制显示位置。

  1. private void setChildFrame(View child, int left, int top, int width, int height) {
  2. child.layout(left, top, left + width, top + height);
  3. }

从上面看出,layout也是一个自上而下的过程,先设置父视图位置,在循环子视图,父视图位置一定程度上决定了子视图位置。

Draw


draw过程调用顺序在measure()和layout()之后,同样的,performTraversals()发起的draw过程最终会调用到mView的draw()函数,对于activity来说就是调用的PhoneWindow.DecorView。

  1. * 1. Draw the background
  2. * 2. If necessary, save the canvas' layers to prepare for fading
  3. * 3. Draw view's content
  4. * 4. Draw children
  5. * 5. If necessary, draw the fading edges and restore layers
  6. * 6. Draw decorations (scrollbars for instance)

根据view源码的注释,

1,绘制背景

2,保存画布图层

3,调用了onDraw方法,子类中实现onDraw方法

4,使用的dispatchDraw方法

View或ViewGroup的子类不用再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作。

有兴趣的可以看看onDraw的源码。

Android View底层到底是怎么绘制的的更多相关文章

  1. 简单研究Android View绘制一 测量过程

    2015-07-27 16:52:58 一.如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Dr ...

  2. Android view的测量及绘制

    讲真,自我感觉,我的水平真的是渣的一匹,好多东西都只停留在知道和会用的阶段,也想去研究原理和底层的实现,可是一看到代码就懵逼了,然后就看不下去了, 说自己不着急都是骗人的,我自己都不信,前两天买了本& ...

  3. 【Android源码解析】View.post()到底干了啥

    emmm,大伙都知道,子线程是不能进行 UI 操作的,或者很多场景下,一些操作需要延迟执行,这些都可以通过 Handler 来解决.但说实话,实在是太懒了,总感觉写 Handler 太麻烦了,一不小心 ...

  4. Android View绘制和显示原理简介

    现在越来越多的应用开始重视流畅度方面的测试,了解Android应用程序是如何在屏幕上显示的则是基础中的基础,就让我们一起看看小小屏幕中大大的学问.这也是我下篇文章--<Android应用流畅度测 ...

  5. Android View 的添加绘制流程 (二)

    概述 上一篇 Android DecorView 与 Activity 绑定原理分析 分析了在调用 setContentView 之后,DecorView 是如何与 activity 关联在一起的,最 ...

  6. 简单研究Android View绘制三 布局过程

    2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...

  7. Android View 如何绘制

    上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感 ...

  8. Android View绘制13问13答

    1.View的绘制流程分几步,从哪开始?哪个过程结束以后能看到view? 答:从ViewRoot的performTraversals开始,经过measure,layout,draw 三个流程.draw ...

  9. Android View的绘制机制流程深入详解(四)

    本系列文章主要着重深入介绍Android View的绘制机制及流程,第四篇主要介绍Android自定义View及ViewGroup的实现方法和流程. 主要介绍了自绘控件.自定义组合控件.自定义继承控件 ...

随机推荐

  1. win 10 和 CentOS 7 双系统安装

    工具及材料 1.一台PC         2.一个U盘,8G以上         3.需要的文件:CentOS-7-x86_64-DVD-1511.iso         4.需要的软件:UltraI ...

  2. 实验-使用VisualVM或JConsole进行对程序进行性能分析

    参考资料: 性能分析神器VisualVM java可视化监控工具 完成下列任务: 1.分析内存堆 使用+进行频繁的字符串拼接 2.CPU性能分析 3.线程分析 编程比较以下几个方法所创建的线程 Exe ...

  3. Zookeeper Api

    如何使用 Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储,但是 Zookeeper 并不是用来专门存储 ...

  4. SpringMVC源码分析--文件上传

    SpringMVC提供了文件上传的功能,接下来我们就简单了解一下SpringMVC文件上传的开发及大致过程. 首先需要在springMVC的配置文件中配置文件上传解析器 <bean id=&qu ...

  5. ROS机器人程序设计(原书第2版)学习镜像分享及使用说明

    ROS机器人程序设计(原书第2版)学习镜像分享及使用说明 系统用于ROS爱好者学习交流,也可用于其他用途,并不局限于ROS. 这款镜像文件是基于一年前的Ubuntu ROS Arduino Gazeb ...

  6. Velocity 语法及其在springMVC中的配置

    强烈推荐具体的整合博客:http://blog.csdn.net/duqi_2009/article/details/47752169 整合文章中有几处问题: xml中配置的vm视图解析器,应该按照本 ...

  7. Leetcode解题-链表(2.2.0)基础类

    1 基类的作用 在开始练习LeetCode链表部分的习题之前,首先创建好一个Solution基类,其作用就是: Ø  规定好每个子Solution都要实现纯虚函数test做测试: Ø  提供了List ...

  8. ORACLE数据库学习之备份与恢复

     oracle数据库的备份与恢复 第一部分:数据库的备份 备份的必要性 因为各种人为或外界的因素可能会造成数据库中灾难性的数据丢失,为了保证数据库中数据的安全,必须采取备份措施保证RDBMS中包含 ...

  9. eclispe 导入android或者java项目出现中文乱码

    中文乱码经常是我们是一个比较麻烦的问题,对于这个问题,我想说一下我的解决思路. 1.到Windows- >Pereferences- >Genral->Workspace- > ...

  10. C链栈实现

    #include <stdlib.h> #include <stdio.h> #include"LinkStack.h" const int TRUE = ...