开篇废话
公司搬迁告一段落,吐槽一句,从此就需要过上车程一个半小时的上下班之旅了。。。。。。
上一篇我们了解了四大组件之一的Activity,应该对于Activity有了一定的了解,这一篇文章,我们一起来回顾一下我们依然比较熟悉的Fragment。
首先,我们要知道,在Android3.0以前,是没有Fragment这个的,但为了让大屏幕设备的UI更合理,更灵活,所以在Android3.0的时候,就出现了Fragment。而我们实际开发中更喜欢使用Fragment来替代之前的Activity的切换,主要是因为,Fragment进行切换更加的节省内存,同时,UI切换的时候,给用户的体验会更加的舒服。
那么, 接下来,我们一起来详细的回顾一下我们实际开发中所遇到的Fragment的相关知识。
技术详情
本次回顾Fragment,准备按如下逻辑进行一一讲述:
1. Fragment为什么被称为第“五”大组件?
2. Fragment的生命周期都有哪些?
3. Fragment是如何通信的?
4. Fragment管理器:FragmentManager是怎么管理Fragment的?
1.Fragment为什么被称为第五大组件?
1.Fragment被称为第五大组件的原因
众所周知,在我们的Android系统当中,有四大组件:Activity,Service,广播,ContentProvider。
在我们实际开发当中,Fragment使用频率,作用都是非常突出的,所以说,将Fragment列为第五大组件也是可以的。
不过也有不少人把View列为第五大组件,但是View与Fragment有一个比较明显的不同之处,就是View是没有生命周期的,而Fragment是有生命周期的,有了生命周期,Fragment就能与Activity一样进行更灵活的处理。
不过,我们需要注意的是,Fragment并不是像Activity一样,完全独立的,虽然拥有自己的生命周期,但是,Fragment必须依附于Activity,同时还要加载到Activity当中去,接下来讲讲Fragment加载到Activity中的方式有哪些。
2.Fragment加载到Activity的两种方式
首先,第一种加载方式,静态加载
把Fragment直接在作为一个xml标签加载到Activity的布局文件中去。下面讲一下实际开发中,我们是怎么使用静态加载的。
首先需要有一个Activity承载Fragment的布局,这个Activity的布局如下:
<?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" >
<fragment
android:id="@+id/fragment_title"
android:name="senduo.com.studydemo.fragment.TitleFragment"
android:layout_width="fill_parent"
android:layout_height="50dp" />
<fragment
android:layout_below="@id/fragment_title"
android:id="@+id/fragment_content"
android:name="senduo.com.studydemo.fragment.ContentFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</RelativeLayout>
这个Activity的java文件ActivityForFragment.java如下(简单例子,不添加任何逻辑业务):
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class ActivityForFragment extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_for_fragment);
}
}
有了承载的Activity之后,就可以手动创建一个Fragment继承自Fragment,我这里用一个TitleFragment和ContentFragment来举例说明,
TitleFragment.java文件为:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import senduo.com.studydemo.R;
public class TitleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_title, container, false);
}
}
对应的布局文件为fragment_title.xml:
<FrameLayout 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:background="@android:color/black"
tools:context="senduo.com.studydemo.fragment.TitleFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@android:color/white"
android:text="我是标题Fragment" />
</FrameLayout>
ContentFragment.java文件为:
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import senduo.com.studydemo.R;
public class ContentFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_content, container, false);
}
}
对应的布局文件为fragment_content.xml:
<FrameLayout 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"
tools:context="senduo.com.studydemo.fragment.ContentFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="我是界面内容Fragment" />
</FrameLayout>
然后看一下运行之后的效果如下:
第二种加载方式,动态加载
我们实际开发中,用的比较多还是使用动态加载,通过FragmentManager和FragmentTransaction来进行动态创建Fragment。
下面详细介绍一下Fragment的动态创建过程
第一步:创建FragmentManager的对象,然后通过这个对象创建一个FragmentTransaction实例对象
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
第二步:调用FragmentTransaction中的add()方法进行添加Fragment的对象
transaction.add(R.id.fragment_content,TitleFragment);
这里需要注意的是,第一个参数传入的不是layout而是Activity中的一个控件的ID,表示Fragment在Activity具体显示在哪里,同时也是在这个FragmentManager队列中的Fragment唯一标识符,在Fragment通信过程中需要用到
第三步:调用FragmentTransaction中的commit()方法,让以上操作进行生效
transaction.commit();
3.FragmentPagerAdapter 和 FragmentStatePagerAdapter的区别
我们实际开发中,使用Fragment,一般都是通过viewpager(左右滑动)控件与Fragment结合进行使用,Fragment用来控制滑动时显示的具体界面,他们之间的结合使用,就会有FragmentPagerAdapter与FragmentStatePagerAdapter的区别。
我们可以有一个简单的概念:当Fragment页面较多的时候,使用FragmentStatePagerAdapter,较少的时候使用FragmentPagerAdapter
而其中的原因,我们主要查看一下FragmentStatePagerAdapter的destroyItem()方法源码:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
可以看到,FragmentStatePagerAdapter的destoryItem() 的方法中的最后一行, mCurTransaction.remove(fragment),是将这个Fragment实例直接从队列中移除,也就是释放了内存
而FragmentPagerAdapter的destoryItem()方法源码:
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
+ " v=" + ((Fragment)object).getView());
mCurTransaction.detach((Fragment)object);
}
最后一行是mCurTransaction.detach((Fragment)object),调用的是detach方法,这个方法只是将Activity的UI与Fragment的UI进行分离,并没有进行回收内存,因此从内存占用角度来说,当页面较少的时候,比较适合使用FragmentPagerAdapter。
2.Fragment的生命周期都有哪些?
首先,我们来看一下Fragment独立的一个生命周期:
总的来看,Fragment的生命周期与Activity的生命周期比较相似。之前我们也谈到过,Fragment本身不能独立存在的,必须依附在某一个Activity上,那么,关于Fragment的实际开发中需要了解的生命周期,我们需要结合一下Activity的生命周期来进行认识:
直接看图倒是可以知道整个流程,但是,方法确实太多了,他们之间的区别和联系其实还是没有办法能够搞清楚,一个一个进行讲解恐怕也会比较枯燥,所以,如果真正想弄懂整个过程,还是需要我们平时的日积月累以及深入探究。这里我就大概描述一下这个过程,希望能够帮助大家更好的进行理解:
- 首先,我们能够看到,当Fragment进行第一次创建的时候,会调用onAttach()方法,表明Fragment与Activity进行关联后进行的回调
- onAttach()方法后,会调用Fragment中的onCreate()方法,初次创建Fragment的时候进行回调,区别于Activity的onCreate()方法,这个只是创建Fragment,但Activity并没有创建完成
- 之后调用的Fragment的onCreateView()方法,表示系统首次绘制Fragment的UI,值得注意的是,从这个方法返回的View必须是Fragment布局的根视图。
- 之后调用的是onViewCreated()方法,表示Fragment的UI界面已经完全绘制好了,这个时候可以进行初始化Fragment的控件资源
- 接下来,就是调用Activity的onCreate()方法,表示Activity创建完成的回调
- 之后,调用的是Fragment的onActivityCreated()方法,表示Activity被渲染,绘制成功以后进行的回调,值得注意的是,这个方法必须在Activity的onCreate()方法之后进行调用
- 接着调用Activity的onStart()方法,表示Activity已经可见了
- 之后调用Fragment的onStart()方法,表示Fragment也已经可见了
- 接着调用Activity的onResume()方法,表示Activity已经可以与用户进行交互了
- 之后调用Fragment的onResume()方法,表示Fragment也可以与用户进行交互了,可以执行处理点击,滑动之类的行为了,已经完成了Fragment从启动到展现的整个操作。
- 接下来就是回退之后的生命周期了,先走Fragment的onPause()方法,表示已经不能与用户进行交互了
- 接着调用Activity的onPause的方法,表示Activity也不能与用户进行交互了
- 接着调用Fragment的onStop()方法,表示Fragment不可见了
- 接着调用Activity的onStop()方法,表示Activity不可见了
- 然后调用onDestoryView()方法,这个方法我们不是很熟悉,但是我们需要知道的是,与这个方法对应的是onCreateView()方法,表示这个Fragment即将结束,然后会被保存。
- 接着会回调Fragment的onDestory()方法,与之前对应的onCreate()方法,表示Fragment不会被使用。
- 接着调用Fragment生命周期的最后一个方法,onDetach()方法,表示整个Fragment已经被销毁
- 最后调用Activity的onDestory()方法,表示整个Activity被回收了。
以上就是一个Fragment从启动到销毁的整个过程,接下来我们来回顾一下Fragment的数据通信。
3. Fragment是如何通信的?
关于Fragment的通信,我们需要有以下三个大的概念:
1. 在Fragment中调用Activity中的方法 ----> 使用getActivity()
2. Activity中调用Fragment中的方法 ----> 常用接口回调,在Fragment定义一个接口,在Activity中进行实现该接口
3. Fragment中调用Fragment中的方法 ----> 首先使用getActivity()获取Activity的方法,然后通过findFragmentById获取到另一个Fragment中的方法
这三种方式,我们需要进行理解他们的使用方式,这样,我们遇到需要实现某一个功能的时候,可以借鉴这些方式进行实现,提供最适合的功能实现方案。
4.Fragment管理器:FragmentManager是怎么管理Fragment的
在我们实际开发过程当中,都需要将某一个Fragment进行显示,隐藏,替换,移除等相关的操作,我们都是通过FragmentManager的操作类FragmentTransaction对象进行操作的。
主要有以下这些方法:
1. add:将新创建的一个Fragment实例加入到FragmentManager的队列中去,切换的时候,Fragment的状态信息或者成员变量都没有发生改变
2. replace:将之前显示的Fragment直接remove销毁掉,然后再把当前的Fragment add进来,也就是需要移除之前的Fragment再重新创建一个新的Fragment实例
3. remove:移除,销毁FragmentManager队列中的Fragment
4. hide:将某一个Fragment实例进行隐藏,但状态信息以及成员变量都没有进行回收
5. show:将某一个Fragment实例进行显示
6. detach:并不是将某一个Fragment实例进行销毁,而是将Fragment的View进行销毁,下次再加载的时候,需要重新绘制View
7. attach: 与detach方法相对应,将之前销毁View的Fragment的实例进行重新绘制View,在实际开发中,这一对我们用的相对比较少,因为论速度,并没有hide/show方式那么快,论占用内存的多少,也没有比add/remove优化多少
干货总结
通过上面四个方面的回顾,我们对于Fragment对认识应该有了一个整体的概念。
不过,千说万说,不如自己动手去实践,最好的方式还是自己亲自将Fragment相关的一些实现用代码实现出来,遇到了坑,就会对某一个问题有了深刻理解。
虽然Google也推荐我们尽量使用少Activity + 多Fragment的方式进行项目开发,但是,Fragment依然有些坑,我们在用的时候还是需要注意的。
下面大概说几个我们在实际开发中使用Fragment的时候需要注意的地方:
1. 在同一个Activity中,只能存在一个id(Fragment的唯一标识)标识的Fragment实例。因为有可能在布局的时候会出现不允许创建的错
误。
2. FragmentManager的作用范围是整个Activity,因此某一个布局中的Fragment ID,不能重复进行替换。因为FragmentManager只认布局
中的ID,如果有多个实例都是用的这一个布局ID,也只会显示一个。
3. Fragment的可见性并没有像Activity那么方便操作,当A Fragment被B Fragment覆盖的时候,A此时是会回调Fragment的onStop()方
法,但当B Fragmeng从栈中回退后,A Fragment无法感知自己正处于栈顶,需要我们开发人员自行监听一些状态来判断A Fragment是否对于
用户可见
4. Fragment的事件传递也需要注意,处于顶部的Fragment需要注意判断是否该消费此点击事件,否则如果存在多个Fragment层叠现象,可能
下层的Fragment会消费此事件。
...
所以,我们在实际开发当中,还是需要了解当前功能使用Fragment是否OK,如果有问题,是否有相应的解决方案。
好了,关于Fragment的一些知识点就先回顾到这里。
如果喜欢本篇文章的内容或者讲述形式,希望给一个喜欢和赞,如果有什么不对的地方,或者需要改进的地方,也希望能够留言说一下,谢谢了。