LayoutInflater.Factory&Factory2
LayoutInflater.Factory&Factory2
LayoutInflater.Factory&Factory2
LayoutInflater 中的 Factory/Factory2
在 LayoutInflater 创建 View 时,会调用 tryCreateView() 方法,会通过我们设置的工厂来创建对象。如果我们设置了 LayoutInflater.Factory2
会优先通过 Factory2,否则通过 LayoutInflater.Factory
;如果都没有设置,就会通过反射来创建 View 对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
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);
}
return view;
}
注意点
- LayoutInflater#tryCreateView 中也可以看出,mFactory2 优先于 mFactory。上面源码可以看出来
- setFactory 和 setFactory2 默认只能设置其中一个,设置多个会抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 = true;
if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
public void setFactory2(Factory2 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 = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
- setFactory 要在 onCreate 之前调用
- AppCompatActivity 默认设置一个 Factory2,如果我们设置 Factory2,会提示
1
The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's
AppCompatActivity 的 Factory2
而我们的 App 一般都是继承自 AppCompatActivity,而 AppCompatActivity 给我们默认设置了 Factory2,这个实现就是 AppCompatDelegateImpl。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// AppCompatActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
// AppCompatDelegateImpl实现了LayoutInflater.Factory2接口
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this); // 设置setFactory2为AppCompatDelegateImpl实例
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
现在看看 AppCompatDelegateImpl 实现接口 Factory2 的 onCreateView 方法
1
2
3
4
5
6
7
8
9
// AppCompatDelegateImpl
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}
最后都调用了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass); // 从主题设置中找viewInflaterClass的配置
if (viewInflaterClassName == null) {
// Set to null (the default in all AppCompat themes). Create the base inflater
// (no reflection)
mAppCompatViewInflater = new AppCompatViewInflater(); // 如果主题中没有设置,那么创建默认值
} else {
try {
Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance(); // 反射创建AppCompatViewInflater对象
} catch (Throwable t) {
Log.i(TAG, "Failed to instantiate custom view inflater "
+ viewInflaterClassName + ". Falling back to default.", t);
mAppCompatViewInflater = new AppCompatViewInflater();
}
}
}
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = (attrs instanceof XmlPullParser)
// If we have a XmlPullParser, we can detect where we are in the layout
? ((XmlPullParser) attrs).getDepth() > 1
// Otherwise we have to use the old heuristic
: shouldInheritContext((ViewParent) parent);
}
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
AppCompatActivity
的 Factory2 总结
- 首先
AppCompatActivity
将自己的生命周期委托给AppCompatDelegate
AppCompatDelegate
的实现类AppCompatDelegateImpl
实现LayoutInfaler.Factory2
接口- 给当前 context 的
LayoutInfalter
设定 Factory2 工厂 LayoutInfalter.infalte
方法调用Factory2.onCreateView
Factory2.onCreateView
间接调用AppCompatViewInflater.createView
AppCompatViewInflater
对象来自AppCompatTheme
的 viewInflaterClass 属性AppCompatViewInflater.createView
方法会通过 name 对控件进行创建
LayoutInflater.Factory/Factory2 应用
- 使用自定义 View 替换系统中的 View
- 高效的引入外部字体的完成 app 中统一所有字体的
- 动态换肤 https://blog.csdn.net/lmj623565791/article/details/51503977
- 无需编写 shape、selector,直接在 xml 设置值 无需自定义View,彻底解放shape,selector吧
- View 拦截操作
- View inflate 耗时统计
view inflate 耗时(只能统计 view 创建的时间,不能统计解析 xml 的时间)
- 继承自
AppCompatActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
//Hook 每一个控件加载耗时
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
//①
long startTime = System.currentTimeMillis();
//②
View view = getDelegate().createView(parent, name, context, attrs);
//③
long cost = System.currentTimeMillis() - startTime;
Log.d(TAG, "加载控件:" + name + "耗时:" + cost);
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
- 继承自 FragmentActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//MainActivity extends FragmentActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
LayoutInflaterCompat.setFactory(getLayoutInflater(), new LayoutInflaterFactory() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
long startTime = System.currentTimeMillis();
View view = null;
try {
view = getLayoutInflater().createView(name, null, attrs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
long cost = System.currentTimeMillis() - startTime;
map.put(parent,new Hodler(parent,view,name,cost));
Log.d("=========", "加载布局:" + name + "耗时:" + cost);
return view;
}
});
super.onCreate(savedInstanceState);
}
相关库
https://github.com/chrisjenx/Calligraphy
https://github.com/JcMinarro/Philology
ViewPump
- LayoutInflater Factory 小结
https://juejin.im/post/5bcd6f1551882577e71c8c88
AppCompatViewInflater
可用于减少反射创建 View
默认 AppCompatViewInflater
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// AppCompatDelegateImpl androidx.appcompat v1.6.1
class AppCompatDelegateImpl extends AppCompatDelegate
implements MenuBuilder.Callback, LayoutInflater.Factory2 {
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if (viewInflaterClassName == null) {
// Set to null (the default in all AppCompat themes). Create the base inflater
// (no reflection) mAppCompatViewInflater = new AppCompatViewInflater();
} else {
try {
Class<?> viewInflaterClass =
mContext.getClassLoader().loadClass(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();
} catch (Throwable t) {
Log.i(TAG, "Failed to instantiate custom view inflater "
+ viewInflaterClassName + ". Falling back to default.", t);
mAppCompatViewInflater = new AppCompatViewInflater();
}
}
}
}
}
AppCompatDelegateImpl
继承自AppCompatDelegate
并实现了 LayoutInflater.Factory2 接口- onCreateView 方法首先会获取
R.styleable.AppCompatTheme
并从中获取R.styleable.AppCompatTheme_viewInflaterClass
,并通过获取到的 viewInflaterClass 来进行对mAppCompatViewInflater
的赋值操作,如果 viewInflaterClass 不为空或者等于AppCompatViewInflater
的类名那么直接进行对AppCompatViewInflater
的创建否则通过反射进行创建,发生异常的情况下也会直接进行对AppCompatViewInflater
的创建。
AppCompatViewInflater 创建 view
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class AppCompatViewInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.view.",
"android.webkit."
};
@Nullable
public final View createView(@Nullable View parent, @NonNull final String name,
@NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
case "ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect. view = createViewFromTag(context, name, attrs);
}
}
}
在 AppCompatViewInflater.createView
方法中对 atgName
为 :
- TextView
- ImageView
- Button
- EditText
- Spinner
- ImageButton
- CheckBox
- RadioButton
- CheckedTextView
- AutoCompleteTextView
- MultiAutoCompleteTextView
- RatingBar
- SeekBar
- ToggleButton 这 14 种控件进行创建替换,并且还提供了一个
createView(Context context, String name, AttributeSet attrs)
方法支持开发者自定义扩展。
自定义 AppCompatViewInflater
- 自定义主题,复写 viewInflaterClass 属性
1
2
3
<style name="FactoryTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="viewInflaterClass">com.xxx.CustomViewInflater</item>
</style>
- 继承类
AppCompatViewInflater
,实现自定义逻辑
1
2
3
4
class CustomViewInflater : AppCompatViewInflater() {
override fun createTextView(context: Context, attrs: AttributeSet) =
RedTextView(context, attrs)
}
- 作用 将自定义的 View 的默认反射创建,改成 new
注意
- 只对上面 14 种 View,不带路径的
- 如果带了全路径的,不会转换,如
android.widget.TextView
本文由作者按照 CC BY 4.0 进行授权