1、概述

对于LayoutInflater setFactory,平时我们很少用到这个API,但是这个API我觉得还是有学习的必要的,能够很多意象不到的问题,准备围绕这方面编写一系列的文章。

本篇包含:

  • setFactory 相关API介绍
  • 可能存在的问题
  • 具体的解决方案及一些实际的用途

ps: 最近维护公众号,部分文章仅在公众号发布主要原因是,公众号上推送的文章和博客中对文章的要求可能不太一样,如果达到我对博客的要求,一定会同步的。

2、setFactory API学习

LayoutInflater大家肯定都不陌生,用的最多的就是其inflate()方法了,今天介绍的就是它的另外的两个方法:

  • setFactory
  • setFactory2

这两个方法的功能基本是一致的,setFactory2是在SDK>=11以后引入的,所以我们要根据SDK的版本去选择调用上述方法。

值得高兴的是,v4包下有个类LayoutInflaterCompat帮我们完成了兼容性的操作,提供的方法为:

LayoutInflaterCompat
- setFactory(LayoutInflater inflater, LayoutInflaterFactory factory)

好了,下面我们就来写段代码看看该方法如何使用:

我们新建一个Activity,在其onCreate中调用setFactory

public class MainActivity extends AppCompatActivity
{private static final String TAG = "MainActivity";@Overrideprotected void onCreate(Bundle savedInstanceState){LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory(){@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs){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));}return null;}});super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}

然后,我们运行项目,你会发现打印的log为(部分log):

MainActivity﹕ name = TextView
MainActivity﹕ layout_width , -2
MainActivity﹕ layout_height , -2
MainActivity﹕ text , @2131099670

这里针对布局文件中的一个TextView,可以看到打印了该TextView的name以及所有的attr相关信息。这些信息有什么用,我们后面的文章会介绍。

那么这个方法能干什么呢?

从字面上理解onCreateView是创建View,那么我们修改部分代码,添加如下代码:

if (name.equals("TextView"))
{Button button = new Button(context, attrs);return button;
}

运行你会发现,界面上的TextView变成了Button

是不是很惊奇,但是我们已经能够确定这个方法的确能够根据布局文件中的信息去创建对应的View了。

你可以会问,谁没事干把TextView换成Button哇,就没什么靠谱的作用吗?

还真有,假设你的项目编写了一半,忽然有个需求需要自定义一个TextView(称为:MyTextView)来替换系统的TextView:

那么现在你就不必去打开以前的布局文件把TextView全部进行修改,直接在BaseActivity里面进行下面的操作就可以了:

if (name.equals("TextView"))
{MyTextView view = new com.zhy.MyTextView(context,attrs);return view;
}

对于自定义的View,你也可以通过比对name(随便设置个name都可以,不需要去完整的编写全路径了),然后直接去new出该对象。这么做有一个好处,相比系统去帮你创建,效率会高一点,因为系统有一些逻辑需要走,并且最终是通过反射的方式帮你创建View。

不过,对于setFactory的使用,可能会面临下面的问题。

现在开发App的时候,我们一般Activity都继承于AppCompatActivity,而在AppCompatActivity中,实际上也调用了setFactory方法。

如果你自己还调用了setFactory就可能带来一些问题,因为setFactory并不能重复调用。

3、setFactory已经被v7包占领

打开AppCompatActivity的源码,找到onCreate,你会发现如下代码:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {getDelegate().installViewFactory();//...
}

installViewFactory的具体实现为:

@Override
public void installViewFactory() {LayoutInflater layoutInflater = LayoutInflater.from(mContext);if (layoutInflater.getFactory() == null) {LayoutInflaterCompat.setFactory(layoutInflater, this);} else {Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"+ " so we can not install AppCompat's");}
}

这里你会发现,AppCompatActivity中也尝试去setFactory,如果我们再其之前调用了setFactory,就会打印上面的info信息,并且造成其setFactroy不会生效,其实从Log中也能看出:

AppCompatDelegate﹕ The Activity's LayoutInflater
already has a Factory installed
so we can not install AppCompat's

这么来看,如果我们使用AppCompatActivity,我们是不应该按照我们前面的方式,直接设置setFactory,否则我们可能会带来一些影响。

我们会带来什么影响呢?

其实AppCompatActivity的setFactory也是想根据name去生成一些类,大家还记得,更新v7包的时候,忽然我们的TextView就支持了一些属性,比如tint属性,以前是不支持的,怎么能够做到使用v7包,然后就能支持且向下兼容的呢?

哈哈,原理就在这里,看下面的代码片段

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;//...
}

