学习程序员首页投稿(暂停使用,暂停投稿)

Android基础知识回顾之Fragment

2017-08-26  本文已影响219人  进击的欧阳

开篇废话

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

上一篇我们了解了四大组件之一的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的一些知识点就先回顾到这里。

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

上一篇下一篇

猜你喜欢

热点阅读