Fragment基本使用(二)

本文深入探讨了Fragment在Android开发中的使用技巧,包括如何利用FragmentTransaction进行操作、动画效果的添加、通信机制以及Fragment的保留策略。文章详细介绍了如何在不同场景下使用Fragment,以及在遇到特定问题时采取的解决方法。

基础

        在使用FragmentTransaction对fragment进行操作时,如果在commit()之前调用了它的addToBackStack()方法,那么本次操作中移除的fragment将会进入stop状态,并不会直接被销毁,而且会进入到回退栈中。如果没有调用addToBackStack(),那么本次操作中移除的fragment将直接被销毁(destoryed).
        addToBackStack(String):它为本次fragment变换(transaction)指定了一个名字(unique name),一般情况下是没有用处的,除非打算使用FragmentManager.BackStackEntry中的api对fragment进行更高级的操作。如:

    private void pop() {
        FragmentManager fm = getSupportFragmentManager();
        int count = fm.getBackStackEntryCount();
        if (count > 0) {
            fm.popBackStack(fm.getBackStackEntryAt(count - 1).getId(),
                    FragmentManager.POP_BACK_STACK_INCLUSIVE);
        }
    }

    void addFragmentToStack() {
        mStackLevel++;
        // Instantiate a new fragment.
        Fragment newFragment = CountingFragment.newInstance(mStackLevel);
        // Add the fragment to the activity, pushing this transaction
        // on to the back stack.
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.setCustomAnimations(R.anim.fragment_slide_right_exit,
                R.anim.fragment_slide_right_enter,
                R.anim.fragment_slide_left_enter,
                R.anim.fragment_slide_left_exit);
        ft.replace(R.id.connent, newFragment);
        ft.addToBackStack(null);
        ft.commit();
    }
        上述首先将fragment添加到backstack中,再通过Fragment#popBackStack()从backstack中回退一个fragment。
         有一点需要注意:弹出fragment时,会将该fragment上的所有fragment都一起弹出。

动画

        可以使用FragmentTransacion#setCustomAnimations()为fragment的进入和退出添加动画。
        其参数分别为:正常进入时的动画,正常退出时动画。从backstack中弹出时进入动画,从backstack中弹出时退出动画。前两个参数好理解,后面两个不好理解。
        比如现在stackback中有f1,f2,f3三个fragment。当使用popBackStack()弹出f3时,f3就是退出,退出时执行的动画为第四个参数指定的;而f2会重新显露出来,f2就是进入,执行的动画就是第三个参数指定的。
        使用v4包中的fragment时,动画为tweent动画;使用系统自带的fragment时,动画为属性动画。

通信

        Fragment与Fragment之间的通信必须通过宿主activity进行,两个fragment之间禁止直接通信。

activity到fragment

分两种情况:

创建fragment时

       通过fragment的Fragment.setArguments()即可。在Fragment中,可以通过getArguments()获得到传递的Bundle对象。

       但对于setArguments()来说,它要求在Fragment被附加到Activity之前调用(也就是commit()之前),所以最好的就是在Fragment被创建后立即调用该方法。为了满足这个条件,在Fragment中添加一个名为newInstance()的静态方法,该方法主要完成:fragment的实例,并且把bundle对象附加给相应的fragment。示例如下:

	/**
	 * bundle为传入Fragment中的参数。当没有参数时可以为null。
	 */
	public static Fragment newInstance(Bundle bundle) {
		MainFragment fragment = new MainFragment();
		if (bundle != null) {
			fragment.setArguments(bundle);
		}
		return fragment;
	}

fragment已存在

        宿主activity中含有fragment的引用,因此可直接调用fragment中的public方法。

fragment到activity

fragment启动activity

        与activity启动activity一样,通过intent将参数传递给activity就行。

