遇見LayoutInflater&Factory

奧體公園

LayoutInflater的獲取

在我們寫listview的adapter的getView方法中我們都會通過LayoutInflater.from(mContext)獲取LayoutInflater實(shí)例浴讯。
現(xiàn)在我們通過源碼來分析一下LayoutInflater實(shí)例的獲瓤肾:

//LayoutInflater的獲取
public abstract class LayoutInflater {
    /**
     * 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;
    }
}

context.getSystemService 是Android很重要的一個API敲霍,它是Activity的一個方法评雌,根據(jù)傳入的NAME來取得對應(yīng)的Object,然后轉(zhuǎn)換成相應(yīng)的服務(wù)對象。以下介紹系統(tǒng)相應(yīng)的服務(wù)。

Name 返回的對象 說明
WINDOW_SERVICE WindowManager 管理打開的窗口程序
LAYOUT_INFLATER_SERVICE LayoutInflater 取得xml里定義的view
ACTIVITY_SERVICE ActivityManager 管理應(yīng)用程序的系統(tǒng)狀態(tài)
POWER_SERVICE PowerManger 電源的服務(wù)
ALARM_SERVICE AlarmManager 鬧鐘的服務(wù)
NOTIFICATION_SERVICE NotificationManager 狀態(tài)欄的服務(wù)
KEYGUARD_SERVICE KeyguardManager 鍵盤鎖的服務(wù)
LOCATION_SERVICE LocationManager 位置的服務(wù)饰序,如GPS
SEARCH_SERVICE SearchManager 搜索的服務(wù)
VEBRATOR_SERVICE Vebrator 手機(jī)震動的服務(wù)
CONNECTIVITY_SERVICE Connectivity 網(wǎng)絡(luò)連接的服務(wù)
WIFI_SERVICE WifiManager Wi-Fi服務(wù)
TELEPHONY_SERVICE TeleponyManager 電話服務(wù)

獲取LayoutInflater服務(wù)

class ContextImpl extends Context {
    /***部分代碼省略****/
    static {
        /***部分代碼省略****/
        registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
            public Object createService(ContextImpl ctx) {
                return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
            }});
        /***部分代碼省略****/
    }
    /***部分代碼省略****/
}

從源碼可以看出LayoutInflater實(shí)例是由 PolicyManager.makeNewLayoutInflater 獲取的,PolicyManager有沒有感覺很熟悉闻妓。上一章 Activity中的Window的setContentView 中我們獲取Activity中的Window的實(shí)例的時候就是通過PolicyManager獲取的菌羽,我們進(jìn)一步往下跟進(jìn)。

public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
    "com.android.internal.policy.impl.Policy";

    private static final IPolicy sPolicy;

    static {
        // Pull in the actual implementation of the policy at run-time
        try {
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
        } catch (InstantiationException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        } catch (IllegalAccessException ex) {
            throw new RuntimeException(
                    POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
        }
    }
    /***部分代碼省略****/

    public static LayoutInflater makeNewLayoutInflater(Context context) {
        //反射獲取實(shí)例
        return sPolicy.makeNewLayoutInflater(context);
    }
}

public class Policy implements IPolicy {
    /***部分代碼省略****/
    public LayoutInflater makeNewLayoutInflater(Context context) {
        //LayoutInflater的最終實(shí)例
        return new PhoneLayoutInflater(context);
    }
    /***部分代碼省略****/
}

PhoneLayoutInflater的實(shí)現(xiàn)


public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    
    public PhoneLayoutInflater(Context context) {
        super(context);
    }
    
    protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
    }
    
    /** Override onCreateView to instantiate names that correspond to the
        widgets known to the Widget factory. If we don't find a match,
        call through to our super class.
    */
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }
    
    public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }
}

LayoutInflater最常使用的方法

在Android中LayoutInflater中最常使用的情況基本都是調(diào)用inflate方法用來構(gòu)造View對象由缆。

