Avoid passing null as the view root (needed to resolve layout parameters on the inflated layout's root element) 报错。
在使用windowmanager做弹窗的时候,会出现这个问题。只是一个警告,正常使用并不报错,代码如下:
LayoutInflater inflater = LayoutInflater.from(getApplication());
//将布局文件填充到View
mFloatLayout = (RelativeLayout) inflater.inflate(R.layout.result_windows, null);
if(mFloatLayout.getParent() == null)
winManager.addView(mFloatLayout, wmParams);
出现这个问题的原因,有一篇文章讲的比较透彻,
点击我!
总结一句话就是说,当我们使用LayoutInflater这个“填充器”填充result_windows这个布局View(文中为mFloatLayout)的时候,后面null为root根ViewGroup对象。也就是说,没有指定根对象。下面是第二个参数的解释:
root Optional view to be the parent of the generated hierarchy.
一个可选的view对象,它是作为生成的对象的父对象。这是我的理解。
LayoutInflater
will automatically attempt to attach the inflated view to the supplied root. However, the framework has a check in place that if you pass
null
for the root it bypasses this attempt to avoid an application crash.
比较快速的修正这个警告的办法:
mFloatLayout = View.inflate(getApplicationContext(), R.layout.result_windows, null);
//添加View填充--------------------------------
if(mFloatLayout.getParent() == null)
winManager.addView(mFloatLayout, wmParams);
下面是使用static方法获取生成的View,警告消除,我们可以看一下,eclipse的参数提示:
root A view group that will be the parent. Used to properly inflate the layout_* parameters
一看之下,貌似也没看明白,搜了点资料。发现其实没区别。View.inflate和Layoutinflater.inflate
/**
* Inflate a view from an XML resource. This convenience method wraps the {@link
* LayoutInflater} class, which provides a full range of options for view inflation.
*
* @param context The Context object for your activity or application.
* @param resource The resource ID to inflate
* @param root A view group that will be the parent. Used to properly inflate the
* layout_* parameters.
* @see LayoutInflater
*/
public static View inflate(Context context, int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
那么看起来,警告是消除了,其实是没有检测到而已,并没有本质上的区别罢了。一篇介绍参数的博文:参数介绍
下面来一个介绍View实例化过程的文章:View实例化过程详解 ,这个文章需要一点耐心来看,总体来说,比自己理解要来的简单一些。
重点看一下,inflate这个方法以及ViewGroup类:
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
/*可以看到通过resource id返回了一个XmlResourceParser,通过类名就可以猜测
这是一个xml的解析类。但点进去一看,发现它只是一个接口,它继承自 XmlPullParser用于pull方式解析xml的接口。和AttributeSet用于获取此view的所有属性。
那么需要能找到它的实现类。先看下面resource类。
*/
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot); //说实话,从这里开始没完没了的函数调用,我就不再往下贴了。。。
} finally {
parser.close();
}
}
.........
...
.
/**
* 真正创建一个view的方法,
* 此方法是用反射获取构造器来实例对象而不是直接new出来这是为了处于性能优化考虑,
* 同一个类名的不同对象,可以直接得到缓存的构造器直接获取一个构造器对象实例。而不需要
* 重复进行new操作。
*
* @param name 此View的全名
* @param prefix 前缀,值为 "android.view."其实就是是否包含包名
* @param attrs 此view的属性值,传递给此view的构造函数
*/
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor constructor = sConstructorMap.get(name); //缓存中是否已经有了一个构造函数
Class clazz = null;
try {
if (constructor == null) {
//通过类名获得一个class对象
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name);
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//通过参数类型获得一个构造器,参数列表为context,attrs
constructor = clazz.getConstructor(mConstructorSignature);
sConstructorMap.put(name, constructor); //把此构造器缓存起来
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object[] args = mConstructorArgs;
args[1] = attrs; //args[0]已经在前面初始好了。这里只要初始化args[1]
return (View) constructor.newInstance(args); //通过反射new出一个对象。。大功告成
} catch (NoSuchMethodException e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (prefix != null ? (prefix + name) : name));
ie.initCause(e);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()));
ie.initCause(e);
throw ie;
}
}
View的子类ViewGroup,可以简单理解为View的一个“组”,包含了一组View,其中重写了一个函数:
//哈哈,果然重写了此方法。其实就是在viewgroup包含的
//子view数组中进行遍历。那么view是什么时候被加入进
//viewgroup中的呢?如果是在代码中写,肯定是直接使用
//addView方法把view加入viewGroup。如果写在xml布局文件
//中,其实是在第二种方法中被加入view的。inflate加载父view
//时会同时把其所有的子view加载完,同时addView到父view中
protected View findViewTraversal(int id) {
if (id == mID) {
return this;
}
final View[] where = mChildren;
final int len = mChildrenCount;
for (int i = 0; i < len; i++) {
View v = where[i];
if ((v.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
v = v.findViewById(id);
if (v != null) {
return v;
}
}
}
return null;
}
人家还是总结的不错的:
也就是说:假如root设置为null或者root!=nul但attachToRoot设置为false,则这个xml加载出来的view不会挂载到别的视图节点上去;
当root!=null并且attachToRoot设置为true时,就会挂载上去,return为root
当root!=nul但attachToRoot设置为false时,这个root用来view生成正确的params布局参数来match root。
这个是我结合前人的基础上的理解,所以,我这里填null,也就导致新生成的windows不会附加于任何View,它是直接面向屏幕的,这个也符合windowManager的特性:整个Android的窗口机制是基于一个叫做 WindowManager,这个接口可以添加view到屏幕,也可以从屏幕删除view。它面向的对象一端是屏幕,另一端就是View,直接忽略我们以前的Activity或者Dialog之类的,这个 WindowManager是全局的,整个系统就是这个唯一的,这也就是说,不可以new出来,它最好也不要依附于任何activity是最好的。
还是有很多不明白的地方,希望明白的朋友回一下。 T ~ T....
谢谢了!