当前位置: 移动技术网 > 移动技术>移动开发>Android > Android中View绘制流程详细介绍

Android中View绘制流程详细介绍

2019年07月24日  | 移动技术网移动技术  | 我要评论

创建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绘制流程详细介绍的全部内容,希望对大家有所帮助。如有不足之处,欢迎留言指出。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网