参考视频教程:    **组件化封装思想实战Android App  ** 转载请注明出处http://blog.csdn.net/yegongheng/article/details/38013349

<br />

Fragment 是什么

今天我们来学习一个比较重要的组件--Fragment。Fragment在应用开发中应用得非常的频繁,特别是在开发一些需要兼容手机设备、平板设备和智能电视等大屏幕设备的应用,Fragment发挥着重要的作用。那说了这么多,Fragment到底是什么呢?在这里我们先简单的用一句话总结就是:Fragment是Android为了应用适配不同设备的大屏幕、支持更加动态和灵活的UI设计所提供的一个组件。
说到Fragment,就会联想到Activity,因为Fragment对象在应用中一般是不能独立存在的,它必须嵌入到activity中,而且Fragment的生命周期直接受所在的activity的影响。例如:当activity暂停时,他拥有的所有的Fragment都暂停了,当activity销毁时,他拥有的所有Fragment都被销毁。然而,当activity运行时(在onResume()之后,onPause()之前),可以单独地操作每个Fragment,比如添加或删除它们。当中执行上述针对Fragment的事务时,可以将事务添加到一个栈中,这个栈被activity管理,栈中的每一条都是一个Fragment的一次事务。有了这个栈,就可以反向执行Fragment的事务,这样就可以在Fragment级支持"返回"键(向后导航)。
当向activity中添加一个Fragment时,它须置于ViewGroup控件中,并且需定义Fragment自己的界面。可以在layout.xml布局文件中声明Fragment,元素为:<fragment>;也可以在代码中创建Fragment,然后把它加入到ViewGroup 控件中。然而,Fragment不一定非要放在activity的界面中,它可以隐藏在后台为activity工作。
Android在3.0之后引入了Fragment的概念,主要目的是用在大屏幕设备-例如平板电脑上,以便支持更加动态和灵活的UI设计。 平板电脑的屏幕尺寸比手机大得多,因此有更多的空间来存放更多的UI组件,并且这些组件之间会产生更多的交互。Fragment允许 这样的一种设计,而不需要你亲自来管理viewhierarchy的复杂变化。通过将Activity的布局分散到Fragment中,你可以在运行时修改 Activity的外观,并在由Activity管理的Back stack中保存那些变化。
例如,一个新闻应用可以在屏幕的左侧使用一个Fragment来展示一个文章的列表,然后在屏幕右侧使用另一个Fragment来展示 一篇文章,两个Fragment并排显示在相同的一个Activity中,并且每一个Fragment拥有它自己的一套生命周期回调方法,并且处理它 们自己的用户输入事件。因此,取代使用一个activity来选择一篇文章而另一个activity来阅读文章的方式,用户可以在同一个activity 中选择一篇文章并且阅读,如图所示:

Fragment在你的应用中应当是一个模块化和可重用的组件。即,因为fragment定义了它自己的布局,以及通过自己的生命周期回 调方法来定义自己的行为,你可以将Fragment包含到多个Activity中,这点特别重要,因为允许你将你的用户体验适配到不同的屏幕尺寸 。举个例子,你可能仅当在屏幕尺寸足够大时,在一个activity中包含多个fragment并且当不属于这种情况时,会启动另一个单独的使用不 同fragment的activity。
继续之前那个新闻的例子-当运行在一个特别大的屏幕时(例如平板电脑),应用可以在activity A中嵌入2个fragment。然而,在一个 正常尺寸的屏幕(例如手机)上,没有足够的空间同事供2个fragment使用。因此,activity A会仅含文章列表的fragment,而当用户选择 一篇文章时,它会启动activity B,它包含阅读文章的fragment B。因此,应用可以同时支持上图中的2种模式。

Fragment生命周期

Fragment虽然必须嵌入到Activity才能使用,但它有自己的一套生命周期回调方法,其生命周期与Activity的生命周期类似,同时它受其所属的Activity(宿主Activity)生命周期的直接影响,例如:当activity暂停时,他拥有的所有的Fragment都暂停了,当activity销毁时,他拥有的所有Fragment都被销毁。下面是Fragment生命周期图(图1.1)以及与宿主Activity生命周期之间的关系图示(图1.2):

