基础
在使用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之间的通信必须通过宿主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已存在
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()。