Android ABC Navigation源码解析和使用封装

写在开始

本文从浅入深,介绍了navigate的使用和源码及使用封装,一文带你了解Google为什么设计这个组件

Navigation基本使用

fragment参数

  • defaultNavHost:会和系统返回键相关联,会判断fragment返回栈里是否还有fragment来操作
  • navGraph:页面路由结构

navigation参数

  • startDestination :默认显示的fragment

页面参数标签

  • argument 创建当前fragment携带的参数
  • action 指定from和destination
  • deeplink 三方应用或通过隐式方法拉起

deeplink原理

1、通过intent.getdata获取uri对象

2、通过uri调用matchDeepLink方法获取deepLinkMatch

3、通过deepLink对象matchingDeepLink.getDestination().buildDeepLinkIds()获取一个id数组

4、数组包含根节点到当前节点的一个id数组

5、遍历打开每一个节点

更多使用方法参照官方文档

NavController源码分析

NavHostFragment

public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    final Context context = requireContext();

    mNavController = new NavHostController(context);//创建了NavHostController来管理跳转
    mNavController.setLifecycleOwner(this);
    ...
    onCreateNavController(mNavController);//添加跳转器
}

onCreateNavController(mNavController);添加了Dialog的导航器和Fragment的导航器,代码如下

@CallSuper
protected void onCreateNavController(@NonNull NavController navController) {
    navController.getNavigatorProvider().addNavigator(
            new DialogFragmentNavigator(requireContext(), getChildFragmentManager()));
    navController.getNavigatorProvider().addNavigator(createFragmentNavigator());
}

NavController类

public NavController(@NonNull Context context) {
    ...
    mNavigatorProvider.addNavigator(new NavGraphNavigator(mNavigatorProvider));
    mNavigatorProvider.addNavigator(new ActivityNavigator(mContext));
}

NavController的构造函数里也有两个导航器分别是Activity和NavGraph

private NavigatorProvider mNavigatorProvider = new NavigatorProvider();

mNavigatorProvider其实就是一个hashMap的封装

private static final HashMap<Class<?>, String> sAnnotationNames = new HashMap<>();

NavGraphNavigator:当页面节点信息解析完成后跳转到默认界面

Navigator:

public abstract class Navigator<D extends NavDestination>

需要传入一个NavDestination,这样一种Navigator只能创建一种页面节点跳转

Android ABC Navigation源码解析和使用封装_第1张图片

名称 作用
Name 导航器名称,不能为空
Extras 提供额外的行为,过场动画、过渡元素
navigate 真正的跳转逻辑
popBackStack 拦截系统返回键实现自己的返回栈

ActivityNavigator

@Navigator.Name("activity")
public class ActivityNavigator extends Navigator<ActivityNavigator.Destination> 

name:activity

ActivityNavigator.Destination:用于获取目标页需要的信息,用来跳转的时候包装Intent

public NavDestination navigate(@NonNull Destination destination, @Nullable Bundle args,
        @Nullable NavOptions navOptions, @Nullable Navigator.Extras navigatorExtras) {
    if (destination.getIntent() == null) {//如果intent为null就不跳转
        throw new IllegalStateException("Destination " + destination.getId()
                + " does not have an Intent set.");
    }
    Intent intent = new Intent(destination.getIntent());//构建intent
    if (args != null) {
        intent.putExtras(args);//添加参数
        String dataPattern = destination.getDataPattern();
        if (!TextUtils.isEmpty(dataPattern)) {
            // Fill in the data pattern with the args to build a valid URI
            StringBuffer data = new StringBuffer();
            Pattern fillInPattern = Pattern.compile("\\{(.+?)\\}");
            Matcher matcher = fillInPattern.matcher(dataPattern);
            while (matcher.find()) {
                String argName = matcher.group(1);
                if (args.containsKey(argName)) {
                    matcher.appendReplacement(data, "");
                    //noinspection ConstantConditions
                    data.append(Uri.encode(args.get(argName).toString()));
                } else {
                    throw new IllegalArgumentException("Could not find " + argName + " in "
                            + args + " to fill data pattern " + dataPattern);
                }
            }
            matcher.appendTail(data);
            intent.setData(Uri.parse(data.toString()));
        }
    }
    if (navigatorExtras instanceof Extras) {//额外信息
        Extras extras = (Extras) navigatorExtras;
        intent.addFlags(extras.getFlags());
    }
    if (!(mContext instanceof Activity)) {//如果当前页面不是activity需要在新activity栈打开
        // If we're not launching from an Activity context we have to launch in a new task.
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    if (navOptions != null && navOptions.shouldLaunchSingleTop()) {
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
    }
    if (mHostActivity != null) {//***把当前节点id加进去,可以知道这个页面是谁打开的,多用于埋点等行为
        final Intent hostIntent = mHostActivity.getIntent();
        if (hostIntent != null) {
            final int hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0);
            if (hostCurrentId != 0) {
                intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId);
            }
        }
    }
    final int destId = destination.getId();
    intent.putExtra(EXTRA_NAV_CURRENT, destId);
    if (navOptions != null) {
        // For use in applyPopAnimationsToPendingTransition()
        intent.putExtra(EXTRA_POP_ENTER_ANIM, navOptions.getPopEnterAnim());
        intent.putExtra(EXTRA_POP_EXIT_ANIM, navOptions.getPopExitAnim());
    }
    //下面就是跳转
    if (navigatorExtras instanceof Extras) {
        Extras extras = (Extras) navigatorExtras;
        ActivityOptionsCompat activityOptions = extras.getActivityOptions();
        if (activityOptions != null) {
            ActivityCompat.startActivity(mContext, intent, activityOptions.toBundle());
        } else {
            mContext.startActivity(intent);
        }
    } else {
        mContext.startActivity(intent);
    }
    if (navOptions != null && mHostActivity != null) {
        int enterAnim = navOptions.getEnterAnim();
        int exitAnim = navOptions.getExitAnim();
        if (enterAnim != -1 || exitAnim != -1) {
            enterAnim = enterAnim != -1 ? enterAnim : 0;
            exitAnim = exitAnim != -1 ? exitAnim : 0;
            mHostActivity.overridePendingTransition(enterAnim, exitAnim);
        }
    }

    // You can't pop the back stack from the caller of a new Activity,
    // so we don't add this navigator to the controller's back stack
    return null;
}

