Android中Fragment监听返回按钮及返回栈BackStack的一些处理

时间:2021-08-03 22:36:28

考虑到耦合性,这篇博客的重点是在Fragment代码里面处理返回按钮的事件,达到返回上一个Fragment的目的,利用一些数据传递,tag,接口什么的最终还是在activity的onBackPressed处理事件的方法就重点不介绍了

我们知道Fragment是没有onBackPressed方法的,所有如果你想达到点击返回按钮就跳转到上一次打开的Fragment:

1,返回栈addToBackStack(推荐)

简单,方便
原理方法就是在replace Fragment的时候将将当前fragment加入返回栈:

  protected void addFragmentWithBackStack(BaseFragment fragment) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.main_frame, fragment, fragment.getClass().getName());
transaction.addToBackStack(null);//将fragment加入返回栈
transaction.commit();
}

transaction.addToBackStack(null)是将当前的Fragment放入一个ArrayList里面,当你点击返回按钮以后就会destroy当前显示的Fragment,并visible上一个Fragment,这里建议是replace而不能是add,他们的区别就不多说了,好处就是用replace在点击返回按钮的时候会重新onstart和onresume,这样在返回的时候就可以更新页面.
你可以在点击返回的时候监听这个事件:
返回的监听方法:

 public void onCreate(@Nullable Bundle savedInstanceState) {
getFragmentManager().addOnBackStackChangedListener(this);//注册监听
context = (AppCompatActivity) getActivity();
super.onCreate(savedInstanceState);

}
..........
//执行的方法
@Override
public void onBackStackChanged() {

if (BaseFragment.this.isVisible()) {
Log.i(this.getClass().getName(), "isVisible");
//当前fragment可见时(如果是界面的跟新在onstart或onresume就好了,isVisible()方法更大的用处是在于可以做些操作避免当前Fragment不被重复加入返回栈).....
} else {
Log.i(this.getClass().getName(), "unisVisible");
//当前fragment不可见时候.......
}
Log.i(this.getClass().getName(), "change");
}

2 监听返回按钮事件KeyEvent.KEYCODE_BACK(有bug)

//FragmentTransaction tx = fragmentManager.beginTransation();
//tx.replace( R.id.fragment, new MyFragment() ).addToBackStack( "tag" //).commit();
//If you require more detailed control (i.e. when some Fragments are visible, you want to suppress the back key) you can set an OnKeyListener on the parent view of your fragment:

//You need to add the following line for this solution to work; thanks skayred
fragment.getView().setFocusableInTouchMode(true);
fragment.getView().requestFocus();
fragment.getView().setOnKeyListener( new OnKeyListener()
{
@Override
public boolean onKey( View v, int keyCode, KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_BACK )
{
return true;
}
return false;
}
} );

这个方法有一个很大的需求就是需要Fragment的rootview获取焦点:
fragment.getView().setFocusableInTouchMode(true);
fragment.getView().requestFocus();
当fragment里editText等抢走焦点的话监听就没有作用了,还要想办法再让他获得焦点(好像并不容易)

3接口

方法的主要思想是写一个公共接口然后在activity里的onBackPressed执行这个接口里的方法在Fragment里实现这个接口,这里提供一个减少Fragment和activity之间耦合度的思想
要点:
1 接口实例化的对象写在application里
2 让接口单利
3 onBackPressed使用完记得吧接口对象制空(记得要有判空操作).
这样就可以避免把接口的实例化方法(set方法)写在activity里使得Fragment必须要调用activity里的方法

此外网上还有很多其他方法,欢迎交流,共同进步

4,Activity定义一个静态变量,在每个Fragment的适当的生命周期方法改变这个变量的值

public class MainActivity extends BaseActivity {
.......
public static String tag="";
@Override
public void onBackPressed() {
/* if (tag.equals("BaseFragment")) {
doYourThingsWhenEquals();
return;
}*/

if (!tag.equals(""))
doYourThingsWhenEquals();
super.onBackPressed();
}


private void doYourThingsWhenEquals() {
FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(tag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
return;//直接结束,不走系统默认的onBackPressed
tag="";//这里重新置空,很重要


}

public class BaseFragment extends Fragment {
@Override
public void onstart(){
MainActivity.tag=fragment.getClass().getName();
}
}

补充 保持返回栈元素惟一

由于返回栈是一个list 所有返回栈可以保留多个相同Fragment的不同实例化的对象
例如:当你由于一些原因连续执行两次方法1里的addFragment(new MyFragment ) 那么当你再点击返回按钮会是当前Fragment的上一个实例化的对象:

这里写了3种错误的尝试和一种正确的方法,欢迎大家跳过错误的尝试继续研究交流正确的方法.

首先是写一个返回栈的方法(为了演示错误的尝试):

 protected void addFragmentWithoutBackStack(BaseFragment fragment) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.main_frame, fragment, fragment.getClass().getName());
// transaction.addToBackStack(null);
transaction.commit();
}

然后修改最上述标题1里的方法

protected void addFragment(BaseFragment fragment) {
FragmentManager fm = getSupportFragmentManager();
fm.popBackStack(fragment.getClass().getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);//这是当前fragment在打开fragment相当于先按了返回键
//这是目前我找到惟一保证Fragment二次打开后返回栈元素惟一而又能刷新的 无bug的方法

FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.main_frame, fragment, fragment.getClass().getName());
transaction.isAddToBackStackAllowed();
transaction.addToBackStack(fragment.getClass().getName());
transaction.commit();
}
//下面是3种**失败**的尝试作为参考
requestsFollowUpFragment 是RequestsFollowUpFragment的一个全局变量
//尝试1 当Fragment是第二次打开时的处理(requestsFollowUpFragment!=null,我们知道ondestory以后Fragment的实例化也并不会随之被回收):不加入返回栈 有bug
if (requestsFollowUpFragment == null) {
requestsFollowUpFragment = new RequestsFollowUpFragment();
addFragment(requestsFollowUpFragment);}
else{
addFragmentWithoutBackStack(requestsFollowUpFragment);
}
//尝试2 当Fragment是第二次打开时(requestsFollowUpFragment!=null)的处理:将当前fragmen变量重新赋值一个新的对象(new 一个新的),有bug ,因为单纯的new 并不走生命周期方法
if (requestsFollowUpFragment == null) {
requestsFollowUpFragment = new RequestsFollowUpFragment();
addFragment(requestsFollowUpFragment);}
else{
requestsFollowUpFragment = new RequestsFollowUpFragment();
}
//尝试3 只当fragment不可见的时候执行此方法,否则什么都不做,有bug ,因为这样再第二次打开的时候也不走生命周期方法,达不到更新的目的
if(requestsFollowUpFragment==null){
Log.i("+++++", "null");
requestsFollowUpFragment = new RequestsFollowUpFragment();}
if (requestsFollowUpFragment.isVisible()) {
Log.i("+++++", "isVisible");
} else {
Log.i("++++", "unisVisible");
// addFragment(requestsFollowUpFragment);
}

写了一大堆其实只是在标题1 的方法基础上添加了一句话

fm.popBackStack(fragment.getClass().getName(), FragmentManager.POP_BACK_STACK_INCLUSIVE);

它的作用先把标记为fragment.getClass().getName()及他之上的所有内容出栈并显示上一个Fragment (这句话之后立即执行了replace,所有显示基本看不到,但上一个页面的确走了整个生命周期方法) ,但是让前一个Fragment重新走了整个生命周期方法对我们来说浪费资源而且没有什么实质性的作用,所以,你懂得……..