什么是Fragment
?
Fragment
意为碎片,片段。在Google的官方文档介绍,Fragment
是一个应用程序的用户界面的一部分或者用来执行某些行为。我们都知道,如果需要跟用户交互,都需要用到Activity
,所以,Fragment
必须是嵌入到Activity
中。
我们可以把多个Fragment
放到一个Activity
里,也可以把一个Fragment
在多个Activity
里复用。我们可以理解Fragment
是Activity
的一个模块。它包含自己的生命周期,可以响应它自己的交互事件。我们可以随意的在运行中的Activity
中把加入或者移除Fragment
。就像我们可以复用多个Activity
一样。
为什么会有 Fragment
?
Fragment
是在 Android 3.0
时加入的。Fragment
的出现一方面是为了缓解 Activity
任务太重的问题;另一方面是为了处理在不同屏幕上UI组件的布局问题,尤其是大屏幕;而且它还提供了一些Activity
不具有的新特性来处理一些在 开发中比较难解决的问题。
在开发中大型的应用时,如果遇到特别复杂的界面,Activity
就会显得特别复杂,就会变得臃肿。如果使用Fragment
把Activity
分割,就会很好的缓解这种情况。实际上Fragment
不仅仅是界面,不仅仅是View
;它实际上干的就是Activity
的事情;它负责的是控制View
和他们之间的逻辑。
将Activity
分割成多个Fragment
后,Activity
就可以脱出身来,不再去处理大量的View
了,它只需要管理Fragment
或者少量的View
。
Fragment
不仅仅可以管理View
,也可以管理一些不可视的任务。比如在Activity
因切换横竖屏而销毁重建时,它仍然可以保留实例。当然它的前提是必须要设置retainInstance
属性。
虽然它是在Android 3.0
时加入的,Google也提供了support包来支持之前的版本。这说明Google对Fragment
的重视,并且提倡使用Fragment
。
初探 Fragment
上面提到Fragment
一方面是为了处理在不同屏幕上UI组件的布局问题,尤其是大屏幕;如下图,是一个新闻app的界面示意图,为了更好的利用屏幕的空间,它要求在比较大的平板上,呈左面的显示方式,左面是一个新闻列表,点击某个新闻标题,在右面显示具体的新闻内容。如果在较小的手机上,就要呈右边的形式,首先显示的一个新闻列表,如果点击某个新闻标题,就在另一屏显示具体的新闻内容。
如果只用Activity
实现,十分的麻烦,但是如果使用Fragment
,就显得很简单了。我们可以先创建两个Fragment
,一个NewsListFragment
用来显示新闻列表,一个NewsDetailFragment
用来显示新闻内容。它们的布局分别为
fragment_news_list
和 fragment_news_detail
;我们设置在Activity
的布局时,可以根据屏幕的大小来编写两个布局,一个包含是右边的样式,一个是左面的样式。然后根据屏幕的大小设置最小宽度限定符,比如large
/sw600dp
,在程序加载的时候,会根据屏幕大小动态去选择需要的布局。具体的步骤我们会后面介绍。
###Fragment
的生命周期
因为Fragment
是依附于Activity
的,那么它必然会受到Activity
生命周期的影响。Google在官网给出了一张图来说明它们两者间生命周期的的关系。
根据上图我们可以看出,Fragment
比Activity
多出几个额外的回调方法:
onAttach(Context):
当Fragment
第一次关联在Context
上时调用。这里Context
就是指的Activity
。
onCreateView(LayoutInflater, ViewGroup, Bundle):
看字面意思就可以知道,该方法是创建Fragment
自己的视图。如果没有图像界面就可以返回null
。该方法跟下面的 onDestroyView()
相对应。
onActivityCreated(Bundle):
当Fragment
所附着的Activity
创建完毕之后调用;并且这个Fragment
的视图也会被实例化。这个方法可以用来做初始化或者用来恢复界面或者状态。
onDestroyView():
跟上面的 onCreateView()
相对应。当Fragment
的视图移除时调用。下一次要展示Fragment
时,视图会重新创建。
onDetach():
跟上面的onAttach()
相对应,当Fragment
与Activity
关联被取消时调用
其它的几个跟
Activity
回调相同的,意思也跟Activity
的回调的用法一样,这里就不一一解释了。
Fragment
的用法
Fragment
静态用法:
在初探Fragment
中提到的就是Fragment
的静态使用方法。下面我们具体实现一下。
所谓静态就是在布局里面写死,不能在程序运行中,动态添加/移除Fragment
。
在初探Fragment
时我们需要在不同屏幕大小实现显示不同的布局。
根据需求,我们首先实现一个实现最简单的只有一个新闻列表的布局,当然我们还是使用Fragment
实现了。
以下的实现都是使用的 Android 3.0之后提供的Fragment,如果要支持以前的版本请将Activity替换成Support包中的FragmentActivity,导包的时候,要导入
import android.support.v4.app.Fragment;
首先我们先创建一个Activity
,叫做NewsActivity
,继承自Activity
,用于显示的布局叫做activity_news
:
public class NewsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news);
}
}
然后创建一个类去继承Fragment
,我们暂且叫它NewsTitleListFragment
。就像这样:
public class NewsTitleListFragment extends Fragment{
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return super.onCreateView(inflater, container, savedInstanceState);
}
}
这个Fragment
是用来显示新闻标题列表的。我们还需要创建一个显示新闻列表的布局,我们使用ListView
来展示列表。我们给它加个背景便于区别。具体如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#ff11ffff"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/lv_news_title_list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
然后我们回到NewsTitleListFragment
,在onCreateView()
中我们来返回我们的布局视图。
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_news_list, container, false);
return view;
}
我们通过LayoutInflater
将布局转换为View
,作为Fragment
的视图。
下面我们准备一些数据,用来填充这个 ListView
:
private String[] mTitles;
void initTitles() {
mTitles = new String[10];
for (int i = 0; i < 10; i++) {
mTitles[i] = "Title = " + i;
}
}
然后在onCreateView()
绑定ListView :
mTitleList = (ListView) view.findViewById(R.id.lv_news_title_list);
并且设置适配器和行点击事件。在Fragment
获取Activity
可以使用getActivity
:
ArrayAdapter adapter = new ArrayAdapter(getActivity(),android.R.layout.simple_list_item_1,mTitles);
mTitleList.setAdapter(adapter);
mTitleList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
startActivity(getActivity(),NewsDetailActivity.class);
}
});
这里的
NewsDetailActivity
,只是一个展示界面就不在具体说了。很简单。
然后将NewsTitleListFragment
使用fragment
标签设置到NewsActivity
的布局里:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:id="@+id/fragment_news_list"
android:name="com.liteng.app.fragments.lifecycle.NewsTitleListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
这里使用 android:name
指向新建的NewsTitleListFragment
。
运行后是这样的:
但是我们要支持在大屏情况下显示的是两个部分:左边是列表,右边是内容。这就需要使用在我们适配时用到的large
限定符。我们在res
目录下,新建一个文件夹layout-large
,然后在里面新建一个同样的布局activity_news
,这样程序运行到大屏幕的设备上时就会自动去加载这个布局。
这样我就需要两个Fragment
,我们类似的新建一个NewsDetailFragment
,其界面布局为fragment_news_detail
。这里我只是模拟一下新闻内容,不再具体做实现。
public class NewsDetailFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_news_detail, container, false);
return view;
}
}
下面是布局内容:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".fragments.lifecycle.NewsListFragment">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_margin="20dp"
android:gravity="center"
android:text="News Title"
android:textSize="20sp"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="The Czech Republic sent two fighter jets to escort Xi's plane when it entered the country's airspace. Xi was received by Czech Foreign Minister Lubomir Zaoralek and other senior officials at the airport.Hailing the long-held friendship between the two countries, Xi, in his written remarks upon arrival, extended sincere greetings and best wishes to the Czech people."
android:textSize="16sp"/>
</LinearLayout>
重点是Activity
显示布局activity_news
,我们在这里面放两个fragment
标签。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<fragment
android:id="@+id/fragment_news_list"
android:name="com.liteng.app.fragments.lifecycle.NewsTitleListFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<fragment
android:id="@+id/fragment_news_detail"
android:name="com.liteng.app.fragments.lifecycle.NewsDetailFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>
但是还有一个问题,我们如果在Activity里区分是大屏还是小屏,我们可以去判断有没有加载具体内容的NewsDetailFragment
,来做区分,然后再去做具体操作;我们在NewsActivity
里通过Fragment
的id,来判断是否存在这个Fragment
:
Fragment fragmentNewsDetail = getFragmentManager().findFragmentById(R.id.fragment_news_detail);
if (fragmentNewsDetail != null) {
Log.e(TAG,"大屏");
}else{
Log.e(TAG,"小屏");
}
如果大家对这里的 getFragmentManager().findFragmentById(R.id.fragment_news_detail)
,有疑惑,我们会在下一节具体介绍。
下面附上一张Activity和Fragment的完整生命周期图,如果有兴趣的话,可以看看。这张图是在网上找的,如果涉及到版权,我会及时删除。