其实就是封装了Activity的跳转

DialogFragmentNavigator

@Navigator.Name("dialog")
public final class DialogFragmentNavigator extends Navigator<DialogFragmentNavigator.Destination>

name:dialog

DialogFragmentNavigator.Destination:只提供className

public NavDestination navigate(@NonNull final 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 = mFragmentManager.getFragmentFactory().instantiate(
            mContext.getClassLoader(), className);//通过类名构造出Fragment
    if (!DialogFragment.class.isAssignableFrom(frag.getClass())) {//判断当前fragment是否是DialogFragment的子类
        throw new IllegalArgumentException("Dialog destination " + destination.getClassName()
                + " is not an instance of DialogFragment");
    }
    final DialogFragment dialogFragment = (DialogFragment) frag;
    dialogFragment.setArguments(args);
    dialogFragment.getLifecycle().addObserver(mObserver);

    dialogFragment.show(mFragmentManager, DIALOG_TAG + mDialogCount++);

    return destination;
}

封装了DialogFragment的跳转

FragmentNavigator

@Navigator.Name("fragment")
public class FragmentNavigator extends Navigator<FragmentNavigator.Destination> 

name:fragment

FragmentNavigator.Destination:也只提供className

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);//创建fragment实例
    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);
    }

//直接调用replace并没有hide或者show,如果需要这里需要重写一个Navigator
    ft.replace(mContainerId, frag);
    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;
    }
}

NavGraphNavigator

@Navigator.Name("navigation")
public class NavGraphNavigator extends Navigator<NavGraph> 

name:navigation

NavGraph:这里传入的不是Destination而是NavGraph

public class NavGraph extends NavDestination

NavGraph是NavDestination的子类

final SparseArrayCompat<NavDestination> mNodes = new SparseArrayCompat<>();

mNodes对象存储着所有的节点信息

private int mStartDestId;
private String mStartDestIdName;

mStartDestId就是资源文件中的默认启动fragment

这个NavGraphNavigation就是在资源文件解析完成后创建的

NavController里有

public void setGraph(@NavigationRes int graphResId, @Nullable Bundle startDestinationArgs) {
    setGraph(getNavInflater().inflate(graphResId), startDestinationArgs);
}

会把资源id传进来

public NavGraph inflate(@NavigationRes int graphResId) {
    Resources res = mContext.getResources();
    XmlResourceParser parser = res.getXml(graphResId);
    final AttributeSet attrs = Xml.asAttributeSet(parser);
    try {
        int type;
        while ((type = parser.next()) != XmlPullParser.START_TAG
                && type != XmlPullParser.END_DOCUMENT) {
            // Empty loop
        }
        if (type != XmlPullParser.START_TAG) {
            throw new XmlPullParserException("No start tag found");
        }

        String rootElement = parser.getName();
        NavDestination destination = inflate(res, parser, attrs, graphResId);
        if (!(destination instanceof NavGraph)) {
            throw new IllegalArgumentException("Root element <" + rootElement + ">"
                    + " did not inflate into a NavGraph");
        }
        return (NavGraph) destination;
    } catch (Exception e) {
        throw new RuntimeException("Exception inflating "
                + res.getResourceName(graphResId) + " line "
                + parser.getLineNumber(), e);
    } finally {
        parser.close();
    }
}

或者在资源文件里定义的属性

private NavDestination inflate(@NonNull Resources res, @NonNull XmlResourceParser parser,
        @NonNull AttributeSet attrs, int graphResId)
        throws XmlPullParserException, IOException {
    Navigator<?> navigator = mNavigatorProvider.getNavigator(parser.getName());
    final NavDestination dest = navigator.createDestination();

    dest.onInflate(mContext, attrs);

    final int innerDepth = parser.getDepth() + 1;
    int type;
    int depth;
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && ((depth = parser.getDepth()) >= innerDepth
            || type != XmlPullParser.END_TAG)) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        if (depth > innerDepth) {
            continue;
        }

        final String name = parser.getName();
        if (TAG_ARGUMENT.equals(name)) {
            inflateArgumentForDestination(res, dest, attrs, graphResId);
        } else if (TAG_DEEP_LINK.equals(name)) {
            inflateDeepLink(res, dest, attrs);
        } else if (TAG_ACTION.equals(name)) {
            inflateAction(res, dest, attrs, parser, graphResId);
        } else if (TAG_INCLUDE.equals(name) && dest instanceof NavGraph) {
            final TypedArray a = res.obtainAttributes(
                    attrs, androidx.navigation.R.styleable.NavInclude);
            final int id = a.getResourceId(
                    androidx.navigation.R.styleable.NavInclude_graph, 0);
            ((NavGraph) dest).addDestination(inflate(id));
            a.recycle();
        } else if (dest instanceof NavGraph) {
            ((NavGraph) dest).addDestination(inflate(res, parser, attrs, graphResId));//递归调用添加所有的navigation
        }
    }

    return dest;
}

