代码改变世界

fragment的介绍与使用

2013-12-08 22:26  ...平..淡...  阅读(1074)  评论(0编辑  收藏  举报

稍稍摘录一段Fragment.java中的说明文档。

/**
 * A Fragment is a piece of an application's user interface or behavior
 * that can be placed in an {@link Activity}.  Interaction with fragments
 * is done through {@link FragmentManager}, which can be obtained via
 * {@link Activity#getFragmentManager() Activity.getFragmentManager()} and
 * {@link Fragment#getFragmentManager() Fragment.getFragmentManager()}.
 *
 * <p>The Fragment class can be used many ways to achieve a wide variety of
 * results. In its core, it represents a particular operation or interface
 * that is running within a larger {@link Activity}.  A Fragment is closely
 * tied to the Activity it is in, and can not be used apart from one.  Though
 * Fragment defines its own lifecycle, that lifecycle is dependent on its
 * activity: if the activity is stopped, no fragments inside of it can be
 * started; when the activity is destroyed, all fragments will be destroyed.
 *
 * <p>All subclasses of Fragment must include a public empty constructor.
 * The framework will often re-instantiate a fragment class when needed,
 * in particular during state restore, and needs to be able to find this
 * constructor to instantiate it.  If the empty constructor is not available,
 * a runtime exception will occur in some cases during state restore.
 */

其中有一点以前不知道,就是Fragment的子类必须包括一个public的空构造方法,否则可能会发生运行时异常。因为framework经常会在需要的时候(比如状态恢复时),重新实例化Fragment类,这个时候它就会查找这个构造方法来进行实例化。

本文主要是参考官方文档(https://developer.android.com/guide/components/fragments.html),记录下Fragment的使用用法。

Design Philosophy(设计理念)

为了在大小屏间更好的利用空间,更好的动态地设计UI

Figure 1. An example of how two UI modules defined by fragments can be combined into one activity for a tablet design, but separated for a handset design.

Creating a Fragment(创建Fragment对象)

方法:创建子类继承自Fragment(或者已存在的子类)

那接下来不得不讨论的就是Fragment的生命周期了。

方法 说明
onAttach()  called once the fragment is associated with its activity.
onCreate() called to do initial creation of the fragment.
onCreateView()

creates and returns the view hierarchy associated with the fragment.

onActivityCreated()

tells the fragment that its activity has completed its own {@link Activity#onCreate Activity.onCreate()}.

onStart()

makes the fragment visible to the user (based on its containing activity being started).

onResume()

makes the fragment interacting with the user (based on its containing activity being resumed).

onPause()

fragment is no longer interacting with the user either because its activity is being paused or a fragment operation is modifying it in the activity.

onStop()

fragment is no longer visible to the user either because its activity is being stopped or a fragment operation is modifying it in the activity.

onDestroyView()

allows the fragment to clean up resources associated with its View.

onDestroy() called to do final cleanup of the fragment's state.
onDetach()

called immediately prior to the fragment no longer being associated with its activity.

 

Adding a user interface(为Fragment添加用户界面)

Fragment一般都是将自己作为Activity的一部分,并在Activity上显示自己的界面。而Fragment中实例化UI界面的操作是在onCreateView方法中。下面这个例子,在onCreateView方法中加载了xml布局文件:

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);
    }
}

onCreateView方法参数解析:

1.inflater

调用其inflate方法,最终会返回一个View对象。inflate方法参数说明:

(1)想要扩展的布局文件的资源id

(2)ViewGroup对象,加载参数1的布局

(3)boolean变量,表明是否将参数1的布局添加到参数2的ViewGroup中(这里为false,是因为系统已经将该布局添加到container了)

2.container

container是存放fragment的layout的ViewGroup对象。抽象么?换种说法:Activity的布局,暂称为A;Fragment的布局,暂称为F。因为Fragment会嵌套到Activity中,布局F自然需要加入到布局A里面。所以这里的container就是指布局A中用来加载布局F的ViewGroup(比如LinearLayout)。

3.savedInstanceState

Bundle对象,保存先前的fragment的实例等数据,用来恢复。

