文章目录

  • 一、LayoutInflater类
    • LayoutInflater被用在哪里
      • 1. LayoutInflater用于代码动态创建View
      • 2. LayoutInflater用于Activity界面初始化View
  • 二、Factory接口

我们每一个安卓开发者都知道xml布局可以显示界面,那么作为有追求的开发者我们要知其然更要知其所以然。搞清楚xml布局在源码层面最终是通过什么方式转换成什么?了解背后原理后又能带来什么样的收益?带着种种疑问我们来深究源码去揭开其魅力的面纱。

一、LayoutInflater类

LayoutInflater被用在哪里

有如下两个地方:

  1. LayoutInflater用于代码动态创建View
  2. LayoutInflater用于Activity界面初始化View

下面就让我们来一一介绍

1. LayoutInflater用于代码动态创建View

以下是一般从xml布局文件创建View对象的方法

方法一:View view = LayoutInflater.from(context()).inflate(layoutResId, viewGroup, false);方法二:View view = View.inflate(viewGroup.getContext(), layoutResId, null);方法三:View view = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(viewGroup.getContext(), layoutResId, null);

这三个方法内部都是context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)拿到LayoutInflater服务实现填充xml布局。
最终的调用是LayoutInflater类内部的inflate方法,如下:

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {final Resources res = getContext().getResources();......//预编译:如果支持,则从布局文件xml预编译生成的dex文件,通过反射来获取对应的View,来减少xml布局用解析器解析的时间//初始化时设置不支持View view = tryInflatePrecompiled(resource, res, root, attachToRoot);if (view != null) {return view;}//如果没有预编译机制看,则获取XML的资源解析器XmlResourceParser parser = res.getLayout(resource);try {return inflate(parser, root, attachToRoot);} finally {parser.close();}}

tryInflatePrecompiled()由于目前在release版本不支持,仅支持CTS tests,所以会通过Resources.getLayout方法去获取xml解析器XmlResourceParser,具体怎么获取我们在这里不做展开讨论,接下来将XmlResourceParser作为参数调用inflate()的重载方法

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {synchronized (mConstructorArgs) {......try {advanceToRootNode(parser); //通过while循环找到布局根节点,类似<LinearLayout>final String name = parser.getName();if (TAG_MERGE.equals(name)) { //根节点是merge,则遍历布局生成Viewif (root == null || !attachToRoot) {throw new InflateException("<merge /> can be used only with a valid "+ "ViewGroup root and attachToRoot=true");}// 内部循环调用createViewFromTag()创建ViewrInflate(parser, root, inflaterContext, attrs, false); } else { //根节点非merge,则通过其他方法创建View对象// Temp is the root view that was found in the xml  1、创建根节点tempfinal View temp = createViewFromTag(root, name, inflaterContext, attrs); //本篇后文分析方法......// Inflate all children under temp against its context.  2、遍历布局生成View// 内部调用rInflate()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) { //将根节点temp添加到rootroot.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;}}

逻辑如下:
1、找到布局的根节点XmlPullParser.START_TAG,例如<LinearLayout>
2、布局根节点是merge的话,则通过递归遍历子布局生成View对象;
3、非merge开头的,则通过createViewFromTag()(后文介绍该方法)创建根节点temp,然后再通过遍历子布局生成View对象;
4、将生成的temp对象添加到root下面。

经过上面一些列的操作后,xml属性的布局文件就转为了View对象,里面存储了所有布局标签的节点。

2. LayoutInflater用于Activity界面初始化View

当我们在Activity中onCreate初始化的时候,会调用setContentView,追踪源码我们会发现调用PhoneWindow里面的setContentView(int layoutResID)方法

    @Overridepublic void setContentView(int layoutResID) {// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window// decor, when theme attributes and the like are crystalized. Do not check the feature// before this happens.if (mContentParent == null) {   //当mContentParent==null,则当前内容没有在窗口出现过,即第一次调用installDecor(); //  创建并添加DecorView} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {  //else表示内容添加过,并且不需要动画,移除removeAllViews()mContentParent.removeAllViews();}if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { //有过度动画标志FEATURE_CONTENT_TRANSITIONSfinal Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());transitionTo(newScene); //添加Scene处理过度动画来启动界面,执行完动画后执行inflate()} else {mLayoutInflater.inflate(layoutResID, mContentParent);   //无过度动画,资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中}mContentParent.requestApplyInsets();final Callback cb = getCallback();if (cb != null && !isDestroyed()) {cb.onContentChanged();}}

hasFeature(FEATURE_CONTENT_TRANSITIONS)方法,这个是判断内容加载是否要使用过度动画。如果内容已经加载过并且不需要动画则removeAllViews();添加完Content后如有过度动画则添加Scene来过度启动,否则调用LayoutInflater.inflate去转换view。
  xml转换成对象View的整个流程图如下:

二、Factory接口

LayoutInflater类中有三个关于Factory的全局变量

 @UnsupportedAppUsageprivate Factory mFactory;@UnsupportedAppUsageprivate Factory2 mFactory2;@UnsupportedAppUsageprivate Factory2 mPrivateFactory;

Factory Factory2是在LayoutInflater.java中定义的两个接口

public interface Factory {@NullableView onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs);
}public interface Factory2 extends Factory {// parent – The parent that the created view will be placed in; note that this may be null.@NullableView onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs);
}

那这个Factory是用来干什么的呢?全文搜索发现在方法tryCreateView中调用

 public final View tryCreateView(@Nullable View parent, @NonNull String name,@NonNull Context context,@NonNull AttributeSet attrs) {......View view;if (mFactory2 != null) {// 调用接口方法onCreateView()创建Viewview = 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);}return view;}

可见调用了Factory接口方法onCreateView返回了View对象,这个tryCreateView方法是由createViewFromTag()方法调用的

 /*** Creates a view from a tag name using the supplied attribute set.* ......*/@UnsupportedAppUsageView createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {......try {View view = tryCreateView(parent, name, context, attrs);// 本文意在研究自定义Factory创建View,以下方法不做详细解析,有兴趣可自行研究if (view == null) {final Object lastContext = mConstructorArgs[0];mConstructorArgs[0] = context;try {if (-1 == name.indexOf('.')) { // 原生View// 最终执行createView()view = onCreateView(context, parent, name, attrs);} else { //自定义Viewview = createView(context, name, null, attrs);}} finally {mConstructorArgs[0] = lastContext;}}return view;} ......}

而createViewFromTag是由inflate方法调用的,那么整个调用的回路为:

可见创建View最终是由Factory接口的实现类来完成的,那么我们就可以通过自定义LayoutInflater.Factory类来控制实现View对象,可以生成不同属性的View对象。那么实现方案可如下蓝色部分:

在setContentView之前将我们自定义的Factory实现类mFactory传递给LayoutInflater,当界面布局通过LayoutInflater去加载View的时候会直接使用我们前面传递过去的mFactory对象来回调,来达到加载我们自定义View的目的。

LayoutInflater Factory创建自定义View相关推荐

  1. Android官方开发文档Training系列课程中文版:创建自定义View之View的创建

    原文地址:http://android.xsoftlab.net/training/custom-views/index.html 引言 Android框架含有大量的View类,这些类用来显示各式各样 ...

  2. Android官方开发文档Training系列课程中文版:创建自定义View之View的优化

    原文地址:http://android.xsoftlab.net/training/custom-views/optimizing-view.html 现在已经完成了一个拥有良好设计的View,它即可 ...

  3. Android官方开发文档Training系列课程中文版:创建自定义View之View的交互

    写在前面的话:这一章很有价值,想要提升安卓知识的一定要读一读.不做安卓的也可以得到其它方面的提升. 原文地址:http://android.xsoftlab.net/training/custom-v ...

  4. Android官方开发文档Training系列课程中文版:创建自定义View之View的绘制

    原文地址:http://android.xsoftlab.net/training/custom-views/custom-drawing.html#draw 自定义View最重要的部分就是它的样子了 ...

  5. 用 layoutInflater打气筒创建一个view对象

    基于测试代码中,前面文章有: 1.SQL语句实现数据库的增删改查:http://blog.csdn.net/zhangli_/article/details/50117367 2.利用API来控制SQ ...

  6. Android开发自定义View

    Android中View组件的作用类似于Swing变成中的JPanel,它只是一个空白的矩形区域,View组件中没有任何内容.对于Android应用的其他UI组件来说,它们都继承了View组件,然后在 ...

  7. android自定义view生命周期,android基础之自定义view

    一.Custom View 1.view的继承关系 view继承关系.png 2.Android 如何绘制试图层次 当activity获取焦点时,它必须提供layout层次的根节点,然后android ...

  8. 自定义View,和Canvas(画布),Paint(画笔),Path(路径)的用法

    重点 怎么自定义控件,Canvas画布,Paint画笔,Path路径的使用 首先为什么要自定义View: 在Android开发中有很多业务场景,原生的控件是无法满足应用,并且经常也会遇到一个UI在多处 ...

  9. 仿 iPhone Assistivetouch 自定义view

    基于公司项目要求,要彷照iPhone Assistivetouch 做一个浮动在窗口的按钮,且按钮有点击响应. 网上搜罗一遍,照葫芦画瓢的做了一个简单的demo.下面介绍一下我的思路: 1.要实现的功 ...

最新文章

  1. 【第41题】【062题库】2019年OCP认证062考试新题
  2. Oracle数据库几个错误
  3. The greatest happiness 2019-11-13
  4. WordPress PHP Fatal Error “Maximum execution time of 30 seconds exceeded” 的解决办法
  5. sql查询非ascii字符_SQL替换:如何在SQL Server中替换ASCII特殊字符
  6. 抢购 mysql 优化_处理抢购、秒杀应用场景降低“超卖”发生几个优化方案(php)...
  7. 947. 移除最多的同行或同列石头2021-01-23
  8. matlab 矩阵矢量化编程
  9. IS-IS详解(十四)——IS-IS路由开销计算与外部路由引入
  10. 挖矿从入门到入狱,百度运维获利 10 万被判 3 年。网友:人生已毁
  11. 高效工作的浏览器插件
  12. 截图工具Snipaste
  13. office2013安装程序找不到_office2007提示“错误1706,安装程序找不到所需文件
  14. 如何在万网注册域名及域名解析?
  15. linpack测试工具使用说明
  16. JAVA名字正则表达式(包含新疆姓名)
  17. Win10 打开图片,提示文件系统错误(-2147219196)
  18. Java 计蒜客——开关灯
  19. 文字转语音软件哪个好,这一款值得推荐
  20. 大数据时代——你是否希望“被遗忘”

热门文章

  1. MySql前瞻,mysql是什么
  2. 戴骨传导耳机会头晕?骨传导耳机真的好吗?
  3. oracle查询姓李,oracle 基本知识
  4. 基于懒人模式的金融社交
  5. 有关于使用arcgis中的dem镶嵌出现的问题
  6. Java NIO及其常用API
  7. Flink Broadcast State实用指南
  8. 京东数据分析:2022休闲食品行业稳健发展,市场集中度高
  9. 我们来了解下ModelAndViewContainer组件---------------【springmvc源码】
  10. execve函数族详解