自定义FragmentNavigator解决Navigation重复创建的问题

最近在学习用Navigation管理Fragment时,我们会发现在切换Fragment的时候并不会像ViewPager一样复用Fragment而是会创建新的Fragment,而这样的后果就是每次切还都会重新请求数据,这样一是用户体验会不好,二是加重了服务器的负担。所以需要想办法搞一搞o(╥﹏╥)o。。。

在查看源码后我们得知,之所以会重复创建Fragment是因为在FragmentNavigator中并没有对Fragment做复用,而是在切换时直接移除了前一个Fragment(源代码如下):

@Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
            @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
        if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            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);//重点:此处并不是show或hide而是直接replace掉了
        ft.setPrimaryNavigationFragment(frag);
 
        final @IdRes int destId = destination.getId();
        final boolean initialNavigation = mBackStack.isEmpty();
        // TODO 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) {
            // Single Top means we only want one instance on the back stack
            if (mBackStack.size() > 1) {
                // If the Fragment to be replaced is on the FragmentManager's
                // back stack, a simple replace() isn't enough so we
                // remove it from the back stack and put our replacement
                // on the back stack in its place
                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 Extras) {
            Extras extras = (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;
        }
    }

所以要达到我们想要的效果需要我们对这个方法进行重写(代码如下):

@Navigator.Name("fixFragment") //这是新的Navigator得名称,千万别忘了加
public class FixFragmentNavigator extends FragmentNavigator {
    private final String TAG = "ReLoadFragmentNavictor";
    private final Context mContext;
    private final FragmentManager mFragmentManager;
    private final int mContainerId;
 
    public FixFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager, int containerId) {
        super(context, manager, containerId);
        mContext = context;
        mFragmentManager = manager;
        mContainerId = containerId;
    }
 
    @Nullable
    @Override
    public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args, @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
 
       if (mFragmentManager.isStateSaved()) {
            Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
                    + " saved its state");
            return null;
        }
        String className = destination.getClassName();
        if (className.charAt(0) == '.') {
            className = mContext.getPackageName() + className;
        }
        Fragment frag = mFragmentManager.findFragmentByTag(className);
        if (null == frag) {
            //不存在,则创建
            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);
        List<Fragment> fragments = mFragmentManager.getFragments();
        for (Fragment fragment : fragments) {
            ft.hide(fragment);
        }
        if (!frag.isAdded()) {
            ft.add(mContainerId, frag, className);
        }
        ft.show(frag);
        ft.setPrimaryNavigationFragment(frag);
 
        final @IdRes int destId = destination.getId();
 
        //通过反射获取mBackStack
        ArrayDeque<Integer> mBackStack;
        try {
            Field field = FragmentNavigator.class.getDeclaredField("mBackStack");
            field.setAccessible(true);
            mBackStack = (ArrayDeque<Integer>) field.get(this);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
 
        final boolean initialNavigation = mBackStack.isEmpty();
        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 Extras) {
            Extras extras = (Extras) navigatorExtras;
            for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
                ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
            }
        }
        ft.setReorderingAllowed(true);
        ft.commit();
        if (isAdded) {
            mBackStack.add(destId);
            return destination;
        } else {
            return null;
        }
    }
    //navigate需要打方法重复类直接复制过来就可以
    @NonNull
    private String generateBackStackName(int backStackIndex, int destId) {
        return backStackIndex + "-" + destId;
    }
}

然后在我们的activity中创建我们自定义的Navigator并设置给NavController:

        Fragment fragmentById = getSupportFragmentManager().findFragmentById(R.id.nav_host);
        //fragment的重复加载问题和NavController有关
        final NavController navController = NavHostFragment.findNavController(fragmentById);
 
        NavigatorProvider provider = navController.getNavigatorProvider();
        //设置自定义的navigator
        FixFragmentNavigator fixFragmentNavictor = new FixFragmentNavigator(this, fragmentById.getChildFragmentManager(), fragmentById.getId());
        provider.addNavigator(fixFragmentNavictor);
 
        //将BottomNavigationView和NaviGraph关联起来
        NavigationUI.setupWithNavController(activityIndexBinding.navBottom,navController);

本以为这就完了,但是在运行起来之后,不断创建的Fragment却告诉我"你以为完了就完了?",在看了各位大佬的博客后我发现思路虽然没错但是还需要我们加一点点细节.....

首先在我们的xml的容器中的属性需要进行部分的修改:

<fragment
            android:id="@+id/nav_host"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:name="androidx.navigation.fragment.NavHostFragment"
            app:navGraph="@navigation/nav_graph"//这条属性删掉,navGraph我们自己创建
            app:defaultNavHost="true"/>

