Fragment入门详解和简单使用

3,875 阅读10分钟

Fragment的介绍

Fragment出现的初衷

Fragmen是在Android3.0(api11)的时候引入的,一开始的目的是为了大屏幕(如平板电脑)。
当时的Android手机普遍较少,在Android使用上面看起来的正常的界面,在平板电脑看起来可能会有点奇怪
比如,会留出比较大的空白的空间,所以,为了解决这种问题,谷歌在Android 3.0的时候推出了Fragment。

我们来看一个例子,新闻应用可以使用一个Fragment在左侧显示文章列表,
使用另一个Fragment在右侧显示文章 — 两个Fragment并排显示在一个 Activity 中,
每个Fragment都拥有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。
因此,用户无需使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,
而是可以在同一个 Activity 内选择文章并进行阅读。详情如下图所示

fragment.png

Fragment的介绍

Fragment翻译成中文“片段”,它具有以下几个特点:
1、Activity中可以有多个Fragment。
2、Fragment有自己的生命周期,能够接收自己的输入事件。
3、Fragmen必须依赖于Activity,不能脱离Activity单独存在,事实上也脱离不了。
4、一个Fragment可以被多个Activity重复使用。
5、可以在Activity中动态的添加和删除Fragment,这一点我们在实际的开发中很经常使用到。

大家感兴趣的话可以看一下谷歌官网对Fragment的介绍

Fragment创建

image.png

它创建方式有两种:
1、自己创建一个类,并且继承自Fragment或者其子类(需要自己创建一个布局)
2、使用Android Studio子的Fragment模板创建,会自动帮我们生成一些代码,谷歌提供了几种模板,大家可以根据需求去选择

Fragment的静态添加

在创建完Fragment之后,如果你是使用的第一种方式创建的话,那么这是一个空的类,我们需要去实现一些方法,
将布局绑定到Fragment中。

如果你是一个新的Fragment,那么你的类的结构应该如下所示

public class TestFragment extends Fragment {

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
}
然后我们需要在onCreateView()函数里面把对应的布局设置上去,我们新建一个fragment_test.xml文件,
fragment_test.xml文件如下:
需要注意的是,使用这种方式添加Fragment,一旦添加就不能在运行时删除.

<TextView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:text="这是测试" />
然后改造onCreateView()函数如下:
....

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    Log.d(TAG, "onCreateView: ");
    return LayoutInflater.from(getContext()).inflate(R.layout.fragment_test, container, false);
}
....
经过以上的处理,我们已经创建好了一个Fragment了,接着我们将这个Fragment添加到Activity里面,然后把它运行起来
修改activity_main.xml文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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=".MainActivity">

    <fragment
        android:id="@+id/test_fragment"
        android:name="com.example.fragment.TestFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>

这边静态添加fragment时需要注意两点:
1、name字段填写的是要加载的fragment,指定要在布局中进行实例化的 `Fragment` 类
2、id 属性提供唯一 ID。必须加上去,否则会报错

需要注意的是,使用这种方式添加Fragment,一旦添加就不能在运行时删除。 并且唯一标识符,重启 Activity 时,系统可使用该标识符来恢复Fragment识符来捕获片段,从而执行某些事务,如将其移除)。可以通过两种方式为Fragment提供ID

  • 为 android:id 属性提供唯一 ID。
  • 为 android:tag 属性提供唯一字符串。

如果不为Fragment提供ID,那么程序会报错。

经过以上的处理,我们就可以把app运行起来了。如果运行起来没有崩溃,并且屏幕上已经显示“这是测试”文本,那么恭喜成功的走到了这一步。

Fragment的生命周期

我们再来看一下Fragment有哪些生命周期,具体的我们可以看下这张图

fragment_lifecycle.png

上图详细的介绍了Fragment中有哪些生命周期
接着我们重写一下这个生命周期的方法,并且把相应的日志打上,观察一下Fragment的生命周期,改造后代码如下:
public class TestFragment extends Fragment {

