Android換膚機制

LayoutInflater

咱們常常使用LayoutInflater將佈局文件渲染成View層級視圖,那麼具體是怎麼使用的呢?目前有四種方式:android

  • context.getSystemService()
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);   
View rootView = inflater.inflate(R.layout.view_layout, null); 
複製代碼
  • LayoutInflater.from(context)
LayoutInflater inflater = LayoutInflater.from(context);  
View rootView = inflater.inflate(R.layout.view_layout, null); 
複製代碼
  • Activity下調用getLayoutInflater()
LayoutInflater inflater = getLayoutInflater();  
View rootView = inflater.inflate(R.layout.view_layout, null); 
複製代碼
  • View的靜態方法,View.inflate()
rootView = View.inflate(context, R.layout.view_layout, null);  
複製代碼

經過源碼能夠得知,第2、第3、第四種方式其實就是第一種方式的封裝,最終獲取View實例的是經過inflater實例的inflate()方法。bash

咱們再看一下Activity的onCreate()方法中的setContentView方法:佈局

public void setContentView(int layoutResID) {
       ...省略
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        ...省略
    }
複製代碼

能夠看到一樣也是使用了inflater的inflate()方法。因此咱們能夠獲得這樣的結論: 不管是咱們本身主動調用inflater的inflate()方法渲染View,仍是Activity經過setContentView來渲染View,都是經過inflater的inflate()方法來完成的。ui

咱們繼續跟蹤inflate()方法,發現最終調用的是inflate(XmlPullParser,ViewGroup, boolean attachToRoot) 方法:spa

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            ...省略
            try {
                ...省略
                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, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, 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);
                        }
                    }                    
                    // Inflate all children under temp against its context.
                    rInflateChildren(parser, temp, attrs, true);                   
                    ...省略
                }

            } catch (XmlPullParserException e) {
                ..省略
            } catch (Exception e) {
                ...省略
            } finally {
                ...省略            
            }
            return result;
        }
    }
複製代碼

咱們省略掉了一些代碼,直接從try代碼塊開始看,不考慮merge標籤(其實merge標籤只是正常渲染的一種特殊狀況),咱們就走到了else語句下,關鍵代碼createViewFromTag()生成了View。code

那咱們繼續跟蹤,看一下createViewFromTag執行了什麼操做:xml

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }
       ...省略
        try {
            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);
            }

            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;
        } catch (InflateException e) {
            throw e;
        } catch (ClassNotFoundException e) {
            ...省略
        } catch (Exception e) {
            ...省略
        }
    }
複製代碼

咱們仍是講部分代碼省略了,從try代碼塊開始看,當mFactory2或者mFactory不爲null時,View是由它們的onCreateView方法生成的,不然使用系統默認建立View的流程。系統默認建立View的流程是經過判斷標籤名稱有沒有包含".",若是沒有則將前綴"android.view."添加到標籤名前面,最終調用LayoutInflater的createView()方法,而後返回View。 到這裏咱們就知道了LayoutInflater根據佈局文件來渲染View的主要流程:先經過佈局文件的資源ID建立XmlResourceParser解析器對象,而後利用該對象遞歸解析佈局文件,根據解析出來的標籤名生成View,最終返回層級視圖View。而若是LayoutInflater設置了Factory2或者Factory,那麼在建立View時都會調用Factory2或者Factory的onCreateView方法,因此咱們能夠在View建立以前在onCreateView中作一些相關邏輯,好比說換膚。對象

LayoutInflater之Factory接口

未完待續...遞歸

相關文章
相關標籤/搜索