Android中View绘制流程详细介绍

创建Window

Window即窗口,这个概念在AndroidFramework中的实现为android.view.Window这个抽象类,这个抽象类是对Android系统中的窗口的抽象。在介绍这个类之前,我们先来看看究竟什么是窗口呢?

实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各种UI元素及响应用户输入事件的一个矩形区域。通常具备以下两个特点:

独立绘制,不与其它界面相互影响;

不会触发其它界面的输入事件;

在Android系统中,窗口是独占一个Surface实例的显示区域,每个窗口的Surface由WindowManagerService分配。我们可以把Surface看作一块画布,应用可以通过Canvas或OpenGL在其上面作画。画好之后,通过SurfaceFlinger将多块Surface按照特定的顺序(即Z-order)进行混合,而后输出到FrameBuffer中,这样用户界面就得以显示。

android.view.Window这个抽象类可以看做Android中对窗口这一宏观概念所做的约定,而PhoneWindow这个类是Framework为我们提供的Android窗口概念的具体实现。接下来我们先来介绍一下android.view.Window这个抽象类。

这个抽象类包含了三个核心组件:

WindowManager.LayoutParams:窗口的布局参数;

Callback:窗口的回调接口,通常由Activity实现;

ViewTree:窗口所承载的控件树。

在Activity的attach方法中通过调用PolicyManager.makeNewWindo创建Window,将一个View add到WindowManager时,WindowManagerImpl创建一个ViewRoot来管理该窗口的根View。并通过ViewRoot.setView方法把该View传给ViewRoot。

final void attach(Context context, ActivityThread aThread,
    Instrumentation instr, IBinder token, int ident,
    Application application, Intent intent, ActivityInfo info,
    CharSequence title, Activity parent, String id,
    NonConfigurationInstances lastNonConfigurationInstances,
    Configuration config) {
  attachBaseContext(context); 

  mFragments.attachActivity(this, mContainer, null); 

  mWindow = PolicyManager.makeNewWindow(this);
  mWindow.setCallback(this);
  mWindow.getLayoutInflater().setPrivateFactory(this); 

创建DecorView

DecorView为整个Window界面的最顶层View。
Activity中的Window对象帮我们创建了一个PhoneWindow内部类DecorView(父类为FrameLayout)窗口顶层视图,然后通过LayoutInflater将xml内容布局解析成View树形结构添加到DecorView顶层视图中id为content的FrameLayout父容器上面。Activity的content内容布局最终会添加到DecorView窗口顶层视图上面。

protected boolean initializePanelDecor(PanelFeatureState st) {
  st.decorView = new DecorView(getContext(), st.featureId);
  st.gravity = Gravity.CENTER | Gravity.BOTTOM;
  st.setStyle(getContext()); 

  return true;
} 

创建ViewRoot并关联View

WindowManagerImpl保存DecorView到mViews,创建对应的ViewRoot;
ViewRoot用于管理窗口的根View,并和global window manger进行交互。ViewRoot中有一个nested class: W,W是一个Binder子类,用于接收global window manager的各种消息, 如按键消息, 触摸消息等。 ViewRoot有一个W类型的成员mWindow,ViewRoot在Constructor中创建一个W的instance并赋值给mWindow。 ViewRoot是Handler的子类, W会通过Looper把消息传递给ViewRoot。 ViewRoot在setView方法中把mWindow传给sWindowSession。

public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow) {
  if (view == null) {
    throw new IllegalArgumentException("view must not be null");
  }
  if (display == null) {
    throw new IllegalArgumentException("display must not be null");
  }
  if (!(params instanceof WindowManager.LayoutParams)) {
    throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
  } 

  final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
  if (parentWindow != null) {
    parentWindow.adjustLayoutParamsForSubWindow(wparams);
  } 

  ViewRootImpl root;
  View panelParentView = null; 

  synchronized (mLock) {
    // Start watching for system property changes.
    if (mSystemPropertyUpdater == null) {
      mSystemPropertyUpdater = new Runnable() {
        @Override public void run() {
          synchronized (mLock) {
            for (ViewRootImpl viewRoot : mRoots) {
              viewRoot.loadSystemProperties();
            }
          }
        }
      };
      SystemProperties.addChangeCallback(mSystemPropertyUpdater);
    } 

    int index = findViewLocked(view, false);
    if (index >= 0) {
      throw new IllegalStateException("View " + view
          + " has already been added to the window manager.");
    } 

    // If this is a panel window, then find the window it is being
    // attached to for future reference.
    if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
        wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
      final int count = mViews != null ? mViews.length : 0;
      for (int i=0; i<count; i++) {
        if (mRoots[i].mWindow.asBinder() == wparams.token) {
          panelParentView = mViews[i];
        }
      }
    } 

    root = new ViewRootImpl(view.getContext(), display); 

    view.setLayoutParams(wparams); 

    if (mViews == null) {
      index = 1;
      mViews = new View[1];
      mRoots = new ViewRootImpl[1];
      mParams = new WindowManager.LayoutParams[1];
    } else {
      index = mViews.length + 1;
      Object[] old = mViews;
      mViews = new View[index];
      System.arraycopy(old, 0, mViews, 0, index-1);
      old = mRoots;
      mRoots = new ViewRootImpl[index];
      System.arraycopy(old, 0, mRoots, 0, index-1);
      old = mParams;
      mParams = new WindowManager.LayoutParams[index];
      System.arraycopy(old, 0, mParams, 0, index-1);
    }
    index--; 

    mViews[index] = view;
    mRoots[index] = root;
    mParams[index] = wparams;
  } 

  // do this last because it fires off messages to start doing things
  try {
    root.setView(view, wparams, panelParentView);
  } catch (RuntimeException e) {
    // BadTokenException or InvalidDisplayException, clean up.
    synchronized (mLock) {
      final int index = findViewLocked(view, false);
      if (index >= 0) {
        removeViewLocked(index, true);
      }
    }
    throw e;
  }
} 

ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁,需要注意它并不是一个View类型,。
它的主要作用如下:

