在开发迭代中,有这么一个场景:

我们给 TextView 定制了不少功能,在下一个版本,需要把程序中的所有 TextView 都替换成我自己的 CustomTextView,这个时候你会怎么做?有没有一种方法在不改动布局文件的情况下就能实现动态替换呢?

原理:layout.xml -> Java 对象

首先我们知道一个 layout.xml 转成 Java 对象,使用的是 LayoutInflater#inflate 方法。

其内部是通过 xml 解析器,解析到标签比如: Linearlayout

  1. inflate() 会调用 createViewFromTag() 实例化 Linearlayout 对象;
  2. createViewFromTag() 方法内部使用了一个 Factory 对象;
  3. Factory 会调用 Factory#onCreateView() 来实例化这个 Linearlayout

伪代码如下:

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
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
...

final String name = parser.getName();

...

final View temp = createViewFromTag(root, name, inflaterContext, attrs);

}

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
...

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

...

view = createView(name, null, attrs);
}

所以我只要给 LayoutInflater 设置一个我们自定义的 Factory 就可以实现动态替换 View。

1
2
3
4
5
public void setFactory(Factory factory) {
}

public void setFactory2(Factory2 factory) {
}

替换方法

其实在 Activity 本身就是一个 LayoutInflater$Factory

1
2
3
4
5
6
7
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback {

}

所以我们只需要实现 Activity 的 onCreateView 就可以实现 View 的动态替换。

1
2
3
4
5
6
7
8
9
public class MyActivity extends Activity {

public View onCreateView(String name, Context context, AttributeSet attrs) {
if (name.equals("TextView")) {
return new CustomTextView(context, attrs);
}
return suqer.onCreateView(name, context, attrs);
}
}