Android LayoutInflater
niuyongzhi 人气:01.在view的加载和绘制流程中:文章链接
我们知道,定义在layout.xml布局中的view是通过LayoutInflate加载并解析成Java中对应的View对象的。那么具体的解析过程是哪样的。
先看onCreate方法,如果我们的Activity是继承自AppCompactActivity。android是通过getDelegate返回的对象setContentView,这个mDelegate 是AppCompatDelegateImpl的实例。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } //getDelegate 返回的是AppCompatDelegateImpl的实例 public void setContentView(@LayoutRes int layoutResID) { this.getDelegate().setContentView(layoutResID); } public AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; } public static AppCompatDelegate create(@NonNull Activity activity, @Nullable AppCompatCallback callback) { return new AppCompatDelegateImpl(activity, callback); }
在AppDelegateImpl中
public void setContentView(int resId) { this.ensureSubDecor(); //contentParent 是 系统布局文件 id 为content的view ViewGroup contentParent = (ViewGroup)this.mSubDecor.findViewById(android.R.id.content)); contentParent.removeAllViews(); LayoutInflater.from(this.mContext).inflate(resId, contentParent); this.mOriginalWindowCallback.onContentChanged(); }
resource 就是传递过来的layout资源id,系统通过XmlPullParser来解析xml。Root是上面得到的contentView。
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(); } }
name 就是定义在布局文件中的控件名字,LinearLayout,TextView等,包括自定义的控件
attrs定义在控件下所有属性,包括宽高颜色背景等。
先通过createViewFromTag拿到布局文件中的root view。
再通过rInflateChildren遍历子View。
最后root.addView(temp, params);将布局文件的root view 添加到contentView中,成为它的一个子View。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { final AttributeSet attrs = Xml.asAttributeSet(parser); final String name = parser.getName(); //在layout.xml中找到的root view final View temp = createViewFromTag(root, name, inflaterContext, attrs); // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); // Inflate all children under temp against its context. //遍历布局文件中定义的子view,将定义在xml的view转换成对应的java对象。 rInflateChildren(parser, temp, attrs, true); if (root != null && attachToRoot) { //将layout中定义的root view 加到contentView中 root.addView(temp, params); } }
createViewFromTag方法,通过name和attrs创建View对象。
再调用rInflateChildren 加载子View,通过循环遍历,把整个layout树转换成Java的View对象。
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); } //开始遍历子view void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { ....... final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } }
createViewFromTag是创建View对象的关键方法。
有两种方式,一种是继承自AppCompactActivity,会通过factory的onCreateView创建view。
另外一种是继承自Activity,没有设置factory,或者通过factory创建view失败,则调用onCreateView方法进行创建。
//将定义在xml的标签通过反射成对应的java对象。 View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) { // 当Activity继承自AppCompactActivity时,会在AppCompactActivity,onCreate时调用 // delegate.installViewFactory()设置factory,然后调用factory的方法创建view View view; if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, context, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, context, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, context, attrs); } //当Activity继承自Activity时,没有设置factory时,执行下面的创建过程 //或者通过上面的方式没有加载到View,也会调用下面的方法创建view对象。 if (view == null) { final Object lastContext = mConstructorArgs[0]; mConstructorArgs[0] = context; try { if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } } finally { mConstructorArgs[0] = lastContext; } } return view; }
先看第一种方法:调用factory的onCreateView方法,是通过调用mAppCompatViewInflater.createView创建的,根据name和attrs,直接调用View的构造函数创建的对象。创建的都是一些系统内置的view对象。
final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext.....){ View view = null; // We need to 'inject' our tint aware Views in place of the standard versions switch (name) { case "TextView": view = createTextView(context, attrs); verifyNotNull(view, name); break; case "ImageView": view = createImageView(context, attrs); verifyNotNull(view, name); break; case "Button": view = createButton(context, attrs); verifyNotNull(view, name); break; case "EditText": view = createEditText(context, attrs); verifyNotNull(view, name); break; ............. return view; }
再看第二种方式:通过反射进行创建。通过反射的方式,可以创建自定义的view对象。
public final View createView(@NonNull Context viewContext, @NonNull String name, @Nullable String prefix, @Nullable AttributeSet attrs){ Class<? extends View> clazz = null; clazz = Class.forName(prefix != null ? (prefix + name) : name, false, mContext.getClassLoader()).asSubclass(View.class); constructor = clazz.getConstructor(mConstructorSignature); constructor.setAccessible(true); //将得到的构造函数保存的map中 sConstructorMap.put(name, constructor); final View view = constructor.newInstance(args); return view; }
通过以上两种方式,就可以完成整个layout 的Java 对象转换。
然后就可以调用view的绘制的方法,执行view绘制流程。onlayout,onMeasure,ondraw。
app换肤的的框架可以通过设置自定义的Factory来实现。这块有机会再写文章探讨。
加载全部内容