图1.1 图1.2
下面将一一介绍Fragment各个回调方法的特性和使用:
onAttach(): 当Fragment被绑定到Activity事调用(Activity会被传入);
onCreate(): 当Fragment被创建时,系统调用该方法。在实现代码中,应当初始化想要在Fragment保持的必要组件,当Fragment暂停或停止后可以被恢复;(此方法在实现一个Fragment时必须)
onCreateView(): 在Fragment第一次绘制界面时系统会调用此方法,为绘制Fragment的UI,方法需返回一个View对象,该对象是Fragment布局的根View。若Fragment不提供UI,则返回null;(此方法在实现一个Fragment时必须)
o nActivityCreated(): 当Activity被创建回调onCreate()方法后,系统调用此方法;
onStart(): 当Fragment呈现给用户变得可见之前调用 ( 在宿主Activity回调完onStart()方法后调用 ) ,此时用户与界面处于可见不可交互的状态。
onResume(): 当用户和界面可以互相交互时调用(在宿主Activity回调完onResume()方法后调用)。
onPause():当需要开启一个新的Activity或替换一个新的Fragment时,系统调用此方法(在宿主Activity回调完onPause()方法后调用)。此时用户将要离开与Fragment界面的交互,因此一般来讲应在此方法中保存一些需要持久化的变化。 (此方法在实现一个Fragment时必须)
onStop():当Fragment变得完全不可见时,系统调用该方法(在宿主Activity回调完onStop()方法后回调),出现此状态的原因可能是另一个Activity覆盖此宿主Activity或此Fragment被替换,此时Fragment面临被销毁。
onDestoryView():当和Fragment关联的View hierarchy被移除时,系统调用该方法。
onDestory():当Fragment被销毁时,系统调用该方法。
onDetach():当Fragment与宿主Activity解除关联时,系统调用该方法。
下面我们通过一个例子来具体了解一下Fragment的生命周期各个回调函数在何时调用,要使用Fragment,需创建一个继承Fragment的子类(LifeCycleFragment),我们这里重写了Fragment的全部生命周期函数,代码如下:


public class LifeCycleFragment extends Fragment {

  private static final String TAG = "FragmentLifeCycle";

  @Override
  public void onAttach(Activity activity) {
    // TODO Auto-generated method stub
    super.onAttach(activity);
    Log.i(TAG, "onAttach");
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    Log.i(TAG, "onCreate");
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    Log.i(TAG, "onCreateView");

    View rootView = inflater.inflate(R.layout.fragment_lifecycle, container, false);
    return rootView;
  }

  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onActivityCreated(savedInstanceState);
    Log.i(TAG, "onActivityCreated");
  }

  @Override
  public void onStart() {
    // TODO Auto-generated method stub
    super.onStart();
    Log.i(TAG, "onStart");
  }

  @Override
  public void onResume() {
    // TODO Auto-generated method stub
    super.onResume();
    Log.i(TAG, "onResume");
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
    Log.i(TAG, "onPause");
  }

  @Override
  public void onStop() {
    // TODO Auto-generated method stub
    super.onStop();
    Log.i(TAG, "onStop");
  }

  @Override
  public void onDestroyView() {
    // TODO Auto-generated method stub
    super.onDestroyView();
    Log.i(TAG, "onDestroyView");
  }

  @Override
  public void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();
    Log.i(TAG, "onDestroy");
  }

  @Override
  public void onDetach() {
    // TODO Auto-generated method stub
    super.onDetach();
    Log.i(TAG, "onDetach");
  }
}

从上面我们可以看到,LifeCycleFragment的每个生命周期函数都打印了日志,我来运行一下程序,看一下日志的打印情况如下图示:

然后我们再点击一下Home键,日志打印的情况如下图示:

然后我们再回到程序界面,日志的打印情况如下图示:

最后我们点击回退键(Back)退出应用,日志的打印情况如下图示:

依据上面对Fragment的管理生命周期的实例学习我们可知,管理Fragment的生命周期,和管理Activity的生命周期很类似,它们一般都包含Resumed、Paused和Stopped这三种状态。然而Fragment和Activity在生命周期方面也有一些差异,其中最大的区别就是各自如何在它的后台堆栈中存储。默认情况下,Activity被停止后,会被压入到由系统管理的Activity的后台堆栈(点击Back键回退到此Activity)。而Fragment被停止后,其实对象状态默认是不会被存放到堆栈中,除非当Fragment停止时调用addToBackStack()方法,此时才会将Fragment的对象状态压入到由宿主Activity管理的Fragment的后台堆栈。

