Fragment 回退栈案例解析
这里主要讲下fragment回退栈的案例解析。当然,fragment本身还是有很多坑的,通常的选项卡一层套用fragment还不至于遇到这些坑,但是随着fragment层次深入,就很容易中奖~
案例情景:
activityC内嵌套一个fragmentOne,然后从fragmentOne跳到fragmentTwo。
activityC内代码如下:
public class ActivityC extends BaseActivity {
private final static String TAG = "ActivityC";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
FragmentOne fragmentB = new FragmentOne();
fragmentTransaction.add(R.id.content_layout, fragmentB);
fragmentTransaction.commit();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
Log.d(TAG, "————————————————");
}
}
fragmentOne内跳转到fragmentTwo的跳转代码如下:
@OnClick(R.id.edit_bt)
public void onClick() {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
FragmentTwo fragmentTwo = new FragmentTwo();
fragmentTransaction.replace(R.id.content_layout, fragmentTwo);
fragmentTransaction.commit();
}
这里用的是fragmentTransaction.replace和fragmentTransaction.commit();而fragmentTransaction.replace是remove和add的结合,并且如果不添加事务到回退栈,前一个Fragment实例会被销毁。
此处FragmentOne跳转到FragmentTwo的log日志如下:
D/FragmentOne: onPause
D/FragmentOne: onStop
D/FragmentOne: onDestroyView
D/FragmentOne: onDestroy
D/FragmentOne: onDetach
D/FragmentTwo: onViewCreated
D/FragmentTwo: onStart
D/FragmentTwo: onResume
若加上回退栈,fragmentTransaction.addToBackStack(null); 此处FragmentOne跳转到FragmentTwo的log日志如下:
D/FragmentOne: onPause
D/FragmentOne: onStop
D/FragmentOne: onDestroyView
D/FragmentTwo: onCreateView
D/FragmentTwo: onStart
D/FragmentTwo: onResume
说明加上了回退栈,fragmentOne未执行onDestroy和onDetach。只是对视图view实例进行销毁。
此时在fragmentTwo中点击返回键,可以正常返回到fragmentOne,并且可以看到fragmentOne之前输入的数据仍在。
在FragmentTwo点击返回键后显示FragmentOne了的log日志如下:
D/FragmentTwo: onPause
D/FragmentTwo: onStop
D/FragmentTwo: onDestroyView
D/FragmentTwo: onDestroy
D/FragmentTwo: onDetach
D/FragmentOne: onCreateView
D/FragmentOne: onStart
D/FragmentOne: onResume
说明之前对FragmentOne的视图层次销毁后,再次返回到FragmentOne时执行了onCreateView,进行视图重建。由于getFragmentManager本身会对栈内的fragment的状态进行保存,这里找到栈内的对应的fragment就可以恢复。
下面看下动图~
BaseFragment的封装优化
下面我们看下对回退栈的封装和“一activity多fragment”模式的运用。
1. 同级式fragment可以采用单独的一个activity和里面多个平级的fragment。
2. 流程式fragment可以采用activity和fragment绑定着使用。
此处的封装是针对第二种情况,在看源码之前大家可以先看下此篇博客,
案例采用的是getSupportFragmentManager,所以引入了V4包。
案例:activityC内嵌套一个fragmentOne,然后从fragmentOne跳到fragmentTwo,从fragmentTwo跳到fragmentThree。
BaseFragmentActivity源码如下:
@SuppressWarnings("RestrictedApi")
public abstract class BaseFragmentActivity extends FragmentActivity {
//获取第一个fragment
protected abstract BaseFragment getFirstFragment();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_base);
getActionBar().hide();
//避免重复添加Fragment
if (null == getSupportFragmentManager().getFragments()) {
BaseFragment firstFragment = getFirstFragment();
if (null != firstFragment) {
addFragment(firstFragment);
}
}
}
//添加fragment
public void addFragment(BaseFragment fragment) {
if (fragment != null) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_layout, fragment, fragment.getClass().getSimpleName())
.addToBackStack(fragment.getClass().getSimpleName())
.commitAllowingStateLoss();
}
}
//移除fragment
public void removeFragment() {
if (getSupportFragmentManager().getBackStackEntryCount() > 1) {
getSupportFragmentManager().popBackStack();
Log.d("removeFragment", "fragment回退栈中的个数=" + getSupportFragmentManager().getBackStackEntryCount());
} else {
finish();
}
}
//返回键返回事件
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (KeyEvent.KEYCODE_BACK == keyCode) {
if (getSupportFragmentManager().getBackStackEntryCount() == 1) {
finish();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
}
getFirstFragment()是获取首次嵌套的fragment实例。
onCreate方法里直接setContentView了布局,这是一个所有activity的根布局,因为继承BaseFragmentActivity的activity都是要嵌套fragment的,所以统一的根布局不会造成过度绘制。
addFragment方法里,以replace的形式替换fragment,并且增加了加入回退栈addToBackStack方法,commitAllowingStateLoss是为了避免状态丢失报错。
removeFragment首先判断回退栈内的fragment个数,若是大于1就抛出一个。否则说明已经剩下最后一个fragment了,可以执行activity的finish了。
BaseFragment的代码如下:
public abstract class BaseFragment extends Fragment {
protected BaseFragmentActivity baseActivity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.baseActivity = (BaseFragmentActivity) context;
}
protected abstract int getLayoutId();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(getLayoutId(), container, false);
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// TODO: 2017/3/8 此阶段fragment是独立生命阶段,可做些通用的操作,如进度条的初始化等
}
//添加fragment
protected void addFragment(BaseFragment fragment) {
if (null != fragment) {
baseActivity.addFragment(fragment);
}
}
//移除fragment
protected void removeFragment() {
baseActivity.removeFragment();
}
}
onAttach方法里面获取依附的activity实例,避免了用getActivity()为Null的情况。
getLayoutId抽象方法获取子fragment的布局。
添加和删除fragment的方法是调用activity的,这里是为了在多层嵌套fragment的添加或返回的时候调用。
activity中的源码如下:
public class ActivityC extends BaseFragmentActivity {
@Override
protected BaseFragment getFirstFragment() {
return FragmentOne.newInstance(getClass().getSimpleName());
}
}
activity中只需要通过getFirstFragment获取fragment实例即可。非常的轻~以后更换界面只需要更换fragment就可以了。
子fragment的源码如下:
public class FragmentOne extends BaseFragment {
private final static String TAG = "FragmentOne";
@BindView(R.id.show_info)
EditText showInfo;
@BindView(R.id.edit_bt)
Button editBt;
@BindView(R.id.back_bt)
Button backBt;
@Override
protected int getLayoutId() {
return R.layout.fragment_one;
}
public static FragmentOne newInstance(String msg) {
FragmentOne fragmentOne = new FragmentOne();
Bundle bundle = new Bundle();
bundle.putString(TAG, msg);
fragmentOne.setArguments(bundle);
return fragmentOne;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = super.onCreateView(inflater, container, savedInstanceState);
ButterKnife.bind(this, rootView);
return rootView;
}
@OnClick({R.id.edit_bt, R.id.back_bt})
public void onClick(View view) {
switch (view.getId()) {
case R.id.edit_bt:
addFragment(FragmentTwo.newInstance(TAG));
break;
case R.id.back_bt:
removeFragment();
break;
}
}
}
newInstance方法是对fragment的初始化,并且通过setArguments传参。
这里结合ButterKnife的运用,省去findviewById和setOnClickListener等代码。
Activity与Fragment之间通信
推荐方式:
- 因为所有的Fragment都是依附于Activity的,并且每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()在activity中获得任何Fragment实例,从而调用依附于它的fragment内的方法(activity内有fragment引用时可直接用)。
- 在fragment里声明接口,在相应的activity内实现此接口即可。这是最佳方法,考虑到fragment的复用性和activity与fragment之间的解耦。
可选方式:
在activity与fragment之间通过handler收发消息建立通信。缺点:容易引起内存泄漏。
通过广播建立通信。缺点:有些大材小用了,广播多用于一对多的情景。
通过EventBus建立通信。缺点:用多了,代码可读性会差,团队协作时维护成本高。
参考文献:
http://blog.csdn.net/tyk0910/article/details/51355026
http://www.jianshu.com/p/d9143a92ad94
http://blog.csdn.net/lmj623565791/article/details/37992017