1、向DecorView分发收到的用户发起的event事件,如按键,触屏,轨迹球等事件;
2、与WindowManagerService交互,完成整个Activity的GUI的绘制。
View绘制基本流程

这里先给出Android系统View的绘制流程:依次执行View类里面的如下三个方法:

measure(int ,int) :测量View的大小
layout(int ,int ,int ,int) :设置子View的位置
draw(Canvas) :绘制View内容到Canvas画布上

整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为根据之前设置的状态,判断是否需要重新计算视图大小(measure)、是否重新需要安置视图的位置(layout)、以及是否需要重绘 (draw)
mesarue()测量过程

主要作用:为整个View树计算实际的大小,即设置实际的高(mMeasuredHeight)和宽(mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的。

具体的调用如下:

ViewRootImpl 的performTraversals方法中,调用measureHierarchy,然后调用performMeasure

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
    try {
      mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    } finally {
      Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
  } 

ViewRoot根对象地属性mView(其类型一般为ViewGroup类型)调用measure()方法去计算View树的大小,回调

View/ViewGroup对象的onMeasure()方法,该方法实现的功能如下:

1、设置本View视图的最终大小,该功能的实现通过调用setMeasuredDimension()方法去设置实际的高(mMeasuredHeight)和宽(mMeasureWidth)

2、如果该View对象是个ViewGroup类型,需要重写onMeasure()方法,对其子视图进行遍历的measure()过程。

对每个子视图的measure()过程,是通过调用父类ViewGroup.java类里的measureChildWithMargins()方法去实现,该方法内部只是简单地调用了View对象的measure()方法。

整个measure调用流程就是个树形的递归过程

measure()方法两个参数都是父View传递过来的,也就是代表了父view的规格。他由两部分组成,高2位表示MODE,定义在MeasureSpec类(View的内部类)中,有三种类型,MeasureSpec.EXACTLY表示确定大小,MeasureSpec.AT_MOST表示最大大小,MeasureSpec.UNSPECIFIED不确定。低30位表示size,也就是父View的大小。对于系统Window类的DecorVIew对象Mode一般都为MeasureSpec.EXACTLY,而size分别对应屏幕宽高。对于子View来说大小是由父View和子View共同决定的。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
      getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
} 
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
   boolean optical = isLayoutModeOptical(this);
   if (optical != isLayoutModeOptical(mParent)) {
     Insets insets = getOpticalInsets();
     int opticalWidth = insets.left + insets.right;
     int opticalHeight = insets.top + insets.bottom; 

     measuredWidth += optical ? opticalWidth : -opticalWidth;
     measuredHeight += optical ? opticalHeight : -opticalHeight;
   }
   mMeasuredWidth = measuredWidth;
   mMeasuredHeight = measuredHeight; 

   mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
 } 