fragment返还到activity

        如果该activity是fragment的托管activity,那么可以直接调用Fragment.getActivity()得到Activity对象,然后调用Activity中的方法将数据传输回去。但这种方法有一个缺点:Fragment与Activity捆绑的太紧。因此,可以通过接口进行通信:在Fragment中定义一个接口,Activity实现该接口并重写其中的方法。在Fragment中,合适时候调用接口中的方法。这样就将数据通过接口中的方法传递到了Activity中。

       当activity不是fragment的托管activity时,记该fragment的托管Activity为activityA,那么在启动activityA时使用startActivityForResult()。在fragment中调用getActivity().setResult()。

fragmentA到fragmentB

托管activity不相同

        如果两个fragment的托管activity不相同。那么就转换为fragment到activity,再从activity到fragment。

托管activity相同

       当在fragmentA中实例fragmentB时,调用fragmentB的setArguments()即可。

如果不是,可以有两种方法。

第一种:通过setTargetFragment()

       调用fragmentA的setTargetFragment(),并将fragmentB当作参数传递进去。也就是说:把fragmentB当作fragmentA的目标fragment。

      有两个fragment,一个是显示列表,一个显示详情。点击列表中的每一条时,更改详情中的内容。并且这两个fragment托管在同一个activity中,具体代码如下:

Activity中的代码

	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		FragmentManager manager = getFragmentManager();
		ListFragment listFragment = new ListFragment();
		DetailFragment detailFragment = new DetailFragment();
		manager.beginTransaction().replace(R.id.lv1, listFragment).commit();
		manager.beginTransaction().replace(R.id.lv2, detailFragment).commit();
		// 将详情Fragment设置为列表Fragment的目标Fragment
		listFragment.setTargetFragment(detailFragment,
				DetailFragment.REQUEST_DETAIL);
	}
ListFragment
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		ListView lv = new ListView(getActivity());
		lv.setAdapter(new ArrayAdapter<String>(getActivity(),
				android.R.layout.simple_list_item_1, date));
		lv.setOnItemClickListener(new OnItemClickListener() {
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				String text = date[position];
				Intent data = new Intent();
				data.putExtra("value", text);
				//activity中设置了目标fragment,这里通过该方法直接获取到目标fragment
				getTargetFragment().onActivityResult(getTargetRequestCode(),
						RESULT_LIST_DETAIL, data);
			}
		});
		return lv;
	}
DetailFragment
	// 因为在ListFragment中调用了该方法,所以DetailFragment重写该方法即可获得前者传递的数据
	public void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (resultCode == ListFragment.RESULT_LIST_DETAIL && data != null
				&& requestCode == REQUEST_DETAIL) {
			String extra = data.getStringExtra("value");
			mTv.setText(extra);
		}
	}
第二种:通过托管activity进行中转

        由于两个fragment是托管于同一个activity,因此,可以先通过接口把fragmentA中的数据传递到托管activity中。再在activity中调用fragmentB的方法,从而可以将参数传递到fragment中。

示例:

FirstFragment

	private OnTitleChangedListener listener;
	
	/**
	 * 在onAttach中判断相应的activity是否已经实现了定义的接口。
	 * 如果没实现,则fragment之间是无法进行通信的,所以抛异常。
	 */
	@Override
	public void onAttach(Activity activity) {
		try {
			listener = (OnTitleChangedListener) activity;
		} catch (Exception e) {
			throw new ClassCastException(
					"activity must be implements OnTitleChangedListener");
		}
		super.onAttach(activity);
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		ListView v = new ListView(getActivity());
		v.setAdapter(new BaseAdapter() {
			public View getView(final int position, View convertView, ViewGroup parent) {
				Button button = new Button(getActivity());
				button.setOnClickListener(new OnClickListener() {
					public void onClick(View v) {
						/*
						 * 调用相应的接口中的方法,把参数传递到相应的activity中。
						 */
						listener.onTitleChanged(position);
					}
				});
				return button;
			}

			public long getItemId(int position) {
				return position;
			}

			public Object getItem(int position) {
				return position;
			}

			public int getCount() {
				return 3;
			}
		});
		return v;
	}
	/**
	 * 定义接口,相应的activity要实现该接口。
	 */
	public interface OnTitleChangedListener {
		/**
		 * 接口中的方法,可以通过该方法把另一个fragment需要的参数传递到相应的activity中。
		 */
		public void onTitleChanged(int titleId);
	}
