如何回到上一个Fragment
项目开中需要经常在不同的Fragment
之间跳转,随时可能从一个Fragment
页面跳到另一个Fragment
页面。
这些页面的切换还是比较简单的,根据业务需求,相应地调用FragmentTransaction
的show()、add()、hide()、replace()
等方法,再提交事务就可以了。
当页面不是很多时,这种方法足以应付了。
但“返回上一页”这种需求随着页面入口的增加,就会越发使人抓狂了。
比如,E页面可能来自A、B、C、D
中的任一个,当需要在E页面“返回上一页”时,要判断应该返回哪一个页面,一旦入口多了就让人很头疼了。
怎么解决这种困境?
想想Activity
中“返回上一页”直接在当前页面finish()
就可以了,多么轻松简单。
Android
系统通过入栈出栈
帮我们维护了Activity
的返回栈,显示栈顶的Activity
。
Fragment
有没有类似的实现呢?
还真有,不过Fragment
的返回栈需要我们手动管理,很好玩的样子 O(∩_∩)O,让我们继续深入一探究竟!
手动管理返回栈
Fragment
返回栈和Activity
返回栈在结构上是一样的,都有入栈和出栈的动作。
所有继承FragmentActivity
的Activity
都可以通过getSupportFragmentManager()
方法获得FragmentManager
,顾名思义,得到的是当前Activity
中所有Fragment
的管理者。
入栈
一次简单的入栈操作示例如下:
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.hide(from).show(to);
transaction.addToBackStack(to.getClass().getName());
transaction.commit();
通过调用FragmentTransaction
的addToBackStack()
方法,事务提交后就会将当前操作入栈。
如果紧接着进行出栈操作,就会将当前的操作反向进行。
出栈
出栈操作就更简单了,只需一行代码,不涉及事务:
fragmentManager.popBackStack();
这个方法是异步的,还有个同步方法popBackStackImmediate()
如果返回栈中数量不为0,则返回true
,否则返回false
。
返回栈中个数
fragmentManager.getBackStackEntryCount();
获取返回栈中实例
fragmentManager.getBackStackEntryAt(0);
通过这些方法,再结合自身业务,我们就能手动维护一个Fragment
返回栈,封装好相应的方法,灵活地入栈和出栈,碰到“返回上一页”这种需求时,只需一个简单的出栈操作就可以了!
现象总结
在手动维护Fragment
返回栈时,发现:如果从A进入B入栈,在B中输入一些数据,返回A出栈,再进入B又入栈,之前在B中输入的数据还能恢复,让人不禁想看看这中间究竟经历了什么。
查看源码,可以看到出栈时只是将栈顶的Fragment
移出了数组,并没有将其销毁。
所以当它再次入栈时,便能恢复之前的数据。
那么生命周期是怎样变化的呢?
经过实验,发现相关生命周期变化如下:
显示新页面时 hide 旧页面
A 到 B
A生命周期无变化
B( onAttach() --> onViewStateRestored()--> onResume() )
B 返回 A
A生命周期无变化
B( onPause() --> onDetach() )
A 再到 B
A生命周期无变化
B( onAttach() --> onViewStateRestored()--> onResume() )
如果在B页面输入了数据,B返回A再到B,B中数据会恢复
显示新页面时 remove 旧页面
A 到 B
A( onPause() --> onDestroyView() )
B( onAttach() --> onViewStateRestored()--> onResume() )
B 返回 A
A( onCreateView() --> onViewStateRestored()--> onResume() )
B( onPause() --> onDetach() )
A 再到 B
A( onPause() --> onDestroyView() )
B( onAttach() --> onViewStateRestored()--> onResume() )
如果在B页面输入了数据,B返回A再到B,B中数据也会恢复
可以看到,再次进入B页面时,它的恢复生命周期会全部再走一遍,所有在恢复生命周期中的方法都会执行,视图需要重新生成。
如果第二次进入时不想再执行某些操作,可以在Fragment
中加个标志位:
private boolean isFirstIn = true;
首次进入后执行isFirstIn = false;
,之后再进入时isFirstIn
将保持为false
。