原文出处:http://www.ccbu.cc/index.php/android/fragment-lifecycle.html
Fragment实在是Android 3.0(API 11)中引入的,译作“碎片”。Fragment作为应用用户接口或行为的一部分而放置在Activity中。Fragment不能独立存在,只能依赖与Activity存在。Fragment拥有自己的生命周期,其生命周期受宿主Activity的控制,状态会随着Activity的状态的改变而发生改变。
因Android各个版本的Fragment有所差异,同时为了兼容低版本,support-v4库中提供了一套兼容Fragment API,最低兼容Android 1.6。
过去support-v4库是一个jar包,24.2.0版本开始,将support-v4库模块化为多个jar包,包含:support-fragment, support-ui, support-media-compat等,这么做是为了减少APK包大小,你需要用哪个模块就引入哪个模块。
Fragment是为了解决碎片态的用户界面而产生的,在处理UI方面有者自己独特的优势。Fragment拥有自己的生命周期,可以处理用户输入事件。一个Activity中可以拥有多个Fragment,一个Fragment可以被多个Activity重用。在Activity中可以动态的添加,删除,和管理Fragment。
基于上面所述的优势,可以看出,Fragment拥有以下以下特点:
上面多次提到Fragment拥有自己的生命周期,那他的生命周期是怎么样的呢,先看一下经典的Fragment生命周期图。
其中,各个生命周期函数的说明如下:
Fragment的是依于Activity而存在的,从他的生命周期图可以看出,Fragment的生命周期与Activity的生命周期是十分相似的。下图展示了fragment的生命周期与Activity生命周期函数的对应关系。
下面通过Fragment的使用实例了解在实际应用中如何来使用Fragment构建UI,同时通过这些使用实例,进一步的理解其生命周期。
创建一个自己的Fragment,只需要继承系统的Fragment或support-v4库中的Fragment类,或者是继承Fragment的子类,并实现需要的方法即可。其中一个默认的不带参数的构造函数,onCreateView()函数是必须的。onCreate,onResume,onPause()等方法也是十分常用的。
public class BlankFragment extends Fragment {
public BlankFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
Fragment作为Activity用户界面的一部分,支持View操作是其最基本的功能;其中onCreateView用来加载Fragment的布局,返回值即为加载进来的View。一般onCreateView通过加载XML布局文件来加载自己的布局,onCreateView提供了加载布局的LayoutInflater,专门供使用者来加载布局。
Fragment还有几个常用的子类,可以帮助我们来更方便的实现想要的特定功能的Fragment。包括以下几个。
静态加载Fragment即是将Fragment像其他普通View一样,直接写在layout的xml文件中。View加载时会自动进行加载。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<fragment
android:name="cc.ccbu.canvassimple.BlankFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
RelativeLayout>
在布局文件中使用fragment标签进行Fragment的添加,并指明android:name
属性制定对应的Fragment类。当系统创建此 Activity 布局时,会实例化在布局中指定的每个片段,并为每个片段调用 onCreateView()
方法,以检索每个片段的布局。系统会直接插入片段返回的 View
来替代
元素。
每个片段都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复片段(您也可以使用该标识符来捕获片段以执行某些事务,如将其移除)。 可以通过三种方式为片段提供 ID:
1.为 android:id 属性提供唯一 ID。
2.为 android:tag 属性提供唯一字符串。
3.如果您未给以上两个属性提供值,系统会使用容器视图的 ID。
静态方式加载Fragment的方式还是显得不够灵活,所以在 Activity 中我们还可以可以根据需要来执行Fragment的添加、移除、替换以及其他操作。 提交给 Activity 的每组更改都称为事务,我们可以通过使用 FragmentTransaction
的 API 来执行一项事务。每次可以将每个事务保存到由 Activity 管理的返回栈内(addToBackStack),从事使能够回退片到之前保持的状态。
Fragment newFragment = new BlankFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
transaction.commit();
上例中,newFragment
会替换在 R.id.fragment_container
ID 所标识的布局容器中的内容;通过调用 addToBackStack() 可将事务保存到返回栈,当用户按返回键时,会执行撤销事务,如果返回栈有之前保存的事务项,则以出栈的形式回退到上一个事务项状态。
如果向FragmentTransaction添加了多个更改(如add()
或 remove()
),并且调用了 addToBackStack()
,则在调用commit()
前应用的所有更改都将作为单一事务添加到返回栈,并且返回按钮会将它们一并撤消。
对于每个片段事务,您都可以通过在提交前调用 setTransition()
来应用过渡动画。
Fragment可以通过实现onCreateOptionsMenu()项Activity的OptionsMenu中添加菜单项。不过需要注意的是,必须在 onCreate() 期间调用 setHasOptionsMenu(),Fragment的onCreateOptionsMenu方法才会被调用。在Fragment中添加的所有菜单项都会被追加到现有菜单项之后。当菜单项被选中时,Fragment会收到对应的 onOptionsItemSelected() 回调。
尽管您Fragment会收到其添加的每个菜单项对应的菜单项回调,但当用户选中菜单项时,Activity 会首先收到相应的回调。 如果 Activity 对菜单项回调的实现不会处理该菜单项,则系统会将事件传递到Fragment的回调。
如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据。使用setArguments,在创建Fragment的时候传递参数,然后在fragment的onCreate方法处获取参数,但是需要注意的是setArguments()方法必须在fragment创建以后,add之前调用。
public static TestFragment newInstance(String str){
Bundle bundle = new Bundle();
bundle.putString("info", str);
TestFragment fragment = new TestFragment();
fragment.setArguments(bundle);
return fragment;
}
Fragment可以通过getActivity方法来获取Activity的实例,从而执行Activity相关的UI操作。但使用getActivity前需要注意Fragment的生命周期与Activity生命周期的对应关系,在部分生命周期函数内调用getActivity是不会返回有效的实例,获取到的是null值。
同样的,Activity中也可以通过调用 findFragmentById() 或 findFragmentByTag()方法,从 FragmentManager 获取Fragment 引用,进而来访问Fragment相应的方法。
有的时候,我们可以通过使用回调方法来实现Activity与Fragment的通信操作。
public class BlankFragment extends Fragment {
...
private OnItemClickListener mItemClickListener = null;
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mItemClickListener = (OnItemClickListener)activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnItemClickListener");
}
}
public interface OnItemClickListener {
void onItemClick(int itemId);
}
}
public class MainActivity extends Activity implements BlankFragment.OnItemClickListener {
...
@Override
public void onItemClick(int itemId) {
Log.d(TAG, "onItemClick : " + itemId);
}
}
上面的例子中,BlankFragment定义了OnItemClickListener接口,宿主Activity必须要实现该接口,在BlankFragment的onAttach方法中,通过强制类型转换,将Activity参数转为OnItemClickListener接口,如果宿主Activity没有实现该接口,会抛出ClassCastException异常。
懒加载主要用于ViewPager加载Fragment页面的情况,且ViewPager的每个子页面都是一个Fragment。默认情况下,ViewPager会执行预加载来提前加载一些页面来使得UI左右滑动效果更加流畅。ViewPager可以通过setOffscreenPageLimit(int limit)
设置预加载页面数量,但有一个最小限制,保证知识加载两个或三个页面。在一些场景下,当ViewPager中的页面不可见时,我们不希望他来加载数据时,那么此时我们就需要通过懒加载方式来加载数据。懒加载的方式库提供应用的初始化速度,同时也可以避免不必要的资源加载。
那Fragment的懒加载该如何实现呢,这里首先来认识一个方法setUserVisibleHint(boolean isVisibleToUser)
。该方法中的isVisibleToUser
参数用来表示当前Fragment是否对用户可见。当Fragment对用户可见或不可见时,该方法都会被调用。所以我们可以依据该方法,在Fragment对用户可见时在去加载数据。但需要注意的一点是,setUserVisibleHint(boolean isVisibleToUser)
方法会多次回调,而且可能会在onCreateView()
方法执行完毕之前回调。所以,在加载数据前,必须满足两个条件:
setUserVisibleHint(boolean isVisibleToUser)
参数为true。onCreateView()
方法已经执行我们可以把这些操作封装到一个基类里。
public abstract class LazyLoadFragment extends Fragment {
private boolean isUserVisible = false;
private boolean isViewCreated = false;
private boolean isDataLoaded = false;
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
isUserVisible = isVisibleToUser;
onLazyLoad();
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
isViewCreated = true;
onLazyLoad();
}
public void onLazyLoad() {
if (!isDataLoaded && isUserVisible && isViewCreated) {
loadData();
}
}
public abstract boolean loadData();
}
在子类中只用实现loadData()方法来加载数据即可。