LayoutInflater

初始化

        常用的方法如下:
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
        通过getSystemService()可知,LayoutInflater的子类是PhoneLayoutInflater。

PhoneLayoutInflater

主体如下:
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    //构造方法,略
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                //createView的主要作用就是将prefix+name形成一个完整的类名,然后通过反射生成具体的实例。后面再说。
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                
            }
        }

        return super.onCreateView(name, attrs);
    }
        从这个类中定义的sClassPrefixList中可以看出,使用这几个包下的View是不用写包名的。这也是为什么我们在xml中可以直接使用TextView,而自定义时却需要写完整包名的原因——因为TextView的包名由系统自动添加上了,自己的却不行。

LayoutInflater#inflate()

        任何一个inflate最终会调用到下面的inflate。注:采用的是pull解析xml布局。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
			//略
            final Context inflaterContext = mContext;//mContext为构造方法中传入的Context对象
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            //记录context,略
            View result = root;//记录根节点的父节点,下简称父节点。
            try {
                // 查找根节点,并使用name记录根节点的名
                int type;
    			//查找过程略,pull解析中的部分
                final String name = parser.getName();
                //如果根节点是merge,就调用rInflate()。
                if (TAG_MERGE.equals(name)) {
                    //调用之前有一个判断,用来保证merge必须有一个root且attachToRoot为true
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // 调用createViewFromTag()建立根节点,并用temp记录根节点。这里的inflaterContext就是构造方法中传入的Context对象
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    //为根结点生成LayoutParams。略。
                    // 填充根节点下的子View
                    rInflateChildren(parser, temp, attrs, true);
                    // 有父节点,并且要添加到父节点中时,把根节点添加到父节点
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    // 根节点找不到父节点或者不需要添加到父节点中时,返回布局的根节点
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            }catch(){
            	//一堆的异常处理,略
            }
            //root为null,或者attachToRoot为false,则返回的是布局生成的根节点。
            return result;
        }
    }
        主体的逻辑见注释。
        整个过程涉及到三个方法:createViewFromTag()——根据tag生成对应的view;rInflateChildren()——用来填充子布局;rInflate()——生成merge布局。

createViewFromTag()

        这里的tag就是xml布局中指定类,也就是传入其中的name。其内部有用工厂模式,生成相应的View实例。如下:
    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) {//context为LayoutInflater构造方法中传入的Context对象。
        //略
        try {
        	//采用工厂直接生成。基本上都可忽略,但可以通过setFactory设置解析工厂,定制自己的解析
            View view;
            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);
            }

            if (view == null) {//各个工厂都生成不了,自己调用相应的方法进行生成
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;//mConstructorArgs是最终传入到View两参数构造方法中的第一个Coontext变量
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } //异常处理,略
    }
        上面的一些工厂类,默认时都是为null,所以工厂解析这段可以直接略去。之所以添加这个判断,是为了扩展。在使用时可以通过LayoutInflater#setFactory()设置不同的工厂类,定制自己的解析类
        所以整个生成view的过程都集中在了onCreateView与createView中。从PhoneLayoutInflater#onCreateView()中可以发现,onCreateView最终调用的还是createView——包括LayoutInflater#onCreateView()也是一样。
并且传入到createView中的第二个参数为当前类的包名+".",这个参数可以和tag组合成一个完整的类名,然后可以通过反射的方法生成对应的类——这也是onCreateView的作用(凑成完整的类名,方便使用反射)。

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 {
            if (constructor == null) {
                // 该类从来没有被加载过,需要用classloader去加载类的字节码文件,不然下面的反射都没办法做
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);
                //判断是否不允许加载inflater某个类
                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);//这里直接扔一个异常。因为布局中使用了不允许使用的类
                    }
                }
                //mConstructorSignature的值为:new Class[] {Context.class, AttributeSet.class};
                //这也是为什么xml中的控件执行的构造函数都是两个参数的
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                sConstructorMap.put(name, constructor);//与第一行代码合用,避免了对类的重复加载,提升性能
            } else {
                //对加载的类通过mFilter进行过滤,一般不执行,略。
            }

            Object[] args = mConstructorArgs;//第一个参数在createViewFromTag中已经指定了,为创建LayoutInflater时传入的Context对象
            args[1] = attrs;

            final View view = constructor.newInstance(args);//反射生成相应的View实例
            if (view instanceof ViewStub) {
                //ViewStub单独处理
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            return view;
        } //catch块略
    }
        主体逻辑见注释。
        这个方法执行完毕之后,就可以根据传入的name与prefix生成一个对应的对象。

