Android基础知识回顾之Fragment

开篇废话

公司搬迁告一段落,吐槽一句,从此就需要过上车程一个半小时的上下班之旅了。。。。。。

上一篇我们了解了四大组件之一的Activity,应该对于Activity有了一定的了解,这一篇文章,我们一起来回顾一下我们依然比较熟悉的Fragment。

首先,我们要知道,在Android3.0以前,是没有Fragment这个的,但为了让大屏幕设备的UI更合理,更灵活,所以在Android3.0的时候,就出现了Fragment。而我们实际开发中更喜欢使用Fragment来替代之前的Activity的切换,主要是因为,Fragment进行切换更加的节省内存,同时,UI切换的时候,给用户的体验会更加的舒服。

reason_for_fragment

那么, 接下来,我们一起来详细的回顾一下我们实际开发中所遇到的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独立的生命周期

总的来看,Fragment的生命周期与Activity的生命周期比较相似。之前我们也谈到过,Fragment本身不能独立存在的,必须依附在某一个Activity上,那么,关于Fragment的实际开发中需要了解的生命周期,我们需要结合一下Activity的生命周期来进行认识:

与Activity结合的Fragment生命周期

直接看图倒是可以知道整个流程,但是,方法确实太多了,他们之间的区别和联系其实还是没有办法能够搞清楚,一个一个进行讲解恐怕也会比较枯燥,所以,如果真正想弄懂整个过程,还是需要我们平时的日积月累以及深入探究。这里我就大概描述一下这个过程,希望能够帮助大家更好的进行理解:

  1. 首先,我们能够看到,当Fragment进行第一次创建的时候,会调用onAttach()方法,表明Fragment与Activity进行关联后进行的回调
  2. onAttach()方法后,会调用Fragment中的onCreate()方法,初次创建Fragment的时候进行回调,区别于Activity的onCreate()方法,这个只是创建Fragment,但Activity并没有创建完成
  3. 之后调用的Fragment的onCreateView()方法,表示系统首次绘制Fragment的UI,值得注意的是,从这个方法返回的View必须是Fragment布局的根视图。
  4. 之后调用的是onViewCreated()方法,表示Fragment的UI界面已经完全绘制好了,这个时候可以进行初始化Fragment的控件资源
  5. 接下来,就是调用Activity的onCreate()方法,表示Activity创建完成的回调
  6. 之后,调用的是Fragment的onActivityCreated()方法,表示Activity被渲染,绘制成功以后进行的回调,值得注意的是,这个方法必须在Activity的onCreate()方法之后进行调用
  7. 接着调用Activity的onStart()方法,表示Activity已经可见了
  8. 之后调用Fragment的onStart()方法,表示Fragment也已经可见了
  9. 接着调用Activity的onResume()方法,表示Activity已经可以与用户进行交互了
  10. 之后调用Fragment的onResume()方法,表示Fragment也可以与用户进行交互了,可以执行处理点击,滑动之类的行为了,已经完成了Fragment从启动到展现的整个操作。
  11. 接下来就是回退之后的生命周期了,先走Fragment的onPause()方法,表示已经不能与用户进行交互了
  12. 接着调用Activity的onPause的方法,表示Activity也不能与用户进行交互了
  13. 接着调用Fragment的onStop()方法,表示Fragment不可见了
  14. 接着调用Activity的onStop()方法,表示Activity不可见了
  15. 然后调用onDestoryView()方法,这个方法我们不是很熟悉,但是我们需要知道的是,与这个方法对应的是onCreateView()方法,表示这个Fragment即将结束,然后会被保存。
  16. 接着会回调Fragment的onDestory()方法,与之前对应的onCreate()方法,表示Fragment不会被使用。
  17. 接着调用Fragment生命周期的最后一个方法,onDetach()方法,表示整个Fragment已经被销毁
  18. 最后调用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的一些知识点就先回顾到这里。

如果喜欢本篇文章的内容或者讲述形式,希望给一个喜欢和赞,如果有什么不对的地方,或者需要改进的地方,也希望能够留言说一下,谢谢了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容