天天看点

Fragment使用详解

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

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

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

知识点

Fragment使用详解

一、简介

1、Fragment的产生

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

灵活动态的体现:

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

2、好处

1、有自己的生命周期

2、依附于activity使activity更加灵活

2.1 可以作为activity界面一部分

2.2 可以单独处理一部分逻辑

2.3 activity可以在运行时动态添加、替换界面。

3、生命周期

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

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

Fragment使用详解

(2)Activity 生命周期对片段生命周期的影响图

Fragment使用详解

(3)各个方法的简单介绍

1、onAttach(Activity): 一旦fragment与activity相关联就被调用。

2、onCreate(Bundle) : fragment初始创建

3、onCreateView(LayoutInflater, ViewGroup, Bundle) ::创建并返回与fragment关联的视图层次结构

4、onActivityCreated(Bundle) :告诉fragment,activity的活动已经完成了自己的创建(Activity#onCreate)。

5、onStart() :fragment对用户可见。

6、onResume() fragment开始与用户交互

7、onPause() fragment不再与用户交互,因为其activity即将暂停或片fragment操作正在activity中修改它。

8、onStop() fragment不再对用户可见,因为它的activity即将停止,或者fragment操作正在activity中修改它。

9、onDestroyView() 允许fragment清理与其View相关联的资源。

10、onDestroy() 调用去做最后清理fragment的状态。

11、onDetach() 在fragment之前立即调用,不再与其活动相关联。

ps:上面这些来自官方文档+个人理解翻译可能不太精确,参考下图。

Fragment使用详解

二、使用

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

1、使用步骤

(1)创建类继承Fragment

(2)实现onCreateView方法。

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

ps:回调方法很多,onCreateView方法比较重要。直接牵涉到view的显示。

(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:

1、要想添加没有 UI 的fragment,请使用 add(Fragment, String) 从 Activity 添加fragment(为fragment提供一个唯一的字符串“标记”,而不是视图 ID)。 这会添加fragment,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 onCreateView() 的调用。因此,您不需要实现该方法。

2、并非只能为非 UI fragment提供字符串标记 , 您也可以为具有 UI 的fragment提供字符串标记 ,但如果fragment没有 UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取片段,则需要使用 findFragmentByTag()。

4、派生子类

系统也提供了fragment 派生的一些子类
  • DialogFragment
  • ListFragment
  • PreferenceFragment

参考

三、相关类

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)调用常见方法后需要注意

1、进行上述操作之后要进行commit 否则操作不会生效。

2、如果您向事务添加了多个更改(如又一个 add() 或 remove()),并且调用了 addToBackStack(),则在调用 commit() 前应用的所有更改都将作为单一事务添加到返回栈,并且返回按钮会将它们一并撤消。

3、对于每个片段事务,您都可以通过在提交前调用 setTransition() 来应用过渡动画。

4、调用 commit() 不会立即执行事务,而是在 Activity 的 UI 线程(“主”线程)可以执行该操作时再安排其在线程上运行。不过,如有必要,您也可以从 UI 线程调用 executePendingTransactions(FragmentManager的方法)或者 commitNow(FragmentTransaction的方法) 以立即执行 commit() 提交的事务。通常不必这样做,除非其他线程中的作业依赖该事务。

5、只能在 Activity 保存其状态之前使用 commit 提交事务,如果试图在该时间之后提交,则会引发异常。这是因为如需恢复 Activity,则提交后的状态可能会丢失。对于丢失提交无关紧要的情况,可以使用 commitAllowingStateLoss

四、与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与 Activity 共享事件。执行此操作的一个好方法是,在fragment内定义一个回调接口,并要求宿主 Activity 实现它。 当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他fragment共享这些信息。

(1)栗子:

如果一个新闻应用的 Activity 有两个片段 一个用于显示文章列表(片段 A),另一个用于显示文章(片段 B) 那么片段 A 必须在列表项被选定后告知 Activity,以便它告知片段 B 显示该文章。
Fragment使用详解

(2)简单实现

传送门

五、常见问题解决方案

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问题

Fragment使用详解
fragmentA中要使用getChildFragmentManager来获得FragmentManager对象,否则你虽然动态的添加了fragmentB当是FragmentA会被覆盖、或者移除。

(1)如下

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、还有更多需要注意的

不一 一列举、碰见了我们需要留意下。

end

参考:

1、安卓官方文档

2、Android Fragment 真正的完全解析

3、https://github.com/Omooo/Android-Notes/blob/master/blogs/Android/Fragment.md

4、三个案例带你看懂LayoutInflater中inflate方法两个参数和三个参数的区别

5、理解和正确使用Java中的断言(assert)