一、概述
上篇我们主要介绍了Activity不被注意的知识点总结,而本篇主要是将我们日常用到的Fragment的知识点做个梳理,因为很多东西一旦不常用就忘得厉害,真是很忧桑啊,好了既然不是过目不忘的天才那就认命好好学习吧,本篇主要从Fragment的创建、其生命周期、其与Activity的通信等几个方面来介绍:
- Fragment的创建
- Fragment的生命周期
- Fragment的保留
- Fragment与Activity的通信
- FragmentManager的分析
- FragmentTransaction与回退栈的分析
- DialogFragment的使用
二、正文
Fragment是一种控制器对象,Activity可委派它完成一些任务。通常这些任务就是管理用户界面,受管的界面可以是一整屏或者是整屏的一部分。Activity视图含有可供Fragment视图插入的位置,如果有多个Fragment要插入,Activity视图也可提供多个位置。根据用户或设备的需要,Activity界面可以在运行时组装甚至重新组装,Activity自身并不具备这样的灵活性,Activity视图可以在运行时切换,但控制视图的代码必须在Activity中实现,各个Activity和特定的用户屏幕牢牢地绑定在了一起。正是采用这种Fragment而不是Activity进行应用的UI管理,使用Fragment及Activity来组装或重新组装用户界面,在整个生命周期过程中,技术上来说Activity视图并没有改变,所以我们就可以绕开Android系统Activity规则的限制,这段定义摘自《Android 编程权威指南》,这里就不过多讨论基础概念了,我们主要来看下经常被用到的一些知识点。
(1)静态添加Fragment
这种方式添加主要分为两步,首先创建待添加的Fragment并关联其相应的xml布局;之后在Activity布局xml文件之中添加我们需要的fragment,这样我们就可以在Activity代码中直接使用该Fragment了,很简单,但这样也就必然会丧失其灵活性,因为这样相当于将fragment的视图与activity的视图牢牢绑在了一起,在Activity中我们根本无法动态切换fragment视图,下面我们来简单看下例子:
这是我们创建的碎片MyFragment1,因为Fragment是在API11(Android 3.0)才被引入的,如果需要向后兼容旧版本设备,那么我们可以使用v4支持库内的android.support.v4.app.Fragment,该类可以使用在任何API4级及更高的版本上
public class MyFragment1 extends Fragment {
//这里的Fragment为android.app.Fragment
@Nullable @Override public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
//第一个参数:布局的资源id
//第二个参数:视图的父视图,通常我们需要父视图来正确配置组件
//第三个参数:告知布局生成器是否将生成的布局添加给父视图
View view = inflater.inflate(R.layout.fragment_layout1, container, false);
return view;
}
}
这是我们上面MyFragment1所关联的布局fragment_layout1,没有特殊的地方,只是很简单的布局
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0ff">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#787878"
android:textSize="20sp"
android:text="这是静态添加Fragment"
android:layout_centerInParent="true"
/>
</RelativeLayout>
这是我们的Activity布局activity_main,这里需要注意的是name属性
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/my_fragment1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="zmj.myfragments.MyFragment1"
android:layout_marginTop="15dp"
/>
</RelativeLayout>
这是我们的活动MainActivity,在这里也存在一个版本问题,在Android 3.0之后的版本中,我们可以直接使用Activity来管理fragment,但是如果我们想要兼容老版本,则必须使用FragmentActivity类,因为在Android 3.0之前的版本中,Activity的内部还没有实现管理Fragment的代码,我们这里的AppCompatActivity继承自FragmentActivity类
public class MainActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
在本例中不需要支持3.0以下的情况,目前测试机API19 4.4.4,运行结果
(2)动态添加Fragment
动态添加Fragment主要分为三个步骤,首先创建待添加的Fragment并关联其相应的xml布局;之后在Activity布局xml文件之中添加< FrameLayout >容器视图用来托管我们需要的Fragment;最后在Activity代码中添加我们需要的fragment。下面是我们的Fragment2,关于版本兼容问题在静态添加时已经说了,我们来直接看下代码
public class MyFragment2 extends Fragment {
//这里的Fragment为android.app.Fragment
@Nullable @Override public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout2, container, false);
return view;
}
}
这是我们碎片MyFragment2所需要的布局fragment_layout2,只是简单的布局
<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:textColor="#787878"
android:textSize="20sp"
android:text="这是动态添加Fragment"
android:layout_centerInParent="true"
/>
</RelativeLayout>
这是我们的活动MainActivity的布局activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/my_fragment2"
android:layout_width="match_parent"
android:layout_height="150dp"
android:background="#f0f"
/>
<fragment
android:id="@+id/my_fragment1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="zmj.myfragments.MyFragment1"
android:layout_below="@id/my_fragment2"
/>
</RelativeLayout>
这是我们的活动MainActivity,关于版本问题在静态添加Fragment的介绍中已经提过了,在这里就不赘述了,相对于静态添加Fragment来说,动态添加Fragment就要灵活多了,我们可以在运行时动态的控制Fragment,也可以添加fragment,可以用其他的fragment替换当前的fragment,也可以移除fragment
public class MainActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
addFragment();
}
private void addFragment() {
//这里的android.app.FragmentManager
FragmentManager fm = getFragmentManager();
MyFragment2 fragment2 = (MyFragment2) fm.findFragmentById(R.id.my_fragment2);
if(fragment2 == null){
fragment2 = new MyFragment2();
fm.beginTransaction()
.add(R.id.my_fragment2, fragment2)
.commit();
}
}
}
在本例中不需要支持3.0以下的情况,目前测试机API19 4.4.4,运行结果
(3)Fragment生命周期
关于Fragment的内容我们也可以查看官网介绍,Fragment依赖于Activity而存在,所以Fragment的状态也间接反映了Activity的状态,两者的生命周期的关键区别在于,Activity的生命周期是系统调用的,而Fragment的生命周期是由Activity调用的,是Activity自己内部的事情,具体的说,是activity的FragmentManager负责调用队列中Fragment的生命周期方法。下面我们来看一下Fragment的生命周期图:
Fragment有一些和Activity不同的生命周期方法,下面我们就来单独看看其的含义,如果你的英文不错,可以去阅读官网,可能理解的会更准确
Fragment生命周期方法 | 含义 |
---|---|
onAttach(…) | 一旦Fragment与Activity关联就会调用 |
onCreateView(…) | 创建并返回与Fragment相关联的视图层次结构 |
onActivityCreated(…) | 告诉Fragment,Activity的onCreate方法调用完成了 |
onDestroyView(…) | 与onCreateView想对应,允许Fragment清理与碎片视图相关联的资源 |
onDetach(…) | 与onAttach相对应,当Fragment与Activity取消关联时调用 |
既然Fragment依赖Activity而存在,那么其生命周期之间必然会存在某种联系
也许图示不够直观,那我们就来拿几种具体的情况来看一下,当我们加载一个Fragment显示到屏幕时,Activity和Fragment所经历的生命周期
/**
* 注意这是我们Activity的onCreate方法:
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("Tag","Activity onCreate() begin");
setContentView(R.layout.activity_main);
Log.i("Tag","Activity onCreate() end");
}
*/
Activity onCreate() begin>>Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()
>>Activity onCreate() end>>Fragment onActivityCreated()
>>Activity onStart()>>Fragment onStart()>>Activity onResume>>Fragment onResume()
//需要注意的是,只有静态添加,才会有Activity onCreate() begin>>...>>Activity onCreate() end
当我们返回键销毁掉正在运行的Activity及它的Fragment时,其所经历的生命周期
Fragment onPause()>>Activity onPause()>>Fragment onStop()>>Activity onStop()
>>Fragment onDestroyView()>>Fragment onDestroy()>>Fragment onDetach()
>>Activity onDestroy()
当我们在Activity运行状态下,动态添加一个Fragment时,其所经历的的生命周期
//在添加Fragment之前,Activity已经经历了onCreate()>>onStart()>>onResume()
//接下来添加Fragment,生命周期流程如下:
Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()
>>Activity onStart()>> Activity onResume()
//没有执行Activity onRestart()方法
//这些情况下,FragmentManager会立即驱使fragment快速跟上activity的步伐,直到与activity最新状态保存一致
我们也可以在Activity获得焦点前添加一个Fragment,即在Activity的onStart()方法之中添加,FragmentManager同样也会立即驱使fragment快速跟上activity的步伐,其所经历的生命周期
Activity onCreate()>>Activity onStart()
>>Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()>>Fragment onStart()
>>Activity onResume()>>Fragment onResume()
//同样的你也可以在其它生命周期方法中添加,思路一样
我们也可以在失去焦点时添加一个Fragment,但这好像失去了意义啊,之所以我在这里说上这个,是因为这种情况下生命周期是有那么一点不同
//直接从运行状态开始了
Activity onPause()>>Fragment onAttach()>>Fragment onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()
>>Fragment onStart()
>>Fragment onStop()>>Activity onStop()
>>Fragment onDestroyView()>>Fragment onDestroy()>>Fragment onDetach()>>Activity onDestroy()
//在这里我们可以看到,fragment并没有执行onResume()和onParse()方法
当我们在Activity运行状态下,横竖屏切换,其生命周期过程
Fragment onPause()>>Activity onPause()>>Fragment onStop()>>Activity onStop()>>Fragment onDestroyView()>>Fragment onDestroy()>>Fragment onDetach()>>Activity onDestroy()
>>Fragment onAttach()>>Fragment onCreate()>>Activity onCreate()>>Fragment onCreateView()>>Fragment onActivityCreated()
>>Activity onStart()>>Fragment onStart()>>Activity onResume()>>Fragment onResume()
//需要注意的是,Activity.onCreate()在Fragment.onCreate()方法之后
还有一点需要注意的是,如果使用了支持库,那么Fragment生命周期的某些方法的调用顺序会略有不同,如在Activity.onCreate()中添加一个Fragment,那么Fragment.onActivityCreated()方法在Activity.onStart()方法之后调用,因为在3.0版本之前,立即从FragmentActivity中调用onActivityCreated()方法是不可能的。
(4)Fragment的保留
通过第三节生命周期的介绍,我们可以知道,当我们旋转屏幕或者设备配置发生变化的时候,Fragment和Activity都会被销毁并被重建,在这个过程中会使之前的数据丢失或成员变量被初始化从而产生问题,解决这个问题我们首先想到的当然是onSaveInstanceState(Bundle bundle)方法
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("TAG_COUNT", "保存的内容");
}
//或者onCreate(@Nullable Bundle savedInstanceState)方法之内
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if(savedInstanceState != null){
(String) savedInstanceState.get("TAG_COUNT");
}
}
虽然onSaveInstanceState(…)方法很方便,但却也有力有不逮的时候,例如在旋转屏幕前我们想要保存Fragment内所有的对象呢,甚至这个Fragment异常复杂呢,那么像我们之前的通过Bundle保存就有点不现实了,好在系统给fragment提供了retainInstance的属性值,默认值为false,调用setRetainInstance(true)方法可保留fragment,而已保留的fragment不会随着activity一起销毁,相反,它会被一直保留并在需要的时候原封不动的传递给新的activity。
正常情况下,当设备发生旋转时,FragmentManager会立即销毁该fragment实例,随后旋转完成后,新的activity的新的FragmentManager会立即新建一个新的Fragment及其视图,而当我们设置了retainInstance属性true时,当设备发生旋转时,该fragment关联的视图会被销毁,但是fragment本身不会被销毁,当重建的时候,新的FragmentManager会找到这个被保留的fragment,并重新创建它的视图。
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//很简单就一句话,测试时可以打印一个对象地址
//你会发现旋转成功后其地址没有改变,说明它还是原来的对象,亲测
setRetainInstance(true);
}
既然retainInstance属性这么方便,那么我们为什么有时还要使用onSaveInstanceState(…)方法呢,哈哈其实两者还是有区别的,主要区别在于数据保存的时间长短,如果使用retainInstance,只有当activity因设备发生改变被销毁时,fragment才会短时间处于被保留状态,如果activity是因系统需要回收内存而被销毁,那么所有被保留的fragment也会被随之销毁;如需持久地保存数据,那么就得使用onSaveInstanceState(…)方法了,例如用户暂时离开应用后,系统因回收内存需要销毁activity,那么保留的fragment也会随之销毁。
(5)Fragment与Activity的相互通信
其实Fragment与Activity之间的交互无外乎几种,第一种,我们想从Fragment内获取它的托管Activity中的数据;第二种,我们想从Activity中获取其管理的Fragment内的数据;第三种,我们想从一个Fragment内对它的托管Activity管理的另一个或多个Fragment进行交互,那么下面我们就来单独看看:
1.我们想从Fragment内获取它的托管Activity中的数据,这种情况我们主要有两种方式,第一种方式简单直接,我们直接在Fragment中使用getActivity()方法获取托管Activity实例,有了实例了,我们当然就能够随心所欲了,当时前提是你不介意你的代码可能耦合成了一堆浆糊,下面我们就来看一个小例子,在MyActivity2所管理的Fragment中获取MainActivity界面传递过来的数据
//这是MainActivity类中的部分代码,作用是点击之后携带数据跳转到MyActivity2
findViewById(R.id.tv_add_fragment).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(MainActivity.this, MyActivity2.class);
intent.putExtra(MyApplication.INTENT_TAG, "我是要传递的数据");
startActivity(intent);
}
});
这是我们跳转至的MyActivity2界面,代码很简单,其管理着一个碎片MyFragment3
public class MyActivity2 extends AppCompatActivity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout2);
addFragment();
}
private void addFragment() {
FragmentManager mf = getFragmentManager();
Fragment fragment = mf.findFragmentById(R.id.fragment3);
if(fragment == null){
fragment = new MyFragment3();
mf.beginTransaction()
.add(R.id.fragment3, fragment)
.commit();
}
}
}
好了,接下来我们就可以在MyFragment3中获取上个界面传递过来的数据了
public class MyFragment3 extends Fragment {
private String content;
private View view;
//展示上个Activity传递过来的数据
private TextView tvShowContent;
@Nullable @Override public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_layout3, container, false);
return view;
}
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
tvShowContent = view.findViewById(R.id.tv_content);
//重点是这行,通过getActivity获得MyActivity2的实例
content = getActivity().getIntent().getStringExtra(MyApplication.INTENT_TAG);
if(content != null){
tvShowContent.setText(content);
}
}
}
上面这种方法虽然简单,但缺点也是相当明显的,那就是让Fragment和某个具体的Activity绑定在了一起,使Fragment失去了灵活的复用性,所以系统还为我们提供了另一个方法,创建Fragment argument来实现Activity到Fragment的数据传递,每个Fragment实例都可以附带一个Bundle对象,接下来我们就来看看修改之后MyFragment3的代码
public class MyFragment3 extends Fragment {
private String content;
private View view;
//展示上个Activity传递过来的数据
private TextView tvShowContent;
@Nullable @Override public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_layout3, container, false);
return view;
}
//因为fragment.setArguments(bundle)方法,要求必须在Fragment创建之后,添加到activity之前完成
//所以Android开发者就有了一个约定俗成的习惯,那就是创建一个静态方法newInstance给fragment
//之后在它的托管Activity中通过newInstance获得MyFragment3对象,而不能直接new MyFragment3()获取对象了
public static Fragment newInstance(String content) {
Fragment fragment = new MyFragment3();
Bundle bundle = new Bundle();
bundle.putString(MyApplication.INTENT_TAG, content);
fragment.setArguments(bundle);
return fragment;
}
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
tvShowContent = view.findViewById(R.id.tv_content);
//我们直接通过获取argument来获取数据,这样就保证了Fragment的通用独立性了
if(getArguments() != null){
content = getArguments().getString(MyApplication.INTENT_TAG);
}
if(content != null){
tvShowContent.setText(content);
}
}
}
这是我们通过newInstance来获取对象的MyActivity2类的部分代码
private void addFragment() {
FragmentManager mf = getFragmentManager();
Fragment fragment = mf.findFragmentById(R.id.fragment3);
if(fragment == null){
//不直接new了,而是通过newInstance获得MyFragment3的实例
fragment = MyFragment3.newInstance(getIntent().getStringExtra(MyApplication.INTENT_TAG));
mf.beginTransaction()
.add(R.id.fragment3, fragment)
.commit();
}
}
通过上面我们会发现,设置argument只能从Activity获取数据,并不能从Fragment去设置Activity的数据,那么这种情况下,我们就可以使用接口回调来实现Fragment到Activity的通信,例如我想在MyFragment3中给MyActivity2的字段赋值,我们可以这么来,下面是我们的接口
public interface IFragmentNotify {
//通知Activity去执行操作
void notifyActivity();
}
接下来就是我们的MyFragment3,其实就是一个简单的回调,估计大家早都用烂了,这里就简单的说下就行了
public class MyFragment3 extends Fragment {
private View view;
//展示上个Activity传递过来的数据
private TextView tvShowContent;
private IFragmentNotify iNotify;
@Nullable @Override public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_layout3, container, false);
return view;
}
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
tvShowContent = view.findViewById(R.id.tv_content);
addClickListener();
}
//没有什么可说的,很简单的回调,估计大家早都用烂了,这里就简单说下
public void setINotify(IFragmentNotify iNotify) {
this.iNotify = iNotify;
}
public void addClickListener() {
tvShowContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(iNotify != null){
iNotify.notifyActivity();
}
}
});
}
}
最后在我们的MyActivity2中实现接口并注册监听就好,很简单,这里就不多解释了,直接看代码吧
public class MyActivity2 extends AppCompatActivity implements IFragmentNotify{
//Activity顶部显示的内容
TextView tvContent;
//当MyFragment3点击时候,回调MyActivity2的此方法
@Override public void notifyActivity() {
tvContent.setText("回调更新了数据");
}
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout2);
addFragment();
init();
}
private void init() {
tvContent = (TextView) findViewById(R.id.tv_set_content);
}
private void addFragment() {
FragmentManager mf = getFragmentManager();
Fragment fragment = mf.findFragmentById(R.id.fragment3);
if(fragment == null){
fragment = new MyFragment3();
//添加监听,现在是通知一个Activity,如果多个,则可以在MyFragment3中通过List<Map<Tag,Activity>>将注册的Activity保存起来,随后再根据条件更新即可
((MyFragment3)fragment).setINotify(this);
mf.beginTransaction()
.add(R.id.fragment3, fragment)
.commit();
}
}
}
2.我们想从Activity中获取其管理的Fragment内的数据或对Fragment操作,当我们Activity中存在着待操作的Fragment对象引用时,我们直接可以使用;如果不存在,那么我们可以通过findFragmentById()或者findFragmentByTag()方法来获取需要的Fragment对象,随后再进行相关操作,例如我们想通过MyActivity2来给MyFragment3赋值,那么代码如下:
//我们在MyFragment3中添加一个公开方法,方便Activity设置内容
public void setContent(String content) {
if(content != null){
tvShowContent.setText(content);
}
}
这是我们的MyActivity2部分代码,通过获取MyFragment3对象,随后调用其提供的setContent()方法设置内容
private void addFragment() {
FragmentManager mf = getFragmentManager();
Fragment fragment = mf.findFragmentById(fragment3);
if(fragment == null){
//如果设置为成员变量,而非局部变量,那么就如我们所说的直接使用引用即可
fragment = new MyFragment3();
mf.beginTransaction()
//添加fragment3时,顺便关联Tag:TAG_FRAGMENT3 = "Fragment3_tag"
.add(fragment3, fragment, TAG_FRAGMENT3)
.commit();
}
}
private void addClickListener() {
findViewById(R.id.tv_set_content).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
FragmentManager mf = getFragmentManager();
MyFragment3 fragment3 = (MyFragment3) mf.findFragmentById(R.id.fragment3);
//MyFragment3 fragment3 = (MyFragment3) mf.findFragmentByTag(TAG_FRAGMENT3);
fragment3.setContent("为MyFragment3设置内容");
}
});
}
3.我们想从一个Fragment内对它的托管Activity管理的另一个或多个Fragment进行交互,最简单的方法当然是直接在Fragment内通过getActivity和findFragmentById获取另一个Fragment的对象了,但我相信这绝对是最糟糕的写法,它会让你的代码乱成一锅浆糊,还是那种浓的流不动的浆糊,而我们应该为了设计的松耦合而努力,不是说功能实现了就万事大吉了,那样是不对的。在这里我就简单的说下我的思路,当然方法肯定不是唯一的,我们应该将Activity当做Fragment之间的枢纽,Fragment有事情了则通知Activity,Activity接收到了通知,则再将任务分发给其它Fragment去处理,下面我就来拿一个简单的例子看下,这是我新建的一个活动MyActivity3,它管理着两个Fragment:
public class MyActivity3 extends AppCompatActivity {
//可以规定不同的tag代表不同的操作
public static final int TAG_TO_MYFRAGMENT = 0;
//将Activity所管理的Fragment都添加到此容器内
List<Fragment> fragmentList = new ArrayList<>();
//接收所管理的fragment发送过来的操作之类,随后下发给所有fragment
public void postEventToFragment(int tag, Object obj) {
for(Fragment fragment : fragmentList){
if(fragment instanceof IPostEvent){
((IPostEvent) fragment).postEvent(tag, obj);
}
}
}
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout3);
addFragment();
}
private void addFragment() {
//首先添加fragment4
FragmentManager fm = getFragmentManager();
MyFragment4 fragment4 = (MyFragment4) fm.findFragmentById(R.id.fragment_top);
if(fragment4 == null){
fragment4 = new MyFragment4();
fm.beginTransaction()
.add(R.id.fragment_top, fragment4)
.commit();
}
//随后再添加fragment5
MyFragment5 fragment5 = (MyFragment5) fm.findFragmentById(R.id.fragment_bottom);
if(fragment5 == null){
fragment5 = new MyFragment5();
fm.beginTransaction()
.add(R.id.fragment_bottom, fragment5)
.commit();
}
//将我们的fragment添加到容器里,方便我们统一管理
fragmentList.add(fragment4);
fragmentList.add(fragment5);
}
}
这是我定义的接口IPostEvent,目的是为了MyActivity3分发指令时,让实现了IPostEvent接口的Fragment都能够接收到命令
interface IPostEvent{
//当fragment发送操作指令时,当前接口的实现类就会调用此方法
void postEvent(int tag, Object obj);
}
这是我们的MyActivity3类所管理的MyFragment4,现在我们的需求是,MyFragment4要与MyFragment5进行交互,所以思路是,MyFragment4去通知MyActivity3,让MyActivity3再通知MyFragment5进行操作
public class MyFragment4 extends Fragment implements IPostEvent{
private View view;
private TextView tvShowContent;
@Nullable
@Override public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_layout4, container, false);
return view;
}
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
tvShowContent = view.findViewById(R.id.tv_fragment4_content);
addClickListener();
}
@Override
public void postEvent(int tag, Object obj) {
//如果自己也需要这条通知,也可以直接根据tag来处理
}
private void addClickListener() {
tvShowContent.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//去通知MyActivity3,在这里为了简单我就直接使用getActivity了
if(getActivity() instanceof MyActivity3){
((MyActivity3) getActivity())
.postEventToFragment(MyActivity3.TAG_TO_MYFRAGMENT
, "我要同步更新数据");
}
}
});
}
}
这就是我们的碎片MyFragment5,其接收MyActivity3传过来的指令,并根据指令Tag做相应的操作
public class MyFragment5 extends Fragment implements IPostEvent{
private View view;
private TextView tvShowContent;
@Nullable
@Override public View onCreateView(LayoutInflater inflater
, @Nullable ViewGroup container
, Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_layout5, container, false);
return view;
}
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
tvShowContent = view.findViewById(R.id.tv_fragment5_content);
}
@Override
public void postEvent(int tag, Object obj) {
switch (tag){
//根据指令tag,做相应的操作即可
case MyActivity3.TAG_TO_MYFRAGMENT:
tvShowContent.setText(obj.toString());
break;
}
}
}
(6)FragmentManager的介绍
关于FragmentManager的内容我们可以查看官网FragmentManager,当然更详细的我们也可以查看FragmentManager源码,在这里我就简单的介绍下,当我们在Activity中要获取FragmentManager时,我们直接调用getFragmentManager()方法即可
FragmentManager fm = getFragmentManager();
如果你只想使用FragmentManager,那么这样一行代码就完全足够了,但是如果你想知其所以然,那么有一些地方就需要注意了,因为FragmentManager是一个抽象类,所以我们获取到的只是FragmentManager的一个实现类(子类FragmentManagerImpl)对象而已,具体的获取过程我们可以接着往下看,这是我们Activity类的部分代码
final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
/**
* Return the FragmentManager for interacting with fragments associated
* with this activity.
*/
public FragmentManager getFragmentManager() {
return mFragments.getFragmentManager();
}
这是我们FragmentController类,我们发现其又调用了mHost.getFragmentManagerImpl()方法
private final FragmentHostCallback<?> mHost;
public static final FragmentController createController(FragmentHostCallback<?> callbacks) {
return new FragmentController(callbacks);
}
public FragmentManager getFragmentManager() {
return mHost.getFragmentManagerImpl();
}
这是我们的抽象类FragmentHostCallback< E >,通过对象组合,最后调用方法的返回值即一个FragmentManagerImpl对象
final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
FragmentManagerImpl getFragmentManagerImpl() {
return mFragmentManager;
}
这就是我们最后获取到的FragmentManagerImpl类
public abstract class FragmentManager {
...//省略代码
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
...
}
...
}
知道了这些我们就明白了,其实我们平时所使用的findFragmentById都是FragmentManagerImpl的方法
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
/**注意只是根据需要展示了FragmentManagerImpl的部分代码*/
ArrayList<Fragment> mAdded;//管理着一个fragment队列
ArrayList<BackStackRecord> mBackStack;//管理着一个回退栈
@Override public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
//addFragment、removeFragment、hideFragment、showFragment
//detachFragment、attachFragment常用方法
public void addFragment(Fragment fragment, boolean moveToStateNow) {
//将fragment添加到容器里,并改变相应的标志位
...
}
//findFragmentById、findFragmentByTag常用方法
public Fragment findFragmentById(int id) {
//从容器里根据id取出相应的fragment
...
}
在这里我只是简单展示了一下FragmentManagerImpl的极少量代码,让我们心里先有个大致印象,当我们介绍事务FragmentTransaction的时候,我们会接触到FragmentManagerImpl更多的方法,明白当我们提交一个事务时,两者是怎么协调处理的。
(7)FragmentTransaction的分析
上面我们说了FragmentManager,通过beginTransaction()方法,我们便能开启一个事务,随后就是我们的一些普通操作了
private void addFragment4() {
FragmentManager fm = getFragmentManager();
//在这里为了直观展示,就加一个局部变量
FragmentTransaction fragmentTransaction = fm.beginTransaction();
MyFragment4 fragment4 = (MyFragment4) fm.findFragmentById(R.id.activity5_fragment);
if(fragment4 == null){
fragment4 = new MyFragment4();
fragmentTransaction.add(R.id.activity5_fragment, fragment4)
//根据需要是否要加入到回退栈
.addToBackStack(null)
.commit();
}
}
我们知道,fm.beginTransaction()实际调用的是其子类FragmentManagerImpl的方法,其实就是个多态思想
@Override public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
通过官网FragmentTransaction我们可以知道,FragmentTransaction也是一个抽象类,我们所使用的事务即其子类BackStackRecord类,类中代码非常多,我们可以查看BackStackRecord源码,在这里,我就根据我们常用的add并commit的流程来简单介绍下BackStackRecord,并了解一下其与FragmentManagerImpl是怎么协调工作的,这是我们的BackStackRecord类
final class BackStackRecord extends FragmentTransaction implements
FragmentManager.BackStackEntry, Runnable {
//在这里我们持有FragmentManagerImpl对象
//当然也就能根据需要调用FragmentManagerImpl方法了
final FragmentManagerImpl mManager;
public BackStackRecord(FragmentManagerImpl manager) {
mManager = manager;
}
}
这是我们的FragmentManagerImpl类,当我们在Activity中beginTransaction()时,我们知道我们创建了一个事务BackStackRecord,并将自身引用传递了过去
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
/**注意只是根据需要展示了FragmentManagerImpl的部分代码*/
ArrayList<Fragment> mAdded;//管理着一个fragment队列
@Override public FragmentTransaction beginTransaction() {
return new BackStackRecord(this);
}
}
之后我们会调用BackStackRecord类的add(int var1, Fragment var2)方法
public FragmentTransaction add(int containerViewId, Fragment fragment) {
//doAddOp(...)这是一个私有方法
//在方法里会创建一个Op对象
//Op对象的作用就是记录一次操作的动作和Fragment引用以及操作使用的动画
doAddOp(containerViewId, fragment, null, OP_ADD);
return this;
}
之后我们接着调用BackStackRecord类的addToBackStack(null)加入回退栈方法
public FragmentTransaction addToBackStack(String name) {
if (!mAllowAddToBackStack) {
throw new IllegalStateException("This FragmentTransaction is not allowed to be added to the back stack.")
}
//我们发现其实这个方法只是将改变了标志位,并没有实际的逻辑代码
//这也是我们为什么必须在commit之前加入回退栈的原因
//因为commit之后加入,改变了标志位的事务才会被提交
mAddToBackStack = true;
mName = name;
return this;
}
之后我们接着在BackStackRecord类中调用commit()方法,提交我们的本次的事务
public int commit() {
return commitInternal(false);
}
//如果提交时,生命周期处于Saving Activity之后
//那么使用commit就会由于丢失信息从而抛出错误
//如果不需要保存信息,可以使用commitAllowingStateLoss
public int commitAllowingStateLoss() {
return commitInternal(true);
}
//在这里我们就简单看下allowStateLoss为false的情况
int commitInternal(boolean allowStateLoss) {
...//省略部分代码
if (mAddToBackStack) {
//使用mAvailBackStackIndices和mBackStackIndices两个数组
//来为BackStackRecord分配Index
mIndex = mManager.allocBackStackIndex(this);
} else {
Index = -1;
}
//这是我们最后提交的方法,会调用FragmentManagerImpl类的enqueueAction方法
mManager.enqueueAction(this, allowStateLoss);
return mIndex;
}
那我们接下来就看下FragmentManagerImpl类的allocBackStackIndex(…)方法,当加入返回栈并在commit事务之前
// Must be accessed while locked.
ArrayList<BackStackRecord> mBackStackIndices;
ArrayList<Integer> mAvailBackStackIndices;
public int allocBackStackIndex(BackStackRecord bse) {
synchronized (this) {
if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
if (mBackStackIndices == null) {
mBackStackIndices = new ArrayList<BackStackRecord>();
}
int index = mBackStackIndices.size();
mBackStackIndices.add(bse);
return index;
} else {
int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
mBackStackIndices.set(index, bse);
return index;
}
}
}
这是我们最后commit提交后调用的FragmentManagerImpl类的enqueueAction(…)方法
ArrayList<Runnable> mPendingActions;//每提交一个事务都在不同线程里
Runnable mExecCommit = new Runnable() {
@Override
public void run() {
//这个方法我就不细写了,其主要作用
//遍历mPendingActions管理的事务线程
//并调用每个线程事务(BackStackRecord)类的run方法
//随后一个个移除相应的事务线程
execPendingActions();
}
};
public void enqueueAction(Runnable action, boolean allowStateLoss) {
if (!allowStateLoss) {
checkStateLoss();
}
synchronized (this) {
if (mDestroyed || mHost == null) {
throw new IllegalStateException("Activity has been destroyed");
}
if (mPendingActions == null) {
//这是管理事务的线程集合,每个线程即代表着一个待处理的事务操作
mPendingActions = new ArrayList<Runnable>();
}
mPendingActions.add(action);
if (mPendingActions.size() == 1) {
mHost.getHandler().removeCallbacks(mExecCommit);
//mHost.getHandler()返回一个handler对象
//即handler.post(Runnable action)
//所以会调用mExecCommit的run方法
mHost.getHandler().post(mExecCommit);
}
}
}
这就是我们的BackStackRecord类的run方法,在这里我们根据Op对象所携带的信息,判断进行哪种操作,随后再调用FragmentManagerImpl的方法,moveToState(…)和是否需要将事务添加到回退栈
public void run() {
...
//根据OP的信息调用相应的操作,例如当我们add操作
switch (op.cmd) {
case OP_ADD:
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
//将当前操作的fragment添加到FragmentManager管理的列表中
mManager.addFragment(f, false);
break;
case OP_REMOVE:
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.removeFragment(f, mTransition, mTransitionStyle);
break;
...
}
...
mManager.moveToState(mManager.mCurState, mTransition, mTransitionStyle, true);
if (mAddToBackStack) {
//将本次事务添加到回退栈
mManager.addBackStackState(this);
}
}
那么最后我们再接着回来看下FragmentManagerImpl类的部分方法
public void addFragment(Fragment fragment, boolean moveToStateNow) {
//将fragment添加到管理队列中,并改变相应的标志位
...
}
//在回退栈中添加一个事务BackStackRecord
void addBackStackState(BackStackRecord state) {
if (mBackStack == null) {
mBackStack = new ArrayList<BackStackRecord>();
}
mBackStack.add(state);
//回调onBackStackChanged
reportBackStackChanged();
}
//这个方法非常重要,每当Fragment的生命周期中状态改变,都会被调用
//这也保证了Fragment与Activity能够保持同步
//具体的代码,感兴趣的可以查看源码了解下
void moveToState(int newState, int transit, int transitStyle, boolean always) {
//Fragment的状态切换的操作逻辑
...
}
到这里分析就已经结束了,开启并提交一个事务,是否将事务添加到返回栈两种情况,我们合二为一的做了介绍,下面我们再来看一下,当我们按返回键时,当有事务在返回栈的情况下,事务是怎么被弹出栈的,这是我们Activity的代码
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
//如果我们Fragment的事务回退栈还有事务能被弹出则返回true,否则返回false
if (!mFragments.getFragmentManager().popBackStackImmediate()) {
//经过一些其他条件的判断,最终会调用finish()方法
finishAfterTransition();
}
}
下面我们来接着看下FragmentManagerImpl类的popBackStackImmediate(…)方法
@Override public boolean popBackStackImmediate() {
checkStateLoss();
//如果存在待处理的事务,则直接返回true
executePendingTransactions();
//参数是固定的,注释看下面
return popBackStackState(mHost.getHandler(), null, -1, 0);
}
这是FragmentManagerImpl类的popBackStackState(…)方法
//1往左移0位,实际还是1,并且0&1=0 (按位与)
public static final int POP_BACK_STACK_INCLUSIVE = 1<<0;
boolean popBackStackState(Handler handler, String name, int id, int flags) {
//如果我们的回退栈为null,那么直接返回false退出Activity
if (mBackStack == null) {
return false;
}
//当返回键的时候,传参是固定的,所以直接走这个分支
if(if (name == null && id < 0 && (flags&POP_BACK_STACK_INCLUSIVE) == 0)) {
int last = mBackStack.size()-1;
if (last < 0) {
return false;
}
//将我们管理的回退栈最顶层的remove掉
final BackStackRecord bss = mBackStack.remove(last);
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
//作用是循环所有Op,找到第一个被删除的fragment
//和最后一个被添加的fragment
bss.calculateBackFragments(firstOutFragments, lastInFragments);
//这是主要方法,注释看下面
bss.popFromBackStack(true, null, firstOutFragments, lastInFragments);
//FragmentManagerImpl管理着一个ArrayList<OnBackStackChangedListener> 集合
//这个方法作用便是循环遍历onBackStackChanged()方法
reportBackStackChanged();
}else {
...
}
}
这是我们需要的BackStackRecord类的popFromBackStack(…)方法
public TransitionState popFromBackStack(boolean doStateMove, TransitionState state
, SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
...
//根据事务的Op携带的信息,知道事务回退栈里的事务是什么操作,例如add
//知道栈里的事务是的类型了,当然back时候就知道怎么相应处理了
//例如事务A是add而来,那么back后执行remove;
//如果事务A是replace而来,那么back后执行先remove掉新的后再add进替换掉那个old的
Op op = mTail;
while (op != null) {
switch (op.cmd) {
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.popExitAnim;
//因为回退栈里的事务里Op标识说明这是添加操作
//所以本次back键直接将这个事务removeFragment即可
mManager.removeFragment(f
, FragmentManagerImpl.reverseTransit(mTransition)
, mTransitionStyle);
}
break;
...//其他操作情况
}
}
...
}
(8)DialogFragment的使用
DialogFragment是Fragment的子类,跟其他Fragment一样,DialogFragment实例也是由托管Activity的FragmentManager管理着的,DialogFragment可通过调用show(…)方法被FragmentManager放置到屏幕上,其实DialogFragment的用法非常简单,下面我们来简单看个例子,这是我们定义的MyDialogFragment类
public class MyDialogFragment extends DialogFragment{
//在这里我们就创建一个简单的AlterDialog,重点不是AlterDialog
@Override public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setTitle("这是我们定义的AlterDialog")
.setPositiveButton(android.R.string.ok, null)
.create();
}
}
随后在我们的MyActivity4中使用即可
public class MyActivity4 extends AppCompatActivity{
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout4);
addClickListener();
}
private void addClickListener() {
findViewById(R.id.tv_show_dialog).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//弹出对话框
addDialogFragment();
}
});
}
private void addDialogFragment() {
//虽然DialogFragment也是一个Fragment,但是在使用上还是有些差别
FragmentManager fm = getFragmentManager();
MyDialogFragment dialogFragment = new MyDialogFragment();
dialogFragment.show(fm, "俺乃String tag");
}
}
为什么有AliterDialog了,我们还要多此一举的使用DialogFragment呢,主要原因有两方面,一方面是因为既然DialogFragment是Fragment,所以其也就具备了Fragment的优点,有自己的生命周期管理、易复用、能够通过FragmentManager使用更多配置选项显示对话框;另一方面,当设备发生旋转时,DialogFragment也不会像AlterDialog那样在旋转后消失。
三、总结
Fragment的知识点真的很多,一时半会也写不完,尤其是本篇主要是在基础知识上做了下简单总结,在实际应用中的灵活用法、常见问题、解决方法都没有涉及,所以只能在后期开发中随时遇到再随时做总结了。