inflat方法解析了argument,deepLink,Action和Include

public NavDestination navigate(@NonNull NavGraph destination, @Nullable Bundle args,
        @Nullable NavOptions navOptions, @Nullable Extras navigatorExtras) {
    int startId = destination.getStartDestination();
    if (startId == 0) {
        throw new IllegalStateException("no start destination defined via"
                + " app:startDestination for "
                + destination.getDisplayName());
    }
    //获取默认的Destination
    NavDestination startDestination = destination.findNode(startId, false);
    if (startDestination == null) {
        final String dest = destination.getStartDestDisplayName();
        throw new IllegalArgumentException("navigation destination " + dest
                + " is not a direct child of this NavGraph");
    }
    //获取相应的Destination
    Navigator<NavDestination> navigator = mNavigatorProvider.getNavigator(
            startDestination.getNavigatorName());
            //跳转
    return navigator.navigate(startDestination, startDestination.addInDefaultArgs(args),
            navOptions, navigatorExtras);
}

Navigator的作用就是统一APP的跳转工作,所有的跳转方式都是由一种跳转方式来跳转

Navigation的动态使用

使用Kotlin DSL

可以在 Kotlin 代码中(而不是在 XML 资源内部)以声明方式构建图形。如果您希望为应用动态构建导航,该方法会非常有用。例如,您的应用可以从外部网络服务下载并缓存导航配置,然后使用该配置在 Activity 的 onCreate() 函数中动态构建导航图。

使用基于 XML 的导航图时,Android 构建流程会解析图形资源文件,并为图形中定义的每个 id 属性指定数字常量。代码中的这些常量可以通过生成的资源类 R.id 来获取。

例如,以下 XML 图代码段使用 id、home 声明了一个 Fragment 目的地:

<navigation ...>
   <fragment android:id="@+id/home" ... />
   ...
navigation>

使用 Kotlin DSL 以编程方式构建图形时,不会发生这种解析和生成常量的过程

必须为具有 id 值的每个目的地、操作和参数定义自己的常量。每个 ID 在配置更改中必须是唯一且一致的

object nav_graph {
    // Counter for id's. First ID will be 1.
    var id_counter = 1

    val id = id_counter++

    object dest {
       val home = id_counter++
       val plant_detail = id_counter++
    }

    object action {
       val to_plant_detail = id_counter++
    }

    object args {
       const val plant_id = "plantId"
    }
}
class GardenActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_garden)

        val navHostFragment = supportFragmentManager
                .findFragmentById(R.id.nav_host) as NavHostFragment

        navHostFragment.navController.apply {
            graph = createGraph(nav_graph.id, nav_graph.dest.home) {
                fragment<HomeViewPagerFragment>(nav_graph.dest.home) {
                    label = getString(R.string.home_title)
                    action(nav_graph.action.to_plant_detail) {
                        destinationId = nav_graph.dest.plant_detail
                    }
                }
                fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
                    label = getString(R.string.plant_detail_title)
                    argument(nav_graph.args.plant_id) {
                        type = NavType.StringType
                    }
                }
            }
        }
    }
}

使用 fragment() DSL 构建器函数定义了两个 Fragment 目的地。该函数需要目的地的 ID。该函数还接受用于其他配置的可选 lambda(如目的地 label)以及用于操作、参数和深层链接的嵌入式构建器函数。

管理每个目的地界面的 Fragment 类将作为放在尖括号 (<>) 中的参数化类型传入。这与在使用 XML 定义的 Fragment 目的地上设置 android:name 属性具有相同的效果。

构建并设置图形后,即可使用 NavController.navigate()home 导航到 plant_detail,如以下示例所示:

private fun navigateToPlant(plantId: String) {  
  val args = bundleOf(nav_graph.args.plant_id to plantId)     
  findNavController().navigate(nav_graph.action.to_plant_detail, args)
}
fragment<FragmentDestination>(nav_graph.dest.fragment_dest_id) {
   label = getString(R.string.fragment_title)
   // arguments, actions, deepLinks...
}
activity(nav_graph.dest.activity_dest_id) {
    label = getString(R.string.activity_title)
    // arguments, actions, deepLinks...

    activityClass = ActivityDestination::class
}
navigation(nav_graph.dest.nav_graph_dest, nav_graph.dest.start_dest) {
   // label, arguments, actions, other destinations, deep links
}
// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
    id = nav_graph.dest.custom_dest_id
}
addDestination(customDestination)

还可以使用一元加号运算符 (+) 将新构造的目的地直接添加到图形中

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    id = nav_graph.dest.custom_dest_id
}

fragment示例