Fragment使用实例

在我们实际开发中,在定义自己的Fragment类时,我们不需要重写Fragment全部的回调函数,但一般来讲,至少需重写并实现Fragment生命周期函数中的onCreate()、onCreateView()和onPause()这几个方法。在重写的onCreate()方法主要实例化一些在Fragment中需持久化的必要组件,在重写的onCreateView()方法中主要是返回当前Fragment的自定义根布局View和初始化布局中的一些控件对象,而在onPause()方法中主要是在用户离开Fragment时保存一些需要持久化的对象状态,下面有实现了三个方法的一个小例子,具体实例代码如下:


public class ExampleFirstFragment extends Fragment {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    /** 这里主要初始化一些在Fragment需使用和持久化的必要组件 */
    super.onCreate(savedInstanceState);
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    /**
     * 使用inflate()方法加载一个自定义的Layout布局,该布局是Fragment的根布局view. inflate()方法的3个参数: 1.想要加载的Layout Resource
     * Id; 2.加载 Layout的父ViewGroup,目的是将Layout挂靠到container上;
     * 3.布尔值,指示是否将Layout附着到ViewGroup中,这里一般指定false,
     * 因为Layout已经附着到container上,若为true,系统将为Layout新建一个ViewGroup作为附着对象,多余;
     */
    View inView = inflater.inflate(R.layout.fragment_main, container, false);

    /** 这里主要是初始化Layout中的控件对象 */
    return inView;
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
    /** 当用户离开此Fragment界面时,系统调用此方法,这里主要保存一些需要持久化的对象状态 */
  }
}

基本了解完如何自定义一个Fragment后,接下来我们将学习如何添加Fragment到Activity中并显示,一般的,有两种添加Fragment的方式,一种为静态添加,另外一种是动态添加。下面通过实例来更深入地学习两种方式的异同:

静态添加Fragment

首先创建两个自定义的Fragment,分别为AddFragmentFirst和AddFragmentSecond。AddFragmentFirst代码如下:


public class AddFragmentFirst extends Fragment {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    View inView = inflater.inflate(R.layout.fragment_addfragmentfirst, container, false);
    return inView;
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
  }
}

AddFragmentSecond代码如下:


public class AddFragmentSecond extends Fragment {

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    View inView = inflater.inflate(R.layout.fragment_addfragmentsecond, container, false);
    return inView;
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
  }
}

然后将两个自定义的Fragment添加到AddByStaticActivity中,AddByStaticActivity的代码如下:


/** @author AndroidLeaf 静态添加Fragment */
public class AddByStaticActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_addbystatic);
  }
}

AddByStaticActivity的布局文件activity_addbystatic的代码如下:




 <LinearLayout 
 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"
 android:orientation="horizontal"
  >
 <fragment
 android:layout_width="0dp"
 android:layout_weight="1"
 android:layout_height="match_parent"
 android:id="@+id/addfragment_first"
 android:name="com.androidleaf.fragmentdemo.fragment.AddFragmentFirst"
  />
 <fragment
 android:layout_width="0dp"
 android:layout_weight="1"
 android:layout_height="match_parent"
 android:id="@+id/addfragment_second"
 android:name="com.androidleaf.fragmentdemo.fragment.AddFragmentSecond"
  />
 </LinearLayout>

以上代码中,<fragment>中的android:name属性指定了Activity layout实例化的Fragment类,同时也可以使用class属性也可完成同样的功能。当系统创建Activity layout时,它实例化layout中的每一个fragment类并回调fragment中的onCreateView()方法,返回并获取fragment的根布局view,然后将根布局View插入到<fragment>中。需要注意的是,每一个<fragment>都需要一个唯一标识,当Activity重启时,可以用来恢复fragment对象状态。一般的目前Android已经提供了三种方式来为fragment提供唯一标识,如下:
1、为android:id提供唯一一个ID;
2、为android:tag提供唯一一个字符;
3、如果以上两种都没有提供,系统默认使用fragment的容器的ID;
我们的示例程序使用了android:id属性为每个fragment提供唯一标识。
那么所有的代码我们都已经写完了,下面我们运行一下程序,运行的效果如下:

