2011 年 Android 开发者面临的最大变化可能是 Android 3.0 引入了片段系统,以及最近 Android 4.0 冰淇淋三明治将片段系统合并到主代码库。片段是一个可选层,可以放在活动和小部件之间,旨在帮助您重新配置活动,以支持大屏幕(例如平板电脑)和小屏幕(例如手机)。然而,片段系统也增加了额外的复杂性,这将需要 Android 开发者社区一些时间来适应。因此,使用片段的公共评论、博客帖子和示例应用就更少了,因为片段是在 Android 之后很久才被引入的。
本章涵盖了片段的基本用途,包括在 Android 3.0 之前的设备上支持片段。
片段不是小部件,像Button
或EditText
。片段不是容器,像LinearLayout
或RelativeLayout
。片段不是活动。
相反,片段集合了小部件和容器。然后可以将片段放入活动中——有时一个活动有几个片段,有时每个活动有一个片段。原因是 Android 屏幕尺寸的变化。
平板电脑的屏幕比手机大。电视的屏幕比平板电脑大。利用额外的屏幕空间是有意义的,正如第 25 章中所描述的那样,该章解释了如何处理多种屏幕尺寸。在那一章中,我们分析了一个EU4You
示例应用,最终以一个活动结束,该活动将在一个不同的布局中为更大尺寸的屏幕加载,该布局具有一个嵌入的WebView
小部件。该活动将检测小部件的存在,并使用它来加载与选定国家相关的网页内容,而不是启动一个单独的浏览器活动或只包含一个WebView
的活动。
然而,第 25 章中概述的场景相当琐碎。想象一下,我们有一个包含 28 个小部件的TableLayout
,而不是一个WebView
。在更大尺寸的屏幕上,我们希望TableLayout
和相邻的ListView
在同一活动中;在较小的屏幕上,我们希望TableLayout
在一个单独的活动中,因为没有足够的空间。为了使用早期的 Android 技术做到这一点,我们需要复制两个活动中所有的TableLayout
处理逻辑,创建一个活动基类并希望两个活动都可以继承它,或者将TableLayout
和它的内容变成一个定制的ViewGroup
…或者做其他事情。这仅仅是针对一个这样的场景——在一个更大的应用中乘以许多活动,复杂性就会增加。
片段减少了复杂性,但没有消除。
对于片段,可以在多个活动中使用的用户界面的每个离散块(基于屏幕大小)都放在一个片段中。根据屏幕大小,正在讨论的活动决定了谁得到片段。
在EU4You
的例子中,我们有两个片段。一个片段代表国家列表。另一个片段表示该国家的详细信息(在我们的例子中,是一个WebView
)。在大屏幕设备上,我们希望两个片段都在一个活动中,而在小屏幕设备上,我们将这些片段放在两个独立的活动中。这为大屏幕用户提供了与上一个版本的EU4You
相同的好处:用更少的点击获得更多信息。然而,我们用片段演示的技术将更具可伸缩性,能够处理比简单的EU4You
的WebView
或【非】场景更复杂的 UI 模式。
在这种情况下,我们的整个 UI 都在片段中。那是不必要的。片段是一种选择加入的技术——你只需要它们用于你的 UI 中可能出现在不同场景的不同活动中的部分。事实上,您的活动根本不会改变(比如说,一个帮助屏幕)可能不会使用任何片段。
片段还为我们提供了其他一些额外的功能,包括:
ListFragment
。轻按一个文件夹会在屏幕上添加第二个ListFragment
,显示该文件夹中的对话。点击一个对话会在屏幕上添加第三个Fragment
,显示该对话中的信息。ListFragment
从屏幕上滑向左边,对话ListFragment
向左滑动并缩小以占据更少的空间,而消息Fragment
从右边滑入。Fragment
时按下后退,则Fragment
滑向右侧,对话ListFragment
向右滑动并扩展以填充更多屏幕,文件夹ListFragment
从左侧滑回。所有这些都不必由开发人员来管理——只需通过FragmentTransaction
添加动态片段,就可以让 Android 自动处理后退按钮,包括反转所有动画。onCreate()
中的setHasOptionsMenu()
来注册对此感兴趣,然后像在活动中一样覆盖片段中的onCreateOptionsMenu()
和onOptionsItemSelected()
。片段还可以注册小部件来拥有上下文菜单,并像处理活动一样处理这些上下文菜单。TabHost
,其中每个标签的内容都是一个片段。类似地,动作栏可以有一个导航模式,用一个Spinner
在模式之间切换,其中每个模式由一个片段表示。如果你可以使用任何运行 Honeycomb 或 Ice Cream Sandwich 的最新设备,启动 Gmail 应用来查看所有片段的功能。
如果片段只适用于 Android 3.0 和更高版本,我们将回到起点,因为今天并不是所有的 Android 设备都运行 Android 3.0 和更高版本。
幸运的是,情况并非如此,因为 Google 已经发布了 Android 兼容性库(ACL),它可以通过 Android SDK 和 AVD 管理器获得(在这里,您可以安装其他 SDK 支持文件,创建和启动您的仿真器 AVD,等等)。ACL 允许您访问从 Android 1.6 开始的 Android 版本上的片段系统。因为绝大多数 Android 设备运行的是 1.6 或更高版本,这允许您在保持向后兼容性的同时开始使用片段。随着时间的推移,对于希望使用该库的应用,该库可能会添加其他特性来帮助实现向后兼容性。
本章中的材料着重于在使用分段时使用 ACL。一般来说,对片段使用 ACL 几乎等同于直接使用原生 Android 3.0 片段类。
由于 ACL 仅支持 Android 1.6 的版本,Android 1.5 设备将无法使用基于片段的应用。这在目前的 Android 设备中只占很小的一部分——在撰写本文时大约是 1%。
建立基于片段的应用的第一步是为每个片段创建片段类。正如您从Activity
(或子类)继承活动一样,您从Fragment
(或子类)继承片段。
在这里,我们将检查Fragments/EU4You_6
示例项目和它定义的片段。
注:本书的约定将是使用“片段”作为通用名词,Fragment
指代实际的Fragment
类。
除了从Fragment
继承之外,片段唯一需要的就是覆盖onCreateView()
。这将作为把片段放在屏幕上的一部分被调用。您需要返回一个代表片段主体的View
。最有可能的是,您将通过一个 XML 布局文件来创建您的片段的 UI,并且onCreateView()
将扩展该片段布局文件。
例如,下面是来自EU4You_6
的DetailsFragment
,它将围绕我们的WebView
显示给定国家的网页内容:
`import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
public class DetailsFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return(inflater.inflate(R.layout.details_fragment, container, false));
}
public void loadUrl(String url) {
((WebView)(getView().findViewById(R.id.browser))).loadUrl(url);
}
}`
注意,我们不是从android.app.Fragment
继承,而是从android.support.v4.app.Fragment
继承。后者是来自 ACL 的Fragment
实现,因此它可以跨 Android 版本使用。
onCreateView()
实现扩展了一个布局,碰巧其中有一个WebView
:
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/browser"
android:layout_
android:layout_
/>
它还公开了一个loadUrl()
方法,宿主活动使用它来告诉片段是时候显示一些 web 内容了,并提供 URL 来做同样的事情。DetailsFragment
中loadUrl()
的实现使用getView()
检索onCreateView()
中创建的View
,找到其中的WebView
,将loadUrl()
调用委托给WebView
。
在Fragment
上有无数其他可用的生命周期方法。更重要的包括活动的标准onCreate()
、onStart()
、onResume()
、onPause()
、onStop()
和onDestroy()
方法的镜子。由于片段是带有小部件的片段,它将实现更多以前可能驻留在这些方法的活动中的业务逻辑。例如,在onPause()
或onStop()
中,由于用户可能不会返回到您的应用,您可能希望将任何未保存的编辑保存到某个临时存储中。在DetailsFragment
的例子中,这里没有真正合格的东西,所以那些生命周期方法被单独留下。
一个肯定会流行的Fragment
子类是ListFragment
。这将一个ListView
封装在一个Fragment
中,旨在简化国家、邮件文件夹、邮件对话等列表的设置。类似于一个ListActivity
,你所需要做的就是用你选择和配置的ListAdapter
调用setListAdapter()
,并且当用户点击列表中的一行时覆盖onListItemClick()
来响应。
在EU4You_6
中,我们有一个代表可用国家列表的CountriesFragment
。它初始化onActivityCreated()
中的ListAdapter
,这个函数在onCreate()
结束保存片段的活动后被调用:
`@Override
public void onActivityCreated(Bundle state) {
super
.onActivityCreated(state);
setListAdapter(new CountryAdapter());
if (state!=null) {
int position=state.getInt(STATE_CHECKED, -1);
if (position>-1) {
getListView().setItemChecked(position, true);
}
}
}`
处理提供给onCreate()
的Bundle
的代码将在本章稍后解释。
CountryAdapter
与之前的EU4You
样本几乎相同,除了在Fragment
上没有getLayoutInflater()
方法,所以我们必须在LayoutInflater
上使用静态from()
方法,并通过getActivity()
提供我们的活动:
`class CountryAdapter extends ArrayAdapter {
CountryAdapter() {
super(getActivity(), R.layout.row, R.id.name, EU);
}
@Override
public View getView(int position, View convertView,
ViewGroup parent) {
CountryWrapper wrapper=null;
if (cOnvertView==null) {
cOnvertView=LayoutInflater
.from(getActivity())
.inflate(R.layout.row, null);
wrapper=new CountryWrapper(convertView);
convertView.setTag(wrapper);
}
else {
wrapper=(CountryWrapper)convertView.getTag();
}
wrapper.populateFrom(getItem(position));
return(convertView);
}
}`
同样,CountryWrapper
与之前的EU4You
样品没有任何不同:
`static class CountryWrapper {
private TextView name=null;
private ImageView flag=null;
private View row=null;
CountryWrapper(View row) {
this.row=row;
name=(TextView)row.findViewById(R.id.name);
flag=(ImageView)row.findViewById(R.id.flag);
}
TextView getName() {
return(name);
}
ImageView getFlag() {
return(flag);
}
void populateFrom(Country nation) {
getName().setText(nation.name);
getFlag().setImageResource(nation.flag); }
}`
国家列表也是一样的:
static {
EU**.add**(new **Country**(R.string.austria, R.drawable.austria,
R.string.austria_url));
EU**.add**(new **Country**(R.string.belgium, R.drawable.belgium,
R.string.belgium_url));
EU**.add**(new **Country**(R.string.bulgaria, R.drawable.bulgaria,
R.string.bulgaria_url));
EU0**.add**(new **Country**(R.string.cyprus, R.drawable.cyprus,
R.string.cyprus_url));
EU**.add**(new **Country**(R.string.czech_republic,
R.drawable.czech_republic,
R.string.czech_republic_url));
EU**.add**(new **Country**(R.string.denmark, R.drawable.denmark,
R.string.denmark_url));
EU**.add**(new **Country**(R.string.estonia, R.drawable.estonia,
R.string.estonia_url));
EU**.add**(new **Country**(R.string.finland, R.drawable.finland,
R.string.finland_url));
EU**.add**(new **Country**(R.string.france, R.drawable.france,
R.string.france_url));
EU**.add**(new **Country**(R.string.germany, R.drawable.germany,
R.string.germany_url));
EU**.add**(new **Country**(R.string.greece, R.drawable.greece,
R.string.greece_url));
EU**.add**(new **Country**(R.string.hungary, R.drawable.hungary,
R.string.hungary_url));
EU**.add**(new **Country**(R.string.ireland, R.drawable.ireland,
R.string.ireland_url));
EU**.add**(new **Country**(R.string.italy, R.drawable.italy,
R.string.italy_url));
EU**.add**(new **Country**(R.string.latvia, R.drawable.latvia,
R.string.latvia_url));
EU**.add**(new **Country**(R.string.lithuania, R.drawable.lithuania,
R.string.lithuania_url));
EU**.add**(new **Country**(R.string.luxembourg, R.drawable.luxembourg,
R.string.luxembourg_url));
EU**.add**(new **Country**(R.string.malta, R.drawable.malta,
R.string.malta_url));
EU**.add**(new **Country**(R.string.netherlands, R.drawable.netherlands,
R.string.netherlands_url));
EU**.add**(new **Country**(R.string.poland, R.drawable.poland,
R.string.poland_url));
EU**.add**(new **Country**(R.string.portugal, R.drawable.portugal,
R.string.portugal_url));
EU**.add**(new **Country**(R.string.romania, R.drawable.romania,
R.string.romania_url));
EU**.add**(new **Country**(R.string.slovakia, R.drawable.slovakia,
R.string.slovakia_url));
EU**.add**(new **Country**(R.string.slovenia, R.drawable.slovenia,
R.string.slovenia_url));
EU**.add**(new **Country**(R.string.spain, R.drawable.spain,
R.string.spain_url));EU**.add**(new **Country**(R.string.sweden, R.drawable.sweden,
R.string.sweden_url));
EU**.add**(new **Country**(R.string.united_kingdom,
R.drawable.united_kingdom,
R.string.united_kingdom_url));
}
…正如Country
的定义一样,来自一个单独的公共类:
`public class Country {
int name;
int flag;
int url;
Country(int name, int flag, int url) {
this.name=name;
this.flag=flag;
this.url=url;
}
}`
当你使用像 Gmail 这样基于片段的应用时,有一件事会让你眼前一亮。当您点击列表中的一行,并且在同一活动中显示(或更新)另一个片段时,您点击的行会保持高亮显示。这与传统的使用ListView
背道而驰,在传统的使用中,列表选择器只有在使用 D-pad、轨迹球或类似的定点设备时才会出现。目的是向用户显示相邻片段的上下文。
实际的实现与您预期的不同。这些ListView
小部件实际上实现了CHOICE_MODE_SINGLE
,通常使用行右侧的RadioButton
来呈现。然而,在ListFragment
中,单选ListFragment
的典型样式是通过一个“激活的”背景。
在EU4You_6
中,这是通过我们的**Country**Adapter
使用的行布局(res/layout/row.xml
)来处理的:
`
android:layout_
android:padding="2dip"
android:min
android:layout_gravity="center_vertical|right"
android:textSize="5mm"
/>
`
注意style
属性,它指向一个activated
样式。这被EU4You_6
定义为本地风格,而不是操作系统提供的风格。事实上,它必须有两个样式的实现,因为“激活”的概念是 Android 3.0 的新功能,不能在以前的 Android 版本中使用。
因此,EU4You_6
有一个向后兼容的空样式的res/values/styles.xml
:
`
`
它还有res/values-v11/styles.xml
。-v11
资源集后缀意味着这将只在 API Level 11 (Android 3.0)及更高版本上使用。这里,风格继承了标准的 Android 全息主题,并使用标准的激活背景色:
`
?android:attr/activatedBackgroundIndicator
`
在CountriesFragment
中,活动将通过enablePersistentSelection()
方法让我们知道CountriesFragment
是否出现在DetailsFragment
旁边——因此需要单选模式:
public void **enablePersistentSelection**() {
**getListView**()**.setChoiceMode**(ListView.CHOICE_MODE_SINGLE);
}
同样,在onListItemClick()
,CountriesFragment
“检查”用户点击的行,从而启用持久高亮:
`@Override
public void onListItemClick(ListView l, View v, int position,
long id) {
l.setItemChecked(position, true);
if (listener!=null) {
listener.onCountrySelected(EU.get(position));
}
}`
listener
对象和对on**Country**Selected()
的调用将在本章后面解释。
ACL 还有另外一个子类Fragment
: DialogFragment
。这用于帮助协调模态Dialog
和基于片段的 UI。
Android 3.0 本身还有两个子类Fragment
,在本文撰写之时,ACL 中还没有:
PreferenceFragment
:用于新型蜂巢式PreferenceActivity
(包含在第 31 章中)WebViewFragment
:一个Fragment
缠绕着一个WebView
拥有一些片段类及其伴随的布局当然很好,但是我们需要将它们与活动联系起来,并让它们出现在屏幕上。在这个过程中,我们必须考虑如何处理多种屏幕尺寸,就像我们对先前版本的EU4You
示例采用的WebView
or-browser 方法一样。
在 Android 3.0 及更高版本中,任何活动都可以托管一个片段。但是,对于 ACL,需要从FragmentActivity
继承才能使用片段。ACL 的这种限制肯定会带来挑战,特别是如果您打算将地图放入片段中,这是我们将在本书后面讨论的主题。其他活动基类造成的问题更少——例如,ListActivity
将由ListFragment
代替。
片段可以通过两种方式添加到活动中:
元素来定义它们。这些片段是固定的,并且将在该活动实例的生存期内一直存在。FragmentManager
和FragmentTransaction
即时添加它们。这为您提供了更多的灵活性,但也增加了复杂性。这种技术不在本书讨论范围之内。处理多种屏幕尺寸的一个很大的限制是,对于任何配置更改,布局都需要有相同的起始片段。因此,活动的小屏幕版本和大屏幕版本可以有不同的片段组合,但是相同屏幕大小的纵向布局和横向布局必须定义相同的片段。否则,当屏幕旋转时,Android 就会出现问题,例如,试图使用不存在的片段。
我们还需要解决片段和活动之间的通信。活动定义了它们持有的片段,因此它们通常知道哪些类实现了这些片段,并可以直接调用这些片段上的方法。然而,这些片段只知道它们是由某个活动托管的,而这个活动可能因情况而异。因此,典型的模式是使用接口进行片段到活动的通信:
当我们完成EU4You_6
活动和它们相应的布局时,我们会看到所有这些。
在早期版本的EU4You
项目中,我们只有一个活动,也叫做EU4You
。在EU4You_6
,我们有两项活动:
EU4You
:在所有屏幕尺寸下显示CountriesFragment
的手柄,以及在更大屏幕上显示DetailsFragment
DetailsActivity
:在较小的屏幕上显示DetailsFragment
虽然我们可以让EU4You
在更小的屏幕上启动浏览器活动,而不是让DetailsActivity
托管一个只有WebView
的DetailsFragment
,但后一种方法对于更多基于片段的应用来说更现实。
首先,我们来看看EU4You
活动的各个部分。
对于普通屏幕设备,我们只想显示CountriesFragment
。这是通过具有适当的
元素的res/layout/main.xml
来实现的:
android:id="@+id/countries"
android:layout_
android:layout_
/>
属性表明哪个 Java 类实现了这个片段。否则,这种布局是不起眼的。
请注意,片段不会像活动那样在清单文件中列出。
对于大屏幕设备,在横向模式下,我们希望将CountriesFragment
和DetailsFragment
并排显示。这样,用户可以点击一个国家并查看详细信息,而无需在活动之间来回切换。这也使我们能够更好地利用屏幕空间。
然而,有一个问题。如果我们想在我们的布局文件中预定义这两个片段,我们必须对使用相同的片段对横向和纵向模式——尽管我们不想在纵向模式下使用EU4You
中的DetailsFragment
(将列表垂直堆叠在WebView
上看起来很奇怪,最好的情况也是如此)。作为一种变通方法,我们将对两个方向使用相同的布局文件,然后在 Java 代码中进行调整。另一个解决问题的方法是让布局文件只有CountriesFragment
并使用FragmentManager
和一个FragmentTransaction
来添加到DetailsFragment
中。不过,在这里,我们将使用其他技巧。
因此,在res/layout-large/
(不是res/layout-large-land/
)中,我们有这样的布局:
android:orientation="horizontal"
android:layout_
android:layout_>
android:layout_weight="30"
android:layout_
android:layout_
/>
android:layout_weight="70"
android:layout_
android:layout_
/>
注意,我们负责片段的定位,所以这里我们使用一个水平的LinearLayout
来包围两个
元素。
当用户在CountriesFragment
中选择一个国家时,我们希望让我们的包含活动知道这一点。在这种情况下,碰巧唯一会主办CountriesFragment
的活动是EU4You
。然而,也许将来不会是这样。因此,我们应该通过一个监听器接口将从CountriesFragment
到它的主机活动的通信抽象出来。
因此,EU4You_6
项目有一个**Country**Listener
接口:
`package com.commonsware.android.eu4you;
public interface Country Listener {
void onCountrySelected(Country c);
}`
CountriesFragment
持有由托管活动提供的**Country**Listener
的实例:
public void **setCountryListener**(CountryListener listener) {
this.listener=listener;
}
并且,当用户点击一个国家并触发onListItemClick()
时,CountriesFragment
调用接口上的on**Country**Selected()
方法:
`@Override
public void onListItemClick(ListView l, View v, int position,
long id) {
l.setItemChecked(position, true);
if (listener!=null) {
listener.onCountrySelected(EU.get(position));
}
}`
这个EU4You
活动时间不长,虽然有点棘手:
`package com.commonsware.android.eu4you;
import android.content.Intent;
import android.content.res.Configuration;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.view.View;
public class EU4You extends FragmentActivity implements Country Listener {
private boolean detailsInline=false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
CountriesFragment countries
=(CountriesFragment)getSupportFragmentManager()
.findFragmentById(R.id.countries);
countries.setCountryListener(this);
Fragment f=getSupportFragmentManager().findFragmentById(R.id.details);
detailsInline=(f!=null &&
(getResources().getConfiguration().orientation==
Configuration.ORIENTATION_LANDSCAPE));
if (detailsInline) {
countries.enablePersistentSelection(); }
else if (f!=null) {
f.getView().setVisibility(View.GONE);
}
}
@Override
public void onCountrySelected(Country c) {
String url=getString(c.url);
if (detailsInline) {
((DetailsFragment)getSupportFragmentManager()
.findFragmentById(R.id.details))
.loadUrl(url);
}
else {
Intent i=new Intent(this, DetailsActivity.class);
i.putExtra(DetailsActivity.EXTRA_URL, url);
startActivity(i);
}
}
}`
我们在onCreate()
的任务是连接我们的片段。片段本身是由我们对setContentView()
的调用创建的,膨胀了我们的布局和其中定义的片段。然而,除此之外,EU4You
还做了以下事情:
CountriesFragment
并将自己注册为**Country**Listener
,因为EU4You
实现了那个接口。DetailsFragment
。如果它存在,并且我们处于横向模式,我们告诉CountriesFragment
启用持续高亮,以提醒用户右侧正在加载什么细节。如果它存在,并且我们处于纵向模式,我们实际上不想要DetailsFragment
,但是需要它与布局模式一致,所以我们将片段的内容标记为GONE
。如果DetailsFragment
不存在,我们不必做任何特别的事情。像findFragmentById()
这样的呼叫的FragmentManager
是通过getFragmentManager()
完成的。然而,ACL 定义了一个单独的getSupportFragmentManager()
,以确保您正在使用 ACL 的FragmentManager
实现,并在更广泛的 Android 版本上工作。
另外,由于EU4You
实现了**Country**Listener
接口,所以它必须实现on**Country**Selected()
。在这里,EU4You
指出我们是否应该路由到DetailsFragment
的内嵌版本。如果应该,那么on**Country**Selected()
将**Country**
传递给DetailsFragment
,因此它加载那个国家的网页。否则,我们启动DetailsActivity
,额外提供 URL。
DetailsActivity
将用于DetailsFragment
未在EU4You
活动中显示的情况,包括以下情况:
DetailsFragment
时EU4You
隐藏了它自己的DetailsFragment
布局中只有我们的
元素,因为没有其他东西可以显示:
android:id="@+id/details"
android:layout_
android:layout_
/>
DetailsActivity
简单地将来自Intent
extra 的 URL 传递给DetailsFragment
,告诉它要显示什么网页内容:
`package com.commonsware.android.eu4you;
import android.support.v4.app.FragmentActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
public class DetailsActivity extends FragmentActivity {
public static final String EXTRA_URL="com.commonsware.android.eu4you.EXTRA_URL";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.details);
DetailsFragment details
=(DetailsFragment)getSupportFragmentManager()
.findFragmentById(R.id.details);
details.loadUrl(getIntent().getStringExtra(EXTRA_URL));
}
}`
在第 19 章中,我们回顾了活动如何处理配置变化,比如屏幕旋转。这如何转化为一个片段的世界?
和往常一样,有好消息,也有其他消息。
好消息是片段有可以覆盖的onSaveInstanceState()
方法,行为很像它们的活动对应物。然后Bundle
在很多地方都可以买到,比如onCreate()
和onActivityCreated()
,尽管没有专门的onRestoreInstanceState()
。
另一个消息是,不仅片段缺少onRetainNonConfigurationInstance()
,而且 ACL 的FragmentActivity
不允许您扩展onRetainNonConfigurationInstance()
,因为那是内部使用的。使用片段的直接 Android 实现的应用不会遇到这个问题。这个限制是很大的,开发人员社区仍然在共同寻找克服这个限制的方法。
片段的总体设计方法倾向于在片段中包含业务逻辑,活动充当片段间导航的编排层,以及片段所不具备的功能(例如,onRetainNonConfigurationInstance()
)。例如,Gmail 应用最初可能在每个活动(例如,文件夹的活动、对话列表的活动、单个对话的活动)中实现其大部分业务逻辑。如今,该应用可能是围绕将业务逻辑委托给片段而构建的,活动只是根据可用的屏幕大小来选择显示哪些片段。
自从 fragments 在 2011 年初首次亮相以来,这已经导致了现有应用的大量重组。例如,ListActivity
可能已经从onListItemClick()
发起了另一个活动。第一次重构会让片段的onListItemClick()
发起一个活动。但是,该片段不知道用户请求的内容是否应该在另一个活动中显示——它可能会转到当前活动中的另一个片段。因此,该片段不应该盲目地调用startActivity()
,而是应该调用其容器活动上的方法(或者,更有可能是由该活动实现的侦听器接口),告诉它点击事件,并让它决定正确的操作过程。