可以看到系统其实是利用setFactory,瞒着我们把TextView等类早就换成AppCompatTextView等类了。

如果你使用AppCompatActivty你可以通过打印textview.getClass()来验证。

ok,看到这里,我们上面的一个问题也就知道答案了:

我们按照前面的方式直接设置setFactory,会带来什么影响呢?

会造成没有办法使用一些新的特性,比如tint等。

4、解决方案

我们具体看下appcompat中onCreateView的全部代码:

@Override
public final View onCreateView(View parent, String name,Context context, AttributeSet attrs) {// First let the Activity's Factory try and inflate the viewfinal View view = callActivityOnCreateView(parent, name, context, attrs);if (view != null) {return view;}// If the Factory didn't handle it, let our createView() method tryreturn createView(parent, name, context, attrs);
}

可以看到其最终是调用:createView方法完成view的创建,并且值得高兴的是该方法是public的。

也就是说,我们可以自己设置factory中,依然可以保证appcompat中创建View的代码的执行。

LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory()
{@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs){//你可以在这里直接new自定义View//你可以在这里将系统类替换为自定义View//appcompat 创建view代码AppCompatDelegate delegate = getDelegate();View view = delegate.createView(parent, name, context, attrs);return view;}
});

下面看一个我们常见的场景。

5、高效统一设置app中所有字体

很多时候我们为了app更加个性,然后整体采用外部引入的字体。

很多开发者的实现是这样的,在BaseActivity的onCreate中去从跟布局去递归遍历所有的View,类似的代码如下:

public void setTypeface(ViewGroup root, Typeface typeface){if(root==null || typeface==null){return;}int count = root.getChildCount();for(int i=0;i

这种方式虽然方便,但是肯定会带来一定性能问题。

说到这,我估计你心理已经有全新的解决方案了,那就是利用setFactory,相关代码如下(在BaseActivity中):

public static Typeface typeface;
@Override
protected void onCreate(Bundle savedInstanceState)
{if (typeface == null){typeface = Typeface.createFromAsset(getAssets(), "hwxk.ttf");}LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory(){@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs){AppCompatDelegate delegate = getDelegate();View view = delegate.createView(parent, name, context, attrs);if ( view!= null && (view instanceof TextView)){((TextView) view).setTypeface(typeface);}return view;}});super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
}

仅仅几行代码就能完成字体的全局统一设置了,而且非常高效。

我在布局文件中放了3个控件,看下效果图:

我们这里是恰好appcompat中对于上述常见的三个控件都进行了AppCompat化。

假设还有继承自TextView的自定义View如何处理呢?

