展开说说:Android Fragment完全解析-卷一

embedded/2024/9/23 20:25:50/

1、是什么

Fragment 中文意思是碎片,Android 3.0推出的一个系统组件,主打一个在应用界面中可模块化又可重复使用。

Fragment 它很独立,它可以定义和管理自己的布局,具有自己的生命周期,并且可以处理自己的输入事件。

Fragment 很粘人,它不能独立存在。它们必须由 activity 或其他 fragment 托管即fragment可以内嵌fragment使用同一 activity 或多个 activity 中可以使用同一 fragment 类的多个实例但是要注意解耦避免让一个 fragment 依赖另一个 fragment 或一个 fragment 操控另一个 fragment。

2、怎么用

Fragment一般是两种用法,使用管理器FragmentManager+事务FragmentTransaction和fragment搭配Viewpager。本文将分析第一种,后面一种会在下一篇文章分析。

activity 是放置应用的全局的界面元素,而Fragment 更适合定义和管理单个屏幕或部分屏幕的界面,可以更轻松地在运行时修改 activity 的外观。大部分应用的首页都采用了Fragment+底部切换选项按钮的布局。

Fragment类有这么样一个提示:

<li>Your activity must extend {@link FragmentActivity}
<li>You must call {@link FragmentActivity#getSupportFragmentManager} to get the
{@link FragmentManager}

也就是说 Fragment 必须嵌入 AndroidX FragmentActivity 中使用并获取管理器FragmentManager。AppCompatActivity 是FragmentActivity 的子类,因此如果我们的Activity是继承AppCompatActivity 就可以放心使用。

举个例子,一个Activity有四个底部选项卡,点击以后会展示四个不同Fragment页面,分别是FragmentHome 、FragmentApp、FragmentMsg、FragmentMy;一般来底部可能是个RadioGroup+RadioButton或者图片加文字的布局,我这里偷懒只写了四个button。

2.1通过新建一个类继承Fragment来创建一个fragment

定义一个自己的FragmentHome 前面说过Fragment也有自己的声明和布局展示,因此我在onCreateView方法使用inflate加载一个布局。类似的还有其他三个,分别是FragmentApp

FragmentMsgFragmentMy

public class FragmentHome extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater,  ViewGroup container, Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_home, null, false);Log.e(TAG, "onCreateView:     --" );return view;
}
}

2.2 将Fragment添加到Activity有两种方法,但是我们最常使用是第一种。

第一种:使用管理器FragmentManager和事务FragmentTransaction

activity布局应包含 一个容器组件来装载Fragment

<LinearLayoutandroid:id="@+id/fragAct_root"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginBottom="@dimen/margin_50"android:visibility="gone"app:layout_constraintTop_toTopOf="parent"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintRight_toRightOf="parent"android:orientation="vertical" />

在Activity中将四个Fragment实例化,并创建FragmentManager

fragmentHome = new FragmentHome();
fragmentApp = new FragmentApp();
fragmentMsg = new FragmentMsg();
fragmentMy = new FragmentMy();
创建管理器FragmentManager:
FragmentManager manager = getSupportFragmentManager();

然后我们做增加、替换或者移除、显示、隐藏等都可以通过manager 获取事务来实现:

FragmentTransaction fragmentTransaction = manager.beginTransaction();

这里需要注意:fragmentTransaction只能提交一次无论是commit()或者commitNow(),超过一次会报错java.lang.IllegalStateException: commit already called

想要展示哪个fragment就通过事务的add或replace方法,添加当前fragment并放到前台展示。

先说add方法,一个简单的封装:add一个新的需要把其他的隐藏了否则可能会谍影重重:

