Class Overview
Fragment是可以在用户界面上放置一个交互或者行为替代Activity。Fragments交互是通过FragmentManager
,它可以通过Activity.getFragmentManager()
and Fragment.getFragmentManager()获得。
Fragment类可以用许多方法展示各种各样的结果,它的核心是,它代表了一个特定的操作或者交互运行在一个更大的Activity中,Fragment与它在的Activity密切相关,它不能单独使用。尽管Fragment有自己的生命周期,但是它的生命周期取决于它的Activity。如果Activity
stopped了,没有fragment在它里面可以被启动。如果一个Activity
destroyed,所有的fragmens将会destroryed。
所有Fragment的子类都必须包括一个无参的构造。framework将重启一个Fragment当需要时在一个特定的状态恢复。需要能找到这个构造函数实例化它,如果无参构造函数不能用,在某些情况下将发生运行时异常当状态恢复时。
Developer Guides
Older Platforms
Fragment API 是在HONEYCOMB之后引入的,如果想在旧平台中使用可以通过
FragmentActivity。
Lifecycle
尽管一个Fragment的生命周期与他自己的Activity有关,它有自己的扩展标准activity的生命周期,它包含基本的activity的生命周期比如onResume(),但是重要的是它的生命周期与activity的生命周期和UI的生命周期相关联。
当一个Fragment引入时以下关键的生命周期方法将会回调:
onAttach(Activity)
当Fragment与它的activity建立关联时。onCreate(Bundle)
当Fragment初始化时回调。onCreateView(LayoutInflater, ViewGroup, Bundle)
创建并返回与之关联的视图。onActivityCreated(Bundle)
告诉 fragment 它的 activity 已经完成了 它的Activity.onCreate()
.onViewStateRestored(Bundle)
告诉 fragment 它所有保存的视图状态已经恢复。onStart()
使Fragment对用户可见(基于其包含活动started)。onResume()
使Fragment与用户进行交互(基于其包含活动resumed)。
当一个Fragment不在被使用,以下生命周期的方法将会回调。
onPause()
fragment 不再是与用户交互, 因为activity paused 或者Fragment操作在activity中修改了。onStop()
fragment不再是用户可见的,因为activity stopped 或者Fragment操作在activity中修改了。onDestroyView()
fragment清除视图资源。onDestroy()
清理和销毁Fragment.onDetach()
当与它的activity解除关联时调用。
Layout
Fragments可以用作应用程序的布局的一部分,让你更容易更好的模块化代码和调整你的用户界面屏幕上运行。作为一个例子,我们可以看一个简单的程序包含一个项目列表,并显示每个项目的细节。
一个activity的布局XML可以包含 <fragment>
tags嵌入 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>
用普通的方法引入布局:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_layout);
}
titles的 fragment,显示一个标题列表,是相当简单的,依靠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();
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);
}
}
}
细节fragment显示选中的项的内容只显示一个文本字符串,基于一个字符串数组的索引被应用程序构建:
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();
}
}
}
然而屏幕可能足以显示标题的列表和详细信息关于当前选择的标题。使用这种布局景观屏幕上,这个替代布局可以置于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将和细节Fragment嵌入在这个活动,并且细节activity将结束本身在配置决定细节在可以就地显示。
当配置变化将引起activity和它的Fragment重新启动,新的实例将使用不同的布局,不包括与之前布局相同的布局。在这种情况下,之前使用的Fragment仍将实例化在新的实例中运行。然而,不再创建一个view通过<fragment> 标签将使他们 的view创建时方法isInLayout()返回false。(这段代码展示了如何确定一个Fragment是否放入一个容器中不在运行在另外一个布局容器中避免破坏创建它的视图层次的情况)。
<fragment>标记的属性是用于控制LayoutParams提供当添加fragment的视图到父容器的时。他们也可以被解析通过fragment的onInflate(Activity, AttributeSet, Bundle)
作为参数。
被实例化的 fragment必须有某种独特的标识符,使它能够被销毁后并重建。可以通过以下方法。
1,如果没有显式地提供,将使用容器的View ID。
2,android:tag可以作为
<fragment> 的属性提供一份特殊标识名字为 fragment.
3,android:id
可以作为
<fragment> 的属性提供一份特殊ID为 fragment.
Back Stack
fragments的改变事物可以通过在activity中用一个返回栈。当用户按下返回键在活动中,返回栈内的fragment将会弹出在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;
}
}
新的fragment的实例的产生可以写在以下函数中,取代当前实例化fragment的显示,取代当前fragment的实例显示并且将其压入返回栈的代码如下:
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();
}
每次调用这个函数后,一个新的实例是在堆栈上,按下返回键会弹出之前当activity创建后的所有fragment。