Fragment-如何监听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;
}
}
上面的代码,没什么难度,就是定义了一个onBackPressed()函数,其返回一个布尔值;意思是,如果对返回事件进行了处理就返回TRUE,如果不做处理就返回FALSE,让上层进行处理。
变量mHandledPress用来指定只处理一次,当处理一次以后这里的onBackPressed()就返回FALSE了.
2、在Fragment3中定义回调函数,将自己实例的引用传出去
(1)、先定义一个接口用做回调,以及对应的变量:
protected BackHandlerInterface backHandlerInterface;
public interface BackHandlerInterface {
public void setSelectedFragment(Fragment3 backHandledFragment);
}
注意,在回调中传进去的是Fragment3的实例!因为我们要在主Activity处理onBackPress()时,调用我们在Fragment3中自己写的onBackPressed()函数,所以我们要传进去Fragment3的实例
(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");
}
}
其实在onAttach()回调时就已经把Fragment与Activity绑定在了一起,所以只要生命流程在onAttach()之后的任意一个生命周期,我们都可以通过getActivity来获取Activity的实例,来进行强制转换,所以在这里我们就换个地方,在onCreate()函数中来做:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!(getActivity() instanceof BackHandlerInterface)) {
throw new ClassCastException("Hosting activity must implement BackHandlerInterface");
} else {
backHandlerInterface = (BackHandlerInterface) getActivity();
}
}
这里抛出异常也没有使用try...catch...来做,而是直接利用instanceof来判断当前Activity是不是BackHandlerInterface的实例,即是否已经派生了BackHandlerInterface,如果没有就直接抛异常,如果派生了就强制转换赋值。
(3)、在适当的位置将自己的实例通过回调传过去。代码如下:
backHandlerInterface.setSelectedFragment(this);
有关这个设置Fragment3实例的代码,只要在生命周期中Fragment3实例已经产生了都可以设置,即可以放在生命周期在onCreate()后的函数里,即onCreate()、onCreateView()、onActivityCreated()、onStart();虽然经过我测试,放在这几个函数中的任意一个都是可行的,但onActivityCreated()后才是Activity最终onCreate()执行完,所以放在onActivityCreated()或onStart()中是最保险的。所以这里放在了onStart()中来处理,代码如下:
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);
}
}
3、在MainActivity中,回退拦截,代码如下:
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();
}
} }
(1)、首先,将MainActivity实现Fragment3.BackHandlerInterface接口
在这里实现setSelectedFragment()函数,代码如下:
public class MainActivity extends FragmentActivity implements Fragment3.BackHandlerInterface {
private Fragment3 selectedFragment;
…………
@Override
public void setSelectedFragment(Fragment3 backHandledFragment) {
this.selectedFragment = backHandledFragment;
}
}
(2)、然后在onBackPressed()回调中进行回退拦截
public void onBackPressed() {
if(selectedFragment == null || !selectedFragment.onBackPressed()) {
super.onBackPressed();
}
}
注意这里的逻辑,在调用super.onBackPressed();的前提是selectedFragment.onBackPressed()返回FALSE,即Fragment3中的onBackPressed()返回FALSE,即不再拦截回退事件,才会执行默认的操作。
二、执行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;
2、然后来看onCreateView的实现
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return getPersistentView(inflater, container, savedInstanceState, R.layout.fragment1);
}
可以看到,相比以前直接返回inflater.inflate(R.layout.fragment1, container,false);重建视图,这里返回的是一个getPersistentView()函数,下面看看这个函数的实现:
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;
}
这段代码就是返回rootView的。即当rootView==null,即第一次创建时,就利用inflater.inflate()来创建初始化状态的视图,当下次再进到这个界面时,比如下面的通过回退操作进入到fragment1时,这时候的rootView就不再是空了。但在onCreateView()中返回的视图是要添加到ViewTree中去的。而这里的rootView视图在上次已经添加到里面去了,一个视图实例不能被add两次,不然就会被下面这个错误!所以,我们针对这种情况,如果rootView已经存在于ViewTree中的时候,要先从ViewTree中移除。
好了,到这里就讲完了,源码都会在下面给出。下面先来看看最终的效果图吧:
Fragment-如何监听fragment中的回退事件与怎样保存fragment状态的更多相关文章
- android fragment轻松监听返回键/Fragment中的popupwindow响应返回键隐藏
现在的开发我们基本上都是一个主activity中放多个fragment,点击返回按钮的时候,直接退出主activity,但是我们在fragment中经常会弹出例如popupWindow这样的布局,用户 ...
- js 实时监听input中值变化
注意:用到了jquery需要引入jquery.min.js. 需求: 1.每个地方需要分别打分,总分为100; 2.第一个打分总分为40; 3.第二个打分总分为60. 注意:需要判断null.&quo ...
- oracle11g的监听配置文件中的program和env两个配置,必须干掉,客户端才能正常连接
oracle11g的监听配置文件中的program和env两个配置,必须干掉,客户端才能正常连接 oracle11g的监听配置文件中的program和env两个配置,必须干掉,客户端才能正常连接 or ...
- Android 监听 Android中监听系统网络连接打开或者关闭的实现代码
本篇文章对Android中监听系统网络连接打开或者关闭的实现用实例进行了介绍.需要的朋友参考下 很简单,所以直接看代码 复制代码 代码如下: package xxx; import android.c ...
- js中对arry数组的各种操作小结 瀑布流AJAX无刷新加载数据列表--当页面滚动到Id时再继续加载数据 web前端url传递值 js加密解密 HTML中让表单input等文本框为只读不可编辑的方法 js监听用户的键盘敲击事件,兼容各大主流浏览器 HTML特殊字符
js中对arry数组的各种操作小结 最近工作比较轻松,于是就花时间从头到尾的对js进行了详细的学习和复习,在看书的过程中,发现自己平时在做项目的过程中有很多地方想得不过全面,写的不够合理,所以说啊 ...
- Spring Boot 监听 Activemq 中的特定 topic ,并将数据通过 RabbitMq 发布出去
1.Spring Boot 和 ActiveMQ .RabbitMQ 简介 最近因为公司的项目需要用到 Spring Boot , 所以自学了一下, 发现它与 Spring 相比,最大的优点就是减少了 ...
- vue监听页面中的某个div的滚动事件,并判断滚动的位置
在开发中常常会遇到这样一个vue页面,页面分为左右两部分,左边是目录树,右边是一个类名为xq-box的div,在xq-box中多个div上下并列布局,每个div中的内容就对应着左边目录树中的相应节点, ...
- 监听页面中的某个div的滚动事件,并将其滚动距离保存到cookie
在html中,写一个id为type的div: <div class="type" id="type"></div> css: .type ...
- Android实现监听控件点击事件
Android实现监听控件点击事件 引言 这篇文章主要想写一下Android实现监听点击事件的几种方法,Activity和Fragment实现起来有些方法上会有些不同,这里也略做介绍. 最近一直在忙一 ...
随机推荐
- NodeJS学习笔记 (11)网络UDP-dgram(ok)
模块概览 dgram模块是对UDP socket的一层封装,相对net模块简单很多,下面看例子. UPD客户端 vs UDP服务端 首先,启动UDP server,监听来自端口33333的请求. se ...
- node使用express命令报错找不到ejs的解决方法
首先确定已经全局安装过好几遍express和express-generator,但一使用express命令直接报找不到ejs模块,全局和本地安装ejs都没用,nodemon模块报同样错误,找不到deb ...
- mpstat---用于多CPU环境下,显示各个可用CPU的状态
mpstat命令指令主要用于多CPU环境下,它显示各个可用CPU的状态系你想.这些信息存放在/proc/stat文件中.在多CPUs系统里,其不但能查看所有CPU的平均状况信息,而且能够查看特定CPU ...
- iostat---监视磁盘CPU相关信息
iostat命令被用于监视系统输入输出设备和CPU的使用情况.它的特点是汇报磁盘活动统计情况,同时也会汇报出CPU使用情况.同vmstat一样,iostat也有一个弱点,就是它不能对某个进程进行深入分 ...
- Object-C,NumberDemo和StringDemo
晚上抽空写了2个Demo.第1个是演示字符串,第2个是演示数组和可变数组.只用到了几个基本的方法和基本语句. NSString字符串对象,NSNumber和NSMutableNumber,if语句,N ...
- HDU 4862 Jump 费用流
又是一个看了题解以后还坑了一天的题…… 结果最后发现是抄代码的时候少写了一个负号. 题意: 有一个n*m的网格,其中每个格子上都有0~9的数字.现在你可以玩K次游戏. 一次游戏是这样定义的: 你可以选 ...
- XML和Schema命名空间详解
来源:https://blog.csdn.net/wanghuan203/article/details/9204337 XML和Schema具有无关平台,技术厂商,简单,规范统一等特点,极具开放性, ...
- 洛谷 P1294 高手去散步
P1294 高手去散步 题目背景 高手最近谈恋爱了.不过是单相思.“即使是单相思,也是完整的爱情”,高手从未放弃对它的追求.今天,这个阳光明媚的早晨,太阳从西边缓缓升起.于是它找到高手,希望在晨读开始 ...
- ArcGIS api for javascript——加入地图并显示当前地图范围
描述 这个示例使用Map.extent property属性接收地图范围的左下角和右上角坐标 "书签". 使用下列行创建地图: var map = new esri.Map(&qu ...
- Android——bootchart
bootchart:android原生自带的开机性能查看机制.通过收集android开机过程中的各种log数据,终于能够图表的形式展现各个进程在开机过程中的性能.(博客不能断-) 撰写不易,转载需注明 ...