Fragment概述
Fragment是Activity中用户界面的一个行为或者说是一部分。主要是支持大屏幕上动态显示和更为灵活的去组合或是交换UI组件,通过将Activity的布局分割成若干个fragment,可以在运行时编辑activity的呈现,并且那些变化会被保存在由activity管理的后台栈里面。
Fragment必须总是被嵌入到一个activity之中,并且fragment的生命周期直接接受其宿主activity的生命周期的影响。你可以认为fragment是activity的一个模块零件,它有自己的生命周期,接收它自己的输入的事件,并且可以在activity运行时添加或者删除。
应该将每一个fragment设计为模块化和可复用化的activity组件。也就是说,你可以在多个activity中引用同一个fragment,因为fragment定义了它自己的布局,并且使用它本身生命周期回调的行为。
- 先看fragment生命周期图:
- 在看Fragment依附于Activity的生命状态图:
Fragment生命周期中的那么多方法,快来学习一下吧!go go go
- new 一个Fragment,Fragment只是调用了自身的空参数构造方法,并没有其他操作。
Fragment生命周期方法含义:
public void onAttach(Context context)
onAttach()方法会在Fragment与Activity窗口关联后立刻调用。从该方法开始,就可以通过Fragment.getActivity()方法获取与Fragment关联的Activtiy窗口对象,但因为Fragment的控件未初始化,所以不能够操作控件。public void onCreate(Bundle savedInstanceState)
在调用完onAttach()执行完之后,立即就会调用onCreate()方法,可以在Bundle对象中获取一些在Activity中传过来的数据。通常会在该方法中读取保存的转态,获取或初始化一些数据。在该方法中不要进行耗时操作,不然Activity窗口不会显示。public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
该方法是Fragment很重要的一个生命周期方法,因为会在该方法中创建Fragment显示的View,其中inflater是用来装载布局文件的,container是<fragment>标签的父标签对应对象,saveInstanceState参数可以获取Fragment保存的转态,如果未保存那么就为null。public void onViewCreated(View view,Bundle savedInstanceState)
Android在创建完Fragment中的View对象之后,会立刻回调该方法。其中view参数就是onCreateView中返回的view,而bundle对象用于一般用途。public void onActivityCreated(Bundle savedInstanceState)
在Activity的onCreate()方法执行完之后,Android系统会立刻调用该方法,表示Activity窗口已经初始化完成,从这一个时候开始,就可以在Fragment中使用getActivity().findViewById(R.id.xxx);来操作Activity中的view了。public void onStart()
这个没啥可讲的,但是一个细节需要知道,当系统调用该方法的时候,fragment已经显示在UI上了,但还不能进行互动,因为onResume()方法还没有执行完。public void onResume()
该方法为fragment从创建到显示Android系统调用的最后一个生命周期方法,调用完该方法时候,fragment就可以与用户互动了。public void onPause()
fragment由活动状态变成非活跃执行的第一个回调方法,通常可以在这个方法中保存一些需要临时暂停的工作。例如:存音乐播放速度,然后在onResume()方法中恢复音乐播放进度。(注意不能执行太耗时的操作,不然会导致另外一个fragment的onResume要展示得等到这个fragment的onPause执行完才会执行)public void onStop()
当onStop()返回的时候,fragment将从屏幕上消失。public void onDestoryView()
该方法的调用意味着在onCreateView()中创建的视图都将被移除。public void onDestroy()
Android在Fragment不再使用时会调用该方法,要注意的是~这是Fragment还和Activity是藕断丝连!并且可以获得Fragment对象,但无法对获得的Fragment进行任何操作。public void onDetach()
为Fragment生命周期中的最后一个方法,当该方法执行完后,Fragment与Activity不再有关联。
Fragment比Activity多了几个额外的生命周期回调方法:
- onAttach(Activity):当Fragment和Activity发生关联时使用。
- onCreateView(LayoutInflater,ViewGroup,Bundle):创建该Fragment的视图
- onActivityCreate(Bundle):当Activity的onCreate()方法返回时调用
- onDestoryView():与onCreateView相对应,当该Fragment的视图被移除时调用
- onDetach():与onAttach()相对应,当Fragment与Activity关联被取消时调用
注意:除了onCreateView()方法,其他的所有的方法如果你重写了,必须调用父类对于该方法的实现
管理fragment生命周期与管理activity生命周期很相像
像activity一样,fragment也有三种状态:
- Resumed
fragment在运行中的activity中可见 - Paused
另一个activity处于前台且得到焦点,但是这个fragment所在的activtiy仍然可见(前台activity部分透明,或者没有覆盖全屏)。 - Stopped
fragment不可见。要么宿主activity已经停止,要么fragment已经从activity上移除,但已被添加到后台栈中。一个停止的fragment仍然活着(所有的状态和成员信息仍然由系统保留着)。但是,它对于用户来讲已经不再可见,并且如果activity被杀掉,它也将被杀掉。
————————————————————————————————
如果activity的进程被杀掉了,在activity被重新创建时,你恢复fragment状态。可以执行fragment的onSaveIntanceState()来保存状态(注意:fragment是在onCreate(),onCreateView()或者onActivityCreate()中进行恢复)
在生命周期方面,activity和fragment之间一个很重要的不同就是在各自的后台栈中是如何存储的。当activity停止时,默认情况下activity被安置在由系统管理的activity后台栈中;fragment仅当在一个事务被移除时,通过显式调用addToBackStack()请求保存的实例,该fragment才被置于由宿主activity管理的后台栈。
要创建一个fragment,必须创建一个fragment的子类,一般情况下,我们至少需要实现以下几个fragment生命周期的方法:onCreate(),onCreateView(),onPause()
inflate()函数需要以下三个参数:@Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); }
(1).要inflate的布局的资源Id
(2).被inflate的布局的父ViewGroup
(3).一个布尔值,表明在inflate期间被inflate的布局是否应该附上ViewGroup(第二个参数container)。(在这个例子中传入的是false,因为系统已经将被inflate的布局插入到容器中(container)——传入true会在最终的布局里创建一个多余的ViewGroup)
1:>显示:add(), replace(), show(), attach()
2:>隐藏:remove(), hide(), detach()
说明:调用show() & hide()方法时,Fragment的生命周期方法并不会被执行,仅仅是Fragment的View被显示或者隐藏。执行replace()(相当于remove和add的合体)时(至少两个Fragment),会执行第二个Fragment的onAttach()方法、执行第一个Fragment的onPause()-onDetach()方法,同时containerView会detach第一个Fragment的View。add()方法执行onAttach()-onResume()的生命周期,相对的remove()就是执行完成剩下的onPause()-onDetach()周期。remove会销毁整个Fragment实例,而detach则只是销毁其视图结构
(因为detach后,只被调用到了onPause
、onStop
和onDestroyView
方法,onDestroy
和onDetach
方法并未被调用到。)
主要的操作都是FragmentTransaction的方法:
a、获取FragmentManage的方式:
getFragmentManager() // v4中,getSupportFragmentManager
b、主要的操作都是FragmentTransaction的方法
FragmentTransaction transaction = fm.benginTransatcion();//开启一个事务
- transaction.add()
往Activity中添加一个Fragment - transaction.remove()
从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁。 - transaction.replace()
使用另一个Fragment替换当前的,实际上就是remove()然后add()的合体 - transaction.hide()
隐藏当前的Fragment,仅仅是设为不可见,并不会销毁, - transaction.show()
显示之前隐藏的Fragment注意:当使用add(),show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged(),这点要切记。
- detach()
会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。//当fragment被加入到回退栈的时候,该方法与*remove()*的作用是相同的, //反之,该方法只是将fragment从视图中移除, //之后仍然可以通过*attach()*方法重新使用fragment, //而调用了*remove()*方法之后, //不仅将Fragment从视图中移除,fragment还将不再可用。
- attach()
重建view视图,附加到UI上并显示。Fragment
只有在attach后,才会受到Activity生命周期的影响,调用到自己的生命周期。 - transatcion.commit()
提交一个事务
注意:当使用add(),show(),hide()跳转新的Fragment时,旧的Fragment回调onHiddenChanged(),不会回调onStop()等生命周期方法,而新的Fragment在创建时是不会回调onHiddenChanged(),这点要切记。
在调用commit()之前,可以将事务添加到fragment事务后台栈中(通过调用addToBackStatck())。这个后台栈由activity管理,并且允许用户通过按BACK键回退到前一个fragment状态。
下面的代码中一个fragment代替另一个fragment,并且将之前的fragment状态保留在后台栈中:
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
注意:
如果添加多个变更事务(例如另一个add()或者remove())并调用addToBackStack(),那么在调用commit()之前的所有应用的变更被作为一个单独的事务添加到后台栈中,并且BACK键可以将它们一起回退。
当移除一个fragment时,如果调用了addToBackStack(),那么之后fragment会被停止,如果用户回退,它将被恢复过来。
调用commit()并不立刻执行事务,相反,而是采取预约方式,一旦activity的界面线程(主线程)准备好便可运行起来。然而,如果有必要的话,你可以从界面线程调用executePendingTransations()立即执行由commit()提交的事务。
只能在activity保存状态(当用户离开activity时)之前用commit()提交事务。如果你尝试在那时之后提交,会抛出一个异常。这是因为如果activity需要被恢复,提交后的状态会被丢失。对于这类丢失提交的情况,可使用commitAllowingStateLoss()
管理Fragment回退栈
- 跟踪回退栈的状态
我们通过实现OnBackStackChangedListener
接口来实现回退栈状态跟踪,具体代码如下://implements接口 public class XXX implements FragmentManager.OnBackStackChangedListener //实现接口所要实现的方法 @Override public void onBackStackChanged() { //do whatevery you want } //设置回退栈监听接口 getSupportFragmentManager().addOnBackStackChangedListener(this);
- 管理回退栈
(1).FragmentTransaction.addToBackStack(String)
将一个刚刚添加的Fragment加入到回退栈中
(2).getSupportFragmentManager().getBackStackEntryCount()
获取回退栈中的实体数量
(3).getSupportFragmentManager().popBackStack(String name, int flags)
根据name立刻弹出栈顶的fragment
(4).getSupportFragmentManager().popBackStack(int id, int flags)
根据id立刻弹出栈顶的fragment
【关于ViewPager适配器相关类:FragmentPagerAdapter与FragmentStatePagerAdapter】
- FragmentPagerAdapter:对于不再需要的fragment,选择调用detach方法,仅销毁视图,并不会销毁fragment实例。
- FragmentStatePagerAdapter:会调用remove方法,销毁不再需要的fragment,当当前事务提交以后,会彻底的将fragment从当前Activity的FragmentManager中移除,state标明,销毁时,会将其onSaveInstanceState(Bundle outState)中的bundle信息保存下来,当用户切换回来,可以通过该bundle恢复生成新的fragment,也就是说,你可以在onSaveInstanceState(Bundle outState)方法中保存一些数据,在onCreate中进行恢复创建。
- 总结:使用FragmentStatePagerAdapter当然更省内存,但是销毁新建也是需要时间的。一般情况下,如果你是制作主页面,就3、4个Tab,那么可以选择使用FragmentPagerAdapter,如果你是用于ViewPager展示数量特别多的条目时,那么建议使用FragmentStatePagerAdapter。
因为Adapter destoryItem 要吗调用detach要吗调用remove,所以不在缓存的队列中则会走非fragment的一些生命周期,不像show()和Hide不调用什么周期。具体参考文章:http://www.jianshu.com/p/25a02f5a15b3
FragmentTabHost中的fragment的生命周期
FragmentTabHost不保存状态是因为切换fragment的时候是使用detach和attach来Fragment的隐藏和显示的,这样的话每次切换肯定要重新加载布局,与fragmentpageradapter相似。
问题:使用FragmentTabHost时,Fragment之间切换时每次都会调用 onCreateView方法,导致每次 Fragment的布局都重绘,无法保持Fragment原有状态。
解决办法:在Fragment onCreateView方法中缓存View
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
if (view == null) {
view = inflater.inflate(R.layout.fragment_tableindex, container, false);
}
// 缓存的viewiew需要判断是否已经被加过parent,
// 如果有parent需要从parent删除,要不然会发生这个view已经有parent的错误。
ViewGroup parent = (ViewGroup) view.getParent();
if (parent != null) {
parent.removeView(view);
}
return view;
}
或者修改fragmenttabHost源码,具体可以参考:http://www.jianshu.com/p/4d4a83945193
Fragment 实现懒加载:
主要方法:
setUserVisibleHint(一般用在Viewpager)表示此fragment是否可见
这个方法仅仅工作在FragmentPagerAdapter中,不能被使用在一个普通的activity中。
因此我这个单个fragment 就是不被调用的 ,可以重写 onHiddenChanged() 方法来得知当前 fragment 的状态。
setUserVisibleHint此方法可能在frgament生命周期以外调用所以,必须判控防止控件未初始化。
所以想要直接在setUserVisibleHint中进行判断,然后直接进行数据的加载是行不通的。我们需要进行双重的判断
可以这样:
public abstract class BaseLazyOnlyOnceFragment extends Fragment { protected View mRootView; public Context mContext; protected boolean isVisible; private boolean isPrepared; private boolean isFirst = true; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); EventBus.getDefault().register(this); mContext = getActivity(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (mRootView == null) { mRootView = inflater.inflate(initViewLayoutId(), null); ButterKnife.bind(this, mRootView); initView(mRootView); } ViewGroup parent = (ViewGroup) mRootView.getParent(); if (parent != null) { parent.removeView(mRootView); } // Log.d("TAG", "fragment->onCreateView"); return mRootView; } @Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Log.d("TAG", "fragment->onActivityCreated"); isPrepared = true; lazyLoad(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); // Log.d("TAG", "fragment->setUserVisibleHint"); if (getUserVisibleHint()) { isVisible = true; lazyLoad(); } else { isVisible = false; onInvisible(); } } //懒加载 protected void lazyLoad() { if (!isPrepared || !isVisible || !isFirst) { return; } Log.d("TAG", getClass().getName() + "->initData()"); initData(); isFirst = false; } //do something protected void onInvisible() { } // 传入布局id public abstract int initViewLayoutId(); //根据View 初始化操作 public abstract void initView(View view); //加载数据 public abstract void initData(); @Override public void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); } @Subscribe(threadMode = ThreadMode.MainThread) public void noneGoEvent(EmptyEvent emptyEvent) { } }
/** * Created by lqh on 2017/7/13. * 这里我们做了三个判断,判断isPrepared和isVisible和isFirst只有全为true,才去执行initData()方法加载网络(或本地)数据。 ①isPrepared参数在系统调用onActivityCreated时设置为true,这时onCreateView方法已调用完毕(一般我们在这方法里执行findviewbyid等方法),确保 initData()方法不会报空指针异常。 ②isVisible参数在fragment可见时通过系统回调setUserVisibileHint方法设置为true,不可见时为false,这是fragment实现懒加载的关键。 ③isFirst确保ViewPager来回切换时BaseFragment的initData方法不会被重复调用,initData在该Fragment的整个生命周期只调用一次,第一次调用initData()方法后马上执行 isFirst = false。 */这是实现只加载一次,而且缓存了View。如果要实现每次fragment可见,就实现加载数据,可以去除isFirst这个判断。
参考:http://www.jianshu.com/p/94bede7d6f46
http://www.jianshu.com/p/38f7994faa6b