对于在PagerAdapter中出现的IllegalStateException: The specified child already has a parent.错误的原因及多种解决办法
第一次遇见这个错误的时候当时崩溃了好么,当时我是这样的:
花了一天时间去分析问题出在哪里,不断用Log去锁定问题在哪,然后找到问题在哪了,有去分析为什么会出现这种问题,出现这种问题又有什么方法可以解决,最终上天眷顾让我的得已通关。
下面是出现的错误提示:
java.lang.IllegalStateException:
The specified child already has a parent. You must call removeView() on the child's parent first.
之后各种Log打印,终于发现问题出现在哪里了,我的viewPager中只有3个轮播图,但是在Adapter中的系统调用的instantiateItem(ViewGroup container, int position)方法却是加载了5次,
05-06 11:59:05.338 6527-6527/? E/sfsd: ---------instantiateItem---------position-0
05-06 11:59:05.342 6527-6527/? E/sfsd: ---------instantiateItem---------position-2
05-06 11:59:05.342 6527-6527/? E/sfsd: ---------instantiateItem---------position-1
05-06 11:59:05.342 6527-6527/? E/sfsd: ---------instantiateItem---------position-1
05-06 11:59:05.342 6527-6527/? E/sfsd: ---------instantiateItem---------position-2
但是上面的Log打印却是出现了这样的情况,position=0表示的是第一轮播图,轮播图总过有3张,本来viewpager默认的
setOffscreenPageLimit(1);// 左右各保留几个对象为1,但是神奇的是,在viewpager初始化的时候却是出现了setOffscreenPageLimit(2);的情况,之后我又尝试性的将view pager的图片添加到5张,居然神奇性的没有问题出现了,一切运行正常,这个不得不引起我的注意了:
我的内心是十万只长的像羊又像长颈鹿的动物奔过,
出现了5张轮播图就没问题,而3张轮播图却会有BUG,这样的情况我自然是不会怀疑是我的代码有问题,首当其冲的当然是想,这个一定是androidAPI的BUG,所以我去查找了一下源码对于默认加载的viewpager的数量进行查看:
private static final int DEFAULT_OFFSCREEN_PAGES = 1;
开始找到的是这个,what f*ck?这不是我想要的,所以我又去viewpager中去找,源码是这样的:
public void setOffscreenPageLimit(int limit) {
if (limit < DEFAULT_OFFSCREEN_PAGES) {
Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "
+ DEFAULT_OFFSCREEN_PAGES);
limit = DEFAULT_OFFSCREEN_PAGES;
}
if (limit != mOffscreenPageLimit) {
mOffscreenPageLimit = limit;
populate();
}
}
找了一圈都不能解决为什么会加载5次的原因,这是我不能忍的,所以我选择了放弃,转而去想解决办法。
解决办法一:
- 从出现异常的结果分析:IllegalStateException: The specified child already has a parent.的字面意思是一个子View已经存在一个父View,你必须先调用该子视图的父视图的 removeView() 方法,这种情况通常出现在动态添加视图的情况下,出现这种错误的原因是一个子控件只允许存在一个父控件,而很多时候在动态添加视图的时候,我们不知道该子视图是否已存在父视图,当已存在的时候就会报错。
是什么意思?往下看↓:
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@mipmap/circle_bg"> <android.support.v4.view.ViewPager android:id="@+id/viewpager_firstpage" android:layout_width="match_parent" android:layout_height="200dp" android:paddingLeft="30dp" android:paddingRight="30dp" > </android.support.v4.view.ViewPager>
`
- 出错的代码主要是在ViewPager的适配器类的instantiateItem方法中,因为在该方法中我们通常会动态的添加视图。在自己的项目的主界面中定义了一个ViewPager用来循环显示多个imageView,而在主界面中imageView是通过container.addView(imageView)来动态获取的,我们定义的viewPager在其外层有一个线性布局。
- 错误分析:因为ViewPager的视图的显示是在PagerAdapter中通过instantiateItem方法来动态添加的,通常我们在该方法中会调用container.addView(viewList.get(position));来添加一个视图,即调用ViewPager的addView来动态添加控件,但是可以看到在我们的R.layout.viewpager的XML文件中ViewPager的外部是LinearLayout布局控件,即此时ViewPager的父控件为LinearLayout,这样就相当于把一个已存在父控件的子控件动态的添加到一个ViewPager容器中,这是不被允许的,因为一个子控件只允许存在一个父容器控件,因此会报错。
- 解决办法:去除R.layout.viewpager的XML文件中ViewPager的外部的LinearLayout布局控件,这样ViewPager就不存在父容器控件。
- 但是通常我们会在viewpager的外部会设置一个父布局,以满足我们复杂的界面要求。所以解决办法一并不是完美的。
解决办法二:
在PagerAdapter中通过instantiateItem方法中动态添加视图前做一个简单的判断,判断待添加的视图是否已存在父控件,若存在则调用removeView()去除。代码如下:
public Object instantiateItem(ViewGroup container, int position) { int newPosition = position % imageViewList.size(); ImageView imageView = imageViewList.get(newPosition); ViewGroup parent = (ViewGroup) imageView.getParent(); if (parent != null) { parent.removeView(imageViewList.get(newPosition)); } // a. 把View对象添加到container中 container.addView(imageView); // b. 把View对象返回给框架, 适配器 return imageView; // 必须重写, 否则报异常 }`
解决办法三:
想到既然出现的问题是由于其本来是3个轮播图却加载了5次,这就导致第二次加载重复的轮播图,也就是第四和第五次加载的是重复的视图,所以这个也是导致父控件已经是container了,再次加载当然也会报错父布局已经存在了,所以我又去翻看源码,有没有什么方法可以将这个父布局直接给干掉,突然我好像是发现了什么:看看这个方法↓:
public Object instantiateItem(ViewGroup container, int position) { return instantiateItem((View) container, position); }
这个方法调用的是后面这个方法return instantiateItem((View) container, position),我好像知道了点什么,看这两个方法的参数,一个是ViewGroup一个是View,那么我们直接重写后面这个方法不就好了吗,就不会出现父布局已经存在的情况,因为这是一个view不能包含子控件的,所以我就可以直接将我的图片资源ID在这里加载,不用在activity中添加了,想到这里我就开始实现它了:
public Object instantiateItem(ViewGroup container, int position) { int newPosition = position % imageViewList.size(); ImageView imageView = imageViewList.get(newPosition); if (newPosition==0) { container.setBackgroundResource(R.mipmap.aaa); } if (newPosition==1) { container.setBackgroundResource(R.mipmap.bbb); } if (newPosition==2) { container.setBackgroundResource(R.mipmap.ccc); } // b. 把View对象返回给框架, 适配器 return imageView; // 必须重写, 否则报异常 }