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绘制流程详细介绍的全部内容,希望对大家有所帮助。如有不足之处,欢迎留言指出。
上一篇: C#使用二维数组模拟斗地主
下一篇: 基础数据类型