layout布局过程

主要作用 :为将整个根据子视图的大小以及布局参数将View树放到合适的位置上。
具体的调用如下:
ViewRootImpl 的performTraversals方法中,调用performLayout

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
    int desiredWindowHeight) {
  mLayoutRequested = false;
  mScrollMayChange = true;
  mInLayout = true; 

  final View host = mView;
  if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
    Log.v(TAG, "Laying out " + host + " to (" +
        host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
  } 

  Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
  try {
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); 

    mInLayout = false;
    int numViewsRequestingLayout = mLayoutRequesters.size();
    if (numViewsRequestingLayout > 0) {
      // requestLayout() was called during layout.
      // If no layout-request flags are set on the requesting views, there is no problem.
      // If some requests are still pending, then we need to clear those flags and do
      // a full request/measure/layout pass to handle this situation.
      ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
          false);
      if (validLayoutRequesters != null) {
        // Set this flag to indicate that any further requests are happening during
        // the second pass, which may result in posting those requests to the next
        // frame instead
        mHandlingLayoutInLayoutRequest = true; 

        // Process fresh layout requests, then measure and layout
        int numValidRequests = validLayoutRequesters.size();
        for (int i = 0; i < numValidRequests; ++i) {
          final View view = validLayoutRequesters.get(i);
          Log.w("View", "requestLayout() improperly called by " + view +
              " during layout: running second layout pass");
          view.requestLayout();
        }
        measureHierarchy(host, lp, mView.getContext().getResources(),
            desiredWindowWidth, desiredWindowHeight);
        mInLayout = true;
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); 

        mHandlingLayoutInLayoutRequest = false; 

        // Check the valid requests again, this time without checking/clearing the
        // layout flags, since requests happening during the second pass get noop'd
        validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
        if (validLayoutRequesters != null) {
          final ArrayList<View> finalRequesters = validLayoutRequesters;
          // Post second-pass requests to the next frame
          getRunQueue().post(new Runnable() {
            @Override
            public void run() {
              int numValidRequests = finalRequesters.size();
              for (int i = 0; i < numValidRequests; ++i) {
                final View view = finalRequesters.get(i);
                Log.w("View", "requestLayout() improperly called by " + view +
                    " during second layout pass: posting in next frame");
                view.requestLayout();
              }
            }
          });
        }
      } 

    }
  } finally {
    Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  }
  mInLayout = false;
} 

host.layout()开始View树的布局,继而回调给View/ViewGroup类中的layout()方法。具体流程如下

1 、layout方法会设置该View视图位于父视图的坐标轴,即mLeft,mTop,mLeft,mBottom(调用setFrame()函数去实现),接下来回调onLayout()方法(如果该View是ViewGroup对象,需要实现该方法,对每个子视图进行布局)。
2、如果该View是个ViewGroup类型,需要遍历每个子视图chiildView,调用该子视图的layout()方法去设置它的坐标值。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
} 
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 = isLayoutModeOptical(mParent) ?
      setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
  if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
    onLayout(changed, l, t, r, b);
    mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; 

    ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnLayoutChangeListeners != null) {
      ArrayList<OnLayoutChangeListener> listenersCopy =
          (ArrayList<OnLayoutChangeListener>)li.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);
      }
    }
  }
  mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
} 

draw()绘图过程

ViewRootImpl的performTraversals方法中,调用了mView的draw方法

mView.draw()开始绘制,draw()方法实现的功能如下:

1、绘制该View的背景

2、为显示渐变框做一些准备操作

3、调用onDraw()方法绘制视图本身(每个View都需要重载该方法,ViewGroup不需要实现该方法)

4、调用dispatchDraw()方法绘制子视图(如果该View类型不为ViewGroup,即不包含子视图,不需要重载该方法)

值得说明的是,ViewGroup类已经为我们重写了dispatchDraw()的功能实现,应用程序一般不需要重写该方法,但可以重载父类函数实现具体的功能。

dispatchDraw()方法内部会遍历每个子视图,调用drawChild()去重新回调每个子视图的draw()方法。

