Android基础—Fragment

2,369 阅读8分钟

设计理念

Android3.0(API11)开始引入Fragment,主要是为了在大屏幕上更好的支持动态和灵活的UI设计。以平板为例,有足够大的空间容纳更多的组件,管理起来无疑过于麻烦。Fragment可以分割Activity的layout,并且具有自己的生命周期,从而使得组件独立管理成为可能。正因为fragment具有独立的生命周期,所以可以定义自己的layout和行为,从而一个fragment可以在多个activity中重复调用。所以我们在设计fragment的时候,要尽量追求模块化和可重用性。一般在平板和TV上普遍使用。

举例如下:在平板上,activity使用一个fragment展示文章列表,使用另一个fragment展示文章内容;在手机上,activity使用一个fragment展示文章列表,点击跳转到另一个activity展示。

Fragment模块化和复用性
Fragment模块化和复用性

Fragment创建

fragment的生命周期和activity非常类似,都有onCreate()、onStart()、onStop()、onPause()回调方法。fragment一般至少要实现三个回调方法:

  1. onCreate():fragment创建时调用,主要初始化一些重要的组件
  2. onCreateView():fragment绘制界面时调用(setContentView(id)),如果fragment有界面的话需要返回一个view;如果fragment没有UI,直接返回空即可
  3. onPause():用户离开fragment,一般要保存数据(用户可能不会再回来)

fragment类型

常见的有三种fragment,分别是DialogFragment、ListFragment、PreferenceFragment

  1. DialogFragment:展示浮动对话框
  2. ListFragment:展示一列数据
  3. PreferenceFragment:以列表形式展示首选项对象的层级关系

创建fragment

fragment一般具有自己的UI,并成为activity UI的一部分。为了给fragment创建UI,必须实现onCreateView()回调方法,并返回一个view(fragment的根layout)。

如果fragment是ListFragment的子类,onCreateView()默认返回ListView,不需要重写。

public static class ExampleFragment extends Fragment {
    @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);
    }
}

container是activity的layout,fragment的layout将会被插入其中;savedInstanceState即保存的fragment被异常销毁前的状态;inflate()有三个参数:

  1. id:flagment的view的资源id
  2. container:activity的layout,fragment的layout将插入其中
  3. false:false表示fragment仅为container提供layout 参数(动态添加、删除、替代fragment时使用);true表示container成为fragment layout的父view(在activity布局文件中定义fragment使用)

把fragment添加到activity中

一般来说,fragment的layout会被嵌入成为activity view层级的一部分。有两种方式可以把fragment添加到activity的layout中:1.静态加载;2.动态加载。

  1. 静态加载:在activity layout中声明fragment,代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     app:layout_behavior="@string/appbar_scrolling_view_behavior"
     tools:context="com.example.chaos.myapplication.MainActivity"
     tools:showIn="@layout/activity_main">
    
     <fragment
         android:id="@+id/fragment_person_list"
         android:name="com.example.chaos.myapplication.PersonListFragment"
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_weight="1" />
    
     <fragment
         android:id="@+id/fragment_person_detail"
         android:name="com.example.chaos.myapplication.DetailFragment"
         android:layout_width="0dp"
         android:layout_height="match_parent"
         android:layout_weight="1" />
    </LinearLayout>

    系统创建该activity的layout时,会将指定的fragment全部实例化并分别调用他们的onCreateView()回调。然后检索fragment的layout,并将onCreateView()返回的view取代<fragment>元素直接插入activity的layout中。

activity重启时,需要恢复fragment的数据,这就要求fragment要有个唯一的标识符(否则无法管理fragment)。有三种方式可以为fragment赋予唯一的id

  1. 使用android:id属性赋予id
  2. 使用android:tag属性赋予唯一标签
  3. 如果id和tag都没提供的话,系统模式使用container view的id

2.. 动态加载:动态添加fragment到ViewGroup中
只要在activity在运行,都可以添加fragment到activity的layout中,只需要指定ViewGroup存放fragment就行。
为了在activity中操作fragment的事务(add、remove、replace),必须使用FragmentTransaction()。代码如下:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

添加fragment使用add()方法,指定ViewGroup和要添加的fragment,对fragmentTransaction做的任何改动都要执行commit()方法才能生效。

ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();

添加一个没有UI的fragment

并非所有fragment都必须有UI,fragment也可以用来执行后台操作,此时,无需UI也可以。该如何添加一个没有UI的fragment呢?

首先,需要先通过findFragmentByTag()找到,因为tag是唯一能标识它的(只能通过add(fragment,string)主动设置tag)。

如果findFragmentByTag()返回null的话,需要通过add(fragment,string)为fragment主动设置tag并添加。

Fragment管理

