在Fragment出现之前,Activity是app中界面的基本组成单位,值得一提的是,作为四大组件之一,它是需要“注册”的。组件的特性使得一个Activity可以在整个app甚至是不同app间被复用。 随着android 3.0中安卓平板的新增,app对不同尺寸屏幕的适配需求更加突出,Fragment大概也因为这样的需要被引入。虽然可以为Activity动态指定不同的layout,但也仅仅是解决一些简单的适配。像手机和平板这样的显著不同的尺寸下,是需要完全不同的界面设计的。此时,界面需要是不同几个部分的组成,根据实际的屏幕大小,它们动态的组成一个界面或者是分离到不同界面中,经典的案例说明就是“列表-详情”界面。虽然Activity可以相互嵌套,但在支持上(设计初衷)显得很笨重,因此,sdk中引入了Fragment,它作为对Activity的一个模块化拆分,类似Activity那样的包含逻辑和View布局的“sub activity”,具有Activity那样的生命周期和回退栈,可以接收事件等。通过Fragment来合理地分解一个复杂界面为多个模块化的子界面,可以满足一些动态的界面组合适配需求,同时也让界面代码更好的复用,并因为更加细分而设计清晰。此外,Fragment是无需注册的,这样它比Activity更加具备动态创建的可能性,基于此甚至出现了一些单一Activity这样的app框架设计。更多它的特性,接下来就一起来探索吧。
Fragment是API 11(android 3.0)中引入的,而support v4库中提供了向前兼容的实现。下面为了突出重点,不使用对应的兼容实现,假设app的最低版本为api 11以上。 从设计上,Fragment应该是一个独立完整的模块化界面组件,包含自身的layout和界面交互逻辑。但在使用上,它离不开Activity,必须内嵌到一个Activity实例中。因为它就是设计来作为部分化的Activity,它的生命周期就是基于所在Activity的生命周期,没必要独立存在。 一个Fragment需要依附到Activity来进行显示。可以通过在Activity的layout中使用
类似定义Activity那样,通过继承Fragment类来定义一个fragment类型。特殊的是,“通常”唯一需要定义的方法是onCreateView()
,它用来提供fragment关联的layout,除非是一个无界面的Fragment。
下面是一个极简单的Fragment定义:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.ViewGroup;
public class ArticleListFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.article_list, container, false);
}
}
方法onCreateView()返回的View就是Fragment对应layout(ViewHierarchy)的root。 参数container是来自所在Activity的layout的一个ViewGroup,最终Fragment的布局被添加作为此 container的childView。inflate()方法最后一个参数表示是否将加载的view添加到container中,因为onCreateView()返回的view默认就会被Activity添加到container中,这里就传递false后方法inflate()返回布局的根View,否则inflate()会返回container参数,具体细节见方法inflate()的说明。
方法inflate()原型:
/**
* Inflate a new view hierarchy from the specified xml resource. Throws
* {@link InflateException} if there is an error.
*
* @param resource ID for an XML layout resource to load (e.g.,
* <code>R.layout.main_page</code>)
* @param root Optional view to be the parent of the generated hierarchy (if
* <em>attachToRoot</em> is true), or else simply an object that
* provides a set of LayoutParams values for root of the returned
* hierarchy (if <em>attachToRoot</em> is false.)
* @param attachToRoot Whether the inflated hierarchy should be attached to
* the root parameter? If false, root is only used to create the
* correct subclass of LayoutParams for the root view in the XML.
* @return The root View of the inflated hierarchy. If root was supplied and
* attachToRoot is true, this is root; otherwise it is the root of
* the inflated XML file.
*/
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
一般的,定义一个Fragment时需要重写Fragment的几个生命周期中的回调方法包括:
/**
* Called to have the fragment instantiate its user interface view.
* This is optional, and non-graphical fragments can return null (which
* is the default implementation). This will be called between
* {@link #onCreate(Bundle)} and {@link #onActivityCreated(Bundle)}.
*
* <p>If you return a View from here, you will later be called in
* {@link #onDestroyView} when the view is being released.
*
* @param inflater The LayoutInflater object that can be used to inflate
* any views in the fragment,
* @param container If non-null, this is the parent view that the fragment's
* UI should be attached to. The fragment should not add the view itself,
* but this can be used to generate the LayoutParams of the view.
* @param savedInstanceState If non-null, this fragment is being re-constructed
* from a previous saved state as given here.
*
* @return Return the View for the fragment's UI, or null.
*/
@Nullable
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return null;
}
上面几个方法基本就是Activity对应生命周期回调方法的一个调用传递,后面会在“Fragment生命周期”中详细介绍各个回调方法的用途,接下来就看看如何在Activity中使用Fragment。
接下来的示例是一个文章列表和文章详情页面的场景。app会在不同屏幕尺寸时动态选择在同一个Activity中同时显示文章列表和对应选择的文章的详情信息,或者单独的一个列表界面,选择一个文章后打开新Activity来显示文章详情。 下面是ArticleListActivity对应的布局文件,它里面同时声明了2个Fragment作为其界面组成:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.news.ArticleDetailFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
通过上面这种在layout中使用<fragment>
标签的方式来为Activity添加Fragment的话,在Fragment中调用方法isInLayout()返回true,该方法用来指示当前Fragment实例是否处在一个View布局中。此外,可以重写方法onInflate()来获得标签中定义的attr。
标签<fragment>
指定的Fragment在Activity的布局加载中会被实例化,onCreateView()返回的View将替换<fragment>
元素在layout中的位置。
对应有View的Fragment,需要为它提供一个标识来访问它,供系统在Activity的重建过程中使用,在对Fragment执行删除,替换等操作时也要用到。提供标识的方式有:
<fragment>
标签所在容器View的ID。在Activity中通过代码来添加Fragment时需要用到FragmentManager和FragmentTransaction。在Activity运行期间,对Fragment的操作是以事务的形式进行的,可以在一次事务中执行多个Fragment相关操作,最后提交事务。
示例如下:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ArticleFragment fragment = new ArticleFragment();
fragmentTransaction.add(R.id.layout_article_detail, fragment);
fragmentTransaction.commit();
上面的代码完成了向布局中id为R.id.layout_article_detail的ViewGroup中添加一个fragment的操作。总之,带界面的Fragment添加到Activity时,就需要指定其View所在的ViewGroup。
一些情况下,可以定义一个没有界面的Fragment,此时它拥有关联的Activity的各种回掉,状态保存和恢复等。可以完成一些Activity运行期间的后台操作。 无界面Fragment没用关联的View,不需要重写onCreateView()方法。此时只会以代码的方式添加它到Activity,并且使用的是FragmentTransaction.add(Fragment fragment, String tag)方法,此时fragment实例不需要ID来标识它,因为它不存在于layout中,使用一个String类型的tag来标记它,之后可以通过FragmentManager.findFragmentByTag(String tag)来访问它。
上面简单的在代码中添加了一个Fragment到当前Activity。这里对可以针对Fragment执行的各种操作进行总结。
首先通过getFragmentManager() 得到一个FragmentManager对象。使用它可以完成以下操作:
在Activity运行期间,可以动态地添加,移除fragment来改变界面显示和行为。这些关于Fragment的修改操作,需要通过FragmentTransaction 来完成,像下面这样获得它:
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FragmentTransaction提供的有关Fragment的操作有:
每一次事务包含一或多个相关Fragment的修改,如add(), remove(), replace()。事务可以记录到activity关联的Fragment的回退栈中。之后用户按下返回键时,本次事务将“回滚”。
// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
上面的代码,使用newFragment替换掉了对应ViewGroup处的fragment,并将事务添加到了回退栈中,这使得以前的fragment不会被销毁,不执行onDestroyView()而仅仅执行了onStop()方法。之后若用户按下返回键,那么newFragment关闭后先前对应的fragment会继续恢复显示。
在一次fragment事务中,最后必须调用commit()来提交事务,若添加多个fragment到一个布局容器中,那么添加顺序决定了它们对应View的展示顺序,类似ViewGroup.addView(View child)那样。
为了让fragment切换时具有动画效果,可以调用setTransition()、setCustomAnimations()等来指定切换动画。
commit()的调用不会立即引起fragment变化操作的执行,而是将事务安排到UI线程中——UI线程准备好就会执行它。可以通过调用FragmentManager.executePendingTransactions()让提交的事务立即执行,如果其它异步任务有这样的需求的话。
当用户离开Activity时(打开其它界面,返回桌面等),Activity的生命周期回调会执行状态保存操作,此时如果执行fragment事务的commit()将引发异常,原因是当Activity之后发生重建过程,事务提交后的状态可能丢失,如果这在设计上不是问题的话,可以选择执行commitAllowingStateLoss()。
Fragment的设计目标就是表现得像一个Activity的一部分,实现上它必须添加到Activity中运行。重要的一点,Fragment的View是插入到Activity的layout的一部分存在的。 因为界面组件的属性,Fragment具备像Activity那样的生命周期回调方法,大多数方法本身就是Activity对应方法的一个调用传递,另一些方法是和Fragment的界面生成相关,或和宿主Activity进行交互的。
有关Fragment的完整生命周期还是蛮复杂的,这个github项目是关于它的一个完整的演示。通常app只用到一些常见的生命周期方法,也是api文档中着重说明的,下面就学习下这些常见的回调。
下图是Activity运行时期(resumed状态),Fragment从添加到移除过程中各个生命周期回调的执行状况:
作为对比,下图表明了宿主Activity生命周期对Fragment的影响:
如图所示,Activity生命周期的变化会直接影响其包含的Fragment。假设以layout文件中
public void onAttach (Activity activity)
在Fragment和它寄宿的Activity关联时调用,方法参数就是关联的Activity。此时Activity可能处在onCreate阶段或resumed阶段。public void onCreate (Bundle savedInstanceState)
创建Fragment时执行,在此作一些状态初始化操作。方法在onAttach(Activity) 之后在 onCreateView(LayoutInflater, ViewGroup, Bundle)之前执行。
此时宿主Activity可能处于创建过程(onCreate执行期间,或者是出于resumed阶段),对于ViewHierarchy还未创建完成,所以不要做和View相关的操作,稍后的onActivityCreated()方法是Activity.onCreate()执行完毕后调用,可以在那里做一些Activity创建完成后的初始化操作。类似Activity那样,如果Fragment是从之前的状态恢复重建,则参数savedInstanceState携带了之前保存的状态数据。
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
Activity获取Fragment提供的View时执行。public void onViewCreated(View view, Bundle savedInstanceState)
onCreateView()返回的view hierarchy创建完成,但还未被添加到Activity布局中的ViewGroup中。可以在此访问Fragment的各个View,进行设置。public void onActivityCreated (Bundle savedInstanceState)
宿主Activity已完成创建,其onCreate()执行完毕。Fragment的View准备就绪,可以在此执行创建过程的最后初始化操作,如获得View对象,恢复状态等。经过上面几个方法的执行,宿主Activity及Fragment的创建过程已经完成。显然,这些创建过程的回调方法仅执行一次。 如果是在Activity创建时添加Fragment,那么上述方法会严格受到宿主Activity的onCreate()执行的影响。 大多数情况下,对Fragment的使用正是通过
在用户离开和返回宿主Activity,或对Fragment执行replace,hide等操作时,它对应执行以下回调。
当Fragment被移除,或者宿主Activity销毁时,它将依次经历下面的回调。
类似Activity那样,一个Fragment实例处在运行中时,只能是以下状态之一。
由于Fragment对象是一个具有生命周期的特殊对象,所以在它的代码中时刻注意一些操作的调用时机,下面列举一些。
作为一个“模块化的界面组件”,Fragment有类似Activity那样的状态保持和恢复机制:当一个未被显式结束的Activity处在后台时,由于内存问题它临时被回收掉,之后若用户再次回来时,对应任务栈中的Activity会被重新创建,而框架提供了和此Activity关联的一些状态保存和恢复的方法。
在Fragment中,重写onSaveInstanceState()来保存一些状态。 之后在 onCreate(), onCreateView(),或 onActivityCreated()中获取保存的状态,进行恢复设置。
另一个Fragment的特性就是“回退栈”。 在通过FragmentTransaction来执行有关fragment的事务时,可以通过addToBackStack()来添加此次事务的操作到回退栈中,这样以后,用户按下返回键后Activity的Fragment状态会恢复为上一次的情形,当回退栈中没有任何Fragment时,才执行Activity本身的onBackPressed()逻辑。
更多关于回退栈的用法,参见FragmentManager的API。
<fragment>
标签添加一个Fragment实例,那么在Fragment的代码中可以通过方法isInLayout()来验证这一点,通常上述方式时方法会返回true。而且可以重写 onInflate (Activity activity, AttributeSet attrs, Bundle savedInstanceState)方法来获得布局文件中标签定义的一些attrs。
例外的情况是,当Activity重建时采用了不同的layout,之前layout中的fragment还会被重新实例化,但此时此fragment对象的View已经不再使用了,此时它的onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)调用时参数container为null,此时方法无需返回任何View。而此时isInLayout()返回false。Fragment难免需要和宿主Activity进行交互,从设计原则上看,因为Fragment表示一个模块化的可复用界面组件,所以它应该可以被放置在不同的Activity中。出于这样的原因,它和其宿主Activity的交互只能是通过接口来进行抽象。
如果没有特殊的抽象需要,Activity本身是完全可以直接调用Fragment的公开方法的。可以获得FragmentManager,然后通过findFragmentById() 或 findFragmentByTag()得到目标Fragment。 当然,在Fragment中也可以getActivity()得到Activity示例然后强转为已知的Activity类型去使用。
Fragment也可以为宿主Activity提供Options Menu、Context Menu和Action Bar菜单,并处理这些动作响应。
不同Fragment之间不能直接进行交互,因为它们应该可以进行各种不同的界面动态组合。最好的情况是,这些Fragment之间是相互不知道的,它们各自具备其独立的界面和行为。 各个Fragment都定义自己的接口来和Activity交互,而它们之间的交互逻辑是Activity本身负责的。 例如,ArticleListFragment表示的文章列表中一个文章被选中后,它通知Activity,然后Activity在根据此Article数据来操作ArticleFragment去显示对应文章内容。
虽然可以像普通对象那样,在创建Fragment后直接通过property来设置一些状态。但是,Fragment具有像Activity那样的,在intent中携带一些数据的能力。
可以通过setArguments()来为Fragment设置一些额外数据,像下面这样:
ArticleFragment f = new ArticleFragment();
// Supply index input as an argument.
Bundle args = new Bundle();
args.putSerializable("article", article);
f.setArguments(args);
return f;
之后,Fragment中使用下面的方式获得这些数据:
Article article = (Article) getArguments().getSerializable ("article");
通常,Activity在添加一个fragment时,可以直接把它的intent的Bundle数据设置给fragment,这在启动Activity的intent中携带的数据是给目标fragment时非常方便:
fragment.setArguments(getIntent().getExtras());
通过setArguments设置的数据,在fragment销毁后重建时也可以获取到。不像普通对象那样,只存在于内存中。 这点类似于Activity的intent的数据,起到类似状态保持那样的效果——构建参数(construction arguments )的保持。
接下来就给出显示“文章列表,文章详情界面”示例的完整代码,列表和详情界面分别由两个Fragment表示,在大尺寸平板这样的屏幕时它们处在同一个Activity中。小尺寸手机设备上时,分别处在两个Activity中。界面效果如下:
数据实体类Article的定义:
public class Article implements Serializable {
public String title;
public String content;
}
一个文章包含标题和内容。
定义ArticleListFragment来显示文章列表,列表项是文章标题。 使用RecyclerView来显示列表,列表项用TextView显示。
ArticleListFragment对应布局文件fragment_article_list.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/list"
android:name="com.example.android.ItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
ArticleListFragment定义:
public class ArticleFragment extends Fragment {
private OnArticleCheckedListener mListener;
private ArrayList<Article> mArticles;
public ArticleFragment() {
mArticles = new ArrayList<>();
for (int i = 0; i < 24; i++) {
Article article = new Article();
article.title = "标题-" + i;
article.content = "文章内容-" + i;
mArticles.add(article);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_article_list, container, false);
if (view instanceof RecyclerView) {
RecyclerView recyclerView = (RecyclerView) view;
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
recyclerView.setAdapter(new ArticleListViewAdapter(mArticles, mListener));
}
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnArticleCheckedListener) {
mListener = (OnArticleCheckedListener) context;
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnArticleCheckedListener {
void onArticleChecked(Article article);
}
}
接口OnArticleCheckedListener定义了某个文章标题被点击查看时的回调。 onAttach()得到的context正是宿主Activity,按照设计,宿主Activity应该实现了OnArticleCheckedListener,这样ArticleListFragment列表项的点击事件就传递给了宿主Activity。
适配器ArticleListViewAdapter的定义:
public class ArticleListViewAdapter extends RecyclerView.Adapter<ArticleListViewAdapter.ViewHolder> {
private final List<Article> mArticles;
private final ArticleFragment.OnArticleCheckedListener mListener;
public ArticleListViewAdapter(List<Article> items, ArticleFragment.OnArticleCheckedListener listener) {
mArticles = items;
mListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.fragment_article_title, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mArticle = mArticles.get(position);
holder.mContentView.setText(mArticles.get(position).content);
holder.mView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (null != mListener) {
mListener.onArticleChecked(holder.mArticle);
}
}
});
}
@Override
public int getItemCount() {
return mArticles.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
public final View mView;
public final TextView mContentView;
public Article mArticle;
public ViewHolder(View view) {
super(view);
mView = view;
mContentView = (TextView) view.findViewById(R.id.content);
}
}
}
布局文件fragment_article_title.xml简单的定义了一个TextView用来显示标题:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:textSize="32sp"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="40dp"/>
以上代码完成了ArticleListFragment的布局和类型定义。
ArticleDetailFragment用来显示具体文章的内容,它的布局文件fragment_article_detail.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/article_content"
android:name="com.example.android.ItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
布局中使用一个TextView简单都显示文章内容。
ArticleDetailFragment类型定义:
public class ArticleDetailFragment extends Fragment {
public static final String ARG_ARTICLE = "ARG_ARTICLE";
private Article mArticle;
public ArticleDetailFragment() {
}
public static ArticleDetailFragment newInstance(Article article) {
ArticleDetailFragment fragment = new ArticleDetailFragment();
Bundle bundle = new Bundle();
bundle.putSerializable(ARG_ARTICLE, article);
fragment.setArguments(bundle);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mArticle = (Article) getArguments().getSerializable(ARG_ARTICLE);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_article_detail, container, false);
if (mArticle != null) {
TextView textView = (TextView) view.findViewById(R.id.article_content);
textView.setText(mArticle.content);
}
return view;
}
}
方法newInstance()方便创建一个携带参数的实例。
对应宿主ArticleListActivity,它在大尺寸屏幕下可以同时显示列表和文章详情,在小尺寸屏幕下只显示标题列表。所以在res/layout-large/和 res/layout/两个目录下分别为它定义不同的布局文件。
在小尺寸屏幕时,Activity只显示ArticleListFragment,上面利用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/layout_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context="com.example.android.ArticleListActivity">
<fragment
android:id="@+id/list_fragment"
android:layout_weight="2"
android:name="com.example.android.ArticleListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/detail_container"
android:layout_weight="2"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
</LinearLayout>
在大尺寸屏幕下,布局中同时提供了R.id.detail_container作为ArticleDetailFragment的容器,这样在选择查看某个标题时, 就会在同一个界面使用ArticleDetailFragment来显示文章内容。
下面是ArticleListActivity的定义,它的onCreate()中根据布局文件是否包含R.id.detail_container来判断当前屏幕是处于大屏幕——显示两个内容面板(towPanel)还是小屏幕(singlePanel)。
public class ArticleListActivity extends Activity implements ArticleListFragment.OnArticleCheckedListener {
private boolean mIsTwoPanel = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_article_list);
View container = findViewById(R.id.detail_container);
mIsTwoPanel = container != null;
}
@Override
public void onArticleChecked(Article article) {
if (mIsTwoPanel) {
Fragment fragment = ArticleDetailFragment.newInstance(article);
getFragmentManager().beginTransaction().replace(R.id.detail_container, fragment).commit();
} else {
Intent intent = new Intent(this, ArticleDetailActivity.class);
intent.putExtra(ArticleDetailFragment.ARG_ARTICLE, article);
startActivity(intent);
}
}
}
ArticleListActivity实现了OnArticleCheckedListener接口,在方法onArticleChecked()中,若当前Activity是大屏幕对应的布局, 则直接替换R.id.detail_container处的fragment用来显示文章内容。否则,启动一个新的ArticleDetailActivity来显示文章内容。 ArticleDetailActivity的定义非常简单,它作为ArticleDetailFragment的宿主,将article参数设置给它。