fragment<PlantDetailFragment>(nav_graph.dest.plant_detail) {
    label = getString(R.string.plant_details_title)
    deepLink("${baseUri}/{id}")
    deepLink("${baseUri}/{id}?name={plant_name}")
    argument(nav_graph.args.plant_id) {
       type = NavType.IntType
    }
    argument(nav_graph.args.plant_name) {
        type = NavType.StringType
        nullable = true
    }
  action(nav_graph.action.to_plant_detail) {
    destinationId = nav_graph.dest.plant_detail
    navOptions {
        anim {
            enter = R.anim.nav_default_enter_anim
            exit = R.anim.nav_default_exit_anim
            popEnter = R.anim.nav_default_pop_enter_anim
            popExit = R.anim.nav_default_pop_exit_anim
        }
        popUpTo(nav_graph.dest.start_dest) {
            inclusive = true // default false
        }
        // if popping exclusively, you can specify popUpTo as
        // a property. e.g. popUpTo = nav_graph.dest.start_dest
        launchSingleTop = true // default false
    }
}
}

使用注解编译时生成

(只提供思路和简单代码实现,以学习为主)

注解类

ActivityDestination
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
annotation class ActivityDestination(val pageUrl: String, val needLogin: Boolean = false, val asStart: Boolean = false)
FragmentDestination
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
annotation class FragmentDestination(val pageUrl: String, val needLogin: Boolean = false, val asStart: Boolean = false)

这两个注解类我们需要拿到页面的url,needLogin和asStart是否作为启动页面

编译时运行类

NavProcessor
@SupportedAnnotationTypes("com.babyname.libnavannotation.ActivityDestination", "com.babyname.libnavannotation.FragmentDestination")
@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
class NavProcessor : AbstractProcessor() {
    private val OUTPUT_FILE_NAME = "navigation.json"
    lateinit var messager: Messager
    lateinit var filer: Filer

    override fun process(set: Set<TypeElement?>, roundEnvironment: RoundEnvironment): Boolean {
       messager.printMessage(Diagnostic.Kind.NOTE,"开始生成json导航")
        var fragmentDestination = roundEnvironment.getElementsAnnotatedWith(FragmentDestination::class.java)
        var activityDestination = roundEnvironment.getElementsAnnotatedWith(ActivityDestination::class.java)
        if (fragmentDestination.isNotEmpty() || activityDestination.isNotEmpty()) {
            var destMap = HashMap<String, JSONObject>()
            handleDestination(fragmentDestination, FragmentDestination::class.java, destMap)
            handleDestination(activityDestination, ActivityDestination::class.java, destMap)

            try {
                val resourcePath = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME).toUri().path
                val appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4)
                val assetsPath = appPath + "src/main/assets/"

                val file = File(assetsPath)
                if (!file.exists()) {
                    file.mkdirs()
                }

                val outPutFile = File(file, OUTPUT_FILE_NAME)
                if (outPutFile.exists()) {
                    outPutFile.delete()
                }

                outPutFile.createNewFile()
                val content = JSON.toJSONString(destMap)
                outPutFile.outputStream().writer(Charsets.UTF_8).buffered().use {
                    it.write(content)
                }


            } catch (e: Exception) {

            }
        }
        return true
    }

    private fun handleDestination(destination: Set<Element>, javaClazz: Class<out Annotation>, destMap: HashMap<String, JSONObject>) {
        destination.forEach { element ->
            val typeElement = element as TypeElement
            val clazzName = typeElement.qualifiedName.toString()
            val id = abs(clazzName.hashCode())
            var needLogin = false
            var asStarter = false
            var pageUrl = ""
            var isFragment = false

            typeElement.getAnnotation(javaClazz).let {
                if (it is FragmentDestination) {
                    pageUrl = it.pageUrl
                    asStarter = it.asStart
                    needLogin = it.needLogin
                    isFragment = true
                }
                if (it is ActivityDestination) {
                    pageUrl = it.pageUrl
                    asStarter = it.asStart
                    needLogin = it.needLogin
                }
                if (destMap.containsKey(pageUrl)) {
                    messager.printMessage(Diagnostic.Kind.ERROR, "不允许有多个相同URL的页面")
                } else {
                    val obj = JSONObject().apply {
                        put("id", id)
                        put("needLogin", needLogin)
                        put("asStarter", asStarter)
                        put("pageUrl", pageUrl)
                        put("clazzName", clazzName)
                        put("isFragment", isFragment)
                    }
                    destMap[pageUrl] = obj

                }
            }


        }
    }

    @Synchronized
    override fun init(processingEnvironment: ProcessingEnvironment) {
        super.init(processingEnvironment)
        messager = processingEnv.messager
        filer = processingEnv.filer
    }
}

这里我们主要就是重新process方法,根据注解的信息生成我们需要的navigation.json文件

这一步就是把需要写死的xml文件动态化

动态读取文件应用

AppConfig
object AppConfig {
    @JvmStatic
    private lateinit var sDestConfig: HashMap<String, Destination>

    @JvmStatic
    private lateinit var sBottomBar: BottomBar

    val destConfig: HashMap<String, Destination>
        get() {
            if (!::sDestConfig.isInitialized) {
                val content = parseFile("navigation.json")
                sDestConfig = JSON.parseObject(content, object : TypeReference<HashMap<String, Destination>>() {})
            }
            return sDestConfig
        }

    fun parseFile(fileName: String): String {
        val assets = AppGlobals.application.resources.assets
        val fileBuilder = StringBuilder()
        assets.open(fileName).reader().buffered().use {
            var lines: String = ""
            while (it.readLine().also { line -> if (line != null) lines = line } != null) {
                fileBuilder.append(lines)
            }
        }
        return fileBuilder.toString()
    }

