fragment基本使用
摘自官网的代码示例
class ExampleActivity : AppCompatActivity(R.layout.example_activity) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 注意这里有判断,当saveInstanceState为空时再创建。 表示activity不是重建的,防止fragment重复创建。
if (savedInstanceState == null) {
val bundle = bundleOf("some_int" to 0)
supportFragmentManager.commit {
setReorderingAllowed(true)
// 在add方法中通过bundle传递参数。 貌似是高版本的用法,在源码中没有看到。
add<ExampleFragment>(R.id.fragment_container_view, args = bundle)
}
}
}
}
class ExampleFragment : Fragment(R.layout.example_fragment) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// 通过requireArguments方法获取参数。
val someInt = requireArguments().getInt("some_int")
...
}
}
其中,requireArguments方法如下,主要是对传参进行了一个校验。
如果没有调用setArgument传参,调用requireArguments方法就会报错。
从方法说明中可知,该方法要在添加之前调用。
回退栈
在添加fragment的同时加入回退栈,
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack("hello") // 只有加了回退栈,才可以结合popbackstack来实现回退处理。
.commit()
需要注意的是,fragment加入回退栈时不能使用commitNow及commitNowAllowingStateLoss提交。
主要是在这两个方法中有disallowAddToBackStack()方法检测是否已经加入回退栈了,如果加入了那么会抛出IllegalStateException异常。
如下代码可以监听回退栈的数据变化。
supportFragmentManager?.addOnBackStackChangedListener {
// 回退栈变化监听,系统接口提供了反注册,注意销毁时反注册
Log.i(TAG, "onCreate: 回退栈 size : ${supportFragmentManager.backStackEntryCount}")
val size = supportFragmentManager.backStackEntryCount
for (i in 0 until size) {
val backstackEntry = supportFragmentManager.getBackStackEntryAt(i)
Log.i(TAG, "第${i}个回退栈元素信息: $backstackEntry, name is ${backstackEntry.name}")
}
}
通过如下代码在activity的某个布局id中添加三个fragment
监听器中会打印如下日志
第0个回退栈元素信息: BackStackEntry{6145541 #0 hello}, name is hello
第1个回退栈元素信息: BackStackEntry{8099e32 #1 1}, name is 1
第2个回退栈元素信息: BackStackEntry{d274363 #2 2}, name is 2
第3个回退栈元素信息: BackStackEntry{326b9c0 #3 3}, name is 3
在FragmentManager中提供了popBackStack()和popBackStack(name, flag)两个方法。
popBackStack()是将栈顶的BackStackEntry弹出栈,在上面的场景中,当前的界面是fragment3,如果调用一次,界面会显示fragment2.
popBackStack(name, flag)方法会通过name找到对应的BackStackEntry对象,然后根据flag的值决定如何弹出。
flag取值有0和FragmentManager.POP_BACK_STATE_INCLUSIVE(该常量值为1)。
在上面的场景中,如果name传2,flag传0,那么会找到BackStackEntry{d274363 #2 2}对象,flag为0表示将当前这个BackStackEntry对象保留在回退栈中,那么此时,只会将fragment3出栈,界面显示fragment2.
如果name传2 ,flag传1也即是FragmentManager.POP_BACK_STATE_INCLUSIVE,表示将name对应的BackStackEntry一同出栈。此时,回退栈中只会保留fragment1.
注意,如果通过name没有找到对应的BackStackEntry对应,那么该方法不会出栈任何元素,回退栈也不会有变化。
返回逻辑
常见的返回处理逻辑如下:
override fun onBackPressed() {
supportFragmentManager.let {
// 回退栈有内容时才处理fragment回退
if (it.backStackEntryCount > 0) {
Log.i(TAG, "onBackPressed: supportFragmentManager.popBackStack() execute")
// 根据业务需要选择弹出方式
it.popBackStack()
// it.popBackStack("hello", 0) // 如果栈中没有对应name的backstackEntry,那么不会有任何响应
// it.popBackStack("hello", FragmentManager.POP_BACK_STACK_INCLUSIVE)
} else {
Log.i(TAG, "onBackPressed: default")
super.onBackPressed()
}
}
}
事务的提交方式
在FragmentTransaction中提供了四种提交方式,分别为commit、comimitNow、commitAllowingStateLoss、commitNowAllowingStateLoss。
下面是commit方法的说明
Schedules a commit of this transaction. The commit does
not happen immediately; it will be scheduled as work on the main thread
to be done the next time that thread is ready.A transaction can only be committed with this method
prior to its containing activity saving its state. If the commit is
attempted after that point, an exception will be thrown. This is
because the state after the commit can be lost if the activity needs to
be restored from its state. See {@link #commitAllowingStateLoss()} for
situations where it may be okay to lose the commit.@return Returns the identifier of this transaction’s back stack entry,
if {@link #addToBackStack(String)} had been called. Otherwise, returns
a negative number.public abstract int commit();
这段说明有两个重点, 一是提交是异步的,二是事务必要要在activity保存状态前提交。
这个规定是为了确保在 Activity 生命周期过程中的正确性。Activity 在销毁和重新创建时,会保存和还原其状态。如果在状态保存之后修改了 FragmentTransaction,系统可能无法正确还原 Fragment 的状态,因此在这个时候进行提交会导致异常。这是 Android 系统为了确保应用的稳定性而设置的一种保护机制。在需要提交事务的时候,通常应该在 Activity 生命周期的早期进行。
有了对commit的理解就很好理解commitAllowingStateLoss方法了,该方法允许丢失Fragment状态,故也没有强制必须要在activity保存状态前提交,相当于提交的时机没有那么多的限制。
so this should only be used for cases where it is okay for the UI state to change unexpectedly on the user.
这两个方法在源码上区别也不大,只是多了对activity保存状态的检测。 具体如下图
通过上述源码可知,commit及commitAllowingStateLoss是通过Handler将添加逻辑post到主线程执行的。
而commitNow及commitNowAllowingStateLoss是立即执行的。
post执行与立即执行具体有啥区别呢?
使用 commitNow
方法时,该事务会立即执行。这意味着直到该事务执行完毕,你的代码会被阻塞。这样可以确保在调用 commitNow
后,你可以立即访问新的 Fragment 状态。
适用情况:
- 当你需要立即执行 FragmentTransaction 并确保立即生效时。
- 当你不希望将事务添加到主线程的消息队列中。
其他情况则推荐使用commit方法。
提交方式 | 是否异步 | 是否允许丢失状态 |
---|---|---|
commit | 是 | 否 |
commitAllowingStateLoss | 是 | 是 |
commitNow | 否 | 否 |
commitNowAllowingStateLoss | 否 | 是 |
状态保存
fragment与activity相同,提供了onSaveInstance回调方法来保存相关状态。
具体用法如下:
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(IS_EDITING_KEY, isEditing)
outState.putString(RANDOM_GOOD_DEED_KEY, randomGoodDeed)
}
// 在onCreate方法中读取保存的数据。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
isEditing = savedInstanceState?.getBoolean(IS_EDITING_KEY, false)
randomGoodDeed = savedInstanceState?.getString(RANDOM_GOOD_DEED_KEY)
?: viewModel.generateRandomGoodDeed()
}
view state的保存与恢复
如果view没有id,那么系统是不会对该view的状态进行保存的。其实也很好理解,如果没有id,那么一般也不会动态修改该view的属性了,故也没有太大的必要保存该view的状态。
父子关系的FragmentManager
Fragments can host one or more child fragments. Inside a fragment, you can get a reference to the
FragmentManager
that manages the fragment’s children throughgetChildFragmentManager()
. If you need to access its hostFragmentManager
, you can usegetParentFragmentManager()
.
注意,在有父子fragment嵌套的地方,使用FragmentManager时一定要注意,否则可能会crash。
下图摘自官网介绍。
参考
1、官网
2、fragment saving-state
3、FragmentManager