关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。
未经允许不得转载
目录
一、导读
我们继续总结学习基础知识,温故知新。
我们在学习布局加载原理的时候,用到了这个类,他的作用还是很多的,这里我们花点时间集中来看一看。
再深入学习一番。
二、概览
LayoutInflater 的作用是将布局XML文件实例化为其相应的View对象,是一个视图填充器。
除了基本的布局解析功能,LayoutInflater 还可以用于实现 动态换肤、 视图转换、 属性转换等多种功能。
这是一个抽象类,下面我们进入学习模式。
public abstract class LayoutInflater
三、使用
3.1 LayoutInflater 实例获取
//1 如果是在 Activity 或 Fragment 可直接获取到实例
LayoutInflater inflater = getLayoutInflater();//调用Activity的getLayoutInflater()
//2 通过 LayoutInflater 的静态方法 from 获取
LayoutInflater inflater = LayoutInflater.from(context);
//3 通过系统服务 getSystemService 方法获取
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
其实最后都是调用context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);来获取,我们一起看看源码
ContextImpl.java
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
SystemServiceRegistry.java
/**
* Gets a system service from a given context.
* @hide
*/
public static Object getSystemService(ContextImpl ctx, String name) {
// 这是一个ArrayMap,在启动时就填充好了数据,我们根据名字来查找对应的service
final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
final Object ret = fetcher.getService(ctx);
return ret;
}
在看看初始化
private static final Map<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new ArrayMap<String, ServiceFetcher<?>>();
static {
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
private static <T> void registerService(@NonNull String serviceName,
@NonNull Class<T> serviceClass, @NonNull ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
SYSTEM_SERVICE_CLASS_NAMES.put(serviceName, serviceClass.getSimpleName());
}
再来看看 CachedServiceFetcher
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
@Override
public final T getService(ContextImpl ctx) {
T ret = null;
for (;;) {
boolean doInitialize = false;
synchronized (cache) {
// 从缓存里面取
T service = (T) cache[mCacheIndex];
if (service != null) {
ret = service;
break; // exit the for (;;)
}
}
if (doInitialize) {
T service = null;
try {
// 缓存里面没有,则创建一个
service = createService(ctx);
} catch (ServiceNotFoundException e) {
} finally {
}
ret = service;
break; // exit the for (;;)
}
}
return ret;
}
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
对于 LayoutInflater 来说,最终获得的服务对象为 PhoneLayoutInflater,这个在静态注册方法的回调里面
return new PhoneLayoutInflater(ctx.getOuterContext());
这其实是一个隐藏的类
/**
* @hide
*/
public class PhoneLayoutInflater extends LayoutInflater {
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);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
}
}
return super.onCreateView(name, attrs);
}
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}
3.2 调用 inflate 方法解析
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
/**
*
* @param resource xml资源id
* @param root 父布局
* @param attachToRoot 是否把inflate得到的View对象添加到root中
* false,表示不直接添加到root,如果要添加view,我们需要手动添加
* true,表示直接添加到root中
* 这里还要注意root为空及不为空时,的意思
*
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 解析预编译的布局
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 构造 XmlPull 解析器
XmlResourceParser parser = res.getLayout(resource);
try {
// 执行解析
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
注意:
我们给控件所指定的layout_weight和layout_height等属性表示一个控件在容器中大小,就是说这个控件必须在容器中,如果没有添加进容器,
其实就是失效的,所以
root为空,即不为填充控件指定父布局,那xml里定义的最外层view的layoutparam将全部无效。
设置root不为空,是为了让布局宽高有效,attachToRoot就是不自动将inflateLayout.xml填充到父容器中。
大家可以自己去做个试验,这块的代码逻辑在inflate方法里面;
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
try {
// "merge" 标签处理,
if (TAG_MERGE.equals(name)) {
} else {
创建 LayoutParams
ViewGroup.LayoutParams params = null;
root不为空 且 attachToRoot 为false, 给子view设置宽高属性,
if (root != null) {
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
root不为空 且 attachToRoot 为true,则将view添加到root中, 此时设置的宽高属性会生效
if (root != null && attachToRoot) {
root.addView(temp, params);
}
root为空,这个时候attachToRoot无论是true还是false都是没有意义的,根节点设置的高宽会失效。
返回创建的view
if (root == null || !attachToRoot) {
result = temp;
}
}
} finally {
}
return result;
}
}
再后面就是通过反射进行view的创建了,我们看看解析的过程。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
// 最终生成的view
View result = root;
try {
// 获取最外层的标签
final String name = parser.getName();
// "merge" 标签处理,
if (TAG_MERGE.equals(name)) {
// 递归执行解析
rInflate(parser, root, inflaterContext, attrs, false);
} else {
根据xml文件生成的view,为xml文件的根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
以 temp 即xml根view 为 root,递归执行解析
rInflateChildren(parser, temp, attrs, true);
}
} finally {
}
return result;
}
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (TAG_REQUEST_FOCUS.equals(name)) {
} else {
// 创建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);
viewGroup.addView(view, params);
}
}
}
后面代码都是view的创建,感兴趣的自行跟踪代码。
3.3
四、 LayoutInflater.Factory (2)
实例化 View 的优先顺序为:Factory2 / Factory -> mPrivateFactory -> PhoneLayoutInflater;
通过源码,我们知道,在setContentView时,我们需要先创建view,首先会对Factory、Factory2的设置判断,
如果有设置那么就会通过设置Factory、Factory2进行生成View,我们姑且简单的理解为是一个拦截器,直接在
这里创建view,然后返回。
使用 Factory2 接口可以拦截实例化 View 对象的步骤,
Factory & Factory2其实就是两个接口,view的创建过程需要自己实现,在这个地方,我们可以做自己想做的事情,
- 比如创建view,添加log,或者其他的。
- 在XML布局中自定义标签名称
- 全局替换系统控件为自定义View
- 替换app中字体
- 全局换肤
- 获取控件加载耗时等
Factory2 继承自 Factory,其方法多了一个父View。
public interface Factory {
@Nullable
View onCreateView(@NonNull String name, @NonNull Context context,
@NonNull AttributeSet attrs);
}
public interface Factory2 extends Factory {
@Nullable
View onCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context, @NonNull AttributeSet attrs);
}
4.1 使用
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// 创建view
View view = getDelegate().createView(parent, name, context, attrs);
// 返回生成的view
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
我们看下view的创建
AppCompatDelegateImpl.java
public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}
final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
Context originalContext = context;
View view = null;
switch (name) {
case "TextView":
view = this.createTextView(context, attrs);
this.verifyNotNull((View)view, name);
break;
case "ImageView":
view = this.createImageView(context, attrs);
this.verifyNotNull((View)view, name);
break;
...
default:
view = this.createView(context, name, attrs);
}
return (View)view;
}
4.2 注意点
需要注意的是,setFactory2一定是要在super.onCreate前调用的,我们从源码中来找答案 。
在 AppCompatDialog & AppCompatActivity 初始化时,都通过setFactory2()设置了拦截器,设置的对象是 AppCompatDelegateImpl:
protected void onCreate(@Nullable Bundle savedInstanceState) {
AppCompatDelegate delegate = this.getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
我们动动小手来跟一下installViewFactory的代码
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
如果AppCompatActivity没有在onCreate之前设置LayoutInflater的Factory,
那么AppCompatActivity会尝试设置一个Factory2,其中Factory2在AppCompatDelegate的具体子类代码中实现
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
}
}
如果我们设置了就用我们的,如果没有就用系统的,
通过上面代码,我们知道,如果没有在onCreate前设置factory,会默认设置一个,如果我们在oncreate后面在设置一个是或抛出异常的,我们看代码
/**
* Like {@link #setFactory}, but allows you to set a {@link Factory2}
* interface.
*/
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);
}
}
AppCompatViewInflater 与 LayoutInflater 的核心流程差不多,主要差别是前者会将等标签解析为AppCompatTextView对象
factory的用途我们后面再写一篇文章一起学习换肤。
五、 推荐阅读
未经允许不得转载