5、绘制滚动条

刷新视图

Android中实现view的更新有两个方法,一个是invalidate,另一个是postInvalidate,其中前者是在UI线程自身中使用,而后者在非UI线程中使用。

requestLayout()方法:会导致调用measure()过程和layout()过程。

说明:只是对View树重新布局layout过程包括measure()和layout()过程,不会调用draw()过程,但不会重新绘制

任何视图包括该调用者本身。

一般引起invalidate()操作的函数如下:

1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。

2、setSelection()方法:请求重新draw(),但只会绘制调用者本身。

3、setVisibility()方法:当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View。

4、setEnabled()方法:请求重新draw(),但不会重新绘制任何视图包括该调用者本身。

总结

以上就是本文关于Android中View绘制流程详细介绍的全部内容,希望对大家有所帮助。如有不足之处,欢迎留言指出。

您可能感兴趣的文章:

  • Android开发实现ListView点击item改变颜色功能示例
  • 详解android 用webview加载网页(https和http)
  • Android利用Paint自定义View实现进度条控件方法示例
  • Android实战RecyclerView头部尾部添加方法示例
(0)

相关推荐

  • Android利用Paint自定义View实现进度条控件方法示例

    前言 View的三大流程:测量,布局,绘制,自定义View学的是啥?无非就两种:绘制文字和绘制图像. 我们在上一篇文章<Android绘图之Paint的使用>中学习了Paint的基本用法,但是具体的应用我们还没有实践过.从标题中可知,本文是带领读者使用Paint,自定义一个进度条控件. 效果图 上图就是本文要实现的效果图. 实现过程 既然是自定义控件,本文的该控件是直接继承View,然后重写View的onMeasure和onDraw方法来实现.其中onMeasure主要作用是测量控件的宽/高.

  • 详解android 用webview加载网页(https和http)

    1.Android 加载https请求的网页的时候 打不开 当load有ssl层的https页面时,如果这个网站的安全证书在Android无法得到认证,WebView就会变成一个空白页,而并不会像PC浏览器中那样跳出一个风险提示框.因此,我们必须针对这种情况进行处理.(这个证书限于2.1版本以上的Android 系统才可以) wv.setWebViewClient(new WebViewClient(){ @override public void onReceivedSslError(WebV

  • Android开发实现ListView点击item改变颜色功能示例

    本文实例讲述了Android开发实现ListView点击item改变颜色功能.分享给大家供大家参考,具体如下: 一.先看看效果图: 二.实现步骤: 1. xml布局 <ListView android:id="@+id/left_listview" android:layout_width="match_parent" android:layout_height="match_parent" android:background="

  • Android实战RecyclerView头部尾部添加方法示例

    最近开启SDK Manager,突然发现android7.0的都有了,这迭代升级还真快.不过国内普遍手机还是停留在4.4+,多则是是处于5.0版本的.Android5.0变化非常大,引入material design,加强权限管理.减少功耗...好像扯远了0 0.现在直接进入主题.在这里先感谢读者的支持!! ListView是有addHeaderView和 addFooterView两个方法的. 但是作为官方推荐的ListView的升级版RecyclerView缺无法实现这两个方法. 那么如果使

  • Android中View绘制流程详细介绍

    创建Window Window即窗口,这个概念在AndroidFramework中的实现为android.view.Window这个抽象类,这个抽象类是对Android系统中的窗口的抽象.在介绍这个类之前,我们先来看看究竟什么是窗口呢? 实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各种UI元素及响应用户输入事件的一个矩形区域.通常具备以下两个特点: 独立绘制,不与其它界面相互影响: 不会触发其它界面的输入事件: 在Android系统中,窗口是独占一个Surface实例的显示区域,每个窗口的S

  • 深入理解Android中View绘制的三大流程

    前言 最近对Android中View的绘制机制有了一些新的认识,所以想记录下来并分享给大家.View的工作流程主要是指measure.layout.draw这三大流程,即测量.布局和绘制,其中measure确定View的测量宽高,layout根据测量的宽高确定View在其父View中的四个顶点的位置,而draw则将View绘制到屏幕上,这样通过ViewGroup的递归遍历,一个View树就展现在屏幕上了. 说的简单,下面带大家一步一步从源码中分析: Android的View是树形结构的: 基本概

  • Android中的Bitmap的详细介绍

    Bitmap简介(摘抄于网络) 位图文件(Bitmap),扩展名可以是.bmp或者.dib.位图是Windows标准格式图形文件,它将图像定义为由点(像素)组成,每个点可以由多种色彩表示,包括2.4.8.16.24和32位色彩. 例如,一幅1024×768分辨率的32位真彩图片,其所占存储字节数为:1024×768×32/(8*1024)=3072KB 位图文件图像效果好,但是非压缩格式的,需要占用较大存储空间,不利于在网络上传送.jpg/png格式则恰好弥补了位图文件的缺点. 在Android

  • Android 自定义View的构造函数详细介绍

     Android自定义View的构造函数 自定义View是Android中一个常见的需求,每个自定义的View都需要实现三个基本的构造函数,而这三个构造函数又有两种常见的写法. 第一种 每个构造函数分别调用基类的构造函数,再调用一个公共的初始化方法做额外初始化. public class MyView extends ListView { public MyView(Context context) { super(context); sharedConstructor(); } public

  • Android中的Looper对象详细介绍

    Java 官网对Looper对象的说明: public class Looperextends ObjectClass used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loo

  • Android view绘制流程详解

    绘制流程 measure 流程测量出 View 的宽高尺寸. layout 流程确定 View 的位置及最终尺寸. draw 流程将 View 绘制在屏幕上. Measure 测量流程 系统是通过 MeasureSpec 测量 View 的,在了解测量过程之前一定要了解这个 MeasureSpec . MeasureSpec MeasureSpec 是一个 32 位的 int 值打包而来的,打包为 MeasureSpec 主要是为了避免过多的对象内存分配. 为了方便操作,MeasureSpec

  • Android自定义View绘制贝塞尔曲线实现流程

    目录 前言 二阶贝塞尔曲线 三阶贝塞尔曲线 前言 对于Android开发,实现贝塞尔曲线还是比较方便的,有对应的API供你调用.由于一阶贝塞尔曲线就是一条直线,实际没啥多大用处,因此,下面主要讲解二阶和三阶. 二阶贝塞尔曲线 在Android中,使用quadTo来实现二阶贝塞尔 path.reset() path.moveTo(startX, startY) path.quadTo(currentX, currentY, endX, endY) canvas.drawPath(path, cur

  • Android自定义View绘制贝塞尔曲线中小红点的方法

    目录 前言 需求 效果图 代码 主要问题 简单画法 使用贝塞尔曲线 前言 上一篇文章用扇形图练习了一下安卓的多点触控,实现了单指旋转.二指放大.三指移动,四指以上同时按下进行复位的功能.今天这篇文章用很多应用常见的小红点,来练习一下贝塞尔曲线的使用. 需求 这里想法来自QQ的拖动小红点取消显示聊天条数功能,不过好像是记忆里的了,现在看了下好像效果变了.总而言之,就是一个小圆点,拖动的时候变成水滴状,超过一定范围后触发消失回调,核心思想如下: 1.一个正方形view,中间是小红点,小红点距离边框有

  • Android中卡顿优化布局详细介绍

    目录 背景 实践过程 如何渲染界面 什么是过度绘制 如何查看绘制维度 界面优化 硬件加速原理 总结 背景 在当下移动互联网后半场,手机已经是人手必备的设备.App是离用户最近的应用,界面又是最直观影响用户体验的关键部分,其流畅度直接影响用户对产品的评价和留存. 技术是服务于人的,如果技术无法给你带来良好的体验,那技术本身的存在就具有争议. 所以界面性能是至关重要的,不可忽视. 实践过程 布局代码是最基础的,但也是最重要的. 首先我们看个简单小案例 不同深浅的颜色来表示过度绘制: 没颜色:没有过度

  • Android中View.post和Handler.post的关系

    目录 前言 为什么要拿这二者来比较? View的渲染起点 View.post的执行流程 Handler.post()能像View.post()一样获取到宽.高数据吗? 前言 View.post和Handler.post是Android开发中经常使用到的两个”post“方法,我们经常通过前者去获取一些View在运行时的渲染数据,或者测量页面的渲染时间.而后者则是Android的核心Handler的一个方法,它会向对应线程的MessageQueue中插入一条Message,在未来的某个事件点得到执行

随机推荐