OK,为Fragment添加界面就结束了,总结:在onCreateView方法中,加入layout布局文件。接下来就是添加Fragment到Activity了。

Adding a fragment to an activity(添加Fragment到Activity)

two methods:

* Declare the fragment inside the activity's layout file. (在Activity的布局文件中添加Fragment,通过<fragment ...>标签)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

 说明:

1.<fragment>中的android:name指明了具体的Fragment类。当系统创建Activity的布局时,它会检查每个<fragment>,并调用指明的Fragment类的onCreateView方法。当onCreateView返回一个View对象后,系统会用该View替换<fragment ...>标签指代的内容

Note:每个Fragment都需要一个唯一标识符ID,用来在Activity restart 的时候恢复Fragment。有三种方法可以提供ID:

(1)android:id                     a unique ID

(2)android:tag                  a unique string

(3)container view的ID      当(1)(2)都没有设置的话

 * Or, programmatically add the fragment to an existing ViewGroup.(在代码中,动态添加fragment到一个已存在的ViewGroup中)

想要在Activity中动态添加(替换/删除)Fragment,需要FragmentTransaction类的实例。该类的实例可通过下面方法获得:

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

 添加Fragment:通过add()方法  (当然还有replace/remove等方法,具体参考官方文档)

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

 Note:add方法的第一个参数是ViewGroup对象的ID,用来添加fragment。 当操作完成后,还需要调用FragmentTransaction::commit方法,才能奏效。 

Adding a fragment without a UI(添加一个无UI的fragment,这个情况我没用过,官方文档说 比如进行后台操作的时候,就不用界面了)

通过 add(Fragment, String)方法添加此Fragment对象。第二个参数是一个字符串,作为tag,唯一识别该Fragment。因为没有UI,就无法设置android:id这类的属性了,就只能通过这里的tag来获取该Fragment了(使用方法 findFragmentByTag (String tag) 来获取Fragment)。 

上面讲完了”创建Fragment“ / ”加载Fragment到Activity“后,接下来说说如何管理Fragment 

Managing Fragments(管理Fragment)

最开始需要先获取 FragmentManager类对象,通过getFragmentManager() 方法。FragmentManager对象可以做以下几件事情:

Performing Fragment Transactions(操作Fragment)

在Activity中使用Fragment很大的特征就是添加/删除/替换。每次把对Fragment所作的操作被称为一个transaction。当commit 这个 transaction 到Activity里时,需要另一个类 FragmentTransaction。该类对象的获取方法如下:

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

 然后进行add()remove(), and replace() 等方法。然后通过commit() 提交transaction到Activity中。

Note:当调用commit()方法前,可以调用addToBackStack()方法将transaction添加到  back stack 中(这个back stack是由Activity管理的)。当用户按返回键后,就可以返回到Fragment执行transaction之前的状态了。

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

 当提交”删除Fragment“的transaction时,如果不调用addToBackStack方法,那么这个Fragment就destroyed了,用户就回不到之前的Fragment了。但反之,调用了addToBackStack方法后再调用commit方法,那么 这个Fragment只是处于stopped状态,按返回键后,就可以resume该Fragment。

当然,如果你一次性对Fragment进行很多操作,然后调用一次commit()方法,系统还是会将这一系列操作作为一次transaction的。

Communicating with the Activity(与Activity交互)

Activity中:通过调用getFragmentManager方法,获得FragmentManager实例。然后通过 findFragmentById() or findFragmentByTag()方法获得Fragment。

Fragment中:通过getActivity方法获得Activity的Context,然后通过findViewById方法获得Activity布局中的控件。

Creating event callbacks to the activity(让Activity响应Fragment的事件)

一个很好的方法就是:

1.在Fragment中实现一个接口,包含一些回调方法。

2.让Activity实现这个接口。

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

这样,当Activity接受到回调方法时,就能够获得Fragment的信息,并且能将信息传递给其他Fragment了。

那怎么在Fragment中怎么调用Activity的实例,为它设置回调方法呢?可以通过getActivity方法,但是更好的是在onAttach方法中,因为onAttach方法的参数就是Fragment所在的Activity的实例。那么,就可以在onAttach方法中将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");
        }
    }
    ...
}

 --------------------------------------------------------------------------------------------------

