简介

我们在学习android的时候,直接在xml中指定android的标签,就能显现出各式各样的界面,但是我们并不了解其中的绘制流程,从而抱着一个学习的心态,从源码角度去查看view的绘制机制。

目录

1.view的绘制流程(一)
2.总结

view的绘制流程

在进行分析之前,我们可以先看看下面的流程图:

Android强制View绘制_android

每个Activity都持有Window的对象,Android 为 Window 提供了唯一的实现类 PhoneWindow。PhoneWindow 终究是 Window,它并不具备多少 View 相关的能力,不过 PhoneWindow 中持有一个 Android 中非常重要的一个 View 对象 Decor(装饰)View,它在 PhoneWindow 中的定义如下:

public class PhoneWindow extends Window{
  // 最顶级view的节点,起到一个window装饰的根组件
  private DecorView mDecor; 
}

查看 DecorView 继承关系得知,DecorView 继承自 FrameLayout。

public class DecorView extends FrameLayout {

}

其中DecorView 的子view是一个LinerLayout布局包含TitleView和ContentView,ContentView的子布局就是FrameLayout。我们平常在Activity中的onCreate()方法中,setContentView(int layoutResID)所传入的布局就是影响着ContentView。

public void setContentView(@LayoutRes int layoutResID) {    
  getWindow().setContentView(layoutResID);    
  . . .
}

其中getWindow()就是PhoneWindow对象,其中的setContentView()方法,源码如下:

@Override
public void setContentView(int layoutResID) {
// mContentParent即为上面提到的ContentView的父容器,若为空则调用installDecor()生成
  if (mContentParent == null) {   
    installDecor();
  } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
    // 具有FEATURE_CONTENT_TRANSITIONS特性表示开启了Transition
    // mContentParent不为null,则移除decorView的所有子View
    mContentParent.removeAllViews();
  }
  if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
  } else {
    // 一般情况会来到这里,调用mLayoutInflater.inflate()方法来填充布局
    mLayoutInflater.inflate(layoutResID, mContentParent);
  }
  . . .
  // cb即为该Window所关联的Activity
  final Callback cb = getCallback();
  if (cb != null && !isDestroyed()) {
    // 调用onContentChanged()回调方法通知Activity窗口内容发生了改变
    cb.onContentChanged();
  }

  . . .
}

我们主要看mLayoutInflater.inflate(layoutResID, mContentParent):

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
  return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
  final Resources res = getContext().getResources();
  . . .
  final XmlResourceParser parser = res.getLayout(resource);
  try {
    return inflate(parser, root, attachToRoot);
  } finally {
    parser.close();
  }
}

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {  
    synchronized (mConstructorArgs) {  
        final AttributeSet attrs = Xml.asAttributeSet(parser);  
        mConstructorArgs[0] = mContext;  
        View result = root;  
        try {  
            int type;  
            while ((type = parser.next()) != XmlPullParser.START_TAG &&  
                    type != XmlPullParser.END_DOCUMENT) {  
            }  
            if (type != XmlPullParser.START_TAG) {  
                throw new InflateException(parser.getPositionDescription()  
                        + ": No start tag found!");  
            }  
            final String name = parser.getName();  
            if (TAG_MERGE.equals(name)) {  
                if (root == null || !attachToRoot) {  
                    throw new InflateException("merge can be used only with a valid "  
                            + "ViewGroup root and attachToRoot=true");  
                }  
                rInflate(parser, root, attrs);  
            } else {  
                View temp = createViewFromTag(name, attrs);  
                ViewGroup.LayoutParams params = null;  
                if (root != null) {  
                    params = root.generateLayoutParams(attrs);  
                    if (!attachToRoot) {  
                        temp.setLayoutParams(params);  
                    }  
                }  
                rInflate(parser, temp, attrs);  
                if (root != null && attachToRoot) {  
                    root.addView(temp, params);  
                }  
                if (root == null || !attachToRoot) {  
                    result = temp;  
                }  
            }  
        } catch (XmlPullParserException e) {  
            InflateException ex = new InflateException(e.getMessage());  
            ex.initCause(e);  
            throw ex;  
        } catch (IOException e) {  
            InflateException ex = new InflateException(  
                    parser.getPositionDescription()  
                    + ": " + e.getMessage());  
            ex.initCause(e);  
            throw ex;  
        }  
        return result;  
    }  
}

从这里我们就可以清楚地看出,LayoutInflater其实就是使用Android提供的pull解析方式来解析布局文件的。我们可以注意到createViewFromTag()这个方法,把节点名和参数传了进去。看到这个方法名,我们就应该能猜到,它是用于根据节点名来创建View对象的。在createViewFromTag()方法的内部又会去调用createView()方法,然后使用反射的方式创建出View的实例并返回。
这样我们就把view的根节点给创建出来了接下来我们继续查看rInflate(parser, temp, attrs):

private void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs)  
        throws XmlPullParserException, IOException {  
    final int depth = parser.getDepth();  
    int type;  
    while (((type = parser.next()) != XmlPullParser.END_TAG ||  
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {  
        if (type != XmlPullParser.START_TAG) {  
            continue;  
        }  
        final String name = parser.getName();  
        if (TAG_REQUEST_FOCUS.equals(name)) {  
            parseRequestFocus(parser, parent);  
        } else if (TAG_INCLUDE.equals(name)) {  
            if (parser.getDepth() == 0) {  
                throw new InflateException("<include /> cannot be the root element");  
            }  
            parseInclude(parser, parent, attrs);  
        } else if (TAG_MERGE.equals(name)) {  
            throw new InflateException("<merge /> must be the root element");  
        } else {  
            final View view = createViewFromTag(name, attrs);  
            final ViewGroup viewGroup = (ViewGroup) parent;  
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);  
            rInflate(parser, view, attrs);  
            viewGroup.addView(view, params);  
        }  
    }  
    parent.onFinishInflate();  
}
总结

可以看到,同样是createViewFromTag()方法来创建View的实例,然后递归调用rInflate()方法来查找这个View下的子元素,每次递归完成后则将这个View添加到父布局当中。这样整个布局文件都解析完成后就形成了一个完整的DOM结构,最终会把最顶层的根布局返回,至此inflate()过程全部结束。