BUG:The specified child already has a parent. You must call removeView() on the child's parent first

在使用ViewPager + Fragment 的时候报错:The specified child already has a parent. You must call removeView() on the child’s parent first.在解释这个问题之前我们需要了解ViewPager的预加载机制。

这里写图片描述

ViewPager的预加载机制

ViewPager会预加载当前页面的左右两边各一个页面,即左边一个页面,右边一个页面。

即:
假设ViewPager中依次存放3个View,分别是A、B、C即:A-->B-->C。
若当前页面时A,那么viewpager会缓存B,但不会缓存C;
若当前页面时B,那么viewpager会缓存A,也会缓存C;
若当前页面时C,那么viewpager会缓存B,但不会缓存A;

我们通过一个Demo演示这个bug

布局通过viewpager + 4个button来实现页面的切换,用fragment填充viewpager。

效果图:

这里写图片描述

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="0dp" 
        android:layout_weight="1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/weixin"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:onClick="click"
            android:text="微信" />

        <Button
            android:id="@+id/contact"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:onClick="click"
            android:text="联系人" />

        <Button
            android:id="@+id/discover"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:onClick="click"
            android:text="发现" />

        <Button
            android:id="@+id/me"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:onClick="click"
            android:text="我" />
    </LinearLayout>

</LinearLayout>

ManiActivity.java

package com.android.weixinfragment2;

import java.util.ArrayList;
import java.util.List;

import com.android.weixinfragment.R;
import com.android.weixinfragment2.fragment.FragmentContacts;
import com.android.weixinfragment2.fragment.FragmentDiscover;
import com.android.weixinfragment2.fragment.FragmentMe;
import com.android.weixinfragment2.fragment.FragmentWX;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;
import android.view.View;

public class MainActivity extends FragmentActivity {

    private ViewPager viewPager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager fm = getSupportFragmentManager();

        List<Fragment> fragList = new ArrayList<Fragment>();
        fragList.add(new FragmentWX());
        fragList.add(new FragmentContacts());
        fragList.add(new FragmentDiscover());
        fragList.add(new FragmentMe());

        viewPager = (ViewPager) findViewById(R.id.viewpager);
        MyAdapter adapter = new MyAdapter(fm, fragList);
        viewPager.setAdapter(adapter);
        viewPager.setCurrentItem(0);
    }

    public void click(View view) {
        switch (view.getId()) {
        case R.id.weixin:
            viewPager.setCurrentItem(0);
            break;

        case R.id.contact:
            viewPager.setCurrentItem(1);
            break;
        case R.id.discover:
            viewPager.setCurrentItem(2);
            break;
        case R.id.me:
            viewPager.setCurrentItem(3);
            break;
        }
    }

}

FragmentWX.java

其中:if条件里面的代码保留,只注释掉if条件和else

//      if (view == null) {
            view = View.inflate(getActivity(), R.layout.fragmentcontacts, null);//保留
//      } else {
//          ViewParent parent = view.getParent();
//          if (parent instanceof ViewGroup) {
//              ((ViewGroup) parent).removeView(view);// 去掉一个parent
//          }
//      }
package com.android.weixinfragment2.fragment;

import com.android.weixinfragment.R;

import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;

public class FragmentWX extends Fragment {

    private ProgressBar pb1;
    private TextView tv1;
    private View view;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
//      if (view == null) {//防止重新加载导致流量消耗
            view = View.inflate(getActivity(), R.layout.fragmentwx, null);
//      } else {
//          ViewParent parent = view.getParent();
//          if (parent instanceof ViewGroup) {
//              ((ViewGroup)parent).removeView(view);//去掉一个parent
//          }
//      }
        pb1 = (ProgressBar) view.findViewById(R.id.pb1);
        tv1 = (TextView) view.findViewById(R.id.tv1);
        handler.sendEmptyMessageDelayed(0, 2000);
        return view;
    }

    // 模拟联网耗时操作
    Handler handler = new Handler() {
        public void handleMessage(android.os.Message msg) {
            pb1.setVisibility(View.GONE);
            tv1.setVisibility(View.VISIBLE);
        };
    };

}
1 为了显示出联网的效果,使用了handler,延迟了2秒,所以viewpager的预加载也就多了2秒。
2 但这样就出现了一个bug:就是当我们从第1个界面进入到第3、4个界面后,再回到第1个页面,第1个页面会重新加载(viewpager只预加载左右两边各1个)
3 而这会消耗用户流量,我们需要做一个判断。即:当前的view == null,创建view并加载数据。放开if的注释,见下面:
if (view == null) {//防止重新加载导致流量消耗
    view = View.inflate(getActivity(), R.layout.fragmentwx, null);
} 
//      else {
//          ViewParent parent = view.getParent();
//          if (parent instanceof ViewGroup) {
//              ((ViewGroup)parent).removeView(view);//去掉一个parent
//          }
//      }
4 解决了上面的问题,但是却导致了另外一个bug,也就是今天要讲的
  The specified child already has a parent. You must call removeView() on the child's parent

这里写图片描述

原因

我们把view添加到viewpager,viewpager会给这个view外面包一层noSaveStateFrameLayout,当view==null时,只包了一层;但但我们返回的是已经创建好的view,即复用了view,那么viewpager会在包一层noSaveStateFrameLayout,导致view有2个parent。所以报错。

解决方法:

方法一:

不使用if (view == null) {…}来实现节约流量的目的,而是通过改变viewpager预加载数量的方式来实现。
即:viewPager.setOffscreenPageLimit(3);//预加载左右两边各3个。
那么若当前页是A,页面B、C、D都会预加载,节约啦用户流量。

方法二:

保留if (view == null) {…}同时在else中去掉一个parent。即:

if (view == null) {
        view = View.inflate(getActivity(), R.layout.fragmentwx, null);
    } else {
        ViewParent parent = view.getParent();
        if (parent instanceof ViewGroup) {
            ((ViewGroup)parent).removeView(view);
        }
    }

点击下载源码(bin目录下含apk)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值