public abstract class LayoutInflater {
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }
    public View inflate(@LayoutRes int resource, @Nullable 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();
        }
    }
    /**
     * @param parser xml數(shù)據(jù)結(jié)構(gòu)
     * @param root 一個可依附的rootview
     * @param attachToRoot 是否將parser解析生產(chǎn)的View添加在root上
     */
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
            //當(dāng)前上下文環(huán)境
            final Context inflaterContext = mContext;
            //所有的屬性集合獲取類
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            //根節(jié)點(diǎn)
            View result = root;

            try {
                // Look for the root node.尋找根節(jié)點(diǎn)
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG &&
                        type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }
                //找不到根節(jié)點(diǎn)拋出異常
                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("**************************");
                }
                //merge標(biāo)簽解析
                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");
                    }
                    //遞歸調(diào)用注祖,添加root的孩子節(jié)點(diǎn)
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml。根據(jù)當(dāng)前的attrs和xml創(chuàng)建一個xml根view
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;

                    if (root != null) {
                        if (DEBUG) {
                            System.out.println("Creating params from root: " +
                                    root);
                        }
                        // Create layout params that match root, if supplied均唉。構(gòu)造LayoutParams
                        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");
                    }

                    // Inflate all children under temp against its context.遞歸調(diào)用是晨,添加temp的孩子節(jié)點(diǎn)
                    rInflateChildren(parser, temp, attrs, 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) {
                        //將xml解析出來的viewgroup添加在root的根下
                        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 (Exception 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;
        }
    }
}

這四個重載的inflate方法最終都是通過inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) 進(jìn)行實(shí)現(xiàn)的。

LayoutInflater的使用中重點(diǎn)關(guān)注inflate方法的參數(shù)含義:

  • inflate(xmlId, null); 只創(chuàng)建temp的View舔箭,然后直接返回temp罩缴。
  • inflate(xmlId, parent); 創(chuàng)建temp的View,然后執(zhí)行root.addView(temp, params);最后返回root层扶。
  • inflate(xmlId, parent, false); 創(chuàng)建temp的View箫章,然后執(zhí)行temp.setLayoutParams(params);然后再返回temp。
  • inflate(xmlId, parent, true); 創(chuàng)建temp的View镜会,然后執(zhí)行root.addView(temp, params);最后返回root檬寂。
  • inflate(xmlId, null, false); 只創(chuàng)建temp的View,然后直接返回temp戳表。
  • inflate(xmlId, null, true); 只創(chuàng)建temp的View桶至,然后直接返回temp。

LayoutInflater解析視圖xml

  • xml視圖樹解析
    遞歸執(zhí)行rInflate生產(chǎn)View并添加給父容器
public abstract class LayoutInflater {
    /**
     * 將parser解析器中包含的view結(jié)合屬性標(biāo)簽attrs生產(chǎn)view添加在parent容器中
     * @param parser xml解析器
     * @param parent 父容器
     * @param attrs  屬性標(biāo)簽集合
     * @param finishInflate 生產(chǎn)view之后是否執(zhí)行父容器的onFinishInflate方法匾旭。
     */
    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
        boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

    void rInflate(XmlPullParser parser, View parent, Context context,
        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)) {  //requestFocus標(biāo)簽解析
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) { //tag標(biāo)簽解析
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) { //include標(biāo)簽解析
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) { //merge標(biāo)簽解析
                throw new InflateException("<merge /> must be the root element");
            } else {
                //View標(biāo)簽解析
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                //View所在容器(ViewGroup)的屬性解析
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                //循環(huán)遍歷xml的子節(jié)點(diǎn)
                rInflateChildren(parser, view, attrs, true);
                //將解析出的view和其對于的屬性參數(shù)添加在父容器中
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }
}
  • 單個View布局的解析
    調(diào)用createViewFromTag镣屹,設(shè)置View的Theme屬性。再調(diào)用CreateView方法創(chuàng)建view