大功告成,两个fragment成功嵌入到Activity中。

动态添加Fragment

上面我们已经通过XML方式成功将fragment嵌入到Activity中(这种嵌入方式我们称为静态添加),但这种添加方式依然不够灵活.于是Android提供了另一种更加灵活的添加方式,也是我们日常开发中用的最多的一种添加方式----动态添加。
动态添加顾名思义就是在程序运行时根据不同的情况动态地添加我们所需的fragment,下面我们将通过实例来学习如何动态添加Fragment。
自定义一个AddByDynamicActivity,并根据屏幕宽高的变化,动态添加AddFragmentFirst和AddFragmentSecond,代码如下:


public class AddByDynamicActivity extends Activity {

  @SuppressLint("NewApi")
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_addbydynamic);

    // 获取屏幕宽高
    DisplayMetrics mDisplayMetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(mDisplayMetrics);
    // 1.根据上下文获取FragmentManager对象
    FragmentManager manager = this.getFragmentManager();

    if (mDisplayMetrics.widthPixels < mDisplayMetrics.heightPixels) {
      // 2.使用获取到的manager对象开启一个事务
      FragmentTransaction mFragmentTransaction01 = manager.beginTransaction();
      // 3.替换Fragment
      mFragmentTransaction01.replace(R.id.container, new AddFragmentFirst());
      // 4.提交事务
      mFragmentTransaction01.commit();
    } else {
      FragmentTransaction mFragmentTransaction02 = manager.beginTransaction();
      mFragmentTransaction02.replace(R.id.container, new AddFragmentSecond()).commit();
    }
  }
}

AddByDynamicActivity的布局文件activity_addbydynamic的代码如下:




 <LinearLayout 
 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"
 android:id="@+id/container"
 android:orientation="horizontal"
  />

AddFragmentFirst和AddFragmentSecond的代码沿用上面学习静态添加Fragment时的代码。编写完成后,我们运行一下程序,显示的运行界面如下图所示:

然后点击Ctrl + F11组合键(这里使用Android模拟器测试),程序显示的界面如下图所示:

根据上面的运行结果我们可知,我们已经成功完成了根据不同的情况动态地添加或替换不同的Fragment对象的测试。接下来我们就此来深入的学习一下动态添加Fragment的具体编码步骤以及需要注意的地方。
首先,要在Activity管理Fragment,我们需要使用Android API提供的FragmentManager类,我们可以通过Activity的getFragmentManager()方法获取FragmentManager对象,而要对Fragment进行添加、删除、隐藏或替换操作,则需对每个需要执行的操作开启一个事务(FragmentTransaction),我们可以通过FragmentManager对象的beginTransaction()方法开启。接着,我们可以根据具体需求使用add()、remove()、hide()或replace()方法来执行对Fragment的管理操作。最后,我们必须将每一个事务都提交到事务管理器中等待执行,Android提供了commit()和commitAllowingStateLoss()两个方法来执行事务提交,两个方法的区别是当使用commit()方法时,宿主Activity必须在保存它的状态(用户离开Activity)之前执行commit()提交操作,否则将会抛出一个异常,这是因为当activity再次被恢复时commit之后的状态将丢失。如果丢失也没关系,那么使用commitAllowingStateLoss()方法即可。这里需要注意的是,当事务提交完成后,事务并不会马上执行,而是由事务管理器安排的队列执行顺序,在必要时在宿主Activity的UI线程中执行。若不想等待要马上执行的话,可以在UI线程调用executePendingTransactions()来立即执行由commit()提交的事务. 但这么做通常不必要,除非事务是其他线程中的任务的一个从属。
依据上面的代码实例,我们下面来总结一下动态添加Fragment的具体步骤:
1、使用Activity的上下文对象的getFragmentManager()方法获取FragmentManager对象;
2、使用获取到的FragmentManager对象开启一个事务(FragmentTransaction),调用beginTransaction()方法执行事务开启;
3、使用开启的事务对象(FragmentTransaction)执行添加、隐藏、删除或替换(add()、hide()、remove() or replace())Fragment的操作;
4、调用commit()或commitAllowingStateLoss()方法执行事务的提交。

