详细参考:http://developer.android.com/reference/android/app/Fragment.html
java.lang.Object | |
↳ | android.app.Fragment |
碎片是一段儿应用程序界面或行为,它可被植入活动内。与碎片的交互是通过FragmentManager
来进行的,它可由Activity.getFragmentManager()
和Fragment.getFragmentManager()
获得。
有多种方式使用碎片类去实现各种各样的效果。在其内部,它表现为运行在一个更大活动内的特殊操作或界面。碎片与活动紧密相连,且不能在活动以外使用碎片。尽管碎片定义了自己的生命周期,但它是依赖于它的活动:如果活动被停止了,在活动内碎片都不能被启动;当活动被销毁时,所有的碎片也将被销毁。
所有碎片的子类都必须包含一个公共的空构造函数。当在需要时,框架往往会重新实例化一个碎片类,尤其是在状态恢复期间,需要能够找到这个构造函数来初始化它。如果没有可用的空构造函数,在状态恢复期间的某些状况下回发生运行时异常。
较老的平台(Older Platforms)
碎片API是由 HONEYCOMB
引入的,凭借
y FragmentActivit
,该版本API在较早的平台上依然可用。详情请参考博文Fragments For All。
生命周期(Lifecycle)
尽管碎片的生命周期被捆绑在它所属的活动上,但除了标准的活动生命周期外,它有属于自己的新特征。它不包括了基本的活动生命周期方法,譬如onResume()
,重要的是还有与活动和UI生成相交互有关的方法。
被用来促使碎片恢复状态(与用户交互)的核心序列方法是:
onAttach(Activity)
,一旦碎片与活动有关联时即被调用。onCreate(Bundle)
,被调用来创建碎片。onCreateView(LayoutInflater, ViewGroup, Bundle)
,创建并返回与碎片有关联的视图层级。onActivityCreated(Bundle)
, 告诉碎片,它的活动已经完成它的Activity.onCreate()
。onStart()
, 使得碎片对用户可见(基于包含它的活动已被启动)。onResume()
,使得用户可以与碎片交互(基于包含它的活动正被唤起) 。
随着碎片不再被使用,它会经历一个相反序列的回调:
onPause()
,碎片不再与用户交互,或许是因为它的活动被暂停了,或者是在它的活动内某个碎片操作正在修改它。onStop()
,碎片不在对用户可见,或许是因为它的活动正在被停止,或者是在它的活动内某个碎片操作正在修改它。onDestroyView()
,允许碎片清理与它的视图有关联的资源。onDestroy()
,被调用来做碎片状态的最后清理。onDetach()
,不再与它的活动有关联之前立即被调用。
碎片可被用作应用程序布局的一部分,它允许你更好地模块化代码以及更容易地把用户界面调整到它正在运行的屏幕上。作为一个例子,我们会看到一个简单的程序,它包含了一个列表,并显现每个列表项的细节。
活动的布局XML可以包括<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>
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fragment_layout); }
标题碎片显示一个主题列表,它相当的简单,它的大部分工作都依靠ListFragment
。注意点击一列表项的实现:根据当前的活动布局,它可以创建并显示一个新的碎片来就地显示项目细节(更多细节在其后),或是启动一个新的活动来显示细节。
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(); ft.replace(R.id.details, 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; } }
当用户选择一个标题时,而在当前活动里没有容纳标题细节的空间,这种情况下,标题碎片的点击代码将启动一个新的活动来显示细节碎片:
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(); } } }
然而,屏幕可能有足够大的空间来显示标题列表和当前被选中的标题细节。为在横向屏幕上使用如此布局,该布局可被放置在layout-land目录下:
<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>
注意,先前的代码将如何适应该可选的UI流: 现在,标题碎片将把细节碎片嵌入到它(标题)的活动内部,并且细节活动将会结束它自己,如果它正在一个可以就地显示的屏幕配置中运行。
当屏幕配置发生变化时,导致持有那些碎片的活动被重新启动,它(活动)的实例可能使用一个不同的布局,该布局不包括和先前布局里一样的碎片。这种情况下,所有先前的碎片将依然被初始化并运行在活动的新实例中。然而,在视图层级中,任何与 <fragment>标记不再有关联的碎片将不会拥有内容视图,并从isInLayout()
中返回false。(这里的代码也显示了如何确定是否容器内的碎片不再运行在该容器内的布局中,并在此情况下避免创建它的视图层级。)
onInflate(Activity, AttributeSet, Bundle)
的参数由碎片来解析。
被初始化的碎片必须具备一些独特的标示,目的是为了它可以被重新关联,如果它的所属活动需要被销毁并重新创建。可以通过如下方式提供标示:
后退堆栈(Back Stack)
修改碎片的事务可被放置在宿主活动内部的后退堆栈里。当用户在活动内按下返回键时,在其结束前,后退堆栈内的任何事务都会被弹出。
例如,考虑这个简单的碎片,它由一个整数参数初始化,并在其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();
每次调用该方法之后,在后退堆栈上都是一个新的记录,并且按下返回按钮将会弹出它使用户返回到活动UI的先前状态。
2012年5月7日, 毕