    val bottomBar: BottomBar
        get() {
            if (!::sBottomBar.isInitialized) {
                val content = parseFile("main_tabs_config.json")
                sBottomBar = JSON.parseObject(content, BottomBar::class.java)
            }
            return sBottomBar
        }

}

这里有两个方法一个是destConfig,一个是bottombar,我们先不关注bottomBar,这里其实就是读取destConfig用于动态生成NaviController的NaviGraph,下面是通过读取的destConfig的数据生成

object NavGraphBuilder {
    fun build(ctrl: NavController, context: Context, fragmentManager: FragmentManager, containsId: Int) {
        val provider = ctrl.navigatorProvider
        //val fragmentNavigator = provider.getNavigator(FragmentNavigator::class.java)
        val fragmentNavigator = FixFragmentNavigator(context, fragmentManager, containsId)
        provider.addNavigator(fragmentNavigator)
        val activityNavigator = provider.getNavigator(ActivityNavigator::class.java)
        val naviGraph = NavGraph(NavGraphNavigator(provider))

        val destConfig = AppConfig.destConfig
        destConfig.values.forEach {
            if (it.isIsFragment) {
                fragmentNavigator.createDestination().run {
                    className = it.clazzName
                    id = it.id
                    addDeepLink(it.pageUrl)
                    naviGraph.addDestination(this)
                }
            } else {
                activityNavigator.createDestination().run {
                    id = it.id
                    addDeepLink(it.pageUrl)
                    setComponentName(ComponentName(AppGlobals.application.packageName, it.clazzName))
                    naviGraph.addDestination(this)
                }
            }
            if (it.isAsStarter) {
                naviGraph.startDestination = it.id
            }
        }
        ctrl.graph = naviGraph
    }
}

**这里通过addDeepLink的方式将Fragment加入NaviGraph的destination,通过addDeepLink和setComponentName的方式将Acitivity加入NaviGraph的destination

FragmentNavigator的种种

要做什么?

找到解决Navigator的replace方案解决方法

基础知识

Fragment的加载方式有哪些?

① 静态加载

即静态的将Fragment添加在XML布局文件中,这种方式在平时开发中使用频率较低。

② 动态加载
即在代码中动态添加Fragment
步骤如下:
1、获取FragmentManager
2、通过FragmentManager获取FragmentTransaction
3、通过FragmentTransaction向指定布局区域添加或者替换Fragment
4、提交修改即可

Fragment的生命周期

Android ABC Navigation源码解析和使用封装_第2张图片
注意:在Fragment和Activity一起启动时,启动前Activity的方法在Fragment方法之前执行。销毁时,Activity方法在Fragment方法之后执行,例如Activity onStart()方法执行完之后才会执行Fragment onStart()的方法,反之,销毁时,Fragment onPause()执行后才会执行Activity onPause()方法。

在onAttach()方法回调的时候,说明Fragment已经附着到了Activity上,在onAttach方法中获取它所在的Activity对象并且保存为全局属性,以便后面的方法中使用,切记不要使用getActivity来获取它所在的activity对象,因为这个方法有的时候会返回为空,因为在某些情况下,Activity有可能被回收。

使用show() hizde()方法来切换多个Fragment页面的时候,Fragment的生命周期

当我们在某个Activity中add了多个Fragment实例,切换不同的Fragment页面的时候,是通过show()和hide()方法来实现的,那么Fragment的生命周期方法是不会走的,只会走onHiddenChanged(boolean hidden)方法,我们可以通过这个方法来监听Fragment页面的显示和隐藏。

但是当我们点击home键,把整体页面切到后台的时候,宿主Activity和它里面的各个Fragment的onPause(),onStop()方法都会执行。我们再点击应用图标把应用当前页面且回到前台的时候,宿主Activity和它里面的各个Fragment的onStart()、onResume()方法都会执行。当然Activity的onRestart()方法也会执行。

要规避Fragment执行逻辑,只需要用isVisible方法进行判断即可。

使用replace() 或者 add() remove() 方法来切换多个Fragment页面的时候,Fragment的生命周期

通过remove()和add()方法显示隐藏Fragment1,被add进来的Fragment的生命周期为

onAttach、onCreate、onCreateView、onActivityCreated、onStart、onResume
被remove掉的Fragment2的生命周期为

onPause、onStop、onDestroyView、onDestroy、onDetach

replace相当于add和remove两个方法一起作用的结果,所以结论和上面一样。这里需要注意的是,调用replace视图是会被销毁重建的,而且调用getActivity方法会返回null,使用Kotlin的时候需要获取Activity然后用?.let方法进行调用,就是出于这个原因,编译期给出的优化方案

当把隐藏(remove掉)的Fragment加入到回退栈中,对其生命周期有什么影响

被添加进来的Fragment2的生命周期和之前没有变化,但是被加入回退栈中的Fragment1的生命周期变为了

onPause、onStop、onDestroyView

只是把Fragment1的界面销毁了,Fragment1实例并没有销毁

此时我们点击返回键

返回的Fragment1的生命周期为

onCreateView、onStart、onResume
Fragment2的生命周期为:

onPause、onStop、onDestroyView、onDestroy、onDetach

如果以ViewPager的形式添加多个Fragment,滑动切换Fragment,那么Fragment切换的时候,生命周期也不会执行,onHiddenChanged也不会执行,可以通过**setUserVisibleHint(boolean isVisibleToUser)**方法监听Fragment页面的显示与隐藏。

当把隐藏(hide掉)的Fragment加入到回退栈中,对其生命周期有什么影响