public abstract class LayoutInflater {
    protected View onCreateView(String name, AttributeSet attrs)
        throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }

    protected View onCreateView(View parent, String name, AttributeSet attrs)
        throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }

    /**
     * 將parser解析器中包含的view結(jié)合屬性標(biāo)簽attrs生產(chǎn)view添加在parent容器中
     * @param parent 父容器
     * @param name view名稱
     * @param context 上下文環(huán)境
     * @param attrs  屬性標(biāo)簽集合
     */
    private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
        return createViewFromTag(parent, name, context, attrs, false);
    }

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.//應(yīng)用theme
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        if (name.equals(TAG_1995)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            /*************************start Factory*/
            //使用LayoutInflater的Factory价涝,對View進(jìn)行修改
            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);
            }
            /*************************end Factory*/

            if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        //創(chuàng)建Android原生的View(android.view包下面的view)
                        view = onCreateView(parent, name, attrs);
                    } else {
                        //創(chuàng)建自定義View或者依賴包中的View(xml中聲明的是全路徑)
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;

        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;

        } catch (Exception e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + name);
            ie.initCause(e);
            throw ie;
        }
    }

    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的構(gòu)造是否進(jìn)行緩存
            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);
                
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                //static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
                constructor = clazz.getConstructor(mConstructorSignature);
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);
                        
                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }
            
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            constructor.setAccessible(true);
            //讀取View的構(gòu)造函數(shù)女蜈,傳入context、attrs作為參數(shù)。View(Context context, AttributeSet attrs)伪窖;
            final View view = constructor.newInstance(args);
            //處理ViewStub標(biāo)簽
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;

        } catch (NoSuchMethodException e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;

        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View "
                    + (prefix != null ? (prefix + name) : name));
            ie.initCause(e);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class "
                    + (clazz == null ? "<unknown>" : clazz.getName()));
            ie.initCause(e);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }
}

LayoutInflater創(chuàng)建View的總結(jié)

  • 在inflate方法中吏廉,通過Resource.getLayout(resource)生產(chǎn)XmlResourceParser對象;
  • 利用該對象實(shí)例生產(chǎn)XmlPullAttributes以便于xml標(biāo)簽中的屬性惰许。然后將這個兩個對象傳遞到rInflate方法中,解析layout對應(yīng)的xml文件史辙;
  • 接著將(父容器汹买、xml中View的名稱、屬性標(biāo)簽)傳遞給createViewFromTag方法創(chuàng)建對應(yīng)的View聊倔;
  • 在createViewFromTag方法中執(zhí)行LayoutInflater.Factory或者LayoutInflater的createView方法晦毙。
  • 在createView方法中我們已知View的類名和View的屬性標(biāo)簽集合,通過Java反射執(zhí)行View的構(gòu)造方法創(chuàng)建View對象耙蔑。這也就是為什么我們在自定義View的時候必須復(fù)寫View的構(gòu)造函數(shù)View(Context context, AttributeSet attrs)见妒;

LayoutInflater.Factory簡介

LayoutInflater.Factory這個類在我們開發(fā)的過程中很少越到。但是我們在查看LayoutInflater解析View源碼的過程中可以看到如果LayoutInflater中有mFactory這個實(shí)例那么可以通過mFactory創(chuàng)建View,同時也能修改入?yún)ttributeSet屬性值甸陌。

public abstract class LayoutInflater {
    /***部分代碼省略****/
    public interface Factory {
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }

    public interface Factory2 extends Factory {
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }
    /***部分代碼省略****/
}
  • LayoutInflater有兩個工廠類须揣,F(xiàn)actory和Factory2,區(qū)別只是在于Factory2可以傳入父容器作為參數(shù)钱豁。
public abstract class LayoutInflater {
    /***部分代碼省略****/
    public void setFactory(Factory factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = factory;
        } else {
            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
        }
    }

    public void setFactory2(Factory2 factory) {
        if (mFactorySet) {
            throw new IllegalStateException("A factory has already been set on this LayoutInflater");
        }
        if (factory == null) {
            throw new NullPointerException("Given factory can not be null");
        }
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }
    /***部分代碼省略****/
}

這兩個方法的功能基本是一致的耻卡,setFactory2是在Android3.0之后以后引入的,所以我們要根據(jù)SDK的版本去選擇調(diào)用上述方法牲尺。

在supportv4下邊也有LayoutInflaterCompat可以做相同的操作卵酪。

public class LayoutInflaterCompat {
    /***部分代碼省略****/
    static final LayoutInflaterCompatImpl IMPL;
    static {
        final int version = Build.VERSION.SDK_INT;
        if (version >= 21) {
            IMPL = new LayoutInflaterCompatImplV21();
        } else if (version >= 11) {
            IMPL = new LayoutInflaterCompatImplV11();
        } else {
            IMPL = new LayoutInflaterCompatImplBase();
        }
    }
    private LayoutInflaterCompat() {
    }
    public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
        IMPL.setFactory(inflater, factory);
    }
}

