第5章Fragment基础
学习目标
掌握Fragment的生命周期。
掌握Fragment的应用。
掌握Fragment与Activity之间的通信。
随着移动设备的快速发展,平板电脑越来越普及,而平板电脑与手机的最大差别就在于屏幕的大小。为了同时兼顾手机和平板电脑的开发,自Android 3.0(API 11)开始引入了Fragment。接下来对Fragment进行详细的介绍。
5.1Fragment概述
Fragment翻译为中文就是“碎片”的意思,它是一种嵌入到Activity中使用的UI片段。一个Activity中可以包含一个或多个Fragment,而且一个Activity可以同时展示多个Fragment。使用它能够让程序更加合理地利用拥有大屏幕空间的移动设备,因此Fragment在平板电脑上应用非常广泛。
Fragment与Activity类似,也拥有自己的布局与生命周期,但是它的生命周期会受到它所在的Activity的生命周期的控制。例如,当Activity暂停时,它所包含的Fragment也会暂停; 当Activity被销毁时,该Activity内的Fragment也会被销毁; 当该Activity处于活动状态时,开发者才可独立地操作Fragment。
为了更加清楚地讲解Fragment的功能,接下来通过一个图例来说明,如图51所示。
图51Fragment的功能
从图51可以看出,在一般的手机或者平板电脑竖屏情况下,Fragment1需要嵌入到Activity1中,Fragment2需要嵌入到Activity2中; 如果在平板电脑横屏的情况下,则可以把两个Fragment同时嵌入到Activity1中,这样的布局既节约了空间,也会更美观。
5.2Fragment生命周期
通过第3章的学习,我们知道Activity生命周期有3种状态,分别是运行状态、暂停状态和停止状态。Fragment与Activity非常相似,其生命周期也会经历这几种状态。
图52Fragment和Activity
生命周期对比图
接下来详细介绍这几种状态。
运行状态: 当嵌入该Fragment的Activity处于运行状态时,并且该Fragment是可见的,那么该Fragment是处于运行状态的。
暂停状态: 当嵌入该Fragment的Activity处于暂停状态时,那么该Fragment也是处于暂停状态的。
停止状态: 当嵌入该Fragment的Activity处于停止状态时,那么该Fragment也会进入停止状态。或者通过调用FragmentTranslation的remove()、replace()方法将Fragment从Activity中移除。
Fragment必须是依存于Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。图52很好地说明了两者生命周期的关系。
可以看到,Fragment比Activity多了几个额外的生命周期回调方法。
onAttach(Activity): 当Fragment与Activity发生关联时调用。
onCreateView(LayoutInflater,ViewGroup,Bundle): 创建该Fragment的视图(加载布局)时调用。
onActivityCreated(Bundle): 当Activity(与Fragment相关联)的onCreate方法返回时调用。
onDestroyView(): 与onCreateView相对应,当与该Fragment关联的视图被移除时调用。
onDetach(): 与onAttach相对应,当Fragment与Activity关联被取消时调用。
以上就是Fragment的生命周期与Activity的生命周期之间的关系,接下来将讲解如何创建Fragment以及Fragment之间的通信。
视频讲解
5.3Fragment的创建
Fragment的创建与Activity的创建类似,要创建一个Fragment必须要创建一个类继承自Fragment。Android系统提供了两个Fragment类,分别是android.app.Fragment和android.support.v4.app.Fragment。继承前者只能兼容Android 4.0以上的系统,继承后者可以兼容更低的版本。接下来将具体讲解Fragment的创建过程。
(1) 创建新项目Chapter5_Fragment,在项目app的java下面的包中分别创建LeftFragment、RightFragment和SecondFragment,创建Fragment的方法如图53和图54所示。
图53选择Android提供的Fragment(Blank)
图54创建自定义的Fragment
(2) Fragment创建时可以自动生成对应的布局文件。修改左侧碎片LeftFragment布局文件fragment_left.xml,代码如下:
(3) 修改右侧碎片RightFragment布局文件fragment_right.xml,代码如下:
(4) 在LeftFragment类中重写onCreateView()方法,onCreateView()方法通过LayoutInflater的inflate()方法将fragment_left布局动态加载进来,代码如下:
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.left_fragment, container, false);
return view;
}
}
(5) 接着修改RightFragment,代码如下:
public class RightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.right_fragment, container, false);
return view;
}
}
(6) 修改fragment_second.xml文件,用来显示单击按钮时更换的界面,代码如下:
(7) 修改SecondFragment作为另一个右侧碎片,代码如下:
public class SecondFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.right_fragment, container, false);
return view;
}
}
(8) 修改activity_main.xml,代码如下:
(9) 可以看到,现在将右侧碎片放在了一个FrameLayout中,这是Android中最简单的一种布局,它没有任何的定位方式,所有的控件都会摆放在布局的左上角。由于这里仅需要在布局中放入一个碎片,因此非常适合使用FrameLayout。
之后将在代码中替换FrameLayout里的内容,从而实现动态添加碎片的功能。修改MainActivity中的代码如下:
public class MainActivity extends FragmentActivity implements View.OnClickListener {
Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button=(Button)findViewById(R.id.button);
button.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.button:
SecondFragment secFragment=new SecondFragment();
FragmentManager fragmentManager=getSupportFragmentManager();
FragmentTransaction transaction=
fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout, secFragment);
transaction.commit();
break;
default:
break;
}
}
}
可以看到,首先给左侧碎片中的按钮注册了一个单击事件,然后将动态添加碎片的逻辑都放在了单击事件中进行。结合代码可以看出,动态添加碎片主要分为如下5步:
① 创建待添加的碎片实例。
② 获取到FragmentManager,在活动中可以直接调用getFragmentManager()方法得到。
③ 开启一个事务,通过调用beginTransaction()方法开启。
④ 向容器内加入碎片,一般使用replace()方法实现,需要传入容器的id和待添加的碎片实例。
⑤ 提交事务,调用commit()方法来完成。
这样就完成了在活动中动态添加碎片的功能,运行程序,可以看到启动界面如图55所示,然后单击按钮,效果如图56所示。
图55启动界面
图56单击按钮结果
上述代码成功实现了向活动中动态添加碎片的功能,不过这时按下键盘上的返回键程序就会直接退出。如果这里想模仿类似返回栈的效果,可以通过FragmentTransaction中提供的一个addToBackStack()方法将一个事务添加到返回栈中,修改MainActivity中的代码如下:
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.button:
SecondFragment secFragment=new SecondFragment();
FragmentManager fragmentManager=getSupportFragmentManager();
FragmentTransaction transaction=fragmentManager.beginTransaction();
transaction.replace(R.id.right_layout,secFragment);
transaction.addToBackStack(null);
transaction.commit();
break;
default:
break;
}
}
这里在事务提交之前调用了FragmentTransaction的addToBackStack()方法,它可以接收一个名字用于描述返回栈的状态,一般传入null即可。现在重新运行程序,并单击按钮将SecondFragment添加到活动中,然后按下返回键,会发现程序并没有退出,而是回到了RightFragment界面,再次按下返回键程序才会退出。
视频讲解
5.4Fragment与Activity之间的通信
由于Fragment与Activity各自存在于一个独立的类中,它们之间并没有明显的方式进行直接通信。在实际开发过程中,经常需要在Activity中获取Fragment实例或者在Fragment中获取Activity实例。接下来详细讲解Fragment和Activity之间的通信。
(1) 在Activity中获取Fragment实例。
为了实现Fragment和Activity之间的通信,FragmentManager提供了一个findFragmentById()的方法,专门用于从布局文件中获取Fragment实例。该方法有一个参数,它代表Fragment在Activity布局中的id。例如,在布局文件中指定SecondFragment的id为R.id.second_fragment,这时就可以使用getFragmentManager().findFragmentById(R.id.second_fragment)方法得到SecondFragment的实例。
为了更好理解,下面通过一段代码讲解,具体的代码如下:
SecondFragment second_frag=(SecondFragment) getFragmentManager()
.findfragmentById(R.id.second_fragmnet);
以上就是在Activity中获取Fragment实例的代码。
(2) 在Fragment中获取Activity实例。
在Fragment中获取Activity实例对象,可以通过在Fragment中调用getActivity()方法来获取与当前Fragment相关联的Activity实例对象。例如在MainActivity中添加了SecondFragment,那么就可以通过在Fragment中调用getActivity()来获取MainActivity实例对象。具体的代码如下:
MainActivity main=(MainActivity)getActivity();
获取到Activity中的实例以后,就可以调用该Activity中的方法了。当Fragment需要使用Context对象时,也可以使用该方法。
以上就是在Activity中获取Fragment实例和在Fragment中获取Activity实例对象的具体代码。接下来通过具体的例子讲解两者之间的通信方式。
为了更好地掌握Fragment与Activity之间的通信,接下来介绍一个左边显示新闻标题,右边展示单击新闻标题以后出现新闻的具体内容的例子,具体的操作步骤如下。
(1) 创建新闻展示项目。
首先创建新闻展示项目,然后修改activity_main.xml中的布局代码,因为需要展示标题和对应的内容,所以需要添加两个FrameLayout,后边将会被Fragment所代替。具体的代码如下:
(2) 创建两个Fragment布局文件。
由于需要实现在一个Activity中展示两个Fragment,因此需要创建相应的Fragment的布局。用来展示新闻标题的布局文件title_layout.xml代码如下:
用来展示右边标题和内容的布局文件content_layout.xml代码如下:
(3) 创建ListView中每一项的内容布局。
由于左边的新闻标题采用了ListView,因此需要创建一个显示ListView中每一项的布局文件,title_item_layout.xml文件的代码如下:
(4) 创建显示标题的Fragment类文件。
创建一个TitleFragment类文件(继承自Fragment类),用来显示左边的新闻标题,具体代码如下:
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
public class TitleFragment extends Fragment {
private View view;
private String[] title;
private String[][] contents;
private ListView listView;
public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle
savedInstanceState){
view=inflater.inflate(R.layout.title_layout,container,false);
//获取Activity实例对象
MainActivity activity=(MainActivity)getActivity();
//获取Activity中的标题
title=activity.getTitle();
//获取Activity中的标题和内容
contents=activity.getSettingText();
if (view!=null){
init();
}
//为listview添加监听
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
//通过activity实例获取另一个Fragment对象
ContentFragment content=(ContentFragment)((MainActivity)getActivity()).getSupportFragmentManager().findFragmentById(R.id.setcontent);
content.setText(contents[i]);
}
});
return view;
}
private void init() {
listView=(ListView)view.findViewById(R.id.titlelist);
if (title!=null){
listView.setAdapter(new MyAdapter());
}
}
//适配器
class MyAdapter extends BaseAdapter{
@Override
public int getCount() {
return title.length;
}
@Override
public Object getItem(int i) {
return title[i];
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
view=View.inflate(getActivity(),R.layout.title_item_layout,null);
TextView titletext=(TextView)view.findViewById(R.id.titles);
titletext.setText(title[i]);
return view;
}
}
}
(5) 创建显示标题和内容的Fragment类文件。
创建一个类ContentFragment(继承自Fragment类),然后编写相应的逻辑代码,用来显示左边单击以后出现的内容,具体代码如下:
package com.jxust.cn.chapter5_news;
import androidx.fragment.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class ContentFragment extends Fragment {
private View view;
private TextView text1,text2;
public void onAttach(Activity activity){
super.onAttach(activity);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
//获取布局文件
view=inflater.inflate(R.layout.content_layout,container,false);
if (view!=null){
init();
}
//获取activity中设置的文字
setText(((MainActivity)getActivity()).getSettingText()[0]);
return view;
}
private void init() {
text1=(TextView)view.findViewById(R.id.show_title);
text2=(TextView)view.findViewById(R.id.show_content);
}
public void setText(String[] text) {
text1.setText(text[0]);
text2.setText(text[1]);
}
}
(6) 编写MainActivity中的代码。
编写好两个Fragment类的代码以后,就需要在MainActivity中添加,具体的代码如下:
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
public class MainActivity extends FragmentActivity {
//设置标题
private String title[]={"标题一","标题二","标题三"};
private String settingText[][]={{"标题一","标题一的内容"},{"标题二","标题二的内容"},{"标题三","标题三的内容"}};
//获取标题数组的方法
public String[] getTitle(){
return title;
}
//获取标题和内容
public String[][] getSettingText(){
return settingText;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建Fragment
TitleFragment titleFragment=new TitleFragment();
ContentFragment contentFragment=new ContentFragment();
//获取事务
FragmentManager fragmentManager=getSupportFragmentManager();
FragmentTransaction transaction=fragmentManager.beginTransaction();
//添加Fragment
transaction.replace(R.id.settitle,titleFragment);
transaction.replace(R.id.setcontent,contentFragment);
//提交事务
transaction.commit();
}
}
(7) 测试运行。
以上就是Activity与Fragment之间的通信过程。上述代码实现了如图57所示的界面。
图57Fragment与Activity通信案例图
从图57可以看出,当单击屏幕左侧的标题以后,右侧的界面也会跟着显示对应的标题和内容,这就说明了本实例实现了Activity与Fragment之间的通信以及Fragment与Fragment之间的通信。需要开发者熟练掌握。
本章小结
本章主要讲解了Fragment的概念、生命周期、Fragment与Activity之间的通信方式以及Fragment和Fragment之间的通信方式,这些知识在平板电脑开发或者考虑到屏幕兼容性开发中经常使用,需要开发者熟练掌握并应用到实际的项目中。
习题
1. 说明Fragment的生命周期。
2. 对于Android的两种事件处理机制,分别写一个案例测试,了解其执行过程。
3. 实现一个类似于5.4节Fragment与Activity之间通信的例子。