然后我们在activity中创建我们需要的navGraph:

 private NavGraph initNavGraph(NavigatorProvider provider, FixFragmentNavigator fragmentNavigator) {
        NavGraph navGraph = new NavGraph(new NavGraphNavigator(provider));
 
        //用自定义的导航器来创建目的地
        FragmentNavigator.Destination destination1 = fragmentNavigator.createDestination();
        destination1.setId(R.id.afragment);
        destination1.setClassName(AFragment.class.getCanonicalName());
        navGraph.addDestination(destination1);
 
 
        FragmentNavigator.Destination destination2 = fragmentNavigator.createDestination();
        destination2.setId(R.id.bfragment);
        destination2.setClassName(BFragment.class.getCanonicalName());
        navGraph.addDestination(destination2);
 
        FragmentNavigator.Destination destination3 = fragmentNavigator.createDestination();
        destination3.setId(R.id.cfragment);
        destination3.setClassName(CFragment.class.getCanonicalName());
        navGraph.addDestination(destination3);
 
        FragmentNavigator.Destination destination4 = fragmentNavigator.createDestination();
        destination4.setId(R.id.dfragment);
        destination4.setClassName(DFragment.class.getCanonicalName());
        navGraph.addDestination(destination4);
 
        FragmentNavigator.Destination destination5 = fragmentNavigator.createDestination();
        destination5.setId(R.id.efragment);
        destination5.setClassName(EFragment.class.getCanonicalName());
        navGraph.addDestination(destination5);
 
        navGraph.setStartDestination(destination1.getId());
 
        return navGraph;
    }

最后给NavController设置NavGraph:

 Fragment fragmentById = getSupportFragmentManager().findFragmentById(R.id.nav_host);
        //fragment的重复加载问题和NavController有关
        final NavController navController = NavHostFragment.findNavController(fragmentById);
 
        NavigatorProvider provider = navController.getNavigatorProvider();
        //设置自定义的navigator
        FixFragmentNavigator fixFragmentNavictor = new FixFragmentNavigator(this, getSupportFragmentManager(), fragmentById.getId());
        provider.addNavigator(fixFragmentNavictor);
 
        NavGraph navDestinations = initNavGraph(provider, fixFragmentNavictor);
        navController.setGraph(navDestinations);
 
        //将BottomNavigationView和NaviGraph关联起来--->自定义navigator后这样设置会有一些奇葩的问题,谁用谁知道(╥╯^╰╥)....
       //NavigationUI.setupWithNavController(activityIndexBinding.navBottom,navController);
        //正确的关联方式o( ̄▽ ̄)d
        activityIndexBinding.navBottom.setOnNavigationItemSelectedListener(item -> {
            navController.navigate(item.getItemId());
            return true;
        });

---------------------------------------------------------------------------------------------------------->

感谢 八归少年 给出的补充,为了防止点击返回键无法退出界面而是退回到切换前的fragment,我们需要处理一下返回键的点击事件:

    @Override
    public void onBackPressed() {
        finish();
    }


更新:附上Demo地址

GitHub - Coursem/FixFragmentNavigatorDemo
Contribute to Coursem/FixFragmentNavigatorDemo development by creating an account on GitHub.
https://github.com/Coursem/FixFragmentNavigatorDemo.git

https://github.com/Coursem/FixFragmentNavigatorDemo.git

------------------------------------------------------------------------------------------------------------->

小发现:

        正常使用原生Navigator时给navController加上setOnNavigationItemSelectedListener可以有效的控制多次的点击同一个item时fragment的多次刷新问题

vNavBottom.setOnNavigationItemSelectedListener {
            // 避免再次点击重复创建
            if (it.isChecked) {
                return@setOnNavigationItemSelectedListener true
            }
            return@setOnNavigationItemSelectedListener NavigationUI.onNavDestinationSelected(
                it, navController
            )
        }


————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weixin_42575043/article/details/108709467

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自定义react-navigation的TabBar,你需要先创建一个自定义组件来代替默认的TabBar。你可以使用react-native的View和Text组件来创建一个自定义TabBar,然后使用react-navigation的TabNavigatorConfig来将自定义TabBar应用到你的TabNavigator上。 以下是一个简单的示例代码: ```javascript import React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import { createBottomTabNavigator } from 'react-navigation'; const CustomTabBar = ({ navigation }) => { const { routes, index } = navigation.state; return ( <View style={{ flexDirection: 'row', height: 56 }}> {routes.map((route, idx) => { const isFocused = index === idx; const tintColor = isFocused ? 'blue' : 'gray'; return ( <TouchableOpacity key={route.key} onPress={() => navigation.navigate(route.routeName)} style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> <Text style={{ color: tintColor }}>{route.routeName}</Text> </TouchableOpacity> ); })} </View> ); }; const TabNavigator = createBottomTabNavigator( { Home: HomeScreen, Profile: ProfileScreen, }, { tabBarComponent: CustomTabBar, } ); ``` 在这个示例中,我们创建了一个名为CustomTabBar的自定义组件,并将其作为tabBarComponent选项传递给TabNavigator。CustomTabBar接收一个navigation对象作为参数,然后使用它来渲染包含所有选项卡的视图。我们使用TouchableOpacity组件为每个选项卡创建可点击的区域,并在被选中时改变选项卡标题的颜色。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值