望远山,知近路,而后自得其乐!

Fragment生命周期及基本使用

什么是Fragment

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的特点

Fragment是为了解决碎片态的用户界面而产生的,在处理UI方面有者自己独特的优势。Fragment拥有自己的生命周期,可以处理用户输入事件。一个Activity中可以拥有多个Fragment,一个Fragment可以被多个Activity重用。在Activity中可以动态的添加,删除,和管理Fragment。

基于上面所述的优势,可以看出,Fragment拥有以下以下特点:

  • 独立性 Fragment拥有独立的生命周期,可以独立处理所有的用户交互事件
  • 模块化 将某一功能封装到一个Fragment中供Activity使用
  • 复用性 一个Fragment可以被多个Activity复用
  • 灵活性 Fragment不但可以按照特定功能进行单独封装,还可以在Activity中进行动态的添加,删除和管理。

Fragment生命周期

上面多次提到Fragment拥有自己的生命周期,那他的生命周期是怎么样的呢,先看一下经典的Fragment生命周期图。

其中,各个生命周期函数的说明如下:

  • onAttach():Fragment和Activity相关联时调用。可以通过该方法获取 Activity引用,还可以通过getArguments()获取参数。
  • onCreate():系统在Fragment被创建时调用。
  • onCreateView():创建Fragment的布局,如果片段未提供 UI,您可以返回 null。
  • onActivityCreated():当Activity完成onCreate()时调用。
  • onStart():当Fragment可见时调用。
  • onResume():当Fragment可见且可交互时调用。
  • onPause():当Fragment不可交互但可见时调用。
  • onStop():当Fragment不可见时调用。
  • onDestroyView():当Fragment的UI从视图结构中移除时调用。
  • onDestroy():销毁Fragment时调用。
  • onDetach():当Fragment和Activity解除关联时调用。

Fragment的是依于Activity而存在的,从他的生命周期图可以看出,Fragment的生命周期与Activity的生命周期是十分相似的。下图展示了fragment的生命周期与Activity生命周期函数的对应关系。

Fragment的使用

下面通过Fragment的使用实例了解在实际应用中如何来使用Fragment构建UI,同时通过这些使用实例,进一步的理解其生命周期。

1. 创建一个新的Fragment

创建一个自己的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。包括以下几个。

  • DialogFragment 显示浮动对话框。使用此类创建对话框可有效地替代使用 Activity 类中的对话框帮助程序方法,因为您可以将片段对话框纳入由 Activity 管理的片段返回栈,从而使用户能够返回清除的片段。
  • ListFragment 显示由适配器(如 SimpleCursorAdapter)管理的一系列项目,类似于 ListActivity。它提供了几种管理列表视图的方法,如用于处理点击事件的 onListItemClick() 回调。
  • PreferenceFragment 以列表形式显示 Preference 对象的层次结构,类似于 PreferenceActivity。这在为您的应用创建“设置” Activity 时很有用处。

2.静态加载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 来替代 <fragment> 元素。

每个片段都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复片段(您也可以使用该标识符来捕获片段以执行某些事务,如将其移除)。 可以通过三种方式为片段提供 ID:
1.为 android:id 属性提供唯一 ID。
2.为 android:tag 属性提供唯一字符串。
3.如果您未给以上两个属性提供值,系统会使用容器视图的 ID。

3.动态加载fragment

静态方式加载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() 来应用过渡动画。

4.添加菜单项

Fragment可以通过实现onCreateOptionsMenu()项Activity的OptionsMenu中添加菜单项。不过需要注意的是,必须在 onCreate() 期间调用 setHasOptionsMenu(),Fragment的onCreateOptionsMenu方法才会被调用。在Fragment中添加的所有菜单项都会被追加到现有菜单项之后。当菜单项被选中时,Fragment会收到对应的 onOptionsItemSelected() 回调。

尽管您Fragment会收到其添加的每个菜单项对应的菜单项回调,但当用户选中菜单项时,Activity 会首先收到相应的回调。 如果 Activity 对菜单项回调的实现不会处理该菜单项,则系统会将事件传递到Fragment的回调。

Fragment通信

1.给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;
    }

2.获取实例

Fragment可以通过getActivity方法来获取Activity的实例,从而执行Activity相关的UI操作。但使用getActivity前需要注意Fragment的生命周期与Activity生命周期的对应关系,在部分生命周期函数内调用getActivity是不会返回有效的实例,获取到的是null值。

同样的,Activity中也可以通过调用 findFragmentById() 或 findFragmentByTag()方法,从 FragmentManager 获取Fragment 引用,进而来访问Fragment相应的方法。

3.使用回调方法

有的时候,我们可以通过使用回调方法来实现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异常。

Fragment懒加载

懒加载主要用于ViewPager加载Fragment页面的情况,且ViewPager的每个子页面都是一个Fragment。默认情况下,ViewPager会执行预加载来提前加载一些页面来使得UI左右滑动效果更加流畅。ViewPager可以通过setOffscreenPageLimit(int limit)设置预加载页面数量,但有一个最小限制,保证知识加载两个或三个页面。在一些场景下,当ViewPager中的页面不可见时,我们不希望他来加载数据时,那么此时我们就需要通过懒加载方式来加载数据。懒加载的方式库提供应用的初始化速度,同时也可以避免不必要的资源加载。

那Fragment的懒加载该如何实现呢,这里首先来认识一个方法setUserVisibleHint(boolean isVisibleToUser)。该方法中的isVisibleToUser参数用来表示当前Fragment是否对用户可见。当Fragment对用户可见或不可见时,该方法都会被调用。所以我们可以依据该方法,在Fragment对用户可见时在去加载数据。但需要注意的一点是,setUserVisibleHint(boolean isVisibleToUser)方法会多次回调,而且可能会在onCreateView()方法执行完毕之前回调。所以,在加载数据前,必须满足两个条件:

  1. setUserVisibleHint(boolean isVisibleToUser)参数为true。
  2. onCreateView()方法已经执行
  3. 数据还未被加载

我们可以把这些操作封装到一个基类里。

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()方法来加载数据即可。

文章评论已关闭!