android开发中使用setContentView和LayoutInflater的点点滴滴

在网上也看了一些牛人的博客。结合自身的理解,将常用的知识点总结一下。

1. 我们在做android开发的过程中,几乎每天都要用到setContentView和LayoutInflater。但是我们并不见得对这两个函数非常了解。今天就从源码的角度分析一下,我们调用了这两个函数以后,android给我们做了什么。我们一般都在OnCreate里边调用setContentView。

那么我们就去Activity中去看看这个方法。

public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initActionBar();
    }
另外还有两个重载的方法:

 public void setContentView(View view) {
        getWindow().setContentView(view);
        initActionBar();
    }
	
 public void setContentView(View view, ViewGroup.LayoutParams params) {
        getWindow().setContentView(view, params);
        initActionBar();
    }

通过这个方法我们不难看出,不管我们是调用哪一个方法,最终都是getWindow()返回window对象,调用window对象中setContentView的方法。window类是个啥呢。通过源码我们发现Window这个类是抽象类,那么在Activity中是如何实例化mWindow呢,我们继续看源码。

 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);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
	.......
}

可以看见11条代码,是在这个地方进行实例化的,其实返回的是phoneWindow,12行代码setCallback后续会用到。我们要搞清楚Window、phoneWindow、DecorView、Activity到底之间是什么关系呢。在网上发现一个哥们总结的就特别好,我就抄过来了。

window相当于显示屏,phoneWindow相当于手机的屏幕,DecorView相当于手机屏幕要显示的内容,Activity是要显示屏幕的载体。专业术语的解释为,window是一个抽象类,phoneWindow是window的子类,DecorView是phoneWindow一个成员类,是整个界面的根,并且

Decorview是FrameLayout子类。这一部分总算是搞清楚window/phonewindow/decorview之间的区别和联系了。

2. 接下来我们就仔细看看phoneWindow这个类中setContentView都做了什么吧。

 public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

如果是第一次调用setContentView的话,mContentParent就一定为null,这时就会调用installDecor()方法。hasFeature(FEATURE_CONTENT_TRANSITIONS)默认是返回false的,也就是说我们在Activity中可以多次调用setContentView,反正都会清空所有的子view。在16行我们会发现通过LayoutInflater将布局文件转化为view,追加到mContentParent中。

那我们先去看看installDecor()方法都做了什么呢。

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            // Set up decor part of UI to ignore fitsSystemWindows if appropriate.
            mDecor.makeOptionalFitsSystemWindows();

            final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                    R.id.decor_content_parent);

            if (decorContentParent != null) {
                mDecorContentParent = decorContentParent;
                mDecorContentParent.setWindowCallback(getCallback());
                if (mDecorContentParent.getTitle() == null) {
                    mDecorContentParent.setWindowTitle(mTitle);
                }
				.....方法太长
}				

如果mDecor为空的话,创建一个Decor对象,这个就是界面的根,然后就是一些属性的初始化。我们看看第十一行代码。

protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        // 根据在AndroidManifest.xml的theme设置style或者requestWindowFeature设置feature。然后确定layoutResource
<span style="white-space:pre">	</span>// 而这些layoutresource早就定义好的,然后通过layoutinflate创建view,add到decorview对象中去。通过findviewbyid找打id为ID_ANDROID_CONTENT的view,这个view就是我们mContentParent。
        int layoutResource;
        int features = getLocalFeatures();
       

        mDecor.startChanging();

        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
		....

        return contentParent;
    }
而我们传递过来的布局id或者是自定义的view,最后都放在了mContentParent的下边了。其他的重载setContentView都大同小异。

我们再看看phonewindow中的setContentView方法的最后部分:

final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }

在当前phonewindow中并没有getCallback方法。最后我们发现在父类window中有。那是谁调用了setCallback呢。我们就需要去Activity中创建mWindow时看看了,我们可以看到mWindow.setCallback(this);说明在phonewindow中onContentChanged的回调是在activity中实现的。事实上在

Activity中onContentChanged方法为空方法。


总结一下setContentView的知识点。

2.1 通过在Activity调用setContentView方法,最终都在调用phoneWindow的setContentView方法。

2.2 在phoneWindow中,我们自己的布局或者自定义的view都添加到了mContentParent下边。

2.3 在phonewindow中,是通过theme和feature来确定使用哪一个layoutresource的,确定以后,就可以将这个layoutresource生成的view添加到跟mDecor中。通过id为ID_ANDROID_CONTENT的找到mContentParent。

2.4 setContentView可以调用多次。

2.5 我们平时的一些findviewbyid都可以放在onContentChanged中实现,因为setContentView执行完之后会回调这个方法。

3. 接下来我们来看看LayoutInflater的用法。我相信做过android开发的人,这个类不可能没有用过,比如在listview中,我们将一个item对应的layout转化为一个view,添加到listview中。

先看看如何实例化LayoutInflater对象。

 LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
可以看出LayoutInflater就是一个系统服务。但是android也提供一个静态方法,创建实例:

/**
     * Obtains the LayoutInflater from the given context.
     */
    public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }
再看看我们平时经常使用几个方法。