OK,就讲到这里了,下面给出一个小例子,先上图:

代码结构截图:

代码如下:

 1 package com.cb.fragmenttest;
 2 
 3 import com.cb.listener.OnTitleItemClickListener;
 4 
 5 import android.os.Bundle;
 6 import android.app.Activity;
 7 import android.app.Fragment;
 8 import android.app.FragmentManager;
 9 import android.util.Log;
10 
11 public class MainActivity extends Activity implements OnTitleItemClickListener {
12     private static final String TAG = "MainActivity";
13 
14     @Override
15     protected void onCreate(Bundle savedInstanceState) {
16         super.onCreate(savedInstanceState);
17         setContentView(R.layout.activity_main);
18         Log.d(TAG, "onCreate");
19     }
20     
21     @Override
22     public void onItemClick(int position) {
23         Log.d(TAG, "onItemClick: position is "+position);
24         ContentFragment contentFragment = (ContentFragment) getFragmentManager().findFragmentById(R.id.content_fragment);
25         contentFragment.updateContent(position);
26     }
27 }
主Activity::MainActivity.java
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="match_parent"
 3     android:layout_height="match_parent"
 4     android:orientation="horizontal" >
 5 
 6     <fragment
 7         android:id="@+id/title_fragment"
 8         android:name="com.cb.fragmenttest.TitleFragment"
 9         android:layout_width="0dp"
10         android:layout_height="match_parent"
11         android:layout_weight="1" />
12 
13     <fragment
14         android:id="@+id/content_fragment"
15         android:name="com.cb.fragmenttest.ContentFragment"
16         android:layout_width="0dp"
17         android:layout_height="match_parent"
18         android:layout_weight="2" />
19 
20 </LinearLayout>
MainActivity::activity_main.xml

 

两个Fragment:TitleFragment、ContentFragment

 1 package com.cb.fragmenttest;
 2 
 3 import com.cb.listener.OnTitleItemClickListener;
 4 import com.cb.utils.Constants;
 5 
 6 import android.app.Activity;
 7 import android.app.Fragment;
 8 import android.os.Bundle;
 9 import android.util.Log;
10 import android.view.LayoutInflater;
11 import android.view.View;
12 import android.view.ViewGroup;
13 import android.widget.AdapterView;
14 import android.widget.AdapterView.OnItemClickListener;
15 import android.widget.ArrayAdapter;
16 import android.widget.ListView;
17 
18 public class TitleFragment extends Fragment implements OnItemClickListener {
19     private static final String TAG = "TitleFragment";
20     private ListView mListView;
21     private OnTitleItemClickListener mCallback;
22 
23     @Override
24     public void onActivityCreated(Bundle savedInstanceState) {
25         super.onActivityCreated(savedInstanceState);
26         Log.d(TAG, "onActivityCreated");
27     }
28 
29     @Override
30     public void onAttach(Activity activity) {
31         super.onAttach(activity);
32         Log.d(TAG, "onAttach: " + activity);
33         try {
34             mCallback = (OnTitleItemClickListener) activity;
35         } catch (ClassCastException e) {
36             e.printStackTrace();
37         }
38     }
39 
40     @Override
41     public void onCreate(Bundle savedInstanceState) {
42         super.onCreate(savedInstanceState);
43         Log.d(TAG, "onCreate");
44     }
45 
46     @Override
47     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
48         Log.d(TAG, "onCreateView");
49         View view = inflater.inflate(R.layout.title_fragment, container, false);
50         mListView = (ListView) view.findViewById(R.id.list);
51         ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, Constants.mTitle);
52         mListView.setAdapter(adapter);
53         mListView.setOnItemClickListener(this);
54         return view;
55     }
56 
57     @Override
58     public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
59         Log.d(TAG, "onItemClick: arg2 is "+arg2);
60         mCallback.onItemClick(arg2);
61     }
62 
63 }
TitleFragment.java
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:id="@+id/linear2"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <TextView
 8         android:layout_width="wrap_content"
 9         android:layout_height="wrap_content"
