嵌套Fragment的使用及遇到The specified child already has a parent. You must call removeView()问题的解决

预备知识


嵌套Tab在Android应用中用途广泛,之前做过的一些东西都是运用了TabActivity。但是由于在Android Developers中说到了“TabActivity was deprecated in API level 13." ,并且建议大家使用Fragment。所以学习了嵌套Fragment的使用,参考了这个博客中的相关思路和代码。


在Android Developers中对于 Fragment中文版看这里)的描述: A Fragment represents a behaviors or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running. 
简单来说,Fragment就是被嵌入在Activity中用来表现UI的一个可模块化和可重用的组件。Fragment在大屏幕设备中应用可以非常广泛,开发者可以通过自己巧妙的设计让UI更加灵活和美观。


创建Fragment

创建Fragment,必须创建一个Fragment的子类。

创建一个自己的Fragment,需要创建一个Fragment的子类。Fragment的生命周期和Activity的生命周期类似,它包含了很多与Activity类似的回调函数。

public void onCreate (Bundle savedInstanceState)
创建fragment的时候调用onCreate()方法
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)

在第一次绘制UI的时候系统调用该方法,为了绘制UI,返回一个fragment布局的根View


Fragment添加到Activity的方法

1.layout文件中声明fragment

  <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1.0"
        android:background="#fffab3" >
    </FrameLayout>

2.将一个fragment添加到viewgroup中,使用FragmentTransaction添加、替换或者删除fragment


private void addFragmentToStack(Fragment fragment) {
		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
		ft.replace(R.id.fragment_container, fragment);
		ft.commit();
	}

Fragment生命周期


异常分析


关于解决 java.lang.IllegalStateException The specified child already has a parent. You must call removeView()的方法


在运行调试的时候会发现,在第二次点击一个相同的tab的时候,会出现上述异常。

这个异常说的是,这个特定的child view已经存在一个parent view了,必须让parent view调用removeView()方法。

经过对fragment的生命周期的分析

运行顺序:点击tab1,点击tab2,再点击tab1.

具体fragment的生命周期是:



对上图进行分析,可以发现,出问题的是viewpager中的view。当切换不同的viewpager(即fragment,每个fragment中装载了一个viewpager)时,调用了startActivity()方法的时候,传入了相同的id,会返回相同的对象。而当我们在第二次调用的时候,传入了相同的id是复用了原来的view,这就导致了view被指定多个parent view

所以解决办法就是,在使用这个view之前首先判断其是否存在parent view,这调用getParent()方法可以实现。如果存在parent view,那么就调用removeAllViewsInLayout()方法。代码如下:


	for (View view : viewList) {
				ViewGroup p = (ViewGroup) view.getParent();
				if (p != null) {
					p.removeAllViewsInLayout();
				}
			}

结果展示


完成之后在模拟器中运行的效果如图:




主要代码


PS:完整工程 点这里下载


TestFragmentActivity.java

package com.test;

import java.util.ArrayList;

import android.annotation.SuppressLint;
import android.app.LocalActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.LinearLayout;

/**
 * 嵌套Fragment的使用
 * 
 * @author zouliping
 * 
 */
public class TestFragmentActivity extends FragmentActivity {

	private LocalActivityManager manager;

	private ArrayList<View> list1 = new ArrayList<View>();
	private ArrayList<View> list2 = new ArrayList<View>();
	private ArrayList<View> list3 = new ArrayList<View>();

	private Fragment[] fragments;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setContentView(R.layout.test_fragments);

		manager = new LocalActivityManager(this, false);
		manager.dispatchCreate(savedInstanceState);

