viewpager异常处理

对于在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; // 必须重写, 否则报异常
      }
    

重点来了,实现的效果怎么样呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值