1. 程式人生 > >關於inflate的幾個方法解析(結合日誌原始碼)

關於inflate的幾個方法解析(結合日誌原始碼)

inflate使我們使用頻率極高的api了,並且他有多個過載的方法,如下:

View inflate(int, ViewGroup)   
View inflate(XmlPullParser, ViewGroup)   
View inflate(int, ViewGroup, boolean)   
View inflate(XmlPullParser, ViewGroup, boolean)

我們要在不同的使用場景下,進行介紹。

  1. 我們一般不使用傳入XmlPullParser解析器的方法,一般都是直接傳入XML檔案,方法內部會將XML轉換成解析器,程式碼如下:
    final XmlResourceParser parser = res.getLayout(resource);

  1. 剩餘的兩個方法主要是最後一個引數(attachToRoot)是否傳入的區別,其實兩個引數的方法,最終會呼叫到三個引數的方法,程式碼如下:
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }

只不過最後一個引數是根據root是否為null來決定的,這也比較好理解,如果你沒有傳入root本身就沒有父view可繫結,所以attachToRoot自然是false

  1. 介紹View inflate(int, ViewGroup) 方法,第二個引數是否傳入null,所產生的不同的結果。

    1. Fragment中使用,在onCreateView中
     @Override
        public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            LogUtils.d("-----"+container.toString()
    ); View inflate = inflater.inflate(R.layout.fragment, container); return inflate; } //如上使用會報錯:Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. //因為container不為null,原因是建立Fragment的時候,系統會預設給Fragment新增一個FrameLayout的父佈局,如果這個時候在把Fragment加入到Activity中的佈局時,就會報錯了 //列印的日誌也證明上面的說法 09-26 04:36:16.725 350-350/test.juyoufuli.com.testapplication D/TestApplication: -----android.widget.FrameLayout{774b98b V.E..... ......ID 0,0-0,0 #7f070041 app:id/fl}

    如何正確使用呢? 兩種方案:

        View inflate = inflater.inflate(R.layout.fragment, null);
         View inflate = inflater.inflate(R.layout.fragment, container,false);
         //根據原始碼可知,root傳入null和attachToRoot傳入false等價
    
    1. 其他常規用法基本原則是不變的,如下面程式碼:
            FrameLayout viewById = findViewById(R.id.fl);
            View inflate = getLayoutInflater().inflate(R.layout.fragment,null);
            LogUtils.d("-----"+inflate.getParent().toString());
            viewById.addView(inflate);
            //如上程式碼空指標錯誤,inflate.getParent()為null,常規填充是不會有父view的。
    
  2. 繼續介紹傳入不同的第三個引數,view會有不同的顯示效果,原始碼的中的關鍵程式碼:

    /**
     * @param root Optional view to be the parent of the generated hierarchy (if
     * <em>attachToRoot</em> is true), or else simply an object that
     * provides a set of LayoutParams values for root of the returned
     *hierarchy (if <em>attachToRoot</em> is false.)
    */ 
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;

            try {
               //主要部分
                if (TAG_MERGE.equals(name)) {
                //如果root為null或者不繫結到root,則佈局效果都是按照父view的來的
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    // 這個傳入的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);
                        //初始化出來的子view不繫結到root上,則設定指定父佈局的引數(算是一組參考的佈局引數),後續需要自己呼叫addview方法,並且並不一定必須add到這個佈局上
                        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);

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    //如果繫結到root上的話,就直接通過addview來加入到root的佈局
                    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.
                    // 未指定父佈局或者不繫結的話直接返回解析好view
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()
                        + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                // Don't retain static reference on context.
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;

                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }

            return result;
        }
    }

上面的註解也還算詳細了,如有問題望各位大佬指點。

如上基本完成分析,下面總結一下使用的注意事項。

  1. 在繫結view的時候要注意是inflate出來的view已經預設添加了父view
  2. 如果還不確定要新增到的view,直接傳入null即可,會減少一些計算邏輯
  3. 如果attachToRoot傳入true,則不可以在呼叫addview方法,將該view新增到其他view上