SetContentView都做了什么

    xiaoxiao2021-03-25  56

    在Activity的onCreate方法中,可以调用setContentView(layout_id),来设置这个Activity的视图。那么setContentView都做了些什么呢?

    以下一个简单的Activity的代码:

    public class ViewDemoActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.view_demo); } } 查看父类Activity的setContentView方法:

    public void setContentView(int layoutResID) { getWindow().setContentView(layoutResID); ... } 这个getWindow()指向哪里呢?我们可以在onCreate中打个log:

    D/ViewDemoActivity(3969): com.android.internal.policy.impl.PhoneWindow 原来是PhoneWindow,来看看它的setContentView方法:

    @Override public void setContentView(int layoutResID) { // 1 if (mContentParent == null) { installDecor(); } else { mContentParent.removeAllViews(); } // 2 mLayoutInflater.inflate(layoutResID, mContentParent); // 3 final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); }这大体可以分为三个步骤:

    第一步:判断父容器是否为空

    为空:生成Decor 不为空:删除 contentParent 所有的子控件。

    第二步:解析layoutResID所代表的xml文件

    LayoutInflater.inflate(...)方法的代码如下: public View inflate(int resource, ViewGroup root) { return inflate(resource, root, root != null); } public View inflate(int resource, ViewGroup root, boolean attachToRoot) { XmlResourceParser parser = getContext().getResources().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); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; View result = root; try { // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); // 根标签为merge时,root不能为空 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, false); } else { // Temp is the root view that was found in the xml View temp; if (TAG_1995.equals(name)) { temp = new BlinkLayout(mContext, attrs); } else { temp = createViewFromTag(root, name, attrs); } ViewGroup.LayoutParams params = null; if (root != null) { // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not attaching. // (If we are, we use addView, below) temp.setLayoutParams(params); } } // 解析temp的所有子View rInflate(parser, temp, attrs, true); // 将temp添加到root中 if (root != null && attachToRoot) { root.addView(temp, params); } // 如果未指定root或者不附加到root,则返回xml所代表的view; 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; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; } return result; } } inflate(...)最终会调用rInflate(...): void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate) 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 if (TAG_1995.equals(name)) { final View view = new BlinkLayout(mContext, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); viewGroup.addView(view, params); } else { final View view = createViewFromTag(parent, name, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) parent.onFinishInflate(); }

    rInflate(...)先特殊处理了几个xml标签,然后在else分支,会递归调用rInflate(...),将xml的子控件添加到parent中,生成完整的contentView

    看了这里,同学们就会明白,为什么不建议在布局文件中做过多地View嵌套了吧,层层递归!

    第三步:通知Callback,ContentView发生改变

    这个getCallback()是什么?打个Log看看: @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); super.setContentView(R.layout.view_demo); Log.d("ViewDemo", getWindow().getCallback().toString()); } 输出结果为: D/ViewDemoActivity( 7228): cn.erhu.android.view.ViewDemoActivity@6562f2c0 原来它就是DemoActivity! 在Activity.java的attach()方法中: final void attach(...) { ... mWindow = PolicyManager.makeNewWindow(this); mWindow.setCallback(this); ... } 原来PhoneWindow的callback就是Activity,并且Activity的onContentChanged()方法是空的,所以我们可以在自己的Activity中重写这个方法,来监听ContentView发生改变的事件。

    转载请注明原文地址: https://ju.6miu.com/read-34517.html

    最新回复(0)