AndroidのLayoutInflater探索


LayoutInflaterはAndroidプログラミングでよく使われる解析レイアウトファイルを生成するためのクラスで、このブログではLayoutInflaterに関する知識を探求します.
LayoutInflaterを使用するには、まずそのインスタンスを取得する必要があります.Androidでは、次の2つの方法があります.
//    
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
//    
LayoutInflater LayoutInflater = LayoutInflater.from(context);

実は方法2は方法1の包装にすぎませんが、通常はブロガーが方法2を使用します.便利ですから.
    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;
    }

LayoutInflaterのインスタンスを取得した後、レイアウトを解析するために使用することができます.LayoutInflaterで与えられた解析関数には、次の4つのリロードがあります.
//      , View
inflate(int resource, ViewGroup root)
//        PULLxml   , View
inflate(XmlPullParser parser, ViewGroup root)
//      , View,      View
inflate(int resource, ViewGroup root, boolean attachToRoot)
//        PULLxml   , View,      View
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

すべての関数は1つのViewを返します.最初の3つのリロード関数は最後に最後のリロード関数を呼び出すので、ここで最後のリロード関数を分析します.その前に、LayoutInflaterで定義されているラベル定数をいくつかリストします.
    private static final String TAG_MERGE = "merge";
    private static final String TAG_INCLUDE = "include";
    // blink    ,       ,      1994     ,   TAG_1995?
    private static final String TAG_1995 = "blink";
    private static final String TAG_REQUEST_FOCUS = "requestFocus";

再ロードされた解析関数:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {

            final AttributeSet attrs = Xml.asAttributeSet(parser);
            //      View   
            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    
                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 {
                	//   blink    
                    // 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);
                        }
                    }

                    //      View
                    // Inflate all children under temp
                    rInflate(parser, temp, attrs, true);

                    // 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;
                    }
                }

            } 
            ...

            return result;
        }
    }

コードには注釈が書いてあるので、通りかかったら分かりにくいはずです.ここでは特殊なラベル解析を解析しないので、コードは42行目まで実行し、createView FromTagメソッドを呼び出します.
    View createViewFromTag(View parent, String name, AttributeSet attrs) {
        ...

        try {
            View view;
            if (mFactory2 != null) view = mFactory2.onCreateView(parent, name, mContext, attrs);
            else if (mFactory != null) view = mFactory.onCreateView(name, mContext, attrs);
            else view = null;

            if (view == null && mPrivateFactory != null) {
                view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
            }
            
            if (view == null) {
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            }

            if (DEBUG) System.out.println("Created view is: " + view);
            return view;

        }
        ...
    }

このメソッドは、解析の最上位レベルのViewを生成するためにonCreateViewまたはcreateViewを呼び出す場所がいくつかあります.onCreateViewメソッドは、最終的にcreateViewメソッドを呼び出すので、createViewメソッドを見てみましょう.
    public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        //       
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        Class<? extends View> clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            //       ,          View
            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                
                ...
                constructor = clazz.getConstructor(mConstructorSignature);
                sConstructorMap.put(name, constructor);
            }
            ...

            //   View  ,    Context,    View  
            Object[] args = mConstructorArgs;
            args[1] = attrs;

            final View view = constructor.newInstance(args);
            //  ViewStub    LayoutInflater
            if (view instanceof ViewStub) {
                // always use ourselves when inflating ViewStub later
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(this);
            }
            return view;

        } 
        ...
    }

注釈を適切に追加すると、コードは難しくなく、主にJavaの反射メカニズムによって対応するViewをインスタンス化し、Viewの構造関数をキャッシュすることによってインスタンス化速度を向上させる.
私たちのinflateメソッドに戻ると、47行目は私たちが入力したroot(つまり親View)が空であるかどうかを判断し、空でない場合は対応する測定基準を生成します(したがって、レイアウトを解析するときは必ず対応する親Viewを入力しなければなりません.そうしないと、正しい測定基準を生成できないと、解析されたレイアウトのサイズが正しくありません).
さらに実行を続行すると、59行目のコードでrInflateメソッドが呼び出され、レイアウトの最上位レベルViewのサブViewの解析が開始されます.
    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();
            
            //   requestFocus  
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_INCLUDE.equals(name)) { //   include  
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, parent, attrs);
            } else if (TAG_MERGE.equals(name)) { //   merge  
                throw new InflateException("<merge /> must be the root element");
            } else if (TAG_1995.equals(name)) { //   blink  
                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();
    }

注記この方法は難しくありません.xmlファイルの解析が完了するまで、自身を繰り返し呼び出すのは時間がかかるプロセスです.
私たちのinflateメソッドに戻って、63行目でrootが空であるかどうかを判断し、空でなくレイアウトに親View(attachToRoot==true)にバインドする必要がある場合、レイアウトは親Viewに追加され、最終的に解析された戻り結果は親View(70行目)になります.空またはバインドされていない場合、返される結果は、解析が必要なレイアウトの最上位ビューです.
以下はinflateワークフローチャートです.
要するに、inflateメソッドを使用する場合は、rootパラメータを追加することを忘れないでください.これにより、解析したレイアウトファイルのサイズが正しくなります.レイアウトファイルの解析が終わったらrootに追加するには、attachToRootをtrueに設定するだけです.