rInflateChildren()

    final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
        一样调用的是rInflate()。只不过最后一个参数为true。

rInflate()

        有一点要注意:解析merge时,最后一个参数为false;解析根节点下的view是最后一个参数为true。
    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) {
            //获取布局中的tag,并使用name存储。
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            //处理几个特殊标签。
            if (TAG_REQUEST_FOCUS.equals(name)) {
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {//如果是正常的View,调用createViewFromTag生成View实例
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);//递归,生成所有的View实例,并添加到ViewGroup中
                viewGroup.addView(view, params);//并将生成的实例添加到parent中。
            }
        }

        if (finishInflate) {//整个布局加载完毕,调用根布局的onFinishInflate()方法。
            parent.onFinishInflate();
        }
    }
        逻辑见注释。
        而且可以得出:对于一个ViewGroup,其子View都生成完毕之后会调用它的onFinishInflate(),但要注意的是此时子View并没有width与height。

总结

        上面就是LayoutInflater的整体过程。从中可以发现:
        1,可以通过setFactory()设置自己的解析工厂类。
        2,LayoutInflater是单例的。
        3,整体执行流程为:inflate()->createViewFromTag()->分两种情况:1)没有factory。如果是系统View,则走onCreateView,获取View的全类名前缀后再走createView。如果是自定义View,则直接走createView。因此,在createView中,一定是可以拼出View全名。2)有factory,则执行factory#onCreateView()——此时传入的name并不完整。因此,在自定义解析类时,需要在factory#onCreateView()中调用createView()生成相应的View实例,必要时需要仿制PhoneLayoutInflater#onCreateView。
        得到view实例后,可以根据view的配置参数进行相应的设置。比如可以调用View#setTag()为view设置一个在布局中指定的tag值。
        4,LayoutInflater的主要作用就是根据xml文件生成文件中指定的各个View的实例,在此过程中会调用含有两个参数的构造函数,但不会对View执行任何measure、layout与draw操作

自定义

class My extends LayoutInflater {
    private LayoutInflater inflater;
    protected My(LayoutInflater original, Context newContext) {
        super(original, newContext);
        inflater = original;
        Log.e("TAG","original fa = "+original.getFactory()+","+inflater.getFactory2());
        setFactory2(new MyF(original));//这里设置的f2,不能直接使用setFactory()。如果使用了在AppCompatActivity中是没有效果的。
    }

    @Override
    public LayoutInflater cloneInContext(Context newContext) {
        LayoutInflater result = new My(inflater,newContext);
        result.setFactory2(new MyF(inflater));
        return result;
    }

    private class MyF implements Factory2 {
        private LayoutInflater inflater;
        private final String[] sClassPrefixList = {
                "android.widget.",
                "android.webkit.",
                "android.app."
        };

        private MyF(LayoutInflater inflater) {
            this.inflater = inflater;
        }

        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            View view = null;
            try {
                if (-1 == name.indexOf('.')) {//这里的name就是传入onCreateView中的name
                    view = onCreateView(name, attrs);
                } else {
                    view = createView(name, null, attrs);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            if(view instanceof TextView){
                ((TextView)view).setText("My = "+ My.this);
            }
            return view;
        }

        private 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) {
                    //empty
                }
            }

            return null;
        }

        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            return onCreateView(name,context,attrs);
        }
    }
}
        其中唯一一点要注意的地方就是调用的是setFactory2(),而不能直接使用setFactory。如果Activity直接继承于AppCompatActivity的话,它会为inflater添加上factory2对象,这样自己设置的factory是没有效果的。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值