    private static final String TAG = "TestFragment";

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
        Log.d(TAG, "onAttach: ");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate: ");
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.d(TAG, "onCreateView: ");
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Log.d(TAG, "onActivityCreated: ");
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.d(TAG, "onStart: ");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d(TAG, "onResume: ");
    }

    @Override
    public void onPause() {
        super.onPause();
        Log.d(TAG, "onPause: ");
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.d(TAG, "onDestroyView: ");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.d(TAG, "onDestroy: ");
    }

    @Override
    public void onDetach() {
        super.onDetach();
        Log.d(TAG, "onDetach: ");
    }
}
经过以上的处理,我们把App重新运行起来,观察一下生命周期。从app运行到app可见,Fragment的生命周期如下:

image.png

接着我们来看一下,从app前台按home键退出到app后台的生命周期,只走了一个onPause()函数

image.png

接着我们从app后台重新返回app前台,看一下具体的生命周期,可以看到一次走了onStart(),onResume()函数

image.png

接着我们再来看一下按返回键退出app的生命周期又是怎么样,可以看到最后是onDeatch()函数,
Fragment从activity分离的函数回调

image.png

以上基本介绍了常见的几种情况的生命周期的回调,接着我们来看一下,没个生命周期函数所对应的意思:
onAttach() 此Fragment已与Activity关联时调用(Activity传递到此方法内)

onCreate() 系统会在创建Fragment时调用此方法。当Fragment经历暂停或停止状态继而恢复后,如果您希望保留此Fragment的基本组件,则应在您的实现中将其初始化。

onCreateView() 系统会在Fragment首次绘制其界面时调用此方法。如要为您的Fragment绘制界面,您从此方法中返回的 View 必须是片段Fragment的根视图。如果片段未提供界面,您可以返回 null。

onActivityCreated()  当 Activity 的 onCreate() 方法已返回时进行调用。

onStart() 当Fragment 对用户可见时调用。这通常与Activity.onStart包含活动的生命周期有关

onResume() 当Fragment可见时调用

onPause() 系统会将此方法作为用户离开Fragment的第一个信号(但并不总是意味着此片段会被销毁)进行调用。 通常,您应在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。

onStop() 当 Fragment 不再启动时调用。这通常与Activity.onStop包含活动的生命周期有关。

onDestroyView() 在移除与Fragment关联的视图层次结构时进行调用。

onDestroy() 在移除与Fragment关联的视图层次结构时进行调用。

onDetach() 在取消Fragment与 Activity 的关联时进行调用。

经过以上生命周期对应的每个函数的详细讲解,再结合上述各个场景生命周期的回调,相信大家已经很好的掌握了Fragment的生命周期了

Fragment的动态添加

动态添加Fragment大致分为一下几步
1、添加一个Fragment的容器,通常为FranmeLayout(只要是ViewGroup即可)
2、拿到FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
3、通过FragmentManager拿到FragmentTransaction
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
4、实例化需要添加的Fragment
TestFragment testFragment = new TestFragment();
5、第一个参数是 ViewGroup,即应放置片段的位置,由资源 ID 指定,第二个参数是要添加的片段。
fragmentTransaction.add(R.id.fl_container, testFragment);
6、提交,一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。
fragmentTransaction.commit();

MainActivity完整代码如下

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //1、拿到FragmentManager
        FragmentManager fragmentManager = getSupportFragmentManager();
        //2、通过FragmentManager拿到FragmentTransaction
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        //3、实例化需要添加的Fragment
        TestFragment testFragment = new TestFragment();
        //4、第一个参数是 ViewGroup,即应放置片段的位置,由资源 ID 指定,第二个参数是要添加的片段。
        fragmentTransaction.add(R.id.fl_container, testFragment);
        //5、提交,一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。
        fragmentTransaction.commit();
    }

}

