安卓 - 源碼 - LayoutInflater(一)

下一節 : 安卓 - 源碼分析 - LayoutInflater(二)

先看一下LayoutInflater的說明:
/**
 * LayoutInflater用於解析並根據xml佈局文件生成對應的View對象
 * 該類不應該被直接調用,而是通過相關方法獲取:
 * Activity.getLayoutInflater / Context.getSystemService
 * 通過該方式,才能獲取正確綁定上下文的LayoutInflater對象。
 * 
 * 如果你的View需要用額外的Factory去創建自定義的LayoutInflater,
 * 可以cloneInContext複製一個LayoutInflater,之後,
 * 通過setFactory方法將你的Factory添加到LayoutInflater中。
 * 
 * 由於性能原因,View的填充過程重度依賴於xml文件的預處理,
 * 所有目前LayoutInflater不支持在運行時直接解析未編譯的xml。
 */

類說明爲我們明確了3點:

不要直接創建該類,需要使用指定方法獲取LayoutInflater對象。

可以通過Factory對LayoutInflater的填充過程進行自定義;
可以對LayoutInflater複製並重新設置Factory。

LayoutInflater只能解析編譯過的xml佈局文件。

其中,所有指定的方法,最終都是通過Context.getSystemService實現:

/*
 *內部實現:
 * getWindow().getLayoutInflater()
 * getWindow()得到的是Activity的mWindow對象:
 * mWindow = new PhoneWindow(this, window);
 * 即調用了PhoneWindow.getLayoutInflater(),其實現:
 * mLayoutInflater = LayoutInflater.from(context);
 */
Activity.getLayoutInflater();

/*
 * 內部實現:
 * LayoutInflater factory = LayoutInflater.from(context);
 * return factory.inflate(resource, root);
 */
View.inflate(Context context, @LayoutRes int resource, ViewGroup root);

/*
 * 內部實現:
 * LayoutInflater LayoutInflater = (LayoutInflater) context
 *         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 */
LayoutInflater.from(context);
然後,我們再看一下內部的幾個接口和對象:
private boolean mFactorySet;       //是否調用過setFactory
private Factory mFactory;          //繼承Factory的對象
private Factory2 mFactory2;        //繼承Factory2的對象
private Factory2 mPrivateFactory;  //由系統維護的對象
private Filter mFilter;            //過濾器
/*
 * 過濾器
 * 如果要填充的View不被過濾器允許,將會拋出InflateException  
 */
public interface Filter {
    boolean onLoadClass(Class clazz);
}

/*
 * Factory
 * 3個參數分別爲:
 * name    :需要填充的Tag名稱,即View名稱;
 * context :View所在的上下文環境;
 * attrs   :View在xml定義中定義的屬性,
 * 當只處理個別View時,其他的View可以返回null。
 */
public interface Factory {
    public View onCreateView(String name, Context context, AttributeSet attrs);
} 

/*
 * Factory2
 * 重載onCreateView,增加了ViewParent參數。
 */
public interface Factory2 extends Factory {
    public View onCreateView(View parent, String name,
                             Context context, AttributeSet attrs);
}

關於過濾器Filter:
通過setFilter方法,可以爲LayoutInflater設置自定義的過濾器

/*
 * 爲LayoutInflater添加過濾器,當一個View不被過濾器允許時,
 * 在填充過程的inflate方法裏將會拋出InflateException,
 * 新設置的filter將會覆蓋之前所有設置的filter。
 */
public void setFilter(Filter filter) {
    mFilter = filter;
    if (filter != null) 
        mFilterMap = new HashMap<String, Boolean>();
}

注意新設置Filter的會覆蓋舊的Filter,同時mFilterMap重新初始化。
mFilterMap的作用:記錄Filter對不同View的過濾結果,當View進入過濾時,優先查找mFilterMap,避免Filter重複使用。

Filter的調用過程將會在後面的解析過程中分析。

關於Factory:
Factory可以讓你對需要填充的View進行自定義。
Factory接口中,方法參數有name、context、attrs,Factory2則多了一個增加parent參數的重載。
應該關注的參數是:name和attrs:

name:
需要填充的View的類名,即對應xml中的Tag名。例如TextView。
你可以根據這個類名自定義View的創建,可以創建對應的View,也可以創建另外的View。
如:AppCompatActivity處理TextView,會返回AppCompatTextView而不是TextView。

attrs:
View屬性,可以對這個屬性進行修改,例如添加新屬性,修改原有屬性等。
通過修改attrs,可以輕鬆打造換膚功能。

使用setFactory方法,對LayoutInflater添加自定義Factory:

/*
 * 可以爲LayoutInflater添加一個實現Factory接口的類去改變View創建的過程,
 * 這個Factory對象不能爲null,且只能設置一次,設置後將不能進行變更,
 * 在解析xml佈局文件過程中,解析到View時,Factory會被調用,
 * 如果Factory返回一個View,這個View則會被添加到控件層次中,
 * 如果返回空,將會調用的默認的onCreateView方法創建View。
 */
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默認爲false,這裏將mFactorySet設爲true
    // 再次調用setFactory將拋出上面的異常
    mFactorySet = true;
    
    // 如果原有的LayoutInflater帶有mFactory
    // 需要保留原有mFactory,下面再去介紹
    if (mFactory == null) 
        mFactory = factory;
    else
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}

也可以使用setFactory2,設置一個Factory2對象。
setFactory2和setFactory方法一樣,但同時會將Factory2設置爲mFactory和mFactory2:

public void setFactory2(Factory2 factory) {
    ...
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}

上面涉及到了一個FactoryMerger類

private static class FactoryMerger implements Factory2 {
    private final Factory mF1, mF2;
    private final Factory2 mF12, mF22;
        
    FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
        mF1 = f1; mF2 = f2; mF12 = f12; mF22 = f22;
    }
        
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        View v = mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF2.onCreateView(name, context, attrs);
    }

    public View onCreateView(View parent, String name, 
                             Context context, AttributeSet attrs) {
        View v = mF12 != null ? 
                    mF12.onCreateView(parent, name, context, attrs) :
                    mF1.onCreateView(name, context, attrs);
        if (v != null) return v;
        return mF22 != null ? 
                   mF22.onCreateView(parent, name, context, attrs) : 
                   mF2.onCreateView(name, context, attrs);
    }
}

這個類實現了Factory2接口,即是Factory的實現類,
它的作用,其實就是保留上一個Factory作爲默認View創建方法。
例如:
現在有一個LayoutInflater:

LayoutInflater inflater;
inflater.setFactory((name, context, attrs) -> {
    if(TextUtils.equals(name, TextView.class.getSimpleName()))
        return new EditText(context,attrs);
    return null;
})

這時候,想得到一個對EditView做處理,同時保留原來Factory行爲的新LayoutInflater:

// 複製原有的LayoutInflater
LayoutInflater newInflater = inflater.cloneInContext(context); 
newInflater.setFactory((name, context, attrs) -> {
    if(TextUtils.equals(name, EditText.class.getSimpleName()))
        return new TextView(context,attrs);
    return null;
})

由於newInflater已經存在一個Factory,所以setFactory會爲我們創建一個FactoryMerger對象:

public void setFactory(Factory factory) {
    if (mFactory == null) 
        mFactory = factory;
    else
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}

當填充EditText時,新的Factory會返回一個TextView對象;
而填充TextView時,則會返回null,這時會調用原有的Factory進行解析,即會返回一個EditText對象。

餘下的分析放在
下一節 : 安卓 - 源碼分析 - LayoutInflater(二)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章