fragment的管理是由FragmentManager实现的,可以在activity中通过getFragmentManager()获取FragmentManager。FragmentManager的功能如下:

  1. 使用findFragmentById()(针对在activity layout提供一个UI的fragment)和findFragmentByTag()(针对没有UI的fragment)获取activity中已存在的fragment
  2. 调用popBackStack()将fragment从返回栈弹出(类似于用户点击back)
  3. 调用addOnBackStackChangedListener()注册监听器监听返回栈的变化

静态加载的fragment一定具有id或tag;动态加载的id一定具有container view的id或tag(fragment如果没有唯一标识符,则无法进行有效管理),动态加载一共有三个方法:

  1. add(Fragment fragment, String tag):适用于有UI和无UI的fragment
  2. add(@IdRes int containerViewId, Fragment fragment):适用于有UI的fragment
  3. add(@IdRes int containerViewId, Fragment fragment,String tag);:适用于有UI的fragment

所以没有UI的fragment,只能通过findFragmentByTag()获取;有UI的fragment可以通过id或tag获取。

也可以使用FragmentManager打开FragmentTransaction,从而操作fragment的事务,如添加或移除。

Fragment事务处理

一个事务就是一系列变化,事务具有原子性、一致性、隔离性、持久性。可以使用FragmentTransaction实现事务,也可以把事务放到由activity管理的返回栈中,便于用户返回之前的fragment的状态.

可以从FragmentManager获取FragmentTransaction实例

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

FragmentTransactioni可以借由add()、remove()、replace()实现对事务的操作,而且每次操作必须实现commit()才能生效。

如果想把fragment加入到返回栈,以便用户退回到fragment原先的状态的话,需要在commit()之前调用addBackStack()方法:

 DetailFragment detailFragment = new DetailFragment();
 fragmentTransaction.add(detailFragment,"");
 fragmentTransaction.addToBackStack(null);
 fragmentTransaction.commit();

为了检索返回栈中的fragment,必须监听onBackPressed()事件,否则的话如果栈中存在其他activity的话,就会直接返回到其他activity中;如果没有activity的话,就会直接退出应用。

@Override
    public void onBackPressed() {
        if (getFragmentManager().getBackStackEntryCount() > 0){
            getFragmentManager().popBackStack();
        }else {
            super.onBackPressed();
        }
    }

添加改变到Transaction中的顺序无关紧要,除非:

  1. 最后调用了commit()方法
  2. 添加了多个改变到同一个container中,添加的顺序决定了container中view层级的顺序

如果没有调用addBackStack(),当remove一个fragment时,commit之后会立刻销毁,用户无法返回;如果添加到返回栈中,remove一个fragment时,commit之后会处于stop状态,用户可以返回到之前的状态(resume)

commit()这个操作运行在主线程中,也就意味着调用commit()之后要排序执行,并不会立刻执行事务。如果想立刻执行的话,可以在commit()之前调用excutePendingTransactions(),不过一般并无此必要。

commit()必须在onSaveInstanceState()之前调用,因为在此之后,activity不会保存数据,导致commit()调用会抛出异常;如果允许状态数据丢失的话,可以调用 commitAllowingStateLoss()。

与Activity的通信

虽然fragment作为对象的实现和activity存在很大不同,但是作为activity的一部分,fragment又和activity紧密联系。

  1. 从fragment中调用activity
    View listView = getActivity().findViewById(R.id.list);
  2. 从activity中调用fragment
    DetailFragment detailFragment1 = (DetailFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_person_detail);

    为activity创建接口

    常见的模型:activity包含两个fragment,左边fragment显示标题列表,右边fragment显示内容。点击左侧的fragment的标题,更新右侧的fragment内容。实现方式就是在左边fragment创建接口,并在接口中实现传值方法,activity实现该接口,监听方法,然后将接受到的值传给右边的fragment。

fragment创建接口和方法:

public static class FragmentA extends ListFragment {
    ...
    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

把activity转换成接口,从而实现activity监听

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
    ...
}

在fragment的onListItemClick事件中调用传值方法给activity

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Append the clicked item's row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
        // Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ...
}

Fragment生命周期

和activity一样,fragment也有三种基本状态:

  1. 运行态:fragment在正在运行的activity中处于可见状态
  2. 暂停态:另一个activity位于前台并获取焦点,fragment所在的activity仍然可见(前台activity半透明或没有全部覆盖fragment所在activity)
  3. 停止态:fragment所在activity处于stop状态,或者fragment被remove掉,但是添加到返回栈中了。处于暂停态的fragment仍然存活(系统仍然保留状态信息和成员变量),但是已不再可见,且activity被销毁时,fragment也会被销毁。

fragment和activity生命周期的一个最大差别在于二者在返回栈中的存储。activity的返回栈是由系统管理的;fragment的返回栈是由activity管理的(当fragment被remove时,调用addBackStack())。

fragment如果需要context对象的话,可以调用getActivity(),但是这个方法要在onAttach()之后、onDetach()之前调用才有效,否则将会返回null。

fragment和activity生命周期比较
fragment和activity生命周期比较