private void addFragment(Fragment selectFragment, String fragmentTag) {//这里你要过滤FragmentManager的fragment列表
List<Fragment> fragments = manager.getFragments();for (Fragment fragment : fragments) {if (fragment.isAdded()) {Log.e(TAG, "initFragment: 已添加-先隐藏 " + fragment.getClass().getSimpleName() + "     " + fragmentHome.isAdded() + "    " + fragmentHome.isVisible());manager.beginTransaction().hide(fragment).commit();}}Fragment fragmentByTag = manager.findFragmentByTag(fragmentTag);if (fragmentByTag != null && fragmentByTag.isAdded()) {Log.e(TAG, "initFragment: 已经添加过了,别再添加-2 " + selectFragment.getClass().getSimpleName() + "     " + selectFragment.isAdded() + "    " + selectFragment.isVisible() + "   " + selectFragment.isHidden());manager.beginTransaction().show(fragmentByTag).commit();} else {if (selectFragment.isAdded()) {Log.e(TAG, "initFragment: 已经添加过了,别再添加-1 " + selectFragment.getClass().getSimpleName() + "     " + selectFragment.isAdded() + "    " + selectFragment.isVisible() + "   " + selectFragment.isHidden());manager.beginTransaction().show(selectFragment).commit();} else {manager.beginTransaction().add(R.id.fragAct_root, selectFragment, fragmentTag).show(selectFragment).commit();Log.e(TAG, "initFragment: 再次添加 fragment   +" + selectFragment.getClass().getSimpleName() + "     " + selectFragment.isAdded() + "    " + selectFragment.isVisible() + "   " + selectFragment.isHidden());}}
}

再说replace方法,如果你不用add,用了replace那一行代码就足够了

manager.beginTransaction().replace(R.id.fragAct_root, fragmentHome).commit();

第二种在Xml布局中写

<fragmentandroid:id="@+id/fragAct_fragment"android:name="com.example.testdemo3.fragment.FragmentApp"android:layout_width="match_parent"android:layout_height="match_parent"/>

和普通组件一样使用在Activity中findviewbyid就可以了使用,这里注意fragment标签要小写、name属性对应的是你自定义fragment的全路径,后续fragment对象的使用方法同上面第一种。

3、常用方法

整体来看,fragment 应用界面中可模块化又可重复使用fragment 的视图层次结构会成为宿主的视图层次结构的一部分,或附加到宿主的视图层次结构并且可以方便将它增加、替换或者移除这些更改的记录会记录在由 activity 管理的返回堆栈中,以便撤消这些更改。

3.1 添加fragment

如需将 Fragment 添加到 FragmentManager通过事务调用 add()。此方法会收到用于存储此 Fragment 的容器的 ID,以及您要添加的 Fragment 的类名。添加的 Fragment 会转为 RESUMED 状态即最上层可以和用户交互的状态

3.2移除fragment

如需从宿主中移除 Fragment,调用 remove(),同时传入通过 findFragmentById() 或 findFragmentByTag() 从 Fragment 管理器检索到的 Fragment 实例。 如果 fragment 的视图之前已添加到容器中,则此时视图会从容器中移除。已移除的 fragment 会转为 DESTROYED 状态不可用。

3.3替换fragment

 replace() 将容器中现有的 fragment 替换为新 fragment 。调用 replace() 等同于对容器中的 Fragment 调用 remove() 后将新的 Fragment 添加到同一容器中就是系统帮你每次移除旧的并加入新的

3.4 提交

事务的提交操作为异步操作,调用 commit() 不会立即执行事务,而是会将事务调度为能在主界面线程上运行就在主界面线程上运行。如果使用 commitNow() 会立即在界面线程上运行 Fragment 事务,一般来说 commit() 即可。

4、避坑指南

4.1如果两次添加同一个Fragment对象,会报以下错误:
    java.lang.IllegalStateException: Fragment already added: FragmentHome{b2a5424 (a7835f58-5a56-49c9-b889-c0d8b8b17d8c) id=0x7f0800c5}  at androidx.fragment.app.FragmentManagerImpl.addFragment(FragmentManagerImpl.java:1379)
4.2如果同一个fragment使用了两个不同的tag进行add就会包如下错误:因此建议将TAG定义成常量
Caused by: java.lang.IllegalStateException: Can't change tag of fragment FragmentHome{595c6d5 (e4ff5b43-2336-422e-a219-008628a5c3b7) id=0x7f0800c5 fragmentHomeTag}: was fragmentHomeTag now FragmentHomeTag
        at androidx.fragment.app.FragmentTransaction.doAddOp(FragmentTransaction.java:172)

