欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android ABC Jetpack学习之一文学会Navigation(附源码解析和使用封装)

程序员文章站 2022-04-15 18:26:45
fragment 参数defaultNavHost会和系统返回键相关联,会判断fragment返回栈里是否还有fragment来操作navGraph页面路由结构navigation参数startDestination 默认显示的fragment页面参数标签argument 创建当前fragment携带的参数action 指定from和destinationdeeplink 三方应用或通过隐式方法拉起原理1、通过intent.getdata获取uri对象2、通过uri调用match...

写在开始

本文从浅入深,介绍了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 Jetpack学习之一文学会Navigation(附源码解析和使用封装)

名称 作用
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 Jetpack学习之一文学会Navigation(附源码解析和使用封装)
注意:在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进行改造吗

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

本文地址:https://blog.csdn.net/qq_35928566/article/details/108701633