天下网吧 >> 网吧天地 >> 天下码农 >> Android >> 正文

NavigationView(BottomNavigationView)+Fragment防重新加载源码

2021-6-25天下码农程序猿

很多老铁用BottomNavigationView+Fragment来实现主体APP的底部导航+页面渲染功能。老实说这个也是Google在Android Studio里推荐的用发。并且明显针对性的做了优化,所以你子啊使用BottomNavigationView+Fragment实现相关功能时你会感受到丝滑般的切换速度。

但是这个方案有一个致命的缺陷,就是每次切换后返回到上一个Fragment时都会重新加载页面,重新执行onCreateView和相关方法,数据和整个页面相当于刷新了一遍,这个对国内用户的习惯和体验来讲都是无法忍受的。所以才有了今天的这篇文章。

实现原理:

自己重写FragmentNavigator类,把核心的navigate方法重新实现,核心代码就是原版的暴力「ft.replace(mContainerId, frag);」这行替换成先判断目标Fragment是否已经存在,如果已经存在就直接返回旧的而不重建一个,不存在的才重建一个。

FixFragment类全代码如下 (核心方法为 navigate方法):

package util;

import android.content.Context;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

import java.util.ArrayDeque;
import java.util.Map;

import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.navigation.NavDestination;
import androidx.navigation.NavOptions;
import androidx.navigation.Navigator;
import androidx.navigation.fragment.FragmentNavigator;

/**
 * @ClassName FixFragmentNavigator
 * @Description TODO
 * @Author liqilin
 * @Date 2021/6/24 15:38
 * @Version 1.0
 */
@Navigator.Name("fixFragment")
public class FixFragmentNavigator extends FragmentNavigator {
    private static final String TAG = "FixFragmentNavigator";
    private static final String KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds";

    private final Context mContext;
    private final FragmentManager mFragmentManager;
    private final int mContainerId;
    private ArrayDeque<Integer> mBackStack = new ArrayDeque<>();

    public FixFragmentNavigator(@NonNull Context context, @NonNull FragmentManager manager,
                                int containerId) {
        super(context, manager, containerId);
        mContext = context;
        mFragmentManager = manager;
        mContainerId = containerId;
    }


    /**
     * {@inheritDoc}
     * <p>
     * This method should always call
     * {@link FragmentTransaction#setPrimaryNavigationFragment(Fragment)}
     * so that the Fragment associated with the new destination can be retrieved with
     * {@link FragmentManager#getPrimaryNavigationFragment()}.
     * <p>
     * Note that the default implementation commits the new Fragment
     * asynchronously, so the new Fragment is not instantly available
     * after this call completes.
     */
    @SuppressWarnings("deprecation") /* Using instantiateFragment for forward compatibility */
    @Nullable
    @Override
    public NavDestination navigate(@NonNull FragmentNavigator.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 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);
        }

        /**
         * 1、先查询当前显示的fragment 不为空则将其hide
         * 2、根据tag查询当前添加的fragment是否不为null,不为null则将其直接show
         * 3、为null则通过instantiateFragment方法创建fragment实例
         * 4、将创建的实例添加在事务中
         */
        Fragment fragment = mFragmentManager.getPrimaryNavigationFragment();
        if (fragment != null) {
            ft.hide(fragment);
        }

        String tag = destination.getId() + "";
        Fragment frag = mFragmentManager.findFragmentByTag(tag);
        if (frag != null) {
            ft.show(frag);
        } else {
            frag = instantiateFragment(mContext, mFragmentManager, className, args);
            frag.setArguments(args);
            ft.add(mContainerId, frag, tag);
        }

        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 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;
        }
    }


    @NonNull
    private String generateBackStackName(int backStackIndex, int destId) {
        return backStackIndex + "-" + destId;
    }
}

做了这个工作后,还需要把对应的xml(通常为mobile_navigation.xml)里面的Fragment标签换成自己写的fixFragment。换完后整个.xml文件应该是类似下面的:


<?xml version="1.0" encoding="utf-8"?>
<navigation 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/mobile_navigation"
    app:itemTextColor="@drawable/main_bottom_navigation"
    app:itemIconTint="@drawable/main_bottom_navigation"
    app:startDestination="@+id/navigation_home">

    <fixFragment
        android:id="@+id/navigation_home"
        android:name="com.XXX.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

    <fixFragment
        android:id="@+id/navigation_dashboard"
        android:name="com.XXX.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fixFragment
        android:id="@+id/navigation_notifications"
        android:name="com.XXX.ui.my.MyFragment"
        android:label="@string/title_my"
        tools:layout="@layout/fragment_my" />

</navigation>

然后去掉默认的关联,取消方法,activity_main.xml里取消


 app:navGraph="@navigation/mobile_navigation"


最后,在.java里面把导航和你的Fragment手动关联起来:


 NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_activity_user_info);
        NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
        NavigationUI.setupWithNavController(navView, navController);

        FragmentManager supportFragmentManager = getSupportFragmentManager();
        NavHostFragment fragment =
                (NavHostFragment)supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_user_info) ;
        Log.d("fragment.getId()",fragment.getId()+"");
        FixFragmentNavigator fragmentNavigator =
                new FixFragmentNavigator(this, supportFragmentManager, fragment.getId());
        navController.getNavigatorProvider().addNavigator(fragmentNavigator);
        navController.setGraph(R.navigation.mobile_navigation);


本文来源:天下码农 作者:程序猿

相关文章
没有相关文章
声明
声明:本站所发表的文章、评论及图片仅代表作者本人观点,与本站立场无关。若文章侵犯了您的相关权益,请及时与我们联系,我们会及时处理,感谢您对本站的支持!联系Email:support@txwb.com,系统开号,技术支持,服务联系QQ:1175525021本站所有有注明来源为天下网吧或天下网吧论坛的原创作品,各位转载时请注明来源链接!
天下网吧·网吧天下
  • 本周热门
  • 本月热门
  • 阅读排行