被添加进来的Fragment2的生命周期和之前没有变化,但是被加入回退栈中的Fragment1的生命周期变为了

Fragment1的界面没有任何变量,Fragment1实例也没有销毁

此时我们点击返回键

返回的Fragment1的生命周期为

Fragment2的生命周期为:

onPause、onStop、onDestroyView、onDestroy、onDetach

不会调用onResume和Activity还是很不一样的,因为系统默认Fragment并没有消失,但是onHiddenChaged会调用

开始改造:

原FragmentNavigator

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);
    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));//注释1
        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;
    }
}

可以看到,原始的FragmentNavigator使用了replace方法替换了Fragment,我们要改造他让他用hide和show的方法来添加Fragment

@Navigator.Name("fixFragment")
class FixFragmentNavigator(private val mContext: Context, private val mFragmentManager: FragmentManager, private val mContainerId: Int) : FragmentNavigator(mContext, mFragmentManager, mContainerId) {
    override fun navigate(destination: Destination, args: Bundle?,
                          navOptions: NavOptions?, navigatorExtras: Navigator.Extras?): NavDestination? {
        if (mFragmentManager.isStateSaved) {
            return null
        }
        var className = destination.className
        if (className[0] == '.') {
            className = mContext.packageName + className
        }
        //val frag = instantiateFragment(mContext, mFragmentManager,className, args)
        //frag.arguments = args
        var ft = mFragmentManager.beginTransaction()
        var enterAnim = navOptions?.enterAnim ?: -1
        var exitAnim = navOptions?.exitAnim ?: -1
        var popEnterAnim = navOptions?.popEnterAnim ?: -1
        var popExitAnim = navOptions?.popExitAnim ?: -1

        //ft.replace(mContainerId, frag)
        val fragmentNow = mFragmentManager.primaryNavigationFragment

        val tag = destination.id.toString()
        var frag: Fragment? = mFragmentManager.findFragmentByTag(tag)
        if (fragmentNow != null && frag != null && frag == fragmentNow) {
            return null
        }
        if (fragmentNow != null) {
            ft.hide(fragmentNow)
        }
        if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
            enterAnim = if (enterAnim != -1) enterAnim else 0
            exitAnim = if (exitAnim != -1) exitAnim else 0
            popEnterAnim = if (popEnterAnim != -1) popEnterAnim else 0
            popExitAnim = if (popExitAnim != -1) popExitAnim else 0
            ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
        }
        if (frag != null) {
            ft.show(frag)
        } else {
            frag = instantiateFragment(mContext, mFragmentManager, className, args)
            frag!!.arguments = args
            ft.add(mContainerId, frag, tag)
        }

        ft.setPrimaryNavigationFragment(frag)
        @IdRes val destId = destination.id

        val mBackStack = FragmentNavigator::class.java.getDeclaredField("mBackStack")
                .also { it.isAccessible = true }.get(this) as ArrayDeque<Int>
        val initialNavigation = mBackStack.isEmpty()
        // TODO Build first class singleTop behavior for fragments
        val isSingleTopReplacement = (navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && mBackStack.peekLast() == destId)

        val isAdded = when {
            initialNavigation -> {
                true
            }
            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

                    // 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))
                }
                false
            }
            else -> {
                true
            }
        }

        if (navigatorExtras is Extras) {
            for ((key, value) in navigatorExtras.sharedElements) {
                ft.addSharedElement(key!!, value!!)
            }
        }
        ft.setReorderingAllowed(true)
        ft.commit()
        // The commit succeeded, update our view of the world
        return if (isAdded) {
            mBackStack.add(destId)
            destination
        } else {
            null
        }
    }

    private fun generateBackStackName(backStackIndex: Int, destId: Int): String? {
        return "$backStackIndex-$destId"
    }

    override fun createDestination(): Destination {
        return FixDestination(this)
    }

    @NavDestination.ClassType(Fragment::class)
    class FixDestination(fragmentNavigator: Navigator<out Destination?>) : Destination(fragmentNavigator) {
        private var mClassName: String? = null

        /**
         * Construct a new fragment destination. This destination is not valid until you set the
         * Fragment via [.setClassName].
         *
         * @param navigatorProvider The [NavController] which this destination
         * will be associated with.
         */
        constructor(navigatorProvider: NavigatorProvider) : this(navigatorProvider.getNavigator(FixFragmentNavigator::class.java)) {}

        @CallSuper
        override fun onInflate(context: Context, attrs: AttributeSet) {
            super.onInflate(context, attrs)
            val a = context.resources.obtainAttributes(attrs,
                    R.styleable.FragmentNavigator)
            val className = a.getString(R.styleable.FragmentNavigator_android_name)
            className?.let { setClassName(it) }
            a.recycle()
        }

        override fun toString(): String {
            val sb = StringBuilder()
            sb.append(super.toString())
            sb.append(" class=")
            if (mClassName == null) {
                sb.append("null")
            } else {
                sb.append(mClassName)
            }
            return sb.toString()
        }
    }
}

