fragment “分段”、“碎片”的意思,一般与Activity一起使用,嵌套在activity中表示为Activity界面的一部分。

它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或移除片段,有点像您可以在不同 Activity 中重复使用的“子 Activity”。

当您将片段作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 ViewGroup 内部,并且片段会定义其自己的视图布局。

知识点

在这里插入图片描述

一、简介

1、Fragment的产生

安卓3.0(api 11)引入了fragment,主要就是为大屏幕(平板)添加更加灵活、动态的支持。

由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 UI 组件的空间更大。利用片段实现此类设计时,您无需管理对视图层次结构的复杂更改。 通过将 Activity 布局分成片段,您可以在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。

2、好处
  • 有自己的生命周期
  • 依附于activity使activity更加灵活
3、生命周期

由于fragment依赖于activity所以其生命周期受activity生命周期影响。fragment的生命周期图如下:

(1)fragment的生命周期图( Activity 运行时)

在这里插入图片描述
(2)Activity 生命周期对片段生命周期的影响图

在这里插入图片描述

在这里插入图片描述

二、使用

要想创建fragment,您必须创建 Fragment 的子类(或已有其子类)。Fragment 类的代码与 Activity 非常相似。它包含与 Activity 类似的回调方法,如 onCreate()、onCreateView()、onStart()、onPause() 和 onStop()。

1、使用步骤

(1)创建类继承Fragment

(2)实现onCreateView方法。

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

(3)使用

  • 静态使用:activity的xml布局中引入即可
  • 动态调用:通过FragmentManager、FragmentTransaction来进行动态添加、替换、移除、fragment从相应的容器中。
2、静态使用

(1)创建子类 实现核心方法

/**
 * LayoutInflater 详情参考:https://blog.csdn.net/u012702547/article/details/52628453
 * */
public class BlankFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        /**
         * 参数:
         * 1、R.layout.fragment_blank 自定义布局的资源id
         *
         * 2、container 将作为自定义布局的父ViewGroup。是否将自定义布局添加到 container(由第三个参数的boolean值决定)
         *
         * 3、boolean值
         *
         * */
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }

}

(2)MainActivity的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"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:orientation="vertical">
    <TextView
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Hello Fragment"/>

    <fragment
            android:id="@+id/ft"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="com.sunnyday.fragmentlearn.BlankFragment"/>

</LinearLayout>

注意:这里fragment必须要有id或者tag的否则崩溃(吧上代码中id去掉时)

Caused by: java.lang.IllegalArgumentException: Binary XML file line #16: Must specify unique android:id, android:tag, or have a parent with an id for com.sunnyday.fragmentlearn.BlankFragment

(3)拓展:为啥需要id这样的标识

每个fragment都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复fragment(也可以使用该标识符来获取 fragment 以执行某些事务,比如将其移出),可以通过三种方式为 fragment 提供 ID:

  • 通过 android:id 属性提供唯一 ID
  • 通过 android:tag 属性提供唯一字符串
  • 如果都没设置,系统会使用容器视图的 ID
3、动态使用

(1)如上修改MainActivity的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        android:orientation="vertical">
    <TextView
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Hello Fragment"/>

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

</LinearLayout>

(2)代码动态添加

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //获得FragmentManager对象
        FragmentManager fragmentManager = getSupportFragmentManager();
        // 通过FragmentManager对象获得FragmentTransaction对象(开启事务)
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentTransaction.add(R.id.fl_layout, new BlankFragment());//动态添加
        fragmentTransaction.commit();//提交  提交  提交(很重要)
    }
}

上例展示了如何向 Activity 添加fragment以提供 UI。不过,您还可以使用fragment为 Activity 提供后台行为,而不展示 UI:

  • 要想添加没有 UI 的fragment,请使用 add(Fragment, String) 从 Activity 添加fragment(为fragment提供一个唯一的字符串“标记”,而不是视图 ID)。 这会添加fragment,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 onCreateView() 的调用。因此,您不需要实现该方法。
  • 并非只能为非 UI fragment提供字符串标记 , 您也可以为具有 UI 的fragment提供字符串标记 ,但如果fragment没有 UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取片段,则需要使用 findFragmentByTag()。
4、派生子类

系统也提供了fragment 派生的一些子类

三、相关类

1、FragmentManager

对fragment进行相关操作,提供了一些方法:
1、调用findFragmentById 或 findFragmentByTag 获取Activity Fragment
2、调用 popBackStack 将 Fragment 从返回栈中弹出
3、调用addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器。
4、调用beginTransaction 获取 FragmentTransaction

2、FragmentTransaction

在 Activity 中使用 Fragment 一大优点是:可以根据用户行为通过它们执行添加、移出、替换fragment以及其操作。

(1)FragmentTransaction常用方法

  • add()
  • remove()
  • replace()

