【Android Fragment】Fragment基础

  • Post author:
  • Post category:其他


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自身的

在这里插入图片描述



Tnd

参考:


安卓官方文档



版权声明:本文为qq_38350635原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。