为Activity创建事件回调方法

在一些情况下,Fragment需要建立与宿主Activity或和同一宿主Activity的姊妹Fragment的通信来完成相关操作功能。Fragment一般可以调用getActivity()方法来访问宿主Activity实例,同样的,宿主Activity也可以调用FragmentManager类中的findFragmentById()或findFramentByTag()方法来得到Fragment的引用对象,并依此来调用Fragment中的方法。而要建立两个属于同一个Activity的Fragment对象的通信,则最好的方法就是借助宿主Activity这个桥梁来完成信息的传递,就比如上面讲的那个列表新闻显示的例子(FragmentA显示新闻标题列表,FragmentB显示新闻的详细内容),当我们点击FragmentA中的列表选项时,则需要将列表选项的值传递给FragmentB,以便FragmentB根据用户点击的列表项显示对应的新闻内容。下面我们将使用代码简单地实现新闻显示这个例子,加深对Fragment的了解。我们先整理一下实现思路:首先定义一个宿主Activity(例子名中为ActivityForCallBackActivity),主要用于装载新闻列表的Fragment(例子中名为ActivityForCallBackFragmentA)和显示新闻内容详细的Fragment(例子中名为ActivityForCallBackFragmentB),然后在ActivityForCallBackFragmentA中定义一个事件回调方法,并在宿主ActivityForCallBackActivity实现其回调接口,这里使用回调主要作用是用于监听用户点击新闻列表项的事件,接着在宿主ActivityForCallBackActivity使用getFragmentManager().findFragmentById()方法获取显示新闻内容详细(ActivityForCallBackFragmentB)的实例,并在实现的回调方法onArticleSelected()方法中调用ActivityForCallBackFragmentB中的setContent()用于设置从ActivityForCallBackFragmentA传递过来的新闻Title。好了,基本思路已经理清,下面看下具体实现的代码。
首先定义一个ActivityForCallBackActivity类作为两个Fragment的宿主Activity,该类的代码如下:


/** @author AndroidLeaf 该示例主要实现新闻阅读例子 */
public class ActivityForCallBackActivity extends Activity implements OnArticleSelectedListener {

  private ActivityForCallBackFragmentB mFragmentB;

  @SuppressLint("NewApi")
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_activityforcallback);
    // 获取ActivityForCallBackFragmentB新闻详细内容对象
    mFragmentB =
        (ActivityForCallBackFragmentB)
            getFragmentManager().findFragmentById(R.id.activityforcallback_fragment_b);
  }

  @Override
  public void onArticleSelected(int itemID, String title) {
    // TODO Auto-generated method stub
    // 详细页显示新闻Title
    mFragmentB.setContent(title);
  }
}

布局文件activity_activityforcallback.xml的代码如下:




 <LinearLayout 
 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"
 android:orientation="horizontal"
  >
 <fragment
 android:layout_width="0dp"
 android:layout_weight="1"
 android:layout_height="match_parent"
 android:id="@+id/activityforcallback_fragment_a" 
 android:name="com.androidleaf.fragmentdemo.fragment.ActivityForCallBackFragmentA"
  />
 <fragment
 android:layout_width="0dp"
 android:layout_weight="2"
 android:layout_height="match_parent"
 android:id="@+id/activityforcallback_fragment_b"
 android:name="com.androidleaf.fragmentdemo.fragment.ActivityForCallBackFragmentB"
  />
 </LinearLayout>

然后创建展示新闻列表的ActivityForCallBackFragmentA.java类,代码如下:


public class ActivityForCallBackFragmentA extends Fragment implements OnItemClickListener {

  private ListView mListView;
  ArrayAdapter<String> adapter = null;
  ArrayList<String> list = null;

  /** 创建一个事件回调函数,用来监听用户点击列表选项的操作 */
  public OnArticleSelectedListener mArticleSelectedListener;

  public interface OnArticleSelectedListener {
    public void onArticleSelected(int itemID, String title);
  }