(2)事务注意点

  • 进行事务操作之后要进行commit 否则操作不会生效。
  • 如果您向事务添加了多个更改(如又一个 add() 或 remove()),并且调用了 addToBackStack(),则在调用 commit() 前应用的所有更改都将作为单一事务添加到返回栈,并且返回按钮会将它们一并撤消。
  • 对于每个片段事务,您都可以通过在提交前调用 setTransition() 来应用过渡动画。
  • 调用 commit() 不会立即执行事务,而是在 Activity 的 UI 线程(“主”线程)可以执行该操作时再安排其在线程上运行。不过,如有必要,您也可以从 UI 线程调用 executePendingTransactions(FragmentManager的方法)或者 commitNow(FragmentTransaction的方法) 以立即执行 commit() 提交的事务。通常不必这样做,除非其他线程中的作业依赖该事务。
  • 只能在 Activity 保存其状态之前使用 commit 提交事务,如果试图在该时间之后提交,则会引发异常。这是因为如需恢复 Activity,则提交后的状态可能会丢失。对于丢失提交无关紧要的情况,可以使用 commitAllowingStateLoss

四、Fragment与activity进行通信

尽管 Fragment 是作为独立于 Activity 的对象实现,并且可在多个 Activity 内使用,但fragment实例会直接绑定到包含它的 Activity

1、activity获取fragment实例

activity可以先获得fragmentManager的实例,然后通过fragmentManager实例的对象调用findFragmentById() 或 findFragmentByTag()来获得Fragment的实例。

2、fragment获取所在activity的实例

fragment可以通过 getActivity() 获得其所依附的 Activity 实例

3、fragmentA与fragmentB通信

由于二者依附同一activity时,我们可以这样做

1、首先在fragmentA中获取activity的实例,
2、然后通过activity的实例获得fragmentManager实例
3、通过fragmentManager实例的对象调用findFragmentById() 或 findFragmentByTag()来获得FragmentB的实例。

4、创建对Activity的事件回调

使用接口回调也可让fragment之间进行通信。具体可看下官方文档的实例。

五、常见问题解决方案

1、创建 Fragment 实例传递数据时使用无参数构造

之所以不用带参的构造方法,原因在于 Activity 在一些特殊情况下会发生销毁并重建的情形,比如屏幕旋转、内存吃紧等;对应的,依附于 Activity 存在的 Fragment 也会发生类似的状况。而一旦重建Fragment 便会调用默认的无参构造函数,导致无法执行有参构造函数进行初始化工作。

public static OneFragment newInstance(int args){
    OneFragment oneFragment = new OneFragment();
    Bundle bundle = new Bundle();
    bundle.putInt("someArgs", args);
    oneFragment.setArguments(bundle);
    return oneFragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Bundle bundle = getArguments();
    int args = bundle.getInt("someArgs");
}
2、getActivity() 引用问题

当 Fragment 中存在类似网络请求之类的异步耗时任务时,该任务执行完毕回调 Fragment 的方法用到 Activity 对象时,可能宿主 Activity 已经销毁,从而引发空指针异常,所以最好都判空。一般情况下,获取 Context,可以通过 getContext() 获取。

3、Fragment 重叠问题

异常情况下:当 Activity 销毁并重建的时候,Activity 重新执行 onCreate 方法,那么就创建两次 Fragment 而导致 UI 重叠。解决如下。

protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fragment);
        mFrameLayout = findViewById(R.id.fl_content);

        if (savedInstanceState != null) {
            mFirstFragment = (FirstFragment) mFragmentManager.findFragmentByTag("fragment1");
        } else {
            mFirstFragment = FirstFragment.newInstance();
            mFragmentTransaction.add(mFirstFragment, "fragment1");
        }
    }
4、fragment 嵌套fragment问题

在这里插入图片描述
fragmentA中要使用getChildFragmentManager来获得FragmentManager对象,否则你虽然动态的添加了fragmentB当是FragmentA会被覆盖、或者移除。

                FragmentManager fragmentManager = getChildFragmentManager();
                FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
                fragmentTransaction.replace(R.id.container,new TestFragment());
                fragmentTransaction.commit();
5、常见问题(点击按钮切换不同的fragment)

fragment 是基于事务的,每次事件就是一个一次性任务。所以每次操作时重新获得一个FragmentTransaction 再操作即可。

 buttonBottom.setTabSelectedListener(new BottomNavigationBar.OnTabSelectedListener() {
            @Override
            public void onTabSelected(int position) {
                FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
                if (position == 0) {
                    fragmentTransaction.replace(R.id.container, new TestFragment());
                } else if (position == 1) {
                    fragmentTransaction.replace(R.id.container, new DiscFragment());
                } else {
                    fragmentTransaction.replace(R.id.container, new GameFragment());
                }
                fragmentTransaction.commit();
            }

            @Override
            public void onTabUnselected(int position) {
            }

            @Override
            public void onTabReselected(int position) {
            }
        });
        
    }
6、fragment中重写startActivityForResult不起作用

这里需要注意startActivityForResult不要使用activity的,要使用fragment自身的
在这里插入图片描述

7、禁用back键
    private fun handleOnSystemBackButtonPressed() {
        (requireActivity() as MainActivity).disableBackPressed(
            this,
            object : OnBackPressedCallback(true) {
                override fun handleOnBackPressed() {
                 //  1、啥也不做,back失效
                 //  2、这里也可主动加其他逻辑然后finish()
                }
            })
    }

Tnd

参考:

安卓官方文档

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