单Activity+多Fragment基于Navigation的封装

6,355 阅读11分钟

Navigation框架的简单封装

你们想要的封装来了 😄😄

首先阅读文本之前,我默认你已经会使用 Navigation 了,默认你已经了解了它的一些常见内部对象了

  • Navigation graph 导航图
  • NavHostFragment 容器
  • NavController 控制器
  • NavOptions 跳转参数

如果你是那么了解 Navigation 那么我推荐你先看看 Navigation 的基本使用,推荐看看这篇文章

好了,既然你了解了 Navigation 的一些基本使用了,我们就可以继续往下看了。

通过前篇我们已经了解,Navigation 才是未来,但是它的使用太过复杂。它的缺点简直无法忍受。不过呢,我们可以慢慢来调(wan)教(shan)它。

我们按照步骤一步一步的解决问题:

  1. 以代码的方式动态添加导航图,无需再定义导航图xml文件。
  2. 对 navigate 和 popBackStack 的封装,跳转和回退的逻辑处理。
  3. 实现自己的FragmentNavigator替换原生的FragmentNavigator。
  4. FragmentNavigator中replace的方式改为add/hide的方式。
  5. FragmentNavigator中处理Fragment生命周期。
  6. 对Fragment构造方法的支持
  7. 启动模式SingleTask的支持
  8. 对Activity重建Fragment导航图恢复的支持
  9. 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,篇幅很长,看到这里也是不容易,觉得不错还请点赞哦。如有错漏还望指正,欢迎大家讨论!

觉得不错,或是对你有一点点的启发,还请点赞支持一下,你的支持是我最大的动力。

到此就完结了!