改造后的源码,主要思路就是用tag来判断是否是已经添加过的Fragment来hide和show,需要注意的是:

  1. addToBackStack方法会pop出之前的Fragment,来节约内存。这里需要把注释1处的addToBackStack方法删除,避免缓存的Fragment被销毁

  2. 需要重写Destination为FixDestination,修改他的mNavigatorName为fixFragment,这里是根据@Navigator.Name(“fixFragment”)来拿到名字的,具体方法如下:

    static String getNameForNavigator(@NonNull Class<? extends Navigator> navigatorClass) {
        String name = sAnnotationNames.get(navigatorClass);
        if (name == null) {
            Navigator.Name annotation = navigatorClass.getAnnotation(Navigator.Name.class);
            name = annotation != null ? annotation.value() : null;
            if (!validateName(name)) {
                throw new IllegalArgumentException("No @Navigator.Name annotation found for "
                        + navigatorClass.getSimpleName());
            }
            sAnnotationNames.put(navigatorClass, name);
        }
        return name;
    }
    

这里也刚好学一下注解的动态使用。主要思想就是拿到传进来类的注解,然后再拿出名字即可。

  1. 这了留一个问题,就是hide和show的改造只是适用于Navigator的Fragment,其他的继续使用原生的FragmentNavigator。

动态绑定NavController和navGraph

NAVGraphBuilder

object NavGraphBuilder {
    fun build(ctrl: NavController, context: Context, fragmentManager: FragmentManager, containsId: Int) {
        val provider = ctrl.navigatorProvider
        val fragmentNavigator = FragmentNavigator(context, fragmentManager, containsId)
        val fixFragmentNavigator = FixFragmentNavigator(context, fragmentManager, containsId)
        provider.addNavigator(fixFragmentNavigator)
        provider.addNavigator(fragmentNavigator)
        val activityNavigator = provider.getNavigator(ActivityNavigator::class.java)
        val navGraph = NavGraph(NavGraphNavigator(provider))

        val destConfig = AppConfig.destConfig
        destConfig.values.forEach {
            if (it.isIsFragment) {
                if (isBottomBar(it.pageUrl)) {
                    fixFragmentNavigator.createDestination(). run {
                        className = it.clazzName
                        id = it.id
                        addDeepLink(it.pageUrl)
                        navGraph.addDestination(this)
                    }
                } else {
                    fragmentNavigator.createDestination().run {
                        className = it.clazzName
                        id = it.id
                        addDeepLink(it.pageUrl)
                        navGraph.addDestination(this)
                    }
                }
            } else {
                activityNavigator.createDestination().run {
                    id = it.id
                    addDeepLink(it.pageUrl)
                    setComponentName(ComponentName(AppGlobals.application.packageName, it.clazzName))
                    navGraph.addDestination(this)
                }
            }
            if (it.isAsStarter) {
                navGraph.startDestination = it.id
            }
        }
        ctrl.graph = navGraph
    }

    private fun isBottomBar(tag: String?): Boolean {
        AppConfig.bottomBar.tabs.forEach {
            if (it.isEnable && tag == it.pageUrl) {
                return true
            }
        }
        return false
    }
}

思路:

  1. 创建FixFragmentNavigator和原生FragmentNavigator
  2. 获取原生的ActivityNavigator
  3. 为他们分别匹配解析出来的Destination,这里我根据底部bar的enable来判断哪些需要使用FixFragmentNavigator
  4. 绑定navGraph后返回即可

使用封装

BaseActivity

open abstract class BaseActivity : AppCompatActivity() {

    val naviController by lazy { Navigation.findNavController(this, getNavCtrlResId()) }
    
    abstract fun getNavCtrlResId():Int
}

BaseFragment

open abstract class BaseFragment : Fragment() {
    val navController : NavController by lazy {
            Navigation.findNavController(requireActivity(), getNavCtrlResId())
    }

    abstract fun getNavCtrlResId():Int
}

NavigationUtil

fun jump(navController: NavController, url: String, bundle: Bundle? = null) {
    destConfig[url]?.id?.let { navController.navigate(it, bundle) }
}

调用

NavigationUtilKt.jump(getNavController(),"main/fragment/orderList",null);
NavigationUtilKt.jump(getNavController(), "main/activity/list", bundle);

BottomNavigationBar封装

BottomBarNavigation.json

{
  "activeColor": "#333333",
  "inActiveColor": "#666666",
  "selectTab": 0,
  "tabs": [
    {
      "size": 24,
      "enable": true,
      "index": 0,
      "pageUrl": "main/tabs/qiming",
      "title": "起名"
    },
    {
      "size": 24,
      "enable": true,
      "index": 1,
      "pageUrl": "main/tabs/jieming",
      "title": "解名"
    },
    {
      "size": 24,
      "enable": true,
      "index": 2,
      "pageUrl": "main/tabs/vip",
      "title": "会员"
    },
    {
      "size": 24,
      "enable": true,
      "index": 3,
      "pageUrl": "main/tabs/mine",
      "title": "我的"
    }
  ]
}

这个不用多说了,就是一个Navigationbar的一个描述性配置

解析AppConfig

object AppConfig {
    @JvmStatic
    private lateinit var sDestConfig: HashMap<String, Destination>

    @JvmStatic
    private lateinit var sBottomBar: BottomBar

    val destConfig: HashMap<String, Destination>
        get() {
            if (!::sDestConfig.isInitialized) {
                val content = parseFile("navigation.json")
                sDestConfig = JSON.parseObject(content, object : TypeReference<HashMap<String, Destination>>() {})
            }
            return sDestConfig
        }

    fun parseFile(fileName: String): String {
        val assets = AppGlobals.application.resources.assets
        val fileBuilder = StringBuilder()
        assets.open(fileName).reader().buffered().use {
            var lines: String = ""
            while (it.readLine().also { line -> if (line != null) lines = line } != null) {
                fileBuilder.append(lines)
            }
        }
        return fileBuilder.toString()
    }

