安卓 - 源码 - 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(二)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容