Fragment是什么
Fragment是一种可以嵌入在Activity当中的UI片段,它能让程序更加合理和充分地利用大屏幕的空间,因而在平板上应用得非常广泛。
Fragment的使用方式
介绍了这么多抽象的东西,是时候学习一下Fragment的具体用法了。首先我们要创建一个平板模拟器,创建模拟器的方法在第1章中已经学过了,这里就不再赘述。这次我们选择创建一个Pixel C平板模拟器,创建完成后启动模拟器
好了,准备工作都完成了,接着新建一个FragmentTest项目,然后开始我们的Fragment探索之旅吧。
Fragment的简单用法
这里我们准备先写一个最简单的Fragment示例来练练手。在一个Activity当中添加两个Fragment,并让这两个Fragment平分Activity的空间。
新建一个左侧Fragment的布局left_fragment.xml,代码如下所示:
Java
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="Button" />
</LinearLayout>
这个布局非常简单,只放置了一个按钮,并让它水平居中显示。
然后新建右侧Fragment的布局right_fragment.xml,代码如下所示:
Java
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#00ff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="24sp"
android:text="This is right fragment" />
</LinearLayout>
可以看到,我们将这个布局的背景色设置成了绿色,并放置了一个TextView用于显示一段文本。接着新建一个LeftFragment类,并让它继承自Fragment。
注意,这里可能会有两个不同包下的Fragment供你选择:一个是系统内置的android.app.Fragment,一个是AndroidX库中的androidx.fragment.app.Fragment。
这里请一定要使用AndroidX库中的Fragment,因为它可以让Fragment的特性在所有Android系统版本中保持一致,而系统内置的Fragment在Android 9.0版本中已被废弃。使用AndroidX库中的Fragment并不需要在build.gradle文件中添加额外的依赖,只要你在创建新项目时勾选了Use androidx.* artifacts选项,AndroidStudio会自动帮你导入必要的AndroidX库。
现在编写一下LeftFragment中的代码,如下所示:
Java
public class LeftFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.left_fragment, container, false);
}
}
这里仅仅是重写了Fragment的onCreateView()方法,然后在这个方法中通过LayoutInflater的inflate()方法将刚才定义的left_fragment布局动态加载进来,整个方法简单明了。接着我们用同样的方法再新建一个RightFragment,代码如下所示:
Java
public class RightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.right_fragment, container, false);
}
}
修改activity_main.xml中的代码,如下所示:
Java
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<fragment
android:id="@+id/rightFrag"
android:name="com.example.fragmenttest.RightFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
可以看到,我们使用了标签在布局中添加Fragment,其中指定的大多数属性你已经非常熟悉了,只不过这里还需要通过android:name属性来显式声明要添加的Fragment类名,注意一定要将类的包名也加上。这样最简单的Fragment示例就已经写好了,现在运行一下程序。
正如我们预期的一样,两个Fragment平分了整个Activity的布局。不过这个例子实在是太简单了,在真正的项目中很难有什么实际的作用,因此下面我们马上来看一看,关于Fragment更加高级的使用技巧。
静态2
再fragment类里面重写onviewcreated方法
其中调用view。findbyid方法
动态添加Fragment
在上一节当中,你已经学会了在布局文件中添加Fragment的方法,不过Fragment真正的强大之处在于,它可以在程序运行时动态地添加到Activity当中。根据具体情况来动态地添加Fragment,你就可以将程序界面定制得更加多样化。
们在上一节代码的基础上继续完善,新建another_right_fragment.xml,代码如下所示:
Java
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:background="#ffff00"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:textSize="24sp"
android:text="This is another right fragment" />
</LinearLayout>
这个布局文件的代码和right_fragment.xml中的代码基本相同,只是将背景色改成了黄色,并将显示的文字改了改。然后新建AnotherRightFragment作为另一个右侧Fragment,代码如下所示:
Java
public class AnotherRightFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.another_right_fragment, container, false);
}
}
代码同样非常简单,在onCreateView()方法中加载了刚刚创建的another_right_fragment布局。这样我们就准备好了另一个Fragment,接下来看一下如何将它动态地添加到Activity当中。
Java
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 定义了一个fragment,用于动态加载LeftFragment -->
<fragment
android:id="@+id/leftFrag"
android:name="com.example.fragmenttest.LeftFragment"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<!-- 定义了一个FrameLayout,可用于占位或加载其他视图组件 -->
<FrameLayout
android:id="@+id/rightLayout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" >
</FrameLayout>
</LinearLayout>
可以看到,现在将右侧Fragment替换成了一个FrameLayout。还记得这个布局吗?在上一章中我们学过,这是Android中最简单的一种布局,所有的控件默认都会摆放在布局的左上角。由于这里仅需要在布局里放入一个Fragment,不需要任何定位,因此非常适合使用FrameLayout。
下面我们将在代码中向FrameLayout里添加内容,从而实现动态添加Fragment的功能。修改MainActivity中的代码,如下所示:
Java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 获取button绑定的OnClickListener
View button = findViewById(R.id.button); // 确保您在XML中定义了一个id为button的视图
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
replaceFragment(new AnotherRightFragment());
}
});
// 初始时加载RightFragment
replaceFragment(new RightFragment());
}
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.rightLayout, fragment); // 确保您在XML中定义了一个id为rightLayout的FrameLayout
transaction.commit();
}
}
可以看到,首先我们给左侧Fragment中的按钮注册了一个点击事件,然后调用replaceFragment()方法动态添加了RightFragment。当点击左侧Fragment中的按钮时,又会调用replaceFragment()方法,将右侧Fragment替换成AnotherRightFragment。
结合replaceFragment()方法中的代码可以看出,动态添加Fragment主要分为5步。
(1) 创建待添加Fragment的实例。
(2) 获取FragmentManager,在Activity中可以直接调用getSupportFragmentManager()方法获取。
(3) 开启一个事务,通过调用beginTransaction()方法开启。
(4) 向容器内添加或替换Fragment,一般使用replace()方法实现,需要传入容器的id和待添加的Fragment实例。
(5) 提交事务,调用commit()方法来完成。
在Fragment中实现返回栈
在上一小节中,我们成功实现了向Activity中动态添加Fragment的功能。不过你尝试一下就会发现,通过点击按钮添加了一个Fragment之后,这时按下Back键程序就会直接退出。如果我们想实现类似于返回栈的效果,按下Back键可以回到上一个Fragment,该如何实现呢?其实很简单,FragmentTransaction中提供了一个addToBackStack()方法,可以用于将一个事务添加到返回栈中。修改MainActivity中的代码,如下所示:
Java
public class MainActivity extends AppCompatActivity {
private void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
// 使用transaction.replace()方法替换fragment
transaction.replace(R.id.rightLayout, fragment);
// 将事务添加到后退栈
transaction.addToBackStack(null);
// 提交事务
transaction.commit();
}
}
这里我们在事务提交之前调用了FragmentTransaction的addToBackStack()方法,它可以接收一个名字用于描述返回栈的状态,一般传入null即可。现在重新运行程序,并点击按钮将AnotherRightFragment添加到Activity中,然后按下Back键,你会发现程序并没有退出,而是回到了RightFragment界面。继续按下Back键,RightFragment界面也会消失,再次按下Back键,程序才会退出。
Fragment和Activity之间的交互
虽然Fragment是嵌入在Activity中显示的,可是它们的关系并没有那么亲密。实际上,Fragment和Activity是各自存在于一个独立的类当中的,它们之间并没有那么明显的方式来直接进行交互。如果想要在Activity中调用Fragment里的方法,或者在Fragment中调用Activity里的方法,应该如何实现呢?为了方便Fragment和Activity之间进行交互,FragmentManager提供了一个类似于findViewById()的方法,专门用于从布局文件中获取Fragment的实例,代码如下所示:
Java
RightFragment rightFragment = (RightFragment) getFragmentManager().findFraqmentById(R.id.right_fragment);
调用FragmentManager的findFragmentById()方法,可以在Activity中得到相应Fragment的实例,然后就能轻松地调用Fragment里的方法了。
掌握了如何在Activity中调用Fragment里的方法,那么在Fragment中又该怎样调用Activity里的方法呢?这就更简单了,在每个Fragment中都可以通过调用getActivity()方法来得到和当前Fragment相关联的Activity实例,代码如下所示:
Java
// 假设 activity 是一个 Activity 类型的引用
if (activity != null && activity instanceof MainActivity) {
MainActivity mainActivity = (MainActivity) activity;
// 现在 mainActivity 是 MainActivity 类型的实例,可以调用 MainActivity 的方法或访问其成员变量
}
这里由于getActivity()方法有可能返回null,因此我们需要先进行一个判空处理。有了Activity的实例,在Fragment中调用Activity里的方法就变得轻而易举了。另外当Fragment中需要使用Context对象时,也可以使用getActivity()方法,因为获取到的Activity本身就是一个Context对象。
这里由于getActivity()方法有可能返回null,因此我们需要先进行一个判空处理。有了Activity的实例,在Fragment中调用Activity里的方法就变得轻而易举了。另外当Fragment中需要使用Context对象时,也可以使用getActivity()方法,因为获取到的Activity本身就是一个Context对象。这时不知道你心中会不会产生一个疑问:既然Fragment和Activity之间的通信问题已经解决了,那么不同的Fragment之间可不可以进行通信呢?说实在的,这个问题并没有看上去那么复杂,它的基本思路非常简单:首先在一个Fragment中可以得到与它相关联的Activity,然后再通过这个Activity去获取另外一个Fragme
Fragment的生命周期
和Activity一样,Fragment也有自己的生命周期,并且它和Activity的生命周期实在是太像了,我相信你很快就能学会,下面我们马上就来看一下。
Fragment的状态和回调
还记得每个Activity在其生命周期内可能会有哪几种状态吗?
没错,一共有运行状态、暂停状态、停止状态和销毁状态这4种。
类似地,每个Fragment在其生命周期内也可能会经历这几种状态,只不过在一些细小的地方会有部分区别。
- 运行状态
- 当一个Fragment所关联的Activity正处于运行状态时,该Fragment也处于运行状态。
- 暂停状态
- 当一个Activity进入暂停状态时(由于另一个未占满屏幕的Activity被添加到了栈顶),与它相关联的Fragment就会进入暂停状态。
- 停止状态
- 当一个Activity进入停止状态时,与它相关联的Fragment就会进入停止状态,或者通过调用FragmentTransaction的remove()、replace()方法将Fragment从Activity中移除,但在事务提交之前调用了addToBackStack()方法,这时的Fragment也会进入停止状态。总的来说,进入停止状态的Fragment对用户来说是完全不可见的,有可能会被系统回收。
- 销毁状态
- Fragment总是依附于Activity而存在,因此当Activity被销毁时,与它相关联的Fragment就会进入销毁状态。或者通过调用FragmentTransaction的remove()、replace()方法将Fragment从Activity中移除,但在事务提交之前并没有调用addToBackStack()方法,这时的Fragment也会进入销毁状态。
结合之前的Activity状态,相信你理解起来应该毫不费力吧。同样地,Fragment类中也提供了一系列的回调方法,以覆盖它生命周期的每个环节。其中,Activity中有的回调方法,Fragment中基本上也有,不过Fragment还提供了一些附加的回调方法,下面我们就重点看一下这几个回调。
- onAttach():当Fragment和Activity建立关联时调用。
- onCreateView():为Fragment创建视图(加载布局)时调用。
- onActivityCreated():确保与Fragment相关联的Activity已经创建完毕时调用。
- onDestroyView():当与Fragment关联的视图被移除时调用。
- onDetach():当Fragment和Activity解除关联时调用。
agment还提供了一些附加的回调方法,下面我们就重点看一下这几个回调。
- onAttach():当Fragment和Activity建立关联时调用。
- onCreateView():为Fragment创建视图(加载布局)时调用。
- onActivityCreated():确保与Fragment相关联的Activity已经创建完毕时调用。
- onDestroyView():当与Fragment关联的视图被移除时调用。
- onDetach():当Fragment和Activity解除关联时调用。
[外链图片转存中...(img-AyI7gc6n-1722145897512)]
在Fragment中你也可以通过onSaveInstanceState()方法来保存数据,因为进入停止状态的Fragment有可能在系统内存不足的时候被回收。保存下来的数据在onCreate()、onCreateView()和onActivityCreated()这3个方法中你都可以重新得到,它们都含有一个Bundle类型的savedInstanceState参数。具体的代码我就不在这里展示了