最后一篇了,如何监听fragment中的回退事件与怎样保存fragment状态
一、如何监听Fragment中的回退事件
1、问题阐述
在Activity中监听回退事件是件非常容易的事,因为直接重写onBackPressed()函数就好了,但当大家想要监听Fragment中的回退事件时,想当然的也想着重写onBackPressed()方法,这时候你会发现:Fragment中根本就没有onBackPressed()方法给你重写。这可怎么破!
想想,在前面的例子中,我们在Activity的一个fragment_container里依次Add进fragment1,fragment2,fragment3,fragment4,在我们点击回退栈时,会将Transaction回退栈中的fragment操作一个个出栈!那,这些回退事件Fragment是从哪来的?
首先,回退事件总是发给Activity的!在发给Activity以后再由Activity自己处理。比如它将Fragment回退栈中的内容一个个出栈这种操作。
其次:大家要知道:Fragment只是Activity中的一个控件而已,虽然我们可能把他做成了像Activity一样大小覆盖整个页面,看起来跟Activity样子上没什么区别,但他还是个控件!系统怎么会给一个控件分发回退事件呢?这当然是不可能的。
2、解决方案
既然清楚了Fragment只是一个控件,而回退事件也只能在Activity中拦截。那我们就可以想办法了。
首先,我们可以在Fragment类中咱们自己写一个onBackPressed()方法来处理回调事件。
然后,可以利用回调,将要处理回退事件的fragment实例,传给Activity。
最后,在拿到fragment实例以后,就可以在Activity的onBackPress()方法中,调用这个fragment实例的onBackPressed()方法了。
这样,我们就在fragment中拦截了回退事件了。
3、实例
下面,我们就通过一个例子来看下效果。
效果图如下:
大家从下面的效果图中也可以看到,当fragment3中点击返回按钮时,捕捉了返回事件,并将fragment3上的TextView显示为”ragment3捕捉到了回退事件哦!”,但我只捕捉一次,当第二次点击时,就退出执行默认操作:即Transaction出栈。
下面看下具体的实现过程:
有关MainActivity布局及fragment的添加就不再讲了,下面直接从回调开始
1、在Fragment3中定义onBackPress()函数及处理:
- public class Fragment3 extends Fragment {
- private boolean mHandledPress = false;
- TextView tv;
- …………
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- tv = (TextView)getView().findViewById(R.id.fragment3_tv);
- }
- public boolean onBackPressed(){
- if (!mHandledPress){
- tv.setText("Fragment3 \n 捕捉到了回退事件哦!");
- mHandledPress = true;
- return true;
- }
- return false;
- }
- }
变量mHandledPress用来指定只处理一次,当处理一次以后这里的onBackPressed()就返回FALSE了.
2、在Fragment3中定义回调函数,将自己实例的引用传出去
(1)、先定义一个接口用做回调,以及对应的变量:
- protected BackHandlerInterface backHandlerInterface;
- public interface BackHandlerInterface {
- public void setSelectedFragment(Fragment3 backHandledFragment);
- }
(2)、然后是给backHandlerInterface变量赋值
跟上篇一样,我们要强制Activity实现这个接口,所以我们使用强制转换的方式来赋值。在上篇中,我们在onAttach()函数中进行的强制转换,代码如下:
- public void onAttach(Activity activity) {
- super.onAttach(activity);
- try{
- backHandlerInterface = (BackHandlerInterface) getActivity();
- }catch (Exception e){
- throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
- }
- }
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- if (!(getActivity() instanceof BackHandlerInterface)) {
- throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
- } else {
- backHandlerInterface = (BackHandlerInterface) getActivity();
- }
- }
(3)、在适当的位置将自己的实例通过回调传过去。代码如下:
- backHandlerInterface.setSelectedFragment(this);
- public void onStart() {
- super.onStart();
- backHandlerInterface.setSelectedFragment(this);
- }
- public class Fragment3 extends Fragment {
- //定义回调函数及变量
- protected BackHandlerInterface backHandlerInterface;
- public interface BackHandlerInterface {
- public void setSelectedFragment(Fragment3 backHandledFragment);
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- //回调函数赋值
- if(!(getActivity() instanceof BackHandlerInterface)) {
- throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
- } else {
- backHandlerInterface = (BackHandlerInterface) getActivity();
- }
- }
- @Override
- public void onStart() {
- super.onStart();
- //将自己的实例传出去
- backHandlerInterface.setSelectedFragment(this);
- }
- }
- public class MainActivity extends FragmentActivity implements Fragment3.BackHandlerInterface {
- private Fragment3 selectedFragment;
- …………
- @Override
- public void setSelectedFragment(Fragment3 backHandledFragment) {
- this.selectedFragment = backHandledFragment;
- }
- @Override
- public void onBackPressed() {
- if(selectedFragment == null || !selectedFragment.onBackPressed()) {
- super.onBackPressed();
- }
- }
- }
在这里实现setSelectedFragment()函数,代码如下:
- public class MainActivity extends FragmentActivity implements Fragment3.BackHandlerInterface {
- private Fragment3 selectedFragment;
- …………
- @Override
- public void setSelectedFragment(Fragment3 backHandledFragment) {
- this.selectedFragment = backHandledFragment;
- }
- }
- public void onBackPressed() {
- if(selectedFragment == null || !selectedFragment.onBackPressed()) {
- super.onBackPressed();
- }
- }
二、执行Replace操作后,怎样保存fragment状态
首先,我们先阐述一个现象,大家先看下面这个DEMO:
这个过程是这样的:
1、首先在Fragment1的EditText中先几个字
2、然后如果调用addFragment()添加Fragment2,然后当从fragment2返回时,发现这几个字还是有的。
3、但如果我们通过调用replace()添加Fragment2的话,会发现,当返回的时候,那几个字没了!
这说明了一个问题,调用addFragment添加的fragment的View会保存到视图树(ViewTree)中,其中各个控件的状态都会被保存。但如果调用replace()来添加fragment,我们前面讲到过,replace()的实现是将同一个container中的所有fragment视图从ViewTree中全部清空!然后再添加指定的fragment。由于repalce操作会把以前的所有视图全部清空,所以当使用Transaction回退时,也就只有重建每一个fragment视图,所以就导致从replace操作回退回来,所有的控件都被重建,以前的用户输入全部没了。
到这里,大家首先要明白一个问题,repalce()操作,会清空同一个container中的所有fragment视图!注意用词:请空的是fragment的VIEW!fragment的实例并不会被销毁!因为fragment的实例是通过FragmentManager来管理的。当fragment的VIEW被销毁时,fragment实例并不会被销毁。他们两个不是同时的,即在fragment中定义的变量,所上次运行中被赋予的值是一直存在的。那fragment实例什么时候会被销毁呢,当然是在不会被用到的时候才会被销毁。那什么时候不会被用到呢,即不可能再回退到这个操作的时候,就会被销毁。
在上面的例子中,fragment1虽然被fragment2的repalce操作把它的视图给销毁了,但在执行replace操作时,将操作加入到了回退栈,这时候,FragmentManager就知道,用户还可能通过回退再次用到fragment1,所以就会保留fragment1的实例。相反,如果,在执行repalce操作时,没有加入到回退栈,那FragmentManager就肯定也知道,用户不可能再回到上次那个Fragment1界面了,所以它的fragment实例就会在清除fragment1视图的同时也被清除了。
说了那么多,现在如果我们想在利用repace操作的时候,同时保存上一个fragment界面的状态,那要怎么办?
方法一:控件状态保存与还原
上面我们讲到,在清除Fragment视图的时候,如果我们将操作同时加入到回退栈,那么它的VIEW虽然从ViewTree中清除了,但它的实例会被保存在FragmentManager中,那它的变量也会一直保存着,直到下次回来。但视图在回来的时候会重建。
那第一个方法来了,我们可以用一个变量来保存EditText当前字符串,在replace前将EditText中的值保存在这个变量中,当返回来再次创建视图时,再次给EditTxt赋值不就好了。
代码如下:
- public class Fragment1 extends Fragment {
- private String mEditStr;
- private EditText editText;
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment1, container, false);
- editText = (EditText)rootView.findViewById(R.id.fragment1_edittext);
- editText.setText(mEditStr);
- return rootView;
- }
- @Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
- Button btnReplace = (Button)getView().findViewById(R.id.fragment1_repalce);
- btnReplace.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mEditStr = editText.getText().toString();
- …………
- }
- });
- …………
- }
- }
第一步:在repalce前保存状态
- mEditStr = editText.getText().toString();
- editText.setText(mEditStr);
方法二:只需要为控件添加ID值
在实时中还遇到一个解决方法,就是给EditText控件添加上id,只要给EditText控件添加上id,不需要上面的那些replace前的值的保存即创建时的还原,它的内容就会被保存。不知道其它控件是否也可以通过添加ID值的方式来保存用户的输入值,即:
- <EditText
- android:id="@+id/fragment1_edittext"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:gravity="top|left"
- android:background="#ffffff"
- android:hint="这里是EditText,在这里输入文字哦"/>
方法三:保存FragmentView视图
方法一和方法二感觉都还是太靠谱的解决方法,既然fragment中的变量都会被保存,那我们直接将Fragment的视图直接保存到变量中,在系统在利用onCreateView()创建视图的时候,我们直接返回保存的视图不就得了。基于上面的想法,代码上我们这样做:
1、创建一个变量,保存Fragment的视图:
- private View rootView;
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- return getPersistentView(inflater, container, savedInstanceState, R.layout.fragment1);
- }
- public View getPersistentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState, int layout) {
- if (rootView == null) {
- // Inflate the layout for this fragment
- rootView = inflater.inflate(layout, container,false);
- } else {
- ((ViewGroup) rootView.getParent()).removeView(rootView);
- }
- return rootView;
- }