Fragment,翻译为碎片、片段,Android 在 Android 3.0(API 11 )中引入了Fragment,主要是为了给大屏幕(如平板电脑)上更加动态和灵活的 UI 设计提供支持,本文带来Fragment完全解析,关于Fragment,你应该了解的一切!
一、Fragment概述
二、静态Fragment用法与解析
三、动态Fragment用法与解析
四、Fragment生命周期
五、Fragment与Activity通信
六、Fragment总结
一、Fragment概述
1.Fragment是什么
2.Fragment的设计目的
这部分主要简单介绍下Fragment是什么,与Activity的关系,以及Fragment的设计目的等部分,主要是给大家一个总体上的把握,之后会有Fragment的用法和详细解析。
1. Fragment是什么
AFragmentrepresents a behavior or a portion of user interface in anActivity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities).
Fragment表示Activity中的行为或者用户界面的一部分,我们可以在单个Activity中组合多个Fragment来构建多格的界面,也可以在多个Activity之间重用同一个Fragment。我们可以把Fragment想象成Activity的一个模块部分,它有自己的生命周期,可以接受用户事件,允许你在Activity正常运行期间添加或者移除,类似一个你可以在不同的Activity之间复用的子Activity。
Fragment必须绑定在Activity之中,它的生命周期也直接受着宿主Activity生命周期的影响。例如,当Activity暂停时,其中的所有Fragment也暂停,当Activity被销毁时,其中的所有Fragment也销毁。但是,当Activity正常运行时,你可以单独地管理每个Fragment,添加或移除。
2. Fragment的设计目的
Android应用运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应不同设备的需要。那么有没有办法可以做到一个App同时适应手机和平板呢?Fragment的出现就是为了解决这样的问题的。
Android 在 Android 3.0(API 11 级)中引入了片段,主要是为了给像平板电脑这类设备的大屏幕更加动态和灵活的 UI 设计提供支持。由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 UI 组件的空间更大。利用片段实现此类设计时,您无需管理对视图层次结构的复杂更改。 通过将 Activity 布局分成片段,您可以在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。
例如,新闻应用可以使用一个Fragment 在左侧显示文章列表,使用另一个Fragment 在右侧显示文章,两个片段并排显示在一个 Activity 中,每个片段都具有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。 因此,用户不需要使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,而是可以在同一个 Activity 内选择文章并进行阅读,如图所示。
二、静态Fragment用法与解析
之所以称之为静态,是因为我们在布局文件里直接把Fragment看做是View作为Activity布局的一部分,大家可以看下面这张图,我已经标出了1、2两部分,的确,它是由两个Fragment组成,上面是标题,下面是内容部分,内容部分还有一个按钮。
下面我们按步骤创建出上面的布局,主要是让大家熟悉Fragment的基本使用,在用到Fragment导入包时,我选择的是不包含V4的包,因为我的minSdkVersion设置成了14,如果你的应用要支持11即Android 3.0以下的版本,则导入的是v4的包。
- 创建TopFragment,主要就是重写onCreateView方法初始化界面。
/**
* Created by JackalTsc on 2016/7/11.
*/
public class TopFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//初始化Fragment界面
View view = inflater.inflate(R.layout.view_fragment_top, container, false);
return view;
}
}
- 创建TopFragment的布局 view_fragment_top.xml,其中只有一个TextView。
<?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="50dp"
android:background="#0000ff"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="应用宝"
android:textColor="#ffffff"/>
</LinearLayout>
- 创建BottomFragment, 方法onCreateView里初始化BottomFragment界面。
/**
* Created by JackalTsc on 2016/7/11.
*/
public class BottomFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
//初始化Fragment界面
View view = LayoutInflater.from(getActivity()).inflate(R.layout.view_fragment_bottom, container, false);
Button btnHome = (Button) view.findViewById(R.id.btn_home);
btnHome.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getActivity(), "选择了首页", Toast.LENGTH_LONG).show();
}
});
return view;
}
}
4.创建BottomFragment的布局 view_fragment_bottom.xml,其中包含一个TextView和一个Button。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="首页内容"
android:textSize="50sp"/>
<Button
android:id="@+id/btn_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:text="首页"/>
</RelativeLayout>
5、创建Activity的布局 layout_activity_fragment.xml,其中包含两个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="vertical">
<fragment
android:id="@+id/fragment_top"
android:name="com.jackaltsc.android.mydemoproject.fragment.TopFragment"
android:layout_width="match_parent"
android:layout_height="50dp"/>
<fragment
android:id="@+id/fragment_bottom"
android:name="com.jackaltsc.android.mydemoproject.fragment.BottomFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>
6、最后我们只需要在Activity里设置布局即可,看,这里的逻辑十分简单,当我们点击评论按钮时,会有一个Toast消息,但是逻辑是在BottomFragment里进行处理的。有人可能会问,做了这么多工作好像很麻烦,但是我们应该记住,如果Fragment的界面有很好的通用性,那么重用的地方就会很多,为我们之后的工作节省不少功夫。
/**
* Created by JackalTsc on 2016/7/11.
*/
public class Fragment1Activity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_activity_fragment);
}
}
静态Fragment小结:
- Fragment里面重写回调方法onCreateView用来初始化Fragment的界面,方法里LayoutInflater对象可以扩展指定布局,返回View即为Fragment布局,这样Fragment即可作为Activity界面的一部分。
- Activity的界面里在包含Fragment时,Fragment的android:name属性为全限定名,用来实例化指定的Fragment,同时别忘了要给Fragment一个唯一的id,这个很重要,系统在Activity恢复时用它来存储状态。
三、动态Fragment用法与解析
动态Fragment主要是可以在Activity正常运行期间的任意时刻添加、删除或者替换Fragment。我们可以看下面这张图,它是由两部分组成,第1部分是内容,第2部分是两个按钮,当点击不同的按钮时,内容部分是会变化的。
下面是详细的步骤。
1、首先是HomeFragment
/**
* Created by JackalTsc on 2016/7/11.
*/
public class HomeFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_fragment_home, container,false);
return view;
}
}
2、它的布局为 layout_fragment_home.xml
<?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:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="50sp"
android:text="首页内容"/>
</LinearLayout>
3、其次是FindFragment
/**
* Created by JackalTsc on 2016/7/11.
*/
public class FindFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.layout_fragment_find, container,false);
return view;
}
}
4、它的布局为 layout_fragment_find.xml
<?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:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="发现内容"
android:textSize="50sp"/>
</LinearLayout>
5、Activity的布局为 layout_activity_fragment2.xml
<?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="vertical">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<Button
android:id="@+id/btn_home"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="首页"/>
<Button
android:id="@+id/btn_find"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:layout_weight="1"
android:text="发现"/>
</LinearLayout>
</LinearLayout>
6、最后Activity里的代码
/**
* Created by JackalTsc on 2016/7/11.
*/
public class Fragment2Activity extends Activity implements View.OnClickListener {
private Button btnHome,btnFind;
private HomeFragment homeFragment;
private FindFragment findFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_activity_fragment2);
//初始化
init();
}
private void init() {
btnHome = (Button) findViewById(R.id.btn_home);
btnFind = (Button) findViewById(R.id.btn_find);
btnHome.setOnClickListener(this);
btnFind.setOnClickListener(this);
getFragment(0);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_home:
Toast.makeText(Fragment2Activity.this, "首页", Toast.LENGTH_SHORT).show();
getFragment(0);
break;
case R.id.btn_find:
Toast.makeText(Fragment2Activity.this, "发现", Toast.LENGTH_SHORT).show();
getFragment(1);
break;
}
}
private void getFragment(int i) {
//Fragment管理器
FragmentManager manager = getFragmentManager();
//事务
FragmentTransaction transaction = manager.beginTransaction();
switch (i) {
case 0:
if (homeFragment == null) {
homeFragment = new HomeFragment();
}
//选中首页 内容部分就替换为首页的Fragment
transaction.replace(R.id.fragment_container, homeFragment);
break;
case 1:
if (findFragment == null) {
findFragment = new FindFragment();
}
//选中发现 内容部分就替换为发现的Fragment
transaction.replace(R.id.fragment_container, findFragment);
break;
}
//只有提交才有效果
transaction.commit();
}
}
这样,你应该可以看到了,点击不同的Button时,内容部分是会切换的。Fragment2Activity里的代码可能比较长,但是并不复杂,我们重点关注getFragment这个函数里的东西,它主要是先用getFragmentManager()获取Fragment管理器,再获得Fragment事务,由事务进行替换,最后commit()。
动态Fragment使用小结
-
关于FragmentManager
FragmentManager,Fragment管理器,是在Activity里进行Fragment交互的接口,可以通过getFragmentManager()方法获取当前Activity相关的事务管理器。 - **关于FragmentTransaction **
FragmentTransaction,Fragment事务,是执行Fragment一系列操作的接口,可以通过FragmentManager对象的beginTransaction()获得,一般常用的操作主要有add,replace,remove,show,hide等等。当然,Fragment的操作最后都要commit提交才有效。
四、Fragment生命周期
Fragment的生命周期与Activity的生命有很多相似的地方,同时也有不同的地方,比如Fragment与Activity一样都有三种状态:
- Resumed
Fragment在运行的Activity里处于可见状态
-
Paused
别的Activity正常运行状态,但是Fragment的宿主Activity仍处于可见状态 -
Stopped
Fragment处于不可见状态
Activity的生命周期对它的影响。官网上的这张图很清楚。
下面介绍几个Fragment中的主要回调方法,
-
onAttach()
当fragment和activity关联时调用。 -
onCreateView()
当创建Fragment的View视图时调用 -
onActivityCreated()
当Activity的onCreate()方法返回时调用 -
onDestroyView()
当移除Fragment的View视图时调用 -
onDetach()
当fragment和activity解除关联时调用。
Activity和Fragment生命周期最大的不同之处在于它们在各自的回退栈中的存储方式不一样。默认情况下,一个Activity在停止时是存放在由系统管理的Activity回退栈中。但是,Fragment在移除的事务处理中,只有在你使用addToBackStack()方法明确请求保存实例状态时,它才会存放在由宿主Activity管理的回退栈中。
关于Fragment回退栈
类似与Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。
五、Fragment与Activity通信
虽然Fragment是实现成一个独立于Activity的对象并且可以在多个Activity中重复使用,但是一个指定的Fragment实例是直接与包含它的Activity绑定的。Fragment与Activity通信情况主要有以下几种。
-
Fragment获取所在的Activity
在Fragment中可以用getActivity()来访问Activity的实例,并且可以执行一些操作,比如查找一个视图:
View listView = getActivity().findViewById(R.id.list);
-
Activity获取它包含的Fragment
Activity 可以通过从FragmentManager获取对Fragment的引用来调用片段中的方法,findFragmentById()或findFragmentByTag()。
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
-
Fragment与Activity共享数据
有时候,如果需要Fragment和Activity共享事件,最好的方法是在Fragment里定义一个内部回调接口,再让包含该Fragment的Activity实现该回调接口,这样Fragment即可调用该回调方法将数据传给Activity。
针对第3种情况,这里有个简单的例子,是我参考疯狂Android讲义上的内容修改而成的,是个不错的学习Demo。如下图所示,分成两个部分,左边是食物的标题,右边是详细介绍。点击不同的标题时,详情部分会切换。
- 首先,新建一个FoodUtil 类,这个类主要包含一个实体FoodEntity 和相关数据集合,在之后会有用处。
/**
* Created by JackalTsc on 2016/7/12.
*/
public class FoodUtil {
//Food实体
public static class FoodEntity {
private int fId;
private String fTitle;
private String fDetail;
public FoodEntity(int fId, String fTitle, String fDetail) {
this.fId = fId;
this.fTitle = fTitle;
this.fDetail = fDetail;
}
@Override
public String toString() {
return getfTitle();
}
public String getfTitle() {
return fTitle;
}
public void setfTitle(String fTitle) {
this.fTitle = fTitle;
}
public int getfId() {
return fId;
}
public void setfId(int fId) {
this.fId = fId;
}
public String getfDetail() {
return fDetail;
}
public void setfContent(String fContent) {
this.fDetail = fDetail;
}
}
//Book相关数据集合
public static List<FoodEntity> foodList = new ArrayList<>();
public static Map<Integer, FoodEntity> foodMap = new HashMap<>();
//添加数据
static {
addData(new FoodEntity(1, "土豆", "属茄科多年生草本植物,块茎可供食用,是全球第四大重要的粮食作物。"));
addData(new FoodEntity(2, "花生", "原名落花生,是我国产量丰富、食用广泛的一种坚果,又名“长生果”、“泥豆”等。"));
addData(new FoodEntity(3, "番茄", "是茄科番茄属一年生或多年生草本植物,体高0.6-2米,全体生粘质腺毛,有强烈气味,茎易倒伏。"));
}
private static void addData(FoodEntity food) {
foodList.add(food);
foodMap.put(food.fId, food);
}
}
2、之后是左边的标题TitleFragment,它是一个继承ListFragment的类,设置了数据适配器时会有列表的效果。
/**
* Created by JackalTsc on 2016/7/12.
*/
public class TitleFragment extends ListFragment {
private Callbacks mCallbacks;
//ListFragment的某项被选中时的回调接口
public interface Callbacks {
public void onItemSelected(Integer id);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//ListFragment创建时设置数据适配器 这里FoodUtil.foodList是我们静态设置的数据集合
setListAdapter(new ArrayAdapter<>(getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1, FoodUtil.foodList));
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
//Activity与Fragment关联时获取初始化回调
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException("BookListFragment 所在的Activity必须实现Callbacks接口!");
}
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null; //解除关联时销毁接口
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
//发生点击事件时 传入数据集合对应位置的id 这样即可获取到详情内容
mCallbacks.onItemSelected(FoodUtil.foodList.get(position).getfId());
}
}
3、之后是右边的食物详细介绍部分,DetailFragment。
public class DetailFragment extends Fragment {
private FoodUtil.FoodEntity mFoodEntity;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//创建时如果有fid 那么就根据fid获取对应的详细内容
if (getArguments().containsKey("fid")) {
mFoodEntity = FoodUtil.foodMap.get(getArguments().getInt("fid"));
}
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.view_fragment_fdetail, container, false);
//如果有数据 就初始化界面数据
if (mFoodEntity != null) {
((TextView) view.findViewById(R.id.tv_title)).setText(mFoodEntity.getfTitle());
((TextView) view.findViewById(R.id.tv_detail)).setText(mFoodEntity.getfDetail());
}
return view;
}
}
4、DetailFragment要有一个布局。
<?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:gravity="center"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp"
android:text="标题"
android:textAppearance="?android:attr/textAppearanceLarge"/>
<TextView
android:id="@+id/tv_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:text="详情"
android:textAppearance="?android:attr/textAppearanceMedium"/>
</LinearLayout>
5、最后是Activity的布局部分,layout_activity_fragment3.xml,注意其中的fragment的name属性换成你自己的TitleFragment的完整路径。
<?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_title"
android:name="com.jackaltsc.android.mydemoproject.fragment.TitleFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<FrameLayout
android:id="@+id/container_food_detail"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"/>
</LinearLayout>
6、Activity里的代码
/**
* Created by JackalTsc on 2016/7/12.
*/
public class Fragment3Activity extends Activity implements TitleFragment.Callbacks {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_activity_fragment3);
}
@Override
public void onItemSelected(Integer id) {
//获取选中时对应的fid
Bundle bundle = new Bundle();
bundle.putInt("fid", id);
//新建Fragment 并填充数据
DetailFragment detailFragment = new DetailFragment();
detailFragment.setArguments(bundle);
//替换详情Fragment部分
getFragmentManager().beginTransaction()
.replace(R.id.container_food_detail, detailFragment)
.commit();
}
}
这样就可以看到效果了,如果有问题,欢迎私信我。
六、Fragment总结
- Fragment总是作为Activity界面的组成部分,Fragment可以调用getActivity()方法获取它所在的Activity,Activity可以调用FragmentManager的findFragmentById()或者findFragmentByTag()来获取Fragment。
- 在Activity运行过程中,可以通过FragmentTransaction对Fragment进行操作,如添加、删除、替换等。
- 一个Activity可以同时组合多个Fragment,一个Fragment也可以被多个Activity复用。
- Fragment可以响应自己的输入事件,并拥有自己的生命周期,但它们的生命周期被其所属的Activity的生命