4.3 事务只能提交一次,无论是commit()或者commitNow(),超过一次会报错

java.lang.IllegalStateException: commit already called

4.4 横竖屏切换处理

我们在activity启动后应该自动展示一个fragment而不是一定要等用户主动触发再去加载展示对吧。如果你放到了onCreate中添加第一个fragment,那么在遇到横竖屏切换的话就会发现不论之前显示的个fragment切换以后都展示第一个fragment了,因为它重走生命周期了。其实呢可以这样避免:

//选出最近的fragment并展示,避免每次横竖屏切换就回到了第一个fragment
List<Fragment> fragments = manager.getFragments();
Fragment firstFragment = fragmentHome;
if (!fragments.isEmpty()) {firstFragment = fragments.get(fragments.size() - 1);
}
//上面2.2中写了这个方法
addFragment(firstFragment, firstFragment.getClass().getSimpleName() + "Tag");

横竖屏切换前的Fragment实例是怎样保存下来的呢?是的,是它。FragmentActivity的onSaveInstanceState方法。


http://www.ppmy.cn/embedded/5919.html

相关文章

pta L1-063 吃鱼还是吃肉

L1-063 吃鱼还是吃肉 分数 10 全屏浏览 切换布局 作者 陈越 单位 浙江大学 国家给出了 8 岁男宝宝的标准身高为 130 厘米、标准体重为 27 公斤&#xff1b;8 岁女宝宝的标准身高为 129 厘米、标准体重为 25 公斤。 现在你要根据小宝宝的身高体重&#xff0c;给出补充营养的…

2024第二十一届五一数学建模B题思路 五一杯建模思路

文章目录 1 赛题思路2 比赛日期和时间3 组织机构4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

蓝桥杯刷题-毕业旅行问题

731. 毕业旅行问题 - AcWing题库 /* 起点变为1 ~ n - 1号点&#xff0c;终点变为0号点 */ #include <bits/stdc.h>using namespace std; #define x first #define y second typedef long long LL; typedef pair<int , int> PII;const int N 10 , M (1 << …

【Android】 镜像及分区

一、Android镜像 序号镜像名称描述1boot.img包含启动所需文件的压缩文件&#xff0c;在启动时会被加载到内存中&#xff0c;并解压执行2 ramdisk.img 包含了用于启动过程的RAM磁盘的内容&#xff0c;RAM磁盘是一个临时的文件系统&#xff0c;在设备启动时&#xff0c;内核会将…

Android10以上MediaProjection截屏

起因 在系统升级到Android10以上之后&#xff0c;之前的截屏方式不能用了&#xff0c;而且必须将MediaProjection放在forground service里面跑才行。网上搜了一圈&#xff0c;都是语焉不详或者没有完整的一个代码应用。只能自己写一个&#xff0c;记录下 代码实现 新建一个S…

Redis:报错Creating Server TCP listening socket *:6379: bind: No error

错误&#xff1a; window下启动redis服务报错&#xff1a; Creating Server TCP listening socket *:6379: bind: No error 原因&#xff1a; 端口6379已被绑定&#xff0c;应该是因为上次未关闭服务 解决&#xff1a; ①依次输入命令&#xff1a; redis-cli.exe &#xff08…

论坛报名 | 中关村论坛“区块链与隐私计算论坛”报名开始!

2024中关村论坛—区块链与隐私计算论坛 正在报名&#xff01; 长安链开源社区作为区块链与隐私计算分论坛协同支持社区&#xff0c;为社区成员单位提供免费参会名额&#xff0c;名额有限先到先得&#xff0c;欢迎积极报名&#xff01; 论坛时间&#xff1a;2024年4月27日&…

FPGA - 基于自定义AXI FULL总线的PS和PL交互

前言 在FPGA - ZYNQ 基于Axi_Lite的PS和PL交互中&#xff0c;介绍了基于基于AXi_Lite的PL和PS交互&#xff0c;接下来构建基于基于Axi_Lite的PS和PL交互。 AXI_GP、AXI_HP和AXI_ACP接口 首先看一下ZYNQ SoC的系统框图&#xff0c;如下图所示。在图中&#xff0c;箭头方向代表…