LayoutInflater.Factory的使用

找到當(dāng)前Activity中的id=R.id.text的TextView將其替換為Button,并修改BackgroundColor谤碳。

public class MainActivity extends Activity {
    final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                if ("TextView".equals(name)) {
                    Log.e(TAG, "name = " + name);
                    int n = attrs.getAttributeCount();
                    //打印所有屬性標(biāo)簽
                    for (int i = 0; i < n; i++) {
                        Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
                    }
                    for (int i = 0; i < n; i++) {
                        if (attrs.getAttributeName(i).equals("id")) {
                            String attributeValue = attrs.getAttributeValue(i);
                            String id = attributeValue.substring(1, attributeValue.length());
                            if (R.id.text == Integer.valueOf(id)) {
                                Button button = new Button(context, attrs);
                                button.setBackgroundColor(Color.RED);
                                return button;
                            }
                        }
                    }
                }
                return null;
            }
        });
        setContentView(R.layout.activity_main);
    }
}

Console輸出:

MainActivity: name = TextView
MainActivity: id , @2131492944
MainActivity: layout_width , -2
MainActivity: layout_height , -2
MainActivity: text , Hello World!

是不是發(fā)現(xiàn)LayoutInflater的Factory功能很好很強(qiáng)大溃卡。

這里提一個問題,如果把上面代碼中的MainActivity的父類修改為AppCompatActivity會怎么樣呢蜒简?我們試著運(yùn)行一下瘸羡。

java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.example.tzx.dexload/com.example.tzx.dexload.MainActivity}: 
java.lang.IllegalStateException: A factory has already been set on this LayoutInflater

程序運(yùn)行報錯:A factory has already been set on this LayoutInflater。這個是在執(zhí)行LayoutInflater的setFactory方法時拋出的異常臭蚁。因?yàn)閙FactorySet=true最铁。。垮兑。冷尉。這個時候我們發(fā)現(xiàn)LayoutInflater的Factory已經(jīng)被設(shè)置過了。具體是在哪里設(shè)置的呢系枪?我們看看下邊LayoutInflater.Factory在Android源碼中的使用部分內(nèi)容雀哨。

LayoutInflater.Factory在Android源碼中的使用

在我們開發(fā)過程是很少使用到LayoutInflater.Factory,但是Android在supportv7中就使用,我們來學(xué)習(xí)一下雾棺。

在AppComPatActivity中的onCreate就進(jìn)行了LayoutInflater.Factory的設(shè)置膊夹。

public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
    TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        getDelegate().installViewFactory();
        getDelegate().onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}
  • 根據(jù)不通的sdk版本做適配
public abstract class AppCompatDelegate {
    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }
    /***部分代碼省略****/

    //對于不同的版本做適配
    private static AppCompatDelegate create(Context context, Window window,
        AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV7(context, window, callback);
        }
    }
}

LayoutInflaterFactory的實(shí)現(xiàn)類,以及觸發(fā)LayoutInflater.setFactory的調(diào)用捌浩。

class AppCompatDelegateImplV7 extends AppCompatDelegateImplBase
    implements MenuBuilder.Callback, LayoutInflaterFactory {
    /***部分代碼省略****/
    @Override
    public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(mContext);
        if (layoutInflater.getFactory() == null) {
            //進(jìn)行setFactory的設(shè)置
            LayoutInflaterCompat.setFactory(layoutInflater, this);
        } else {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                    + " so we can not install AppCompat's");
        }
    }
    /***部分代碼省略****/
    @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        final boolean isPre21 = Build.VERSION.SDK_INT < 21;

        if (mAppCompatViewInflater == null) {
            //具體的實(shí)現(xiàn)類放刨,下文會有講到
            mAppCompatViewInflater = new AppCompatViewInflater();
        }

        // We only want the View to inherit it's context if we're running pre-v21
        final boolean inheritContext = isPre21 && mSubDecorInstalled
                && shouldInheritContext((ViewParent) parent);

        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
                true /* Read read app:theme as a fallback at all times for legacy reasons */
        );
    }
}
  • 根據(jù)不同的版本找到LayoutInflater的包裝類
public final class LayoutInflaterCompat {
    
