Navigation框架的简单封装
你们想要的封装来了 😄😄
首先阅读文本之前,我默认你已经会使用 Navigation 了,默认你已经了解了它的一些常见内部对象了
- Navigation graph 导航图
- NavHostFragment 容器
- NavController 控制器
- NavOptions 跳转参数
如果你是那么了解 Navigation 那么我推荐你先看看 Navigation 的基本使用,推荐看看这篇文章。
好了,既然你了解了 Navigation 的一些基本使用了,我们就可以继续往下看了。
通过前篇我们已经了解,Navigation 才是未来,但是它的使用太过复杂。它的缺点简直无法忍受。不过呢,我们可以慢慢来调(wan)教(shan)它。
我们按照步骤一步一步的解决问题:
- 以代码的方式动态添加导航图,无需再定义导航图xml文件。
- 对 navigate 和 popBackStack 的封装,跳转和回退的逻辑处理。
- 实现自己的FragmentNavigator替换原生的FragmentNavigator。
- FragmentNavigator中replace的方式改为add/hide的方式。
- FragmentNavigator中处理Fragment生命周期。
- 对Fragment构造方法的支持
- 启动模式SingleTask的支持
- 对Activity重建Fragment导航图恢复的支持
- Activity返回键的拦截和Fragment的自定义返回逻辑
一、动态导航图
其他的地方我们都可以忍受,这个实在忍不了,有多少个Fragment就得写多少个导航图,并且只要添加一个 Fragment 我们最多还可能要写其他前99个 Fragment 可能跳转到这个 Fragment 的导航图。究极折磨!
那其实我们所有的xml配置或者布局都是通过Java代码来加载的,比如自定义View的自定义属性
<declare-styleable name="CircleImageView">
<attr name="civ_border_width" format="dimension" />
<attr name="civ_border_color" format="color" />
<attr name="civ_border_overlay" format="boolean" />
<attr name="civ_fill_color" format="color" />
<attr name="civ_circle_background_color" format="color" />
</declare-styleable>
通过Java代码拿到定义的属性:
我们的Activity的页面定义的xml,其实我们也可以把它认为成定义的各种属性和标签,一样是通过Java转换为视图树的。
可能有些同学就了解View的构架流程,如我们在xml中定义的 TextView 为什么在运行之后变成了 AppCompatTextView 。其原理就是加载类的内部的转换。
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
同样的道理,Navigation 的导航图xml 是不是也是通过Java代码构架的呢? 答案是肯定的! 其实源码中就已经有定义的手动创建导航图的扩展方法
inline fun NavController.createGraph(
@IdRes id: Int = 0,
@IdRes startDestination: Int,
builder: NavGraphBuilder.() -> Unit
): NavGraph = navigatorProvider.navigation(id, startDestination, builder)
基于此我们创建一个自己的创建导航图方法
//重点方法 根据传入的Fragment-Class 创建导航图,并绑定自己自定义的FragmentNavigator
fun NavHostFragment.loadRootFragment(root: KClass<out Fragment>) {
val context = activity ?: return
navController.apply {
navigatorProvider.addNavigator(
FragmentNavigator(
context,
childFragmentManager,
id
)
)
val startDestId = root.hashCode()
graph = createGraph(startDestination = startDestId) {
destination(
FragmentNavigatorDestinationBuilder(
provider[MyFragmentNavigator::class],
startDestId,
root
).apply {
label = "home"
})
}
}
}
二、navigate 和 popBackStack 的封装
上面的代码我们创建了导航图,但是没用啊,只有一个 RootFragment 无法跳转到别的应用啊。
所以我们定义方法,在 navigate 跳转之前,把当前的 Fragment 添加到导航图中去,然后按照导航图跳转到指定的 action。
//NavHost进行fork,以便扩展方法到自己的NavHost上
class MyNavHost(
@PublishedApi internal val context: Context, navHost: NavHost
) : NavHost by navHost
//重点方法
//主要执行次方法,在push中手动注册目的地并绑定路由图 并通过navigate的方法手动跳转到目的地
//这种方法只能通过args传递参数
fun MyNavHost.start(
clazz: KClass<out Fragment>,
arguments: Bundle? = null,
extras: Navigator.Extras? = null,
optionsBuilder: NavOptions.() -> Unit = {}
) = with(navController) {
val node = putFragment(requireActivity(), clazz) //因为其他地方也用到putFragment,这里抽出来做为方法
navigate(
node.id, arguments,
convertNavOptions(clazz, NavOptions().apply(optionsBuilder)),
extras
)
}
//存入Fragment到导航图
@PublishedApi
internal fun NavController.putFragment(
activity: FragmentActivity,
clazz: KClass<out Fragment>
): FragmentNavigator.Destination {
val destId = clazz.hashCode()
lateinit var destination: FragmentNavigator.Destination
if (graph.findNode(destId) == null) {
destination = (FragmentNavigatorDestinationBuilder(
navigatorProvider[FragmentNavigator::class],
destId,
clazz
).apply {
label = clazz.qualifiedName
}).build()
graph.plusAssign(destination)
} else {
destination = graph.findNode(destId) as FragmentNavigator.Destination
}
return destination
}
思路是不是瞬间清晰了,我们在定义下一回退与管理的扩展方法,我们就可以跑起来试试了。
//Fragment的栈返回
fun MyNavHost.pop() {
navController.popBackStack()
}
//返回到指定栈
fun NavHost.popTo(clazz: KClass<out Fragment>, include: Boolean = false) {
navController.popBackStack(clazz.hashCode(), include)
}
//让Activity直接finish
fun Fragment.finishActivity() {
requireActivity().finish()
}
//创建Fragmetn中使用navigator的实例
val Fragment.navigator
get() = MyNavHost(requireContext()) {
val clazz = this::class
requireParentFragment().findNavController().apply {
putFragment(requireActivity(), clazz)
}
}
跑起来试试,Activity与Fragment的关键代码:
Activity的xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:viewBindingIgnore="true">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
app:defaultNavHost="true" />
</FrameLayout>
Activity创建导航图:
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
navHostFragment.loadRootFragment(Demo11OneFragment1::class)
Fragment的跳转方法
fun nav2Page2() {
//跳转的几种方式,跳转Class文件
navigator.start(
Demo11OneFragment2::class,
arguments = bundleOf("name" to "zhangsan", "age" to "18"),
) {
applySlideInOut()
}
}
Fragment的返回方法
fun back2Page1() {
navigator.popTo(Demo11OneFragment1::class, true)
}
fun back2Page2() {
navigator.pop()
}
效果如下:
可以看到跳转和回退都已经没有问题了,但是我在页面的EditText中输入了文本,返回的时候给我给干没了,这是实例没有保存啊!
三,四、FragmentNavigator的自定义与replace方法替换
我们继承自FragmentNavigator并重写几个重要的方法。
@Navigator.Name("ignore")
public class MyFragmentNavigator extends FragmentNavigator {
private static final String TAG = "MyFragmentNavigator";
private static final String KEY_BACK_STACK_IDS = "myFragmentNavigator:backStackIds";
private ArrayDeque<Integer> mBackStack = new ArrayDeque<>();
private final Context mContext;
private final FragmentManager mFragmentManager;
private final int mContainerId;
public MyFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
super(context, manager, containerId);
mContext = context;
mFragmentManager = manager;
mContainerId = containerId;
}
//重点实现这个方法
@Nullable
@Override
public NavDestination navigate(@NonNull FragmentNavigator.Destination destination, @Nullable Bundle args,
@Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final Fragment frag = instantiateFragment(mContext, mFragmentManager, className, args);
frag.setArguments(args);
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
// ft.replace(mContainerId, frag);
//Add的方式替换replace方式,并处理生命周期
if (mFragmentManager.getFragments().size() > 0) {
Fragment lastFragment = mFragmentManager.getFragments().get(mFragmentManager.getFragments().size() - 1);
ft.hide(lastFragment);
if (frag.isAdded()) {
ft.show(frag);
} else {
ft.add(mContainerId, frag);
}
} else {
ft.replace(mContainerId, frag);
}
ft.setPrimaryNavigationFragment(frag);
final @IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
if (mBackStack.size() > 1) {
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.addToBackStack(generateBackStackName(mBackStack.size(), destId));
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
isAdded = true;
}
if (navigatorExtras instanceof FragmentNavigator.Extras) {
FragmentNavigator.Extras extras = (FragmentNavigator.Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
}
核心就是替换replace方法为add方法。
五、生命周期的处理
还记得ViewPager的懒加载中,我们是如何处理Fragment的生命周期的吗?
本质就是内部帮你处理和切换MaxLifecycle:
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.STARTED); mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
网上有些相关的封装是通过回调,监听当前的是否在前台,和之前的是否在后台,然后通过接口回调手动的调用相关的生命周期。这个太麻烦,并且息屏和后台情况和销毁重建各方面的处理都不一样,更复杂,一不小心就会出问题。
这里我们直接setMaxLifecycle就能实现生命周期的处理
// ft.replace(mContainerId, frag);
//Add的方式替换replace方式,并处理生命周期
if (mFragmentManager.getFragments().size() > 0) {
Fragment lastFragment = mFragmentManager.getFragments().get(mFragmentManager.getFragments().size() - 1);
ft.hide(lastFragment);
ft.setMaxLifecycle(lastFragment, Lifecycle.State.STARTED);
if (frag.isAdded()) {
ft.show(frag);
} else {
ft.add(mContainerId, frag);
}
} else {
ft.replace(mContainerId, frag);
}
好了我们把我们自定义的MyFragmentNavigator设置到我们的扩展方法
loadRootFragment 扩展方法中 FragmentNavigator::class 替换为 MyFragmentNavigator::class
putFragment 扩展方法中 FragmentNavigator::class 替换为 MyFragmentNavigator::class
然后我们再看看生命周期的回调
六、对Fragment构造方法的支持
其实到这一步已经是可以用了,但是 Navigation 本身也是只能通过 Fragment 的class 来进行导航,参数通过专门的 arguments 来传递。我们该如何实现Fragment的构造来创建呢?
先什么都不改,看看能不能通过构造来跳转!万一能行呢 😂😂
加入一些构造方法的start方式
//通过Fragment的构造对象来实现loadRoot
inline fun <reified T : Fragment> NavHostFragment.loadRootFragment(
noinline returnFragmentBlock: () -> T
) {
val clazz = T::class
FragmentCaches[clazz.qualifiedName!!] = returnFragmentBlock
loadRootFragment(T::class)
}
//这种方式可以通过构造传递参数
inline fun <reified T : Fragment> MyNavHost.start(
noinline optionsBuilder: NavOptions.() -> Unit = {},
arguments: Bundle? = null,
noinline returnFragmentBlock: () -> T
) {
val clazz = T::class
FragmentCaches[clazz.qualifiedName!!] = returnFragmentBlock
start(clazz, arguments = arguments, optionsBuilder = optionsBuilder)
}
OK,我们来试试
navigator.start({ applySlideInOut() }) {
Demo11OneFragment3("zhangsan")
}
class Demo11OneFragment3(private val name: String?):Fragment {}
毫无例外的报错了,报错信息如下
Unable to instantiate fragment Demo11OneFragment3: could not find Fragment constructor
其实看到新的扩展方法,start的时候还是调用之前的start,还是传递的Class,你定义的Fragment根本没用到。
那我们到底该如何使用呢?想到一句话,没什么功能是中间层不能解决的,如果不行就再加一层。我们可以使用一个 ContainnerFragment 作为一个中间层,把我们构造的 Fragment 放入到这个中间层 ContainnerFragment 中。
具体代码如下:
internal class NavContainerFragment : Fragment() {
private val _vm: FragmentViewModel by viewModels()
private val className by lazy {
requireNotNull(arguments?.getString(REAL_FRAGMENT))
}
private val _real: Class<out Fragment> by lazy {
Class.forName(className) as Class<out Fragment>
}
internal val mRealFragment: Fragment
get() {
return _vm.fragment ?: run {
val frag = FragmentCaches[className]?.invoke()
.also { FragmentCaches.remove(className) }
?: _real.newInstance()
frag.apply {
retainInstance = true
}
}.also { _vm.fragment = it }
}
override fun onAttach(context: Context) {
super.onAttach(context)
mChildFragmentManager.beginTransaction().apply {
mRealFragment.arguments = arguments
add(R.id.container, mRealFragment)
commitNow()
}
}
//这个中间层Fragment内部加载的是我们缓存中的Fragment实例
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
sharedElementEnterTransition = mRealFragment.sharedElementEnterTransition
sharedElementReturnTransition = mRealFragment.sharedElementReturnTransition
return FrameLayout(requireContext()).apply {
addView(inflater.inflate(R.layout.nav_container_fragment_layout, container, false)
.apply { appendBackground() })
}
}
private fun View.appendBackground() {
val a: TypedArray =
requireActivity().theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
val background = a.getResourceId(0, 0)
a.recycle()
setBackgroundResource(background)
}
internal companion object {
const val REAL_FRAGMENT = "realFragment"
}
data class FragmentViewModel(var fragment: Fragment? = null) : ViewModel()
}
然后再我们自定义的 MyFragmentNavigator 中重写 instantiateFragment 方法,让它返回我们中间层 ContainerFragment 。代码如下:
@Deprecated
@NonNull
public Fragment instantiateFragment(@NonNull Context context,
@NonNull FragmentManager fragmentManager,
@NonNull String className,
@SuppressWarnings("unused") @Nullable Bundle args) {
//Unable to instantiate fragment Demo11OneFragment3: could not find Fragment constructor
// 这里想要使用构造方法的Fragment初始化,使用了包装类Fragment,让包装类默认空参初始化,
// 内部的fragment再加载我们真正的Fragment,从而实现构造的Fragment可用
Fragment fragment = super.instantiateFragment(context, fragmentManager, "androidx.fragment.app.NavContainerFragment", args);
if (fragment instanceof NavContainerFragment) {
Bundle bundle = new Bundle();
bundle.putString(REAL_FRAGMENT, className);
if (args != null) bundle.putAll(args);
fragment.setArguments(bundle);
} else {
fragment.setArguments(args);
}
return fragment;
}
好了我们再改下构造的方式来运行项目试试:
//Activity加载跟视图
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
// navHostFragment.loadRootFragment(Demo11OneFragment1::class)
navHostFragment.loadRootFragment {
Demo11OneFragment1("测试直接构造方法初始化")
}
//跳转的方法-通过构造传递参数
navigator.start({ applySlideInOut() }) {
Demo11OneFragment3(arguments?.getString("name"))
}
//跳转的方法,我们还能通过构造传参+bundle传参同时生效
navigator.start(
//启动选项
{
applySlideInOut()
launchMode = LaunchMode.STANDARD
},
//参数
arguments = bundleOf("name" to "zhangsan", "age" to "18")
) {
//直接创建实例的的方式
Demo11OneFragment2(callback)
}
效果:
七、启动模式与NavOptions的封装
上面有些代码是对 NavOptions 的封装了,一些启动模式,启动动画的封装
class NavOptions {
var launchMode: LaunchMode = LaunchMode.STANDARD
@AnimRes
@AnimatorRes
var enterAnim = -1
@AnimRes
@AnimatorRes
var exitAnim = -1
@AnimRes
@AnimatorRes
var popEnterAnim = -1
@AnimRes
@AnimatorRes
var popExitAnim = -1
}
@PublishedApi
internal fun convertNavOptions(
clazz: KClass<out Fragment>,
navOptions: NavOptions
): androidx.navigation.NavOptions {
return androidx.navigation.NavOptions.Builder().apply {
setEnterAnim(navOptions.enterAnim)
setExitAnim(navOptions.exitAnim)
setPopEnterAnim(navOptions.popEnterAnim)
setPopExitAnim(navOptions.popExitAnim)
//Single Top 调用setLaunchSingleTop Api实现
setLaunchSingleTop(navOptions.launchMode == LaunchMode.SINGLE_TOP)
//Single Task 调用setPopUpTo来实现
if (navOptions.launchMode == LaunchMode.SINGLE_TASK) {
setPopUpTo(clazz.hashCode(), true)
}
}.build()
}
enum class LaunchMode {
STANDARD, SINGLE_TOP, SINGLE_TASK
}
//默认的左右入场出场动画
fun NavOptions.applySlideInOut() {
enterAnim = R.anim.open_enter
exitAnim = R.anim.open_exit
popEnterAnim = R.anim.close_enter
popExitAnim = R.anim.close_exit
}
//默认的上下透明动画
fun NavOptions.applyFadeInOut() {
enterAnim = R.anim.nav_default_enter_anim
exitAnim = R.anim.nav_default_exit_anim
popEnterAnim = R.anim.nav_default_enter_anim
popExitAnim = R.anim.nav_default_exit_anim
}
内部对Single Task的实现就是做了PopUpTo的操作。比较取巧了。 具体的效果可以看看上面的gif效果
八、对Activity重建Fragment导航图恢复的支持
我们可以对Framgnet的父布局Activity做一个自定义的ViewModel进行恢复导航图,避免出现Activity重建的时候导航图失效的问题
internal class NavViewModel : ViewModel() {
val nodes = SparseArrayCompat<NavDestination>()
}
internal fun FragmentActivity.saveToViewModel(destination: NavDestination) {
val vm = ViewModelProvider(this)[NavViewModel::class.java]
if (vm.nodes.keyIterator().asSequence().any {
it == destination.id
}) return
vm.nodes.put(destination.id, destination)
}
internal fun FragmentActivity.removeFromViewModel(id: Int) {
val vm = ViewModelProvider(this)[NavViewModel::class.java]
vm.nodes.remove(id)
}
每次更新导航图的时候都需要更新到ViewModel
//存入Fragment到导航图
@PublishedApi
internal fun NavController.putFragment(
activity: FragmentActivity,
clazz: KClass<out Fragment>
): FragmentNavigator.Destination {
val destId = clazz.hashCode()
lateinit var destination: FragmentNavigator.Destination
if (graph.findNode(destId) == null) {
destination = (FragmentNavigatorDestinationBuilder(
navigatorProvider[MyFragmentNavigator::class],
destId,
clazz
).apply {
label = clazz.qualifiedName
}).build()
graph.plusAssign(destination)
activity.saveToViewModel(destination)
} else {
destination = graph.findNode(destId) as FragmentNavigator.Destination
}
return destination
}
九、Fragment的自定义返回逻辑
默认的返回都是 Navigation 处理的,有Fragment回退栈,到RootFragment了就返回Activity,已经很完美了,但是如果我们要自定义的返回怎么办?
方法有很多,这里我说一下我的思路,我的思路的是重写Activity的 onBackPressed 方法,在里面找到当前的Fragment栈,如果当前的Fragment实现了自定义的 IOnBackPressed 接口,就调用自定义的 IOnBackPressed 方法,否则就调用 super 方法
具体代码如下:
interface IOnBackPressed {
// Fragment的返回事件处理
// 返回值 -> 是否穿透事件,交给Activity处理
fun onBackPressed(): Boolean = false
}
如果子Fragment需要自定义的返回逻辑,那么重写 onBackPressed 方法
override fun onBackPressed() {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment
val fragments = navHostFragment.getAllFragments() //内部可以获取到Fragment栈
val fragment = fragments[fragments.size - 1]
if (fragment !is IOnBackPressed) {
super.onBackPressed()
} else {
if ((fragment as IOnBackPressed).onBackPressed()) {
super.onBackPressed()
}
}
}
通过 NavHostFragment 取到当前的Fragment栈集合,注意我们栈中的 Fragment 是 NavContainerFragment ,我们转换的时候需要处理!
//获取全部的Fragment
fun NavHostFragment.getAllFragments(): List<Fragment> {
val list = arrayListOf<Fragment>()
navController.mBackStack.forEachIndexed { index, entry ->
entry.destination.let { des ->
if (des is FragmentNavigator.Destination) {
val frag = childFragmentManager.fragments[index - 1]
list.add((frag as NavContainerFragment).mRealFragment)
}
}
}
return list
}
比如我们的Demo1Fragment需要处理自定义返回逻辑
class Demo11OneFragment1(private val test: String) : Fragment ,IOnBackPressed {
//返回事件- 不穿透交给自己处理
override fun onBackPressed(): Boolean {
finishCurActivity()
return false
}
private var firstTime: Long = 0
private fun finishCurActivity() {
if (System.currentTimeMillis() - firstTime > 2000) {
toast("再按一次,退出该页面")
firstTime = System.currentTimeMillis()
} else {
finishActivity()
}
}
}
这样实现了 Demo1Fragment 的自定义返回,而Demo2Fragment 和 Demo3Fragment 就是默认的返回。
效果如下:
十、总结
总体的思路应该也是很清晰了,大家如果有兴趣可以跑下Demo,源码在此!
思路大致差不多,只是实现的方式有多种,目前这么封装也算是可以用了,如果有什么问题大家也可以基于源码自行修改哦。
本文实现依赖为:
androidx.appcompat:appcompat:1.3.1
androidx.fragment:fragment-ktx:1.3.6
androidx.navigation:navigation-fragment-ktx:2.3.5
androidx.navigation:navigation-ui-ktx:2.3.5
版本为:
Activity版本1.2.4
Fragment版本1.3.6
Navigation版本2.3.5
2022-06-28 补充
有人觉得页面重建是不是有问题,能否重建,如果以构造的方式传参,重建之后是不是会丢失?这里做一个补充测试!
跳转到fragment3之后,我们退出到后台,然后不保存活动,重新进入Activity,打印出重建回调如下:
生命周期的回调如下,只会走当前页面的OnResume
构造方法传参,返回到fragment1,正常的打印吐司
//启动页面
navigator.start(
//启动选项
{
applySlideInOut()
launchMode = LaunchMode.STANDARD
},
//参数
arguments = bundleOf("name" to "zhangsan", "age" to "18")
) {
//直接创建实例的的方式
Demo11OneFragment2(callback)
}
//回调
val callback: (Int, String) -> Unit = { int, str ->
toast("int : $int ; str: $str")
}
效果如图:
所以重建 Activity 中的 Navigation 的 Fragment 和重建 Activity 并没有本质的区别。可以放心使用的。
Ok,篇幅很长,看到这里也是不容易,觉得不错还请点赞哦。如有错漏还望指正,欢迎大家讨论!
觉得不错,或是对你有一点点的启发,还请点赞支持一下,你的支持是我最大的动力。
到此就完结了!