对应的Activity:
/*
 * 实现FirstFragment中定义的接口,方便FirstFragment传递数据
 */
public class MainActivity extends Activity implements OnTitleChangedListener {

	private FragmentManager manager;
	private FirstFragment firstFragment;
	private SecondFragment secondFragment;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		manager = getFragmentManager();
		firstFragment = new FirstFragment();
		secondFragment = new SecondFragment();
		change(R.id.fragment_container_top, firstFragment);
		change(R.id.fragment_container_bottom, secondFragment);
	}
	/*
	 * 当FirstFragment调用相应的方法时,参数会传递到Activity中。
	 * 再调用SecondFragment中的方法。这样参数就由FirstFragment传递到
	 * SecondFragment中了。至于SecondFragment怎么操作,不是Activity,FistFragment操心的。
	 */
	@Override
	public void onTitleChanged(int titleId) {
		secondFragment.setText(titleId);//SecondFragment定义的方法,用于接收参数,并进行操作。
	}
	public void change(int containerId, Fragment clazz) {
		try {
			FragmentTransaction transaction = manager.beginTransaction();
			transaction.replace(containerId, clazz, clazz.getClass()
					.getSimpleName());
			transaction.commit();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Fragment的保留

需求       

        如果在Fragment中播放音频或视频时,切换了屏幕方向。虽然可以通过onSaveInstanceState()将进度记录下来,但是播放过程仍旧会中断。这个时候就需要将fragment保留下来。

操作

       想要进行Fragment的保留,操作非常简单,重写Fragment.onCreate(),在该方法中调用setRetainInstance(true);即可。

说明

       当设备配置发生变化时,总会销毁Fragment与Activity的视图,但并没有立刻销毁该fragment实例。这是因为,新的配置可能会有新的资源来匹配,当有更合适的资源时,就会重新构建新的视图,所以需要销毁旧的视图。

       视图销毁之后,FragmentManager会检查每一个fragment的mRetainInstance的值(setRetainInstance()改变的就是该变量的值)。如果该值为false(初始默认值),就会销毁fragment实例。随后新Activity中的新FragmentManager会建立新的fagment实例及视图。但是当该值为true时,该fragment的视图会被销毁,本身的实例不会被销毁。当新activity被创建后,新的FragmentManager会找到被保留的fragment实例,并重新创建它的视图。

       在这个过程中,虽然fragment仍然存在,但是已经没有任何activity托管它。把这个状态叫做保留状态。保留状态的存在时间非常短暂,从旧的activity被销毁后到新的activity建立前。

       如果一个fragment被保留,在onDestroyView()之后并调用onDestroy(),而是直接调用onDetach()。在重新被添加到activity时,会调用onAttach(),然后直接调用onCreateView(),而不会调用onCreate()。这是因为fragment被保留时,并不会销毁实例,只销毁视图,所以不需要调用onDestory()与onCreate()。但是它的托管activity会被重新实例化,因为需要调用onDetach()与onAttach()。

       只有当activity因设备配置发生改变而被销毁时,fragment才会短暂地处于保留状态。如果activity是因为操作系统需要回收内存而被销毁,则所有被保留的fragment也会随之销毁。

与onSaveInstanceState()的比较

       两者的主要区别在于数据可以保存多久。如果只需要短暂的保留数据以应对设备配置的变化,用保留fragment更方便。如果有需要长久地保存的东西,则应覆盖onSaveInstanceState()。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值