  @Override
  public void onAttach(Activity activity) {
    // TODO Auto-generated method stub
    super.onAttach(activity);
    try {
      // 将宿主Activity对象强制转换成OnArticleSelectedListener实例,主要是为了确保宿主Activity实现监听接口
      mArticleSelectedListener = (OnArticleSelectedListener) activity;
    } catch (ClassCastException e) {
      // TODO: handle exception
      throw new ClassCastException(
          activity.toString() + " must implement OnArticleSelectedListener");
    }
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    /** 初始化新闻列表的值 */
    list = new ArrayList<String>();
    list.add("a");
    list.add("b");
    list.add("c");
    list.add("d");
    list.add("e");
    list.add("f");
    list.add("g");
    adapter =
        new ArrayAdapter<String>(
            getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, list);
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    View inView = inflater.inflate(R.layout.fragment_activityforcallback_a, container, false);
    mListView = (ListView) inView.findViewById(R.id.callback_listview);
    mListView.setAdapter(adapter);
    mListView.setOnItemClickListener(this);
    return inView;
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
  }

  @Override
  public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    // TODO Auto-generated method stub
    // 将选中的列表选项ID和Title传递给实现监听接口的宿主Activity
    mArticleSelectedListener.onArticleSelected(position, list.get(position));
  }
}

布局文件fragment_activityforcallback_a.xml的代码如下:




 <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"
 android:id="@+id/callback_a_container"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
  >
 <ListView 
 android:layout_width="match_parent"
 android:id="@+id/callback_listview"
 android:layout_height="wrap_content"
  />
 </RelativeLayout>

接着创建显示详细新闻内容的ActivityForCallBackFragmentB.java类,代码如下:


public class ActivityForCallBackFragmentB extends Fragment {

  private TextView mTextView;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    View inView = inflater.inflate(R.layout.fragment_activityforcallback_b, container, false);
    mTextView = (TextView) inView.findViewById(R.id.callback_textview);
    return inView;
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
  }

  public void setContent(String titleContent) {
    if (!TextUtils.isEmpty(titleContent)) {
      mTextView.setText(titleContent);
    }
  }
}

布局文件fragment_activityforcallback_b.xml代码如下:




 <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"
 android:background="#FF0000"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
  >
 
 <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_centerInParent="true"
 android:textSize="50sp"
 android:textColor="#000000"
 android:id="@+id/callback_textview"
  />
 
 </RelativeLayout>

代码编写完成,接下来运行程序,效果图如下图示:

在Fragment中添加ActionBar
在Fragment添加ActionBar的方法其实和在Activity添加ActionBar的方法类似,在这里就不再赘述,需要了解ActionBar使用方法的读者,可以学习(ActionBar简介和使用实例)这篇文章。不过读者使用时需要注意的一点是,当用户在Fragment中选择一个菜单项时,其宿主Activity会首先接收到对应的回调.如果Activity的on-item-selected回调函数实现并没有处理被选中的项目, 然后事件才会被传递到Fragment的回调。在文章末尾提供下载的Demo中有关于在Fragment添加ActionBar的具体例子,由于较为简单,这里就不贴出代码,读者可以下载Demo来进行学习。

利用Fragment实现应用在手机和平板上的兼容实例

Fragment起初是专门为了应用程序适配手机小屏幕和平板电脑大屏幕以达到应用在两者上运行能有最好的界面显示效果而设计的,例如对于屏幕尺寸较大的平板电脑,它有比一般手机设备更大的空间来显示更多的视图控件对象,那系统在运行应用时,当检测到是运行在较大屏幕尺寸的设备上时,系统将自动加载更大的屏幕界面布局,然后可以动态地添加Fragment来填充剩余的视图空间,最后实现更多的界面交互。不过由于Fragment有自己独立的生命周期,并可以在Activity中动态地被添加、删除或替换的特性,许多开发者在开发手机应用时也喜欢频繁使用Fragment来实现更加灵活的UI视图(即便应用不考虑或不需要在平板电脑上很好的显示界面)。下面我们通过一个新闻列表显示实例来学习如何使用Fragment来实现手机和平板的的适配,首先创建一个宿主Activity(例子中名为AdapterMobileAndPadActivity),主要用于装载Fragment和显示视图布局,代码如下:


public class AdapterMobileAndPadActivity extends Activity {

  @SuppressLint("NewApi")
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_adaptermobile_and_pad);
  }
}