    val bottomBar: BottomBar
        get() {
            if (!::sBottomBar.isInitialized) {
                val content = parseFile("main_tabs_config.json")
                sBottomBar = JSON.parseObject(content, BottomBar::class.java)
            }
            return sBottomBar
        }

}

这里看bottomBar的逻辑就行了,其实就是解析了Json

BottomBar解析类

class BottomBar @SuppressLint("RestrictedApi") constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : BottomNavigationView(context!!, attrs, defStyleAttr) {
    private val config: BottomBar = AppConfig.bottomBar

    @JvmOverloads
    constructor(context: Context?, attrs: AttributeSet? = null) : this(context, attrs, 0) {
    }

    companion object {
        private val sIcons = intArrayOf(R.drawable.tab_menu_icon_qiming, R.drawable.tab_menu_icon_jieming, R.drawable.tab_menu_icon_vip, R.drawable.tab_menu_icon_mine)
        fun getItemId(pageUrl: String): Int {
            val destination: Destination = AppConfig.destConfig[pageUrl] ?: return -1
            return destination.id
        }
    }

    init {
        val state = arrayOfNulls<IntArray>(2)
        state[0] = intArrayOf(android.R.attr.state_selected)
        state[1] = intArrayOf()
        val colors = intArrayOf(Color.parseColor(config.activeColor), Color.parseColor(config.inActiveColor))
        val stateList = ColorStateList(state, colors)
        itemTextColor = stateList
        itemIconTintList = null
        //LABEL_VISIBILITY_LABELED:设置按钮的文本为一直显示模式
        //LABEL_VISIBILITY_AUTO:当按钮个数小于三个时一直显示,或者当按钮个数大于3个且小于5个时,被选中的那个按钮文本才会显示
        //LABEL_VISIBILITY_SELECTED:只有被选中的那个按钮的文本才会显示
        //LABEL_VISIBILITY_UNLABELED:所有的按钮文本都不显示
        labelVisibilityMode = LabelVisibilityMode.LABEL_VISIBILITY_SELECTED
        val tabs: List<BottomBar.TabsBean> = config.tabs
        for (tab in tabs) {
            if (!tab.isEnable) {
                continue
            }
            val itemId = getItemId(tab.pageUrl)
            if (itemId < 0) {
                continue
            }
            val menuItem: MenuItem = menu.add(0, itemId, tab.index, tab.title)
            menuItem.setIcon(sIcons[tab.index])
        }

        //此处给按钮icon设置大小
        var index = 0
        for (tab in config.tabs) {
            if (!tab.isEnable) {
                continue
            }
            val itemId = getItemId(tab.pageUrl)
            if (itemId < 0) {
                continue
            }
            val iconSize = Util.dpToPx(context, tab.size.toFloat())
            val itemView = (getChildAt(0) as BottomNavigationMenuView)
                    .getChildAt(index) as BottomNavigationItemView
            itemView.setIconSize(iconSize)
            if (TextUtils.isEmpty(tab.title)) {
                val tintColor = if (TextUtils.isEmpty(tab.tintColor)) Color.parseColor("#ff678f") else Color.parseColor(tab.tintColor)
                itemView.setIconTintList(ColorStateList.valueOf(tintColor))
                //禁止掉点按时 上下浮动的效果
                itemView.setShifting(false)
                /**
                 * 如果想要禁止掉所有按钮的点击浮动效果。
                 * 那么还需要给选中和未选中的按钮配置一样大小的字号。
                 *
                 * 在MainActivity布局的AppBottomBar标签增加如下配置,
                 * @style/active,@style/inActive 在style.xml中
                 * app:itemTextAppearanceActive="@style/active"
                 * app:itemTextAppearanceInactive="@style/inActive"
                 */
            }
            index++
        }

        //底部导航栏默认选中项
        if (config.selectTab !== 0) {
            val selectTab: BottomBar.TabsBean = config.tabs[config.selectTab]
            if (selectTab.isEnable) {
                val itemId = getItemId(selectTab.pageUrl)
                //这里需要延迟一下 再定位到默认选中的tab
                //因为 咱们需要等待内容区域,也就NavGraphBuilder解析数据并初始化完成,
                //否则会出现 底部按钮切换过去了,但内容区域还没切换过去
                post { selectedItemId = itemId }
            }
        }
    }
}

Xml文件调用

<com.pjk.babyname.View.BottomBar
    android:id="@+id/bottom_nav"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/color_FFFFFF"
    app:itemBackground="@null"
    app:itemTextColor="@drawable/navigate_color_selector"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"/>

问答

1. 为什么Fragment的getActivity在kotlin中不是非null

因为只有Fragment的onAttach调用之后,getActivity才不为null

2. 为什么官方的FragmentNavigator使用replace而不是hide/show

1. 为了节约内存空间,避免在多层嵌套的情况下内存占用爆炸的问题

2. 配合ViewModle和DataBinding等其他jetpack工具,视图是被缓存的,所以加载起来很快,故官方使用了replace,这样做对性能的影响没有想象中的那么大

3. 为什么Google要开发Navigation

通过Navigation,安卓统一了常见组件的跳转方式,为跳转协议等应用进行了铺垫

4. 可以对FragmentDialog进行改造吗

可以,针对类型进行区分,然后改造就行了

你可能感兴趣的:(Android,Dev,ABC,Android,jetpack,Navigation,源码)