到这,你已经成功的学会了Fragment动态加载的方式了,接着我们来看下一些需要了解和注意的点。

在 Activity 中使用片段的一大优点是,您可以通过片段执行添加、移除、替换以及其他操作,从而响应用户交互。提交给 Activity 的每组更改均称为事务,并且您可使用 FragmentTransaction 中的 API 来执行一项事务。

在上面的代码中我们有拿到事务,接下来我们可以拿事务来做一些其它的操作

FragmentManager fragmentManager = getSupportFragmentManager();
//这句话的意思就是拿到Fragment的事务
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

这时候我们可以拿fragmentTransaction,您可以使用 add()remove() 和 replace() 等方法,为给定事务设置您想要执行的所有更改。然后,如要将事务应用到 Activity,您必须调用 commit()。也就是说我们在重调用add()、remove()、replace()函数时,我们必须重新调用commity()才能生效。

接着我们来看一下fragmentTransaction这个类的常用的几个函数的意义
add() 将Fragment添加到Activity 如果多次调用add()没有调用remove()或者hide()函数的话则多个fragment会覆盖在一起。
hide() 隐藏添加的Fragment。
remove() 删除添加的Fragment。
replace() 往某个容器替换Fragment。
show() 显示以前隐藏的Fragment。
commit() 将事务提交。
attach() 会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。
detach() 重建view视图,附加到UI上并显示。
addToBackStack() 将事务添加到后台堆栈。这就意味着事务在提交之后将在堆栈里面,并切返回的时候将会从堆栈退出

更加详细的用法我们可以看一下官方文档 里面的讲解非常详细。 大家在实际的开发中可以根据自己需求选择适合自己的函数去使用。

Fragment的回退栈

Fragment可以通过添加返回栈可以达到Activity跳转并且可以返回上一个页面的效果,我们可以掉用下面这个函数为您的Fragment添加回退栈

fragmentTransaction.addToBackStack(null)

修改ainActivity代码如下

//1、拿到FragmentManager
FragmentManager fragmentManager = getSupportFragmentManager();
//2、通过FragmentManager拿到FragmentTransaction
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
//3、实例化需要添加的Fragment
TestFragment testFragment = new TestFragment();
//4、第一个参数是 ViewGroup,即应放置片段的位置,由资源 ID 指定,第二个参数是要添加的片段。
fragmentTransaction.add(R.id.fl_container, testFragment);
//5、为Fragment添加回退栈
fragmentTransaction.addToBackStack(null);
//6、提交,一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。
fragmentTransaction.commit();

修改activity_main.xml代码如下

<?xml version="1.0" encoding="utf-8"?>
<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=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:text="你好"
        android:layout_height="match_parent" />

    <FrameLayout
        android:id="@+id/fl_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

修改fragment_test.xml代码如下

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#fff000"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:text="这是测试" />

</LinearLayout>

我们重新运行一下代码,可以看到如下图效果

image.png

然后我们再按一下返回键,可以看到如下图效果

image.png

很明显,现在的Fragment的回退栈的功能已经实现了,是不是很简单。

Fragment传递和接收数据

向Fragment传递数据一般是使用Bundle传递数据,再调用Fragment的setArguments()函数 具体代码如下,往TestFragment添加如下代码

public static void instance() {
 TestFragment testFragment = new TestFragment();
 Bundle bundle = new Bundle();
 bundle.putString("test", "test");
 testFragment.setArguments(bundle);
}

然后我们重写onViewCreate()函数去接收这个数据,具体代码如下:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    Bundle arguments = getArguments();
    if (arguments != null) {
        String result = arguments.getString("test");
        Log.d(TAG, "onViewCreated: " + result);
    }
}

getArguments()就是拿到我们刚才传递过来的Bundle,然后我们再通过bundle拿到刚才传递过来的数据即可。

注意:这边接收的数据类型和Key必须与传递过来的Key和数据类型完全一致才能成功的接收到数据