Fragment是可以代替Activity的一个应用程序的界面或行为。Fragment都是是通过FragmentManager进行管理的,而获取它的方式包括Activity.getFragmentManager()和fagment.getFragmentManager()。
碎片类可以使用多种方法来实现各种各样的结果。如果是运行在一个很大的Activity中,在它的核心,它代表一个特定的操作或接口。一个Fragment片段和他所处的Activity联系紧密,并且不能单独使用。虽然Fragment定义了它自己的生命周期,但是他的生命周期还是依赖于它所处的Activity:如果Activity被停止,它里面的Fragment都不能启动;当Activity被销毁掉,那么所有的Fragment都会被销毁掉。
所有Fragment的子类都必须包含一个公开的无参构造方法。当需要Fragment的时候。框架往往会重新实例化这个Fragment,特别是在状态恢复并且需要能够找到这个构造函数的实例化。如果没有可用的无参构造函数,会在状态还原的某些情况下发生运行时异常。
涵盖的主题有:
1、Older Platforms 旧平台
2、LifeCycle 生命周期
3、Layout 布局
4、Back Stack 返回栈
Older Platforms 旧平台
而HONEYCOMB是在API3.1版本被介绍的,之前没有,如果想要低版本的Activity兼容Fragment即旧平台,需要通过FragmentActivity使用。
LifeCycle 生命周期
虽然一个Fragment的生命周期和他所在的Activity有关,但它在标准的Activity生命周期中也有自己的规律。它包括基本的Activity的生命周期的方法,比如onResume(),但是同样重要的是和它紧密相连行为和UI生成相关的方法。
被调用的生命周期方法的核心系列,使Fragment恢复到开始时状态(与用户交互)的是:
1.onAttach(Activity) 使Fragment与他的Activity相关联
2.onCreate(Bundle) Fragment的初始创建
3.onCreateView(LayoutInflater,ViewGroup,Bundle) 创建并返回与该Fragment关联的视图层次结构
4.onActivityCreated(Bundle) 告诉Fragment,它所在的Activity已经完成了onCreate()方法
5.onViewStateRestored(Bundle) 告诉Fragment,它的视图层次结构的所有已保存的状态已恢复
6.onStart() 开始让用户可见的片段(基于包含它的Activity已经开始)
7.onResume() 让fragment开始与用户进行交互(基于其 所在的activity也正在恢复)
Fragment不再被使用,它会通过一系列回调进行:
1.onPause() Fragment不再与用户交互,因为它的Activity被暂停或者fragment操作正在修改它的activity
2.onStop() Fragment是不再可见用户,因为它的Activity被停止或者fragment操作正在修改它的activity
3.onDestoryView() 允许Fragment清理与它的视图相关联的资源
4.onDestory() 做最后的清理
5.onDetach() 立即调用之前,该Fragment不再与它的活动有关
Layout 布局
Fragment可以作为你的应用程序的布局的一部分,可以让你更好的模块化代码并且更容易的调整用户界面。举个例子,我们可以看一个简单的程序,包括一个项目的列表并显示每个项目的细节。
一个Activty的布局XML可以包含<fragment>标签,以在布局中嵌入fragment的实例。例如,下面一个简单的布局,嵌入一个fragment:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles" android:layout_width="match_parent" android:layout_height="match_parent" /> </FrameLayout>布局在Activity 中的普通方式:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_layout); }标题fragment,显示列表的标题,是相当简单的,依靠ListFragment能完成其大部分工作。注意单机一个项目的实现:取决于当前Activity的布局,它可以创建和显示一个新的fragment来显示细节的位置,或者开始一个新的Activity去显示细节。
public static class TitlesFragment extends ListFragment { boolean mDualPane; int mCurCheckPosition = 0; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // Populate list with our static array of titles. setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES)); // Check to see if we have a frame in which to embed the details // fragment directly in the containing UI. View detailsFrame = getActivity().findViewById(R.id.details); mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE; if (savedInstanceState != null) { // Restore last state for checked position. mCurCheckPosition = savedInstanceState.getInt("curChoice", 0); } if (mDualPane) { // In dual-pane mode, the list view highlights the selected item. getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); // Make sure our UI is in the correct state. showDetails(mCurCheckPosition); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt("curChoice", mCurCheckPosition); } @Override public void onListItemClick(ListView l, View v, int position, long id) { showDetails(position); } /** * Helper function to show the details of a selected item, either by * displaying a fragment in-place in the current UI, or starting a * whole new activity in which it is displayed. */ void showDetails(int index) { mCurCheckPosition = index; if (mDualPane) { // We can display everything in-place with fragments, so update // the list to highlight the selected item and show the data. getListView().setItemChecked(index, true); // Check what fragment is currently shown, replace if needed. DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.details); if (details == null || details.getShownIndex() != index) { // Make new fragment to show this selection. details = DetailsFragment.newInstance(index); // Execute a transaction, replacing any existing fragment // with this one inside the frame. FragmentTransaction ft = getFragmentManager().beginTransaction(); if (index == 0) { ft.replace(R.id.details, details); } else { ft.replace(R.id.a_item, details); } ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); ft.commit(); } } else { // Otherwise we need to launch a new activity to display // the dialog fragment with selected text. Intent intent = new Intent(); intent.setClass(getActivity(), DetailsActivity.class); intent.putExtra("index", index); startActivity(intent); } } }显示选定项目的内容的详细信息片段只显示一个基于字符串数组的索引的一个字符串,该字符串数组内置于应用程序中的索引:
public static class DetailsFragment extends Fragment { /** * Create a new instance of DetailsFragment, initialized to * show the text at 'index'. */ public static DetailsFragment newInstance(int index) { DetailsFragment f = new DetailsFragment(); // Supply index input as an argument. Bundle args = new Bundle(); args.putInt("index", index); f.setArguments(args); return f; } public int getShownIndex() { return getArguments().getInt("index", 0); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { if (container == null) { // We have different layouts, and in one of them this // fragment's containing frame doesn't exist. The fragment // may still be created from its saved state, but there is // no reason to try to create its view hierarchy because it // won't be displayed. Note this is not needed -- we could // just run the code below, where we would create and return // the view hierarchy; it would just never be used. return null; } ScrollView scroller = new ScrollView(getActivity()); TextView text = new TextView(getActivity()); int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getActivity().getResources().getDisplayMetrics()); text.setPadding(padding, padding, padding, padding); scroller.addView(text); text.setText(Shakespeare.DIALOGUE[getShownIndex()]); return scroller; } }在这种情况下,当用户点击一个标题时,在当前Activity中没有详细的容器,所以标题片段的点击码将启动一个新的Activity来显示细节片段:
public static class DetailsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { // If the screen is now in landscape mode, we can show the // dialog in-line with the list so we don't need this activity. finish(); return; } if (savedInstanceState == null) { // During initial setup, plug in the details fragment. DetailsFragment details = new DetailsFragment(); details.setArguments(getIntent().getExtras()); getFragmentManager().beginTransaction().add(android.R.id.content, details).commit(); } } }然而,屏幕可能大到足以显示标题的列表和当前选定的标题的详细信息。要使用这样的布局在一个大屏幕上,这种替代布局可以实现:
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment" android:id="@+id/titles" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" /> <FrameLayout android:id="@+id/details" android:layout_weight="1" android:layout_width="0px" android:layout_height="match_parent" android:background="?android:attr/detailsElementBackground" /> </LinearLayout>请注意,以前的代码将如何调整到这个替代的用户界面流程:标题片段现在将嵌入在这个活动的细节片段,以及细节活动将完成自己,如果它是在一个配置中运行的细节可以显示。
当一个配置更改导致托管这些fragment的Activity重新启动时,它的新实例可能会使用不同的布局,它不包括以前布局相同的fragment。在这种情况下,所有以前的fragment仍然会被实例化并且在新的实例中运行。然而任何不再与视图层次<fragment>标签相关的都不会再有他们包含的视图中被创建并且isInLayout()会返回false。
在<fragment>标签的属性是用来控制提供附加fragment视图的父容器。他们可以在onInflate作为参数。
该fragment被实例化,必须有某种独特的标识符,它可以重新与父Activity需要销毁并重新创建一个先前的实例相关联。这可以提供这些方式:
- 如果没有显示提供,容器的视图将被使用
- android: 这个标签可以在<fragment>中使用,为改fragment提供一个特定的标签名称
- android:id 可以为该fragment提供特定的标识符
BackStack 返回栈
修改后的fragment可以放置在拥有Activity的内部后台堆栈上。当用户在Activity返回时,在Activity本身完成之前,在后台堆栈上 的任何事物都被弹出了。
例如,考虑一下这个简单的fragment,是使用一个整型参数并显示在UI一个TextView:
public static class CountingFragment extends Fragment { int mNum; /** * Create a new instance of CountingFragment, providing "num" * as an argument. */ static CountingFragment newInstance(int num) { CountingFragment f = new CountingFragment(); // Supply num input as an argument. Bundle args = new Bundle(); args.putInt("num", num); f.setArguments(args); return f; } /** * When creating, retrieve this instance's number from its arguments. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mNum = getArguments() != null ? getArguments().getInt("num") : 1; } /** * The Fragment's UI is just a simple text view showing its * instance number. */ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.hello_world, container, false); View tv = v.findViewById(R.id.text); ((TextView)tv).setText("Fragment #" + mNum); tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb)); return v; } }一个函数,创建一个新的片段的实例,替换任何当前的片段实例被显示和推动,改变到后面的堆栈可以写为:
void addFragmentToStack() { mStackLevel++; // Instantiate a new fragment. Fragment newFragment = CountingFragment.newInstance(mStackLevel); // Add the fragment to the activity, pushing this transaction // on to the back stack. FragmentTransaction ft = getFragmentManager().beginTransaction(); ft.replace(R.id.simple_fragment, newFragment); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); ft.addToBackStack(null); ft.commit(); }在每次调用这个函数后,会在堆栈上生成一个新的条目,然后点击返回会弹出并且返回用户之前的界面上。