		initViews();
	}

	/**
	 * 初始化Views
	 */
	private void initViews() {
		findViewById(R.id.tv1).setOnClickListener(listener);
		findViewById(R.id.tv2).setOnClickListener(listener);
		findViewById(R.id.tv3).setOnClickListener(listener);

		fragments = new FragmentParent[3];
		fragments[0] = FragmentParent.newInstance(list1, new String[] {
				"page1_1", "page1_2", "page1_3" });
		fragments[1] = FragmentParent.newInstance(list2, new String[] {
				"page2_1", "page2_2", "page2_3" });
		fragments[2] = FragmentParent.newInstance(list3, new String[] {
				"page3_1", "page3_2", "page3_3" });

		initPager();

		findViewById(R.id.tv1).performClick();
	}

	/**
	 * 获取view
	 * 
	 * @param id
	 * @param intent
	 * @return
	 */
	private View getView(String id, Intent intent) {
		return manager.startActivity(id, intent).getDecorView();
	}

	/**
	 * 根据parent position初始化viewPager
	 */
	private void initPager() {
		Intent intent;

		// tab1
		intent = new Intent(TestFragmentActivity.this, Test1Activity.class);
		list1.add(getView("tab1_1", intent));
		intent = new Intent(TestFragmentActivity.this, Test2Activity.class);
		list1.add(getView("tab1_2", intent));
		intent = new Intent(TestFragmentActivity.this, Test1Activity.class);
		list1.add(getView("tab1_3", intent));

		// tab2
		intent = new Intent(TestFragmentActivity.this, Test1Activity.class);
		list2.add(getView("tab2_1", intent));
		intent = new Intent(TestFragmentActivity.this, Test2Activity.class);
		list2.add(getView("tab2_2", intent));
		intent = new Intent(TestFragmentActivity.this, Test1Activity.class);
		list2.add(getView("tab2_3", intent));

		// tab3
		intent = new Intent(TestFragmentActivity.this, Test1Activity.class);
		list3.add(getView("tab2_1", intent));
		intent = new Intent(TestFragmentActivity.this, Test2Activity.class);
		list3.add(getView("tab2_2", intent));
		intent = new Intent(TestFragmentActivity.this, Test1Activity.class);
		list3.add(getView("tab2_3", intent));
	}

	private OnClickListener listener = new OnClickListener() {
		@Override
		public void onClick(View v) {
			switch (v.getId()) {
			case R.id.tv1:
				addFragmentToStack(fragments[0]);
				break;
			case R.id.tv2:
				addFragmentToStack(fragments[1]);
				break;
			case R.id.tv3:
				addFragmentToStack(fragments[2]);
				break;
			}

		}
	};

	private void addFragmentToStack(Fragment fragment) {
		FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
		ft.replace(R.id.fragment_container, fragment);
		ft.commit();
	}

	/**
	 * 嵌套Fragment
	 * 
	 */
	@SuppressLint("ValidFragment")
	public final static class FragmentParent extends Fragment {

		/**
		 * 工厂方法,返回一个新的FragmentParent的实例
		 * 
		 * @param list
		 * @param str
		 * @return
		 */
		public static final FragmentParent newInstance(ArrayList<View> list,
				String[] str) {
			FragmentParent framentParent = new FragmentParent();
			Bundle bundle = new Bundle();
			bundle.putSerializable("pager_view_list", list);
			bundle.putStringArray("pager_title_ary", str);
			framentParent.setArguments(bundle);
			return framentParent;
		}

		@Override
		public View onCreateView(LayoutInflater inflater, ViewGroup container,
				Bundle savedInstanceState) {
			Log.d("test fragment", "fragment create view");

			LinearLayout convertView = (LinearLayout) inflater.inflate(
					R.layout.viewpager_fragments, container, false);
			ViewPager pager = (ViewPager) convertView.findViewById(R.id.pager);

			@SuppressWarnings("unchecked")
			final ArrayList<View> viewList = (ArrayList<View>) getArguments()
					.getSerializable("pager_view_list");
			final String[] titles = getArguments().getStringArray(
					"pager_title_ary");

			for (View view : viewList) {
				ViewGroup p = (ViewGroup) view.getParent();
				if (p != null) {
					p.removeAllViewsInLayout();
				}
			}

			pager.setAdapter(new PagerAdapter() {

				@Override
				public boolean isViewFromObject(View view, Object obj) {
					return view == obj;
				}

				@Override
				public int getCount() {
					return viewList.size();
				}

				@Override
				public void destroyItem(ViewGroup container, int position,
						Object object) {
					container.removeView(viewList.get(position));
				}

				@Override
				public CharSequence getPageTitle(int position) {
					return titles[position];
				}

				@Override
				public Object instantiateItem(ViewGroup container, int position) {
					container.addView(viewList.get(position));
					return viewList.get(position);
				}
			});

			return convertView;
		}

		@Override
		public void onStart() {
			super.onStart();
			Log.d("test fragment", "fregment start");
		}

		@Override
		public void onStop() {
			super.onStop();
			Log.d("test fragment", "fregment stop");
		}

		@Override
		public void onResume() {
			super.onResume();
			Log.d("test fragment", "fregment resume");
		}

		@Override
		public void onDestroy() {
			super.onDestroy();
			Log.d("test fragment", "fregment destroy");
		}

		@Override
		public void onDetach() {
			super.onDetach();
			Log.d("test fragment", "fregment detach");
		}

	}
}


布局文件 test_fragments.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <FrameLayout
        android:id="@+id/fragment_container"
        android:layout_width="fill_parent"
        android:layout_height="0dip"
        android:layout_weight="1.0"
        android:background="#fffab3" >
    </FrameLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:background="@android:color/black"
        android:orientation="horizontal" >

        <TextView
            android:id="@+id/tv1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:text="tab1"
            android:textColor="#ffffff"
            android:textIsSelectable="true"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/tv2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="10dp"
            android:text="tab2"
            android:textColor="#ffffff"
            android:textIsSelectable="true"
            android:textSize="25sp" />

        <TextView
            android:id="@+id/tv3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:layout_marginLeft="15dp"
            android:layout_marginTop="10dp"
            android:text="tab3"
            android:textColor="#ffffff"
            android:textIsSelectable="true"
            android:textSize="25sp" />
    </LinearLayout>

</LinearLayout>

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页