很简单,调用LayoutInflater的createView方法就可以了,或者数量并不多,可以自己手动new也没有问题,参考代码如下:

 LayoutInflaterCompat.setFactory(inflater, new LayoutInflaterFactory()
{@Overridepublic View onCreateView(View parent, String name, Context context, AttributeSet attrs){View view ;//if(name.equals("自定义View") view = new ...//或者if (1 == name.indexOf('.')//表明是自定义View{inflater.createView(name,null,attrs);}AppCompatDelegate delegate = getDelegate();if(view == null)view = delegate.createView(parent, name, context, attrs);if ( view!= null && (view instanceof TextView)){((TextView) view).setTypeface(typeface);}return view;}
}

ok,具体的细节自己在琢磨琢磨。

6、扩展

setFactory还非常适合一个场景,就是换肤,换肤需要解决的核心问题有两个:

  • 外部资源的加载
  • 定位到需要换肤的View

第一个资源加载的问题可以通过构造AssetManager,反射调用其addAssetPath就可以完成。

第二个问题,就可以利用在onCreateView中,根据view的属性来定位,例如你可以让需要换肤的view添加一个自定义的属性skin_enabled=true(最开始有打印属性),并且利用一些手段拿到构造到的view,就能在View构造阶段定位的需要换肤的View。

关于这种方式换肤,会在后面的文章进行介绍。

那么本文就是setFactory相关系列的第一篇了。

通过本文的阅读,我相信你或多或少收获了一些东西,比如appcompat如何对一些View增加特性且向下兼容;我们如何提升自定义View的构建速度(自行new);如果使用自定义View替换系统中的View;以及如何高效的引入外部字体的完成app中统一所有字体的。

Android 探究 LayoutInflater setFactory相关推荐

  1. Android技能树 — LayoutInflater Factory小结

    前言 今天早上地铁上在洋神的公众号上看到了一篇干货,就给转过来了. 前段时间流行起来了突然不愿意写Shape,Selector文件的文章,然后各种方案,编写自定义View等.那时候大家应该都看到了一篇 ...

  2. Android 中LayoutInflater(布局加载器)之介绍篇

    本文出自博客Vander丶CSDN博客,如需转载请标明出处,尊重原创谢谢 博客地址:http://blog.csdn.net/l540675759/article/details/78099358 前 ...

  3. android layoutinflater 高度,探究LayoutInflater和RecyclerView中item设置宽高无效

    1. LayoutInflater是做什么的 Instantiates a layout XML file into its corresponding {@link android.view.Vie ...

  4. Android中LayoutInflater的使用

    Inflater英文意思是膨胀,在Android中应该是扩展的意思吧. LayoutInflater 的作用类似于 findViewById(),不同点是LayoutInflater是用来找layou ...

  5. android 中LayoutInflater 的使用

    Inflater 英文意思是膨胀,在Android 中应该是扩展的意思吧. LayoutInflater的作用类似于findViewById(),不同点是LayoutInflater是用来找layou ...

  6. android service layoutinflater,[转]Android LayoutInflater详解

    在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById().不同点是LayoutInflater是用来找res/layout/下的xml布局文件,并且实例 ...

  7. Android中LayoutInflater()方法

    在实际开发中LayoutInflater这个类还是非常有用的,它的作用类似于findViewById().不同点是LayoutInflater是用来找res/layout/下的xml布局文件,并且实例 ...

  8. ANDROID 探究oom内幕

    从早期G1的192MB RAM开始,到现在动辄1G -2G RAM的设备,为单个App分配的内存从16MB到48MB甚至更多,但OOM从不曾离我们远去.这是因为大部分App中图片内容占据了50%甚至7 ...

  9. android 探究marginLeft、marginStart、marginRight、marginEnd

    目录 一.RTL介绍 二.举例 一.RTL介绍 从Android 4.2开始,Android SDK支持一种从右到左(RTL,Right-to-Left)UI布局的方式,不过在中国习惯的于都方式是从左 ...

最新文章

  1. Swift:用UICollectionView整一个瀑布流
  2. codevs1079 回家
  3. QueryPath, php上的jQuery
  4. 从源码分析RocketMQ系列-Consumer消息接收逻辑
  5. 太原理工大学荣获2020(第二届)集成电路EDA设计精英挑战赛一等奖
  6. matlab2c使用c++实现matlab函数系列教程-hankel函数
  7. erlang的timer定时器浅析
  8. WBE漏洞-SQL注入之报错盲注
  9. 小白系统初始化配置资源失败怎么办
  10. 易飞ERP工作流解决方案之【第三方OA系统集成】
  11. 如何查看自己的外网 IP 地址
  12. java-assured,如何使用Rest-Assured java中的证书进行HTTPS GET调用
  13. 架构搜索文献笔记(9):《CurveLane-NAS: Unifying Lane-Sensitive Architecture Search and Adaptive Point》
  14. 【转】胡侃学习(理论)计算机
  15. 《SQL必知必会》学习笔记——第十二课 连结表
  16. Mac电脑如何给IDEA配置IDEA 公司发行的适合程序员编程字体
  17. 苹果商标计算机辅助设计,苹果 Apple MacBook Pro 15英寸 2019 详细评测报告
  18. Dynamo 如何生成管道
  19. 请更换备份电池 pos机_免费邮寄低费率POS机安全吗?为什么那么多免费POS机?请看视频...
  20. 腾讯企业邮箱发件接口返回Recipient address rejected: ERS-RBL.

热门文章

  1. 安装linux办公软件,Centos7如何安装开源办公软件Libreoffice
  2. W7程序计算机面板介绍,win7系统隐藏任意程序运行界面的详细步骤
  3. php转移动,php移动或拷贝文件夹
  4. java 值对象_java 中设计模式(值对象)的实例详解
  5. java return用法_Java枚举的高级用法之多键值的映射使用
  6. MYSQL 密码相关记录
  7. 设计聊天机器人技术栈的终极攻略
  8. Javascript 控制 Flash FLV视频播放器 --国外开源
  9. swift-初探webView与JS交互
  10. 微信内置浏览器的JsAPI(WeixinJSBridge续)[转载]