API中关于Fragment的介绍
Fragment是一种放置在#Activity中的向用户展示或供用户操作的UI界面。通过Activity.getFragmentManager()或者Fragment.getFragmentManager()来获取#FragmentManager对象来管理与Fragment的交互。
有很多中不同的Fragment使用方式来达到各种各样的效果。其核心是,它将一个庞大的Activity中部分独立的操作或者界面独立了出来。Fragment与它依附的Activity的关系是紧密的,并且不能被Activity之外的事物使用。尽管Fragment有自己的生命周期,但这个生命周期决定于他所依附的Activity:Activity停止,Fragment停止;Activity销毁,Fragment销毁。
Fragment的子类必须有一个公共的无参构造函数,系统会在必要时比如在状态还原期间重建Fragment实例,这就需要这个构造方法。如果这个无参构造没有找到,有时会发生运行时异常。
旧版本兼容
Fragment是在android 3.0版本引入进来的。老版本可以通过v4包下的android.support.v4.app.FragmentActivity来实现相同功能。可以看看这篇Fragments For All来了解更多细节。
生命周期
虽然Fragment的生命周期是与它所在的Activity相关联,但是在标准的activity生命周期处理逻辑之外,它有它自己的处理:它里面包含了像onResume这种基本的activity的生命周期方法。但是更为重要的是这些方法让界面交互和UI之间产生联系。
下面列出的是Fragment从启动到展现给用户所调用的核心生命周期方法:
- onAttach() 调用该方法时Fragment会被连接到它的父Activity上。
- onCreate() 调用该方法来进行Fragment的初始化创建。
- onCreateView() 创建并返回Fragment绑定的view。
- onActivityCreated() 告诉Fragment它所依附的Activity完成了onCreate()方法。
- onViewStateRestored() 告诉Fragment保存过的状态已经恢复。
- onStart() Fragment对用户可见(基于Activity也调用onStart())。
- onResume() Fragment对用户是活动的,可获取焦点的。
如果Fragment不再使用,那么它会调用如下生命周期方法:
- onPause() Fragment不再允许有用户交互的操作,原因是Fragment附属的Activity要么也处于暂停状态,要么就是Fragment中的操作正在修改Activity。
- onStop() Fragment对用户不可见,原因是Fragment附属的Activity要么也处于停止状态,要么就是Fragment中的操作正在修改Activity。
- onDestroyView() 当Fragment的View被分离时,调用该方法,清理绑定View的相关资源,解除绑定。
- onDestroy() 在整个生命周期结束时调用该方法,清除所有的资源,包括结束线程和关闭数据库连接等。
- onDetach() 当Fragment从它的父Activity上分离时,调用该方法。
上述过程,可以图示:
在布局中的fragment标签
Fragment可以作为布局Layout中的一部分,方便开发者去更好的模块化代码以及创建出能够适配屏幕的灵活的用户界面。下面,我们就结合一个例子来说明(详见ApiDemos)。
这个示例工程首先是展示一个带有很多条目的列表,我们在看列表的同时还可以看到条目中更细节的内容。一个页面的布局XML可以嵌入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>
在FragmentLayout中正常加载布局:
setContentView(R.layout.fragment_layout);
TitlesFragment展示了一个标题的列表,继承自ListFragment的关系,父类做了很多工作,导致其代码相当简单。注意这个点击条目事件的实现:是直接在当前页面创建显示新的Fragment来展示条目细节,还是打开一个新页面来展示,取决于当前页面的布局。
/**
* This is the "top-level" fragment, showing a list of items that the
* user can pick. Upon picking an item, it takes care of displaying the
* data to the user as appropriate based on the currrent UI layout.
*/
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);
}
}
}
在工程中有一串字符串数组,当你点选某个条目时,DetailsFragment就展示对应条目对应的字符串内容。
/**
* This is the secondary fragment, displaying the details of a particular
* item.
*/
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;
}
}
如果当前布局中,没有显示详细内容的容器,那么当用户点击标题时,就会启动一个新的页面来显示详细内容。
/**
* This is a secondary activity, to show what the user has selected
* when the screen is not large enough to show it all in one 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文件夹下的横屏布局来切换:
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- Top-level content view for the layout fragment sample. This version is
for display when in landscape: we can fit both titles and dialog. -->
<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.sean.zq.android.view.view.fragment.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>
而这时先前的页面流程也变成了:页面中将会同时嵌入TitlesFragment和DetailsFragment,而如果DetailsActivity已经运行,会直接finish()。
Activity配置的改变会导致内嵌其中的Fragment会重新创建新实例,并且可能使用和之前不同的布局。即便这样,新实例化的所有Fragment都会被初始化。然而任何那个是不再关联和fragment标签在布局中将不会有它的内容布局创建过的和将会返回否从isInLayout().这里的代码也展示了怎样你可以决定如果一个fragment放置了在一个容器是不再运行在布局中那个容器和避免创建它自己的布局在那个时候。
当Fragment中d的view和它的父容器连接上时,可以通过LayoutParams来控制fragment标签的属性,也可以onInflate()方法中作为参数被解析。
被实例化时必须有一些唯一的标识,这样它才可以被结合和一个先前的实例,Fragment需要一些唯一的标识才能父Activity在销毁或重建时,被识别出是先前已经创建过的实例。android:tag和android:id可以为fragment提供唯一的tag名和id名,如果fragment没有显式提供这两个,那么将会使用父容器的view ID 。
后退栈
Fragment可以创建一个事务将自己加入父Activity的后退栈中,当用户点击返回键,Fragment的事务将先于Activity的从栈中弹出。
举个例子,初始化一个Fragment就用一个数字进行标号,并把标号用TextView显示在UI上(参见ApiDemos):
方法中展示了创建一个新的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();
}
之后的每一次添加都会调用这个方法,把Fragment加入后退栈中,之后,用户按返回键,Fragment就会按后进先出的顺序从栈中弹出。
在onCreateView()方法中创建绑定的Fragment的View。
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.addtask_frag, container, false);
mTitle = (TextView) root.findViewById(R.id.add_task_title);
mDescription = (TextView) root.findViewById(R.id.add_task_description);
setHasOptionsMenu(true);
return root;
}