布局文件activity_adaptermobile_and_pad分为两处地方,一个是在res/layout文件路径下,另一个是res/layout-land文件路径下,如下图所示:

res/layout/activity_adaptermobile_and_pad布局文件代码如下:




 <LinearLayout 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"
 android:orientation="horizontal" >
 
 <fragment
 android:layout_width="0dp"
 android:layout_weight="1"
 android:layout_height="match_parent"
 android:id="@+id/adapter_article_list"
 android:name="com.androidleaf.fragmentdemo.fragment.AdapterArticleListFragment"
  />
 </LinearLayout>

res/layout-land/activity_adaptermobile_and_pad布局文件代码如下:




 <LinearLayout 
 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"
 android:orientation="horizontal"
  >
 <fragment
 android:layout_width="0dp"
 android:layout_weight="1"
 android:layout_height="match_parent"
 android:id="@+id/adapter_article_list"
 android:name="com.androidleaf.fragmentdemo.fragment.AdapterArticleListFragment"
  />
 <FrameLayout 
 android:layout_width="0dp"
 android:layout_weight="3"
 android:layout_height="match_parent"
 android:id="@+id/details_container"
  />
 
 </LinearLayout>

两个文件夹中的同名布局文件分别为手机和平板界面的显示提供视图布局,当应用运行时,Android系统会根据当前运行环境来判断应用是否运行在大屏幕设备上,如果运行在大屏幕设备上,则加载res/layout-land下的布局文件,否则默认加载res/layout下的布局文件。关于动态加载界面布局的更多内容,可以学习( Android官方提供的支持不同屏幕大小的全部方法 )这篇文章。
接下来创建新闻列表显示界面的Fragment(例子中名为AdapterArticleListFragment),具体的代码如下:


public class AdapterArticleListFragment extends ListFragment {

  /** 判断当前加载的是否为大屏幕布局 */
  private boolean isScreenPad;
  /** 用来记录上次选中的项 */
  private int mCurrentPosition;

  /** 列表测试数据 */
  public static String[] articleTitles = {
    "a", "b", "c", "d", "e", "f", "g",
  };

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
  }

  @Override
  public void onActivityCreated(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onActivityCreated(savedInstanceState);

    // 绑定数据列表
    setListAdapter(
        new ArrayAdapter<>(
            getActivity(), android.R.layout.simple_list_item_1, android.R.id.text1, articleTitles));

    View details = getActivity().findViewById(R.id.details_container);
    // 检测是否使用大屏幕尺寸的布局
    isScreenPad = details != null && details.getVisibility() == View.VISIBLE;

    if (savedInstanceState != null) {
      // 获取上次离开界面时列表选项值
      mCurrentPosition = savedInstanceState.getInt("currentChoice", 0);
    }
    if (isScreenPad) {
      // 设置列表选中的选项高亮
      getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);

      showDetails(mCurrentPosition);
    }
  }

  @Override
  public void onListItemClick(ListView l, View v, int position, long id) {
    // TODO Auto-generated method stub
    super.onListItemClick(l, v, position, id);
    showDetails(position);
  }

  /** 离开界面时保存当前选中的选项的状态值 */
  @Override
  public void onSaveInstanceState(Bundle outState) {
    // TODO Auto-generated method stub
    super.onSaveInstanceState(outState);
    outState.putInt("currentChoice", mCurrentPosition);
  }

  /** @param index */
  public void showDetails(int index) {
    mCurrentPosition = index;
    if (isScreenPad) {
      getListView().setItemChecked(index, true);

      AdapterArticleDetailFragment mDetailFragment =
          (AdapterArticleDetailFragment)
              getActivity().getFragmentManager().findFragmentById(R.id.details_container);
      // 若mDetailFragment为空或选中非当前显示的Fragment
      if (mDetailFragment == null || mDetailFragment.showIndex() != index) {
        mDetailFragment = AdapterArticleDetailFragment.newInstance(index);
        // 替换Fragment实例对象
        getActivity()
            .getFragmentManager()
            .beginTransaction()
            .replace(R.id.details_container, mDetailFragment)
            .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
            .commit();
      }
    } else {
      Intent mIntent = new Intent();
      mIntent.setClass(getActivity(), AdapterArticleDetailActivity.class);
      Bundle mBundle = new Bundle();
      mBundle.putInt("index", index);
      mIntent.putExtras(mBundle);
      getActivity().startActivity(mIntent);
    }
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
  }
}