10         android:layout_gravity="center_horizontal"
11         android:text="Titles"
12         android:textColor="#0000ff" />
13 
14     <ListView
15         android:id="@+id/list"
16         android:layout_width="match_parent"
17         android:layout_height="wrap_content" />
18 
19 </LinearLayout>
TitleFragment::title_fragment.xml 
 1 package com.cb.fragmenttest;
 2 
 3 import com.cb.utils.Constants;
 4 
 5 import android.app.Activity;
 6 import android.app.Fragment;
 7 import android.os.Bundle;
 8 import android.util.Log;
 9 import android.view.LayoutInflater;
10 import android.view.View;
11 import android.view.ViewGroup;
12 import android.widget.TextView;
13 
14 public class ContentFragment extends Fragment{
15     private static final String TAG = "ContentFragment";
16     
17     private TextView mTextView;
18 
19     @Override
20     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
21         Log.d(TAG, "onCreateView");
22         View view = inflater.inflate(R.layout.content_fragment, container, false);
23         mTextView = (TextView)view.findViewById(R.id.text);
24         return view;
25     }
26 
27     public void updateContent(int position) {
28         Log.d(TAG, "updateContent, mContent["+position+"] is "+Constants.mContent[position]);
29         mTextView.setText(Constants.mContent[position]);
30     }
31     
32 }
ContentFragment.java
 1 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:id="@+id/linear2"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <TextView
 8         android:layout_width="wrap_content"
 9         android:layout_height="wrap_content"
10         android:layout_gravity="center_horizontal"
11         android:text="Contents"
12         android:textColor="#0000ff"
13         android:textIsSelectable="true" />
14 
15     <ScrollView 
16         android:layout_width="wrap_content"
17         android:layout_height="wrap_content">
18     <TextView
19         android:id="@+id/text"
20         android:layout_width="wrap_content"
21         android:layout_height="wrap_content"
22         android:textSize="20sp" />
23     </ScrollView>
24 
25 </LinearLayout>
ContentFragment::content_fragment.xml

接口文件

1 package com.cb.listener;
2 
3 public interface OnTitleItemClickListener {
4     void onItemClick(int position);
5 }
MainActivity需要实现的接口

常量类

 1 package com.cb.utils;
 2 
 3 public class Constants {
 4 
 5     public static final String[] mTitle = { "Monday", "Tuesday", "Wednesday" };
 6 
 7     public static final String[] mContent = {
 8             "Monday (Listeni/ˈmʌndeɪ/ or /ˈmʌndi/) is the day of the week between Sunday and Tuesday. According to the traditional Christian, Islamic and Hebrew calendars, it is the second day of the week. But according to international standard ISO 8601 it is the first day of the week. The name of Monday is derived from Old English Mōnandæg and Middle English Monenday, which means \"moon day\".",
 9             "Tuesday (Listeni/ˈtjuːzdeɪ/, /ˈtjuːzdi/, /ˈtuːzdeɪ/ or /ˈtuːzdi/) is a day of the week occurring after Monday and before Wednesday. According to some commonly used calendars (esp. in the US), it is the third day of the week, but according to international standard ISO 8601, it is the second day of the week. The English name is derived from Old English Tiwesdæg and Middle English Tewesday, meaning \"Tīw's Day\", the day of Tiw or Týr, the god of single combat, victory and heroic glory in Norse mythology. Tiw was equated with Mars in the interpretatio romana, and the name of the day is a translation of Latin dies Martis.", 
10             "Wednesday (Listeni/ˈwɛdənzdeɪ/, Listeni/ˈwɛnzdeɪ/[1] or /ˈwɛnzdi/[2]) is the day of the week between Tuesday and Thursday. According to international standard ISO 8601 adopted in most western countries it is the third day of the week. In countries that use the Sunday-first convention Wednesday is defined as the fourth day of the week. It is the fourth day of the week in the Judeo-Christian calendar as well, and was defined so in the ancient Mesopotamian and biblical calendars. The name is derived from Old English Wōdnesdæg and Middle English Wednesdei, \"day of Wodanaz\", ultimately a calque of dies Mercurii \"day of Mercury\"." +
11             "Wednesday is in the middle of the common Western five-day workweek that starts on Monday and finishes on Friday." };
12 }
用到的一些常量