public View inflate(int resource, ViewGroup root) {
        return inflate(resource, root, root != null);
}
	
public View inflate(XmlPullParser parser, ViewGroup root) {
        return inflate(parser, root, root != null);
}

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }

        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) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
<span style="white-space:pre">			</span>// 生成根节点的属性
            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
                }
<span style="white-space:pre">				</span>// 判断xml布局文件的合法性
                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()
                            + ": No start tag found!");
                }


                final String name = parser.getName();
                
                if (DEBUG) {
                    System.out.println("**************************");
                    System.out.println("Creating root view: "
                            + name);
                    System.out.println("**************************");
                }
<span style="white-space:pre">				</span>// 如果是merge,root必须不为空,attachToRoot必须为true,否则抛异常
                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, false);
                } else {
<span style="white-space:pre">					</span>// 创建最外层的view
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, attrs, false);


                    ViewGroup.LayoutParams params = null;
<span style="white-space:pre">					</span>// 我们都知道在android开发时,在xml中设置一下属性时都是带layout开头,比如layout_width,表示在父布局中占有多宽。这也是为啥前边带有layout字眼的原因吧。
<span style="white-space:pre">					</span>// 如果root不为空的话,根据在xml中根节点设置的属性和root节点本身,进一步确定布局根节点的布局参数LayoutParams
                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // 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);
                        }
                    }


                    if (DEBUG) {
                        System.out.println("-----> start inflating children");
                    }
<span style="white-space:pre">					</span>// 递归生成根节点的子view
                    // Inflate all children under temp
                    rInflate(parser, temp, attrs, true, true);
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }


                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }


                    // Decide whether to return the root that was passed in or the
                    // top view found in xml.
                    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;
            }


            Trace.traceEnd(Trace.TRACE_TAG_VIEW);


            return result;
        }
    }

我们可以分四种情况解析:

3.1 当root != null,attachToRoot=true时,生成的temp时,会根据xml布局的属性和root创建LayoutParams,最后将root.addView(temp, params)到root中,最后返回root。

3.2 当root != null,attachToRoot=false时,生成的temp时,会根据xml布局的属性和root创建LayoutParams,并temp.setLayoutParams(params);设置到temp中,最后返回temp。

3.3 当root = null,attachToRoot=true时,生成temp,并且返回temp。

3.4 当root = null,attachToRoot=false时,生成temp,并且返回temp。


那么看一个Demo吧。在网上发现一牛人举的例子就特别好:

package com.example.test;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class InflateAdapter extends BaseAdapter {
    private LayoutInflater mInflater = null;

    public InflateAdapter(Context context) {
        mInflater = LayoutInflater.from(context);
    }

    @Override
    public int getCount() {
        return 8;
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        //说明:这里是测试inflate方法参数代码,不再考虑性能优化等TAG处理
        return getXmlToView(convertView, position, parent);
    }

    private View getXmlToView(View convertView, int position, ViewGroup parent) {
        View[] viewList = {
                mInflater.inflate(R.layout.textview_layout, null),
//                mInflater.inflate(R.layout.textview_layout, parent),
                mInflater.inflate(R.layout.textview_layout, parent, false),
//                mInflater.inflate(R.layout.textview_layout, parent, true),
                mInflater.inflate(R.layout.textview_layout, null, true),
                mInflater.inflate(R.layout.textview_layout, null, false),

                mInflater.inflate(R.layout.textview_layout_parent, null),
//                mInflater.inflate(R.layout.textview_layout_parent, parent),
                mInflater.inflate(R.layout.textview_layout_parent, parent, false),
//                mInflater.inflate(R.layout.textview_layout_parent, parent, true),
                mInflater.inflate(R.layout.textview_layout_parent, null, true),
                mInflater.inflate(R.layout.textview_layout_parent, null, false),
        };

        convertView = viewList[position];

        return convertView;
    }
}

Actvity的代码很简单,我就不贴了。看看运行结果吧:


我的item布局有两种,textview_layout如下:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="40dp"
    android:text="textview_layout"
    android:background="#ffa0a00c"/>

textview_layout_parent如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content" >

    <TextView
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="100dp"
        android:layout_height="40dp"
        android:background="#ffa0a00c"
        android:text="textview_layout_parent" />

</LinearLayout>

分析这八个item出现的原因:

1. 对应我们上面说的第四种情况,就是单纯的创建view,并没有布局参数,当getview返回这个inflate转换的view时,layoutparams是为null,那么listview默认就给这个view一个宽和高,我们写死的宽高并没有生效。

2. 对应第二种情况,创建一个view,并且根据listview和布局参数,设置了layoutparams,所以,我们在布局文件中写死的宽高生效了。

3. 同1.

4. 同1,所有第三、四item写死的宽和高没有生效。

5. 对应第四种情况,生成的view没有布局参数,listview就给出默认值,但是内部的textview内部的布局参数确实可以的。

6. 对应第二种情况,能够生成布局参数,并set到view里边,所有这个view写的wrap_content是生效的。

7. 同5

8. 同5 布局中写的wrap_content是没有生效的。


通过以上的分析,算是对LayoutInflate比较了解吧。





















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值