然后创建新闻内容详细显示的Fragment(例子中名为AdapterArticleDetailFragment),具体的代码如下:


public class AdapterArticleDetailFragment extends Fragment {

  private TextView titleContent;
  public static int index;

  /**
   * 实例化 AdapterArticleDetailFragment对象
   *
   * @param index
   * @return AdapterArticleDetailFragment
   */
  public static AdapterArticleDetailFragment newInstance(int index) {

    AdapterArticleDetailFragment mAdapterArticleDetailFragment = new AdapterArticleDetailFragment();
    Bundle mBundle = new Bundle();
    mBundle.putInt("index", index);
    // 保存当前选中的选项ID
    mAdapterArticleDetailFragment.setArguments(mBundle);

    return mAdapterArticleDetailFragment;
  }

  /**
   * 获取当前显示的选项ID
   *
   * @return int index
   */
  public int showIndex() {
    if (getArguments() == null) {
      return 0;
    }
    return getArguments().getInt("index", 0);
  }

  @Override
  public void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
  }

  @Override
  public View onCreateView(
      LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    View inView = inflater.inflate(R.layout.fragment_articledetails, container, false);
    titleContent = (TextView) inView.findViewById(R.id.articledetails_textview);
    // 设置详情页的内容
    titleContent.setText(
        AdapterArticleListFragment.articleTitles[getArguments().getInt("index", 0)]);
    return inView;
  }

  @Override
  public void onPause() {
    // TODO Auto-generated method stub
    super.onPause();
  }
}

布局文件fragment_articledetails的代码如下:




 <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"
 android:background="#FF0000"
 android:paddingBottom="@dimen/activity_vertical_margin"
 android:paddingLeft="@dimen/activity_horizontal_margin"
 android:paddingRight="@dimen/activity_horizontal_margin"
 android:paddingTop="@dimen/activity_vertical_margin"
  >
 
 <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:layout_centerInParent="true"
 android:textSize="55sp"
 android:textColor="#000000"
 android:id="@+id/articledetails_textview"
  />
 
 </RelativeLayout>

对于手机设备运行应用时,由于没有足够的空间同时容纳两个Fragment,因此需要新开启一个Activity(例子中名为AdapterArticleDetailActivity),具体的代码如下:


public class AdapterArticleDetailActivity extends Activity {

  @SuppressLint("NewApi")
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_articledetails_activity);

    if (savedInstanceState == null) {
      AdapterArticleDetailFragment mArticleDetailFragment = new AdapterArticleDetailFragment();
      mArticleDetailFragment.setArguments(getIntent().getExtras());
      getFragmentManager()
          .beginTransaction()
          .add(R.id.article_details_act, mArticleDetailFragment)
          .commit();
    }
  }
}

布局文件activity_articledetails_activity代码如下:




 <LinearLayout 
 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"
 android:orientation="horizontal"
 android:id="@+id/article_details_act"
  >
 </LinearLayout>

代码编写完毕,最后在Android手机模拟器上运行该程序,效果如图下:

依据上图效果我们来分析下,当手机竖屏显示时,只显示新闻列表,点击选项新开启一个Activity用于显示新闻详细内容,然后点击Ctrl + F11切换至横屏,Android系统自动适配屏幕加载res/layout-land下的布局,同时显示新闻列表项和新闻内容详细页面。然后在Android 平板模拟器上运行该程序,效果图如下:

自此,我们便达到了应用在手机和平板上都能完美显示视图界面的效果,Fragment的强大之处相信读者也已经切身感受到了。

总结:由于Fragment其可独立管理自己的生命周期且可以被动态地添加、删除和替换的特性,它在实际开发中应用得非常频繁,因此开发人员必须深刻地理解Fragment各方面特点并熟练地运用。一般学习Fragment需要着重学习以下几点:(1)Fragment的生命周期管理;(2)如何动态添加、删除和替换Fragment;(3)如何建立Fragment与宿主Activity和建立Fragment与Fragment之间的通信;(4)利用Fragment适配手机和平板的视图界面显示。

源代码下载,请戳这里!