    interface LayoutInflaterCompatImpl {
        public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory);
        public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater);
    }

    static class LayoutInflaterCompatImplBase implements LayoutInflaterCompatImpl {
        @Override
        public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory) {
            LayoutInflaterCompatBase.setFactory(layoutInflater, factory);
        }

        @Override
        public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater) {
            return LayoutInflaterCompatBase.getFactory(layoutInflater);
        }
    }

    static final LayoutInflaterCompatImpl IMPL;
    static {
        final int version = Build.VERSION.SDK_INT;
        if (version >= 21) {
            IMPL = new LayoutInflaterCompatImplV21();
        } else if (version >= 11) {
            IMPL = new LayoutInflaterCompatImplV11();
        } else {
            IMPL = new LayoutInflaterCompatImplBase();
        }
    }

    /***部分代碼省略****/

    private LayoutInflaterCompat() {
    }

    public static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
        IMPL.setFactory(inflater, factory);
    }

    public static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
        return IMPL.getFactory(inflater);
    }
}
  • FactoryWrapper類通過調(diào)用LayoutInflaterFactory的onCreateView方法,實(shí)現(xiàn)了LayoutInflater.Factory接口尸饺。最終調(diào)用了LayoutInflater的setFactory方法进统,使得在LayoutInflater.createViewFromTag中創(chuàng)建View的時候通過Factory進(jìn)行床架。
public interface LayoutInflaterFactory {
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

class LayoutInflaterCompatBase {
    
    static class FactoryWrapper implements LayoutInflater.Factory {

        final LayoutInflaterFactory mDelegateFactory;

        FactoryWrapper(LayoutInflaterFactory delegateFactory) {
            mDelegateFactory = delegateFactory;
        }

        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            //調(diào)用LayoutInflaterFactory實(shí)現(xiàn)類的onCreateView(null, name, context, attrs)方法
            return mDelegateFactory.onCreateView(null, name, context, attrs);
        }

        public String toString() {
            return getClass().getName() + "{" + mDelegateFactory + "}";
        }
    }

    static void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
        //最終調(diào)用了LayoutInflater的setFactory方法浪听,對Factory進(jìn)行設(shè)置
        inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
    }

    static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
        LayoutInflater.Factory factory = inflater.getFactory();
        if (factory instanceof FactoryWrapper) {
            return ((FactoryWrapper) factory).mDelegateFactory;
        }
        return null;
    }

}
  • 在LayoutInflaterFactory的實(shí)現(xiàn)類之一AppCompatDelegateImplV7中螟碎,找到了setFactory的實(shí)際使用意義實(shí)際意思秫舌。
  • 在LayoutInflater.createViewFromTag方法中調(diào)用Factory.onCreateView(name, context, attrs)方法
  • Factory的實(shí)現(xiàn)類FactoryWrapper中够挂,調(diào)用LayoutInflaterFactory的onCreateView(null, name, context, attrs)方法
class AppCompatViewInflater {
    /***部分代碼省略****/
    public final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        final Context originalContext = context;

        // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
        // by using the parent's context
        if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            // We then apply the theme on the context, if specified
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }

        View view = null;

        // We need to 'inject' our tint aware Views in place of the standard framework versions
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            case "CheckBox":
                view = new AppCompatCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = new AppCompatRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = new AppCompatRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = new AppCompatSeekBar(context, attrs);
                break;
        }

        if (view == null && originalContext != context) {
            // If the original context does not equal our themed context, then we need to manually
            // inflate it using the name so that android:theme takes effect.
            view = createViewFromTag(context, name, attrs);
        }

        if (view != null) {
            // If we have created a view, check it's android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
        }
}
  • AppCompatViewInflater作為LayoutInflaterFactory的的onCreateView方法的最終實(shí)現(xiàn)類,通過createView方法替換了一些我們想要自己替換的View刑赶。比如:
原始View 實(shí)際創(chuàng)建的View
TextView AppCompatTextView
ImageView AppCompatImageView
Button AppCompatButton
…… ……

在appcompat使用自定義的LayoutInflater.Factory

這里我們有兩種書寫方式:

這里必須寫在 super.oncreate 之前克伊,否則還會繼續(xù)報錯酥郭。

  • 繼續(xù)使用 LayoutInflater.from(this).setFactory 方法。
LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {
    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        AppCompatDelegate delegate = getDelegate();
        //調(diào)用AppCompatDelegate的createView方法將第一個參數(shù)設(shè)置為null
        View view = delegate.createView(null, name, context, attrs);
        if ("TextView".equals(name)) {
            Log.e(TAG, "name = " + name);
            int n = attrs.getAttributeCount();
            for (int i = 0; i < n; i++) {
                Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
            }
            for (int i = 0; i < n; i++) {
                if (attrs.getAttributeName(i).equals("id")) {
                    String attributeValue = attrs.getAttributeValue(i);
                    String id = attributeValue.substring(1, attributeValue.length());
                    if (R.id.text == Integer.valueOf(id)) {
                        Button button = new Button(context, attrs);
                        button.setBackgroundColor(Color.RED);
                        button.setAllCaps(false);
                        return button;
                    }

                }
            }
        }
        return view;
    }
});
  • 使用LayoutInflaterCompat.setFactory方法
LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
   @Override
   public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        //appcompat 創(chuàng)建view代碼
        AppCompatDelegate delegate = getDelegate();
        View view = delegate.createView(parent, name, context, attrs);
        if ("TextView".equals(name)) {
            Log.e(TAG, "name = " + name);
            int n = attrs.getAttributeCount();
            for (int i = 0; i < n; i++) {
                Log.e(TAG, attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
            }
            for (int i = 0; i < n; i++) {
                if (attrs.getAttributeName(i).equals("id")) {
                    String attributeValue = attrs.getAttributeValue(i);
                    String id = attributeValue.substring(1, attributeValue.length());
                    if (R.id.text == Integer.valueOf(id)) {
                        Button button = new Button(context, attrs);
                        button.setBackgroundColor(Color.RED);
                        button.setAllCaps(false);
                        return button;
                    }

                }
            }
        }
        return view;
   }
});

兩種寫法的原理是相同的答毫,因?yàn)樯厦嬷v述的LayoutInflater.Factory的實(shí)現(xiàn)類FactoryWrapper實(shí)現(xiàn)onCreateView方法的時候調(diào)用的AppCompatDelegate.onCreateView的時候第一個參數(shù)傳遞的值就是null褥民。

Android應(yīng)用中的換膚(夜間模式)是不是也利用的是LayoutInflater.Factory原理實(shí)現(xiàn)的呢,我們一起期待下一篇關(guān)于Android換膚文章洗搂。

參考文章:
Android 探究 LayoutInflater setFactory

文章到這里就全部講述完啦消返,若有其他需要交流的可以留言哦~!~耘拇!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末撵颊,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惫叛,更是在濱河造成了極大的恐慌倡勇,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘉涌,死亡現(xiàn)場離奇詭異妻熊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)仑最,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門扔役,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人警医,你說我怎么就攤上這事亿胸∨髑眨” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵侈玄,是天一觀的道長婉刀。 經(jīng)常有香客問我,道長序仙,這世上最難降的妖魔是什么突颊? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮潘悼,結(jié)果婚禮上洋丐,老公的妹妹穿的比我還像新娘。我一直安慰自己挥等,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布堤尾。 她就那樣靜靜地躺著肝劲,像睡著了一般。 火紅的嫁衣襯著肌膚如雪郭宝。 梳的紋絲不亂的頭發(fā)上辞槐,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音粘室,去河邊找鬼榄檬。 笑死,一個胖子當(dāng)著我的面吹牛衔统,可吹牛的內(nèi)容都是我干的鹿榜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼锦爵,長吁一口氣:“原來是場噩夢啊……” “哼舱殿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起险掀,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤沪袭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后樟氢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冈绊,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年埠啃,在試婚紗的時候發(fā)現(xiàn)自己被綠了死宣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡霸妹,死狀恐怖十电,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤鹃骂,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布台盯,位于F島的核電站,受9級特大地震影響畏线,放射性物質(zhì)發(fā)生泄漏静盅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一寝殴、第九天 我趴在偏房一處隱蔽的房頂上張望蒿叠。 院中可真熱鬧,春花似錦蚣常、人聲如沸市咽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽施绎。三九已至,卻和暖如春贞绳,著一層夾襖步出監(jiān)牢的瞬間谷醉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工冈闭, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留俱尼,地道東北人。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓萎攒,卻偏偏與公主長得像遇八,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耍休,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容