view的绘制机制(一)
简介
我们在学习android的时候,直接在xml中指定android的标签,就能显现出各式各样的界面,但是我们并不了解其中的绘制流程,从而抱着一个学习的心态,从源码角度去查看view的绘制机制。
目录
1.view的绘制流程(一)
2.总结
view的绘制流程
在进行分析之前,我们可以先看看下面的流程图:
每个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()方法,源码如下:
@Overridepublic 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(" cannot be the root element"); } parseInclude(parser, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException(" 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()过程全部结束。
我们继续学习view的绘制机制(二)
更多相关文章
- 手把手带你搭建 Elasticsearch 集群
- Android基础知识(2)—事件处理
- Intent介绍及Intent在Activity中的使用方法
- Android高手秘笈之View的绘制你应该知道的一切
- Android(安卓)弹无虚发之第五弹:来点儿不一样的Toast(自定义Toast,
- Android(安卓)adt v22.6.2 自动创建 appcompat_v7 解决方法,最低
- 关于Android的面试题目汇总
- Android(安卓)学习
- Android前台画面和后台service之间通信的方法之Broadcast(转)