之前的一篇文章Android Jetpack之Navigation对Navigation的使用进行了练习,并且看了一下Navigation的源码。虽然Navigation的功能很强大,不过在xml中配置感觉还是不够灵活,随着项目的增大,页面多了之后xml会变的非常庞大不利于维护。而且使用Navigation做底部导航的时候,每次都会新建Fragment,这个也不是我们想要的,因此来改造一下Navigation
通过上一篇中查看源代码我们知道,在xml中配置的navigation,最终会被解析成一个一个的Destination对象然后放到一个导航图NavGraph中。然后通过NavController交给ActivityNavigator、FragmentNavigator等去执行导航。
改造自后的Navigation,不用在xml中配置,只需在页面上添加相关的注解就可以了,然后通过注解拿到页面信息自己组建导航图,本部分主体路参考了慕课网短视频实战项目
先看一下改造之后的用法,4个Fragment和底部导航栏
@FragmentDestination(pageUrl = WanRouterKey.FRAGMENT_MAIN_TABLES_HOME, asStarter = true)
class HomeFragment : BaseFragment(){}
@FragmentDestination(pageUrl = WanRouterKey.FRAGMENT_MAIN_TABLES_APPLY)
class ApplyFragment : BaseFragment() {}
@FragmentDestination(pageUrl = WanRouterKey.FRAGMENT_MAIN_TABLES_FIND)
class FindFragment : BaseFragment() {}
@FragmentDestination(pageUrl = WanRouterKey.FRAGMENT_MAIN_TABLES_MINE)
class MineFragment : BaseFragment() {}
四个Fragment分别添加FragmentDestination注解,pageUrl是导航路径为常量。HomeFragment注解中的asStarter 参数代表是启动的第一个页面
MainActivity的xml中,继承系统BottomNavigationView自定义底部底部图标和文字,省去在res/menu文件夹下的xml配置
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.chs.lib_core.navigation.BottomBarView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
android:background="?android:attr/windowBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="@id/nav_view"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
MainActivity的onCreate中
val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
NavGraphBuilder.build(navController,this,R.id.nav_host_fragment)
nav_view.setNavController(navController)
到此一个底部导航就完成了,不用向原生Navigation一样在res/navitaion和res/menu文件夹中使用想xml文件配置了,是不是简单了灵活许多。
如何实现上面功能呢
OK 思路有啦开始干,首先创建两个java Module:lib_annotation 和 lib_compiler 用来编写注解类和注解处理器
lib_annotation :中编写ActivityDestination和FragmentDestination分别用来标记activity和fragment
@Target(ElementType.TYPE)
public @interface ActivityDestination {
/**
* @return 页面路径
*/
String pageUrl();
/**
*
* @return 是否需要登录
*/
boolean needLogin() default false;
/**
* @return 是否是启动页
*/
boolean asStarter() default false;
/**
* @return 是否属于主页中的tab页面 首页tab有可能点击去一个新的activity
*/
boolean isBelongTab() default false;
}
@Target(ElementType.TYPE)
public @interface FragmentDestination {
/**
* @return 页面路径
*/
String pageUrl();
/**
*
* @return 是否需要登录
*/
boolean needLogin() default false;
/**
* @return 是否是启动页
*/
boolean asStarter() default false;
}
lib_compiler 中编写注解处理器,来解析带有注解的类
首先在build.gradle中添加相关依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':lib_annotation')
implementation 'com.alibaba:fastjson:1.2.59'
compileOnly 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}
lib_annotation是前面定义的注解module,fastjson用来生成json对象,auto-service用来编译时自动执行注解处理器
注解处理器NavProcessor
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"com.chs.lib_annotation.ActivityDestination","com.chs.lib_annotation.FragmentDestination"})
@SupportedOptions("moduleName")
public class NavProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
private String outFileName;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//日志工具
messager = processingEnv.getMessager();
//文件处理工具
filer = processingEnv.getFiler();
//获取gradle中配置的内容作为生成文件的名字
outFileName = processingEnv.getOptions().get("moduleName") + "_nav.json";
messager.printMessage(Diagnostic.Kind.NOTE,"moduleName:"+outFileName);
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
//拿到带有这两个注解的类的集合
Set extends Element> fragmentElement = roundEnv.getElementsAnnotatedWith(FragmentDestination.class);
Set extends Element> activityElement = roundEnv.getElementsAnnotatedWith(ActivityDestination.class);
if(!fragmentElement.isEmpty()||!activityElement.isEmpty()){
Map destMap = new HashMap<>();
handleDestination(fragmentElement,FragmentDestination.class,destMap);
handleDestination(activityElement,ActivityDestination.class,destMap);
FileOutputStream fos = null;
OutputStreamWriter writer = null;
//将map转换为json文件,保存到app/src/asset中
try {
//filer.createResource方法用来生成源文件
//StandardLocation.CLASS_OUTPUT java文件生成class文件的位置,/build/intermediates/javac/debug/classes/目录下
FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", outFileName);
String resourcePath = resource.toUri().getPath();
messager.printMessage(Diagnostic.Kind.NOTE,"resourcePath:"+resourcePath);
String appPath = resourcePath.substring(0,resourcePath.indexOf("build"));
String assetPath = appPath + "src/main/assets";
File assetDir = new File(assetPath);
if(!assetDir.exists()){
assetDir.mkdir();
}
File assetFile = new File(assetDir,outFileName);
if(assetFile.exists()){
assetFile.delete();
}
assetFile.createNewFile();
String content = JSON.toJSONString(destMap);
fos = new FileOutputStream(assetFile);
writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8);
writer.write(content);
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(fos!=null){
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(writer!=null){
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return true;
}
private void handleDestination(Set extends Element> elements, Class extends Annotation> desAnnotationClazz,
Map destMap) {
for (Element element : elements) {
//TypeElement代表类或者接口,因为定义的注解是写在类上面的,所以可以直接转换成TypeElement
TypeElement typeElement = (TypeElement) element;
//获取全类名
String className = typeElement.getQualifiedName().toString();
int id = Math.abs(className.hashCode());
String pageUrl = null;
boolean needLogin = false;
boolean asStarter = false;
boolean isFragment = true;
boolean isBelongTab = false;
// messager.printMessage(Diagnostic.Kind.NOTE,"className:"+className);
Annotation annotation = element.getAnnotation(desAnnotationClazz);
//根据不同的注解获取注解的参数
if(annotation instanceof FragmentDestination){
FragmentDestination destination = (FragmentDestination) annotation;
pageUrl = destination.pageUrl();
needLogin = destination.needLogin();
asStarter = destination.asStarter();
isFragment = true;
}else if(annotation instanceof ActivityDestination){
ActivityDestination destination = (ActivityDestination) annotation;
pageUrl = destination.pageUrl();
needLogin = destination.needLogin();
asStarter = destination.asStarter();
isFragment = false;
isBelongTab = destination.isBelongTab();
}
//将参数封装成JsonObject后放到map中保存
if(destMap.containsKey(pageUrl)){
messager.printMessage(Diagnostic.Kind.ERROR,"不允许使用相同的pagUrl:"+className);
}else {
JSONObject jsonObject = new JSONObject();
jsonObject.put("id",id);
jsonObject.put("className",className);
jsonObject.put("pageUrl",pageUrl);
jsonObject.put("needLogin",needLogin);
jsonObject.put("asStarter",asStarter);
jsonObject.put("isFragment",isFragment);
jsonObject.put("isBelongTab",isBelongTab);
destMap.put(pageUrl,jsonObject);
}
}
}
}
注解和注解处理器写完了,现在去项目中使用以下
首先在build.gradle中android闭包下面添加如下代码来配置生成json的名字前缀
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName()]
}
}
然后引入注解和注解处理器,主项目是kotlin项目,所以使用kapt引入,java项目使用annotationProcessor引入
implementation project(path: ':lib_annotation')
kapt project(path: ':lib_compiler')
然后rebuild项目,可以看到app/src/main/assets目录下面生成了app_nav.json文件,打开文件可以看到
{
"main/tabs/MineFragment": {
"isFragment": true,
"isBelongTab": false,
"asStarter": false,
"needLogin": false,
"className": "com.chs.bigsea.ui.mine.MineFragment",
"pageUrl": "main/tabs/MineFragment",
"id": 1818876842
},
"main/tabs/FindFragment": {
"isFragment": true,
"isBelongTab": false,
"asStarter": false,
"needLogin": false,
"className": "com.chs.bigsea.ui.find.FindFragment",
"pageUrl": "main/tabs/FindFragment",
"id": 1292676074
},
"main/tabs/ApplyFragment": {
"isFragment": true,
"isBelongTab": false,
"asStarter": false,
"needLogin": false,
"className": "com.chs.bigsea.ui.apply.ApplyFragment",
"pageUrl": "main/tabs/ApplyFragment",
"id": 1185318390
}
}
HomeFragment因为在另一module,所以生成的文件也在另一个module中了
json文件生成完成,下一步就是来构建导航图了,新建一个NavGraphBuilder类来构建
fun build(navController: NavController, activity: FragmentActivity, containerId: Int) {
val navigatorProvider = navController.navigatorProvider
val fragmentNavigator = CustomFragmentNavigator(
activity, activity.supportFragmentManager,
containerId
)
navigatorProvider.addNavigator(fragmentNavigator)
val activityNavigator = navigatorProvider.getNavigator(ActivityNavigator::class.java)
val destinationMap = NavConfig.getDestinationMap()
val navGraph = NavGraph(NavGraphNavigator(navigatorProvider))
for ((key, destination) in destinationMap) {
if (destination.isFragment) {
val fragmentDestination = fragmentNavigator.createDestination()
fragmentDestination.className = destination.className!!
fragmentDestination.id = destination.id
fragmentDestination.addDeepLink(destination.pageUrl!!)
navGraph.addDestination(fragmentDestination)
} else {
if(destination.isBelongTab){
val activityDestination = activityNavigator.createDestination()
activityDestination.id = destination.id
activityDestination.setComponentName(
ComponentName(
Utils.getApp().packageName,
destination.className!!
)
)
activityDestination.addDeepLink(destination.pageUrl!!)
navGraph.addDestination(activityDestination)
}
}
if (destination.asStarter) {
navGraph.startDestination = destination.id
}
}
navController.graph = navGraph
}
构建导航图有三个比较大的部分
解析json文件:
class NavConfig {
companion object {
private var sDestinationMap: HashMap<String, Destination> = HashMap()
private var sBottomBar: BottomBar? = null
fun getDestinationMap(): HashMap<String, Destination> {
if (sDestinationMap.size == 0) {
val jsons = parseNavFile()
for (json in jsons){
val destination: HashMap<String, Destination> = GsonUtils.fromJson(json,
object : TypeToken<HashMap<String, Destination>>(){}.type)
sDestinationMap.putAll(destination)
}
}
return sDestinationMap
}
fun getBottomBar(): BottomBar {
if (sBottomBar == null) {
val jsonContent = parseFile("main_tabs_config.json")
sBottomBar = GsonUtils.fromJson(jsonContent, BottomBar::class.java)
}
return sBottomBar!!
}
/**
* 解析assets中特定文件
*/
private fun parseFile(s: String): String {
val assets = Utils.getApp().resources.assets
val open = assets.open(s)
val stringBuilder = StringBuilder()
val bufferedReader = BufferedReader(InputStreamReader(open))
bufferedReader.use {
var line: String?
while (true) {
line = it.readLine() ?: break
stringBuilder.append(line)
}
}
return stringBuilder.toString()
}
/**
* 解析assets下的所有的导航相关的文件
*/
private fun parseNavFile():List<String>{
val jsons = mutableListOf<String>()
val assets = Utils.getApp().resources.assets
val list = assets.list("");
if (list != null) {
for (item in list){
if(item.contains("_nav")){
jsons.add(parseFile(item))
}
}
}
return jsons
}
}
}
解析主要就是流的读取,遍历asssets目录下是文件,找到导航相关的json文件,解析成对象放到一个map中保存,供创建导航图的时候使用。
自定义FragmentNavigator:
@Navigator.Name("customfragment")
class CustomFragmentNavigator(context: Context, manager: FragmentManager, containerId: Int) :
Navigator<FragmentNavigator.Destination>() {
private val TAG = "CustomFragmentNavigator"
private val KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds"
private var mContext: Context = context
private var mFragmentManager: FragmentManager = manager
private var mContainerId = containerId
private val mBackStack = ArrayDeque<Int>()
override fun navigate(
destination: FragmentNavigator.Destination,
args: Bundle?,
navOptions: NavOptions?,
navigatorExtras: Extras?
): NavDestination? {
if (mFragmentManager!!.isStateSaved) {
Log.i(
TAG,
"Ignoring navigate() call: FragmentManager has already"
+ " saved its state"
)
return null
}
var className = destination.className
if (className[0] == '.') {
className = mContext!!.packageName + className
}
val ft = mFragmentManager!!.beginTransaction()
var enterAnim = navOptions?.enterAnim ?: -1
var exitAnim = navOptions?.exitAnim ?: -1
var popEnterAnim = navOptions?.popEnterAnim ?: -1
var popExitAnim = navOptions?.popExitAnim ?: -1
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)
}
val frg = mFragmentManager!!.primaryNavigationFragment
if (frg != null) {
ft.hide(frg)
}
val tag = destination.id.toString()
var fragment = mFragmentManager!!.findFragmentByTag(tag)
if (fragment == null) {
fragment = mFragmentManager.getFragmentFactory().instantiate(mContext.classLoader, className)
fragment!!.arguments = args
ft.add(mContainerId, fragment, tag)
} else {
ft.show(fragment)
}
ft.setPrimaryNavigationFragment(fragment)
@IdRes val destId = destination.id
val initialNavigation = mBackStack.isEmpty()
var np = navOptions
np = NavOptions.Builder().setLaunchSingleTop(true).build()
val isSingleTopReplacement = (!initialNavigation
&& np.shouldLaunchSingleTop())
val isAdded: Boolean
isAdded = if (initialNavigation) {
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))
}
false
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size + 1, destId))
true
}
if (navigatorExtras is FragmentNavigator.Extras) {
for ((key, value) in navigatorExtras.sharedElements) {
ft.addSharedElement(key!!, value!!)
}
}
ft.setReorderingAllowed(true)
ft.commit()
(return if (isAdded) {
mBackStack.add(destId)
destination
} else {
null
})
}
override fun createDestination(): FragmentNavigator.Destination {
return FragmentNavigator.Destination(this)//To change body of created functions use File | Settings | File Templates.
}
override fun popBackStack(): Boolean {
if (mBackStack.isEmpty()) {
return false
}
if (mFragmentManager!!.isStateSaved) {
Log.i(
TAG,
"Ignoring popBackStack() call: FragmentManager has already"
+ " saved its state"
)
return false
}
mFragmentManager?.popBackStack(
generateBackStackName(mBackStack.size, mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE
)
mBackStack.removeLast()
return true
}
override fun onSaveState(): Bundle? {
val b = Bundle()
val backStack = IntArray(mBackStack.size)
var index = 0
for (id in mBackStack) {
backStack[index++] = id
}
b.putIntArray(
KEY_BACK_STACK_IDS,
backStack
)
return b
}
override fun onRestoreState(savedState: Bundle) {
if (savedState != null) {
val backStack =
savedState.getIntArray(KEY_BACK_STACK_IDS)
if (backStack != null) {
mBackStack.clear()
for (destId in backStack) {
mBackStack.add(destId)
}
}
}
}
private fun generateBackStackName(backStackIndex: Int, destId: Int): String {
return "$backStackIndex-$destId"
}
}
自定义FragmentNavigator是为了把其内部的navigate方法中的replace改成hide和show,虽然可以继承FragmentNavigator重写navigate方法,但是该方法中用到的mBackStack变量是私有的,需要反射拿到,所以把FragmentNavigator中的相关代码复制一份到自定义的CustomFragmentNavigator中,这样不需要继承也不需要反射了。
首先定义一个json文件,里面保存底部导航栏的tab信息,放到app/src/assets 目录下面
{
"activeColor": "#333333",
"inActiveColor": "#666666",
"selectTab": 0,
"tabs": [
{
"size": 24,
"enable": true,
"index": 0,
"pageUrl": "main/tabs/HomeFragment",
"title": "首页"
},
{
"size": 24,
"enable": true,
"index": 1,
"pageUrl": "main/tabs/ApplyFragment",
"title": "应用"
},
{
"size": 24,
"enable": true,
"index": 2,
"pageUrl": "main/tabs/FindFragment",
"title": "发现"
},
{
"size": 24,
"enable": true,
"index": 3,
"pageUrl": "main/tabs/MineFragment",
"title": "我的"
}
]
}
然后继承BottomNavigationView,解析上面的json,创建出menu对象,添加到BottomNavigationView中
class BottomBarView : BottomNavigationView, BottomNavigationView.OnNavigationItemSelectedListener {
private var navController: NavController? = null
companion object {
val sIcons = arrayOf(
R.drawable.icon_tab_home, R.drawable.icon_tab_apply,
R.drawable.icon_tab_find, R.drawable.icon_tab_mine
)
}
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
@SuppressLint("RestrictedApi")
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
setOnNavigationItemSelectedListener(this)
val bottomBar = NavConfig.getBottomBar()
val states = arrayOfNulls<IntArray>(2)
states[0] = intArrayOf(android.R.attr.state_selected)
states[1] = intArrayOf()
val colors = intArrayOf(
Color.parseColor(bottomBar.activeColor),
Color.parseColor(bottomBar.inActiveColor)
)
itemIconTintList = ColorStateList(states, colors)
itemTextColor = ColorStateList(states, colors)
//设置文本和字体一直都显示
labelVisibilityMode = LabelVisibilityMode.LABEL_VISIBILITY_LABELED
val tabs = bottomBar.tabs
//添加menu
for (tab in tabs) {
if (!tab.enable) {
continue
}
val itemId: Int = getItemId(tab.pageUrl)
if (itemId < 0) {
continue
}
val menu = menu
val menuItem = menu.add(0, itemId, tab.index, tab.title)
menuItem.setIcon(sIcons[tab.index])
}
//设置menu的大小 添加完所有的itemMenu之后才改变大小,因为每次添加都会先移除所有的item,排序之后再放入到容器中
var index = 0
for (tab in tabs) {
if (!tab.enable) {
continue
}
val itemId: Int = getItemId(tab.pageUrl)
if (itemId < 0) {
continue
}
val size = SizeUtils.dp2px(tab.size.toFloat())
val menuView: BottomNavigationMenuView = getChildAt(0) as BottomNavigationMenuView
val itemView: BottomNavigationItemView = menuView.getChildAt(index) as BottomNavigationItemView
itemView.setIconSize(size)
if (TextUtils.isEmpty(tab.title)) { //title为空的一般是中间的按钮 有那种中间变大的按钮
val tintColor =
if (TextUtils.isEmpty(tab.tintColor)) Color.parseColor("#ff678f") else Color.parseColor(
tab.tintColor
)
itemView.setIconTintList(ColorStateList.valueOf(tintColor))
//禁止上下浮动的效果
itemView.setShifting(false)
}
index++
}
//底部导航栏默认选中项
if (0 != bottomBar.selectTab) {
val selectTab = tabs[bottomBar.selectTab]
if (selectTab.enable) {
val itemId = getItemId(selectTab.pageUrl)
//延迟一下在切换,等待NavGraphBuilder解析完成
post { selectedItemId = itemId }
}
}
}
private fun getItemId(pageUrl: String): Int {
val destination = NavConfig.getDestinationMap()[pageUrl]
return destination?.id ?: -1
}
fun setNavController(navController: NavController) {
this.navController = navController
}
override fun onNavigationItemSelected(item: MenuItem): Boolean {
navController?.navigate(item.itemId)
return true;
}
}
原生的BottomNavigationView,会自动解析我们定义在res/menu中的xml文件,创建出MenuItem对象然后add到Menu对象中。
我们将底部信息配置在json文件中,自己解析,自己创建menu,这样就灵活多了,而且可以根据后台规则动态改变底部导航栏的数量。
到这里一个比较好用的底部导航+Fragment就完成了。
前面的底部导航+Fragment的例子,所有页面都在首页,并且通过底部BottomNavigationView点击完成切换页面,那如果我们在另一个activity中,点击跳转到新的activity,或者进行组件之间跳转该怎么办呢。
我知道在组建完成一个导航图之后,会将这个导航图设置给NavController,NavController是最终用来控制导航的控制器。通过NavController中的navigate方法传入需要导航的页面在导航图中的id就可以实现跳转了。
然而NavController是从MainActivity中初始化的
val navController = Navigation.findNavController(this, R.id.nav_host_fragment)
NavGraphBuilder.build(navController,this,R.id.nav_host_fragment)
nav_view.setNavController(navController)
我们在别的类中不容易拿到NavController对象
好像都不够优雅,后来有看了遍文档,文中说Navigation其实主要是为了那种单activity多fragment的应用设计的。如果你是多Activity的应用,建议每个Activity对应一个导航图和一个NavHostFragment。
对哦,一个应用中不一定只有一个NavController,我们可以专门创建一个组件之间activity跳转的导航图和NavController,用来管理不同activity之间跳转,说干就干
class NavManager {
companion object{
private var sNavController:NavController? = null
private val sMavManager = NavManager()
fun get():NavManager{
setNavController()
return sMavManager
}
private fun setNavController() {
if(sNavController == null){
val navController = NavController(Utils.getApp())
val navigatorProvider = navController.navigatorProvider
val navGraph = NavGraph(NavGraphNavigator(navigatorProvider))
val activityNavigator = navigatorProvider.getNavigator(ActivityNavigator::class.java)
val destinationMap = NavConfig.getDestinationMap()
val activityDestinationStart:ActivityNavigator.Destination = getStartDestination(activityNavigator)
navGraph.addDestination(activityDestinationStart)
for ((key, destination) in destinationMap) {
if (!destination.isFragment&&!destination.isBelongTab){
val activityDestination = activityNavigator.createDestination()
activityDestination.id = destination.id
activityDestination.setComponentName(
ComponentName(Utils.getApp().packageName, destination.className!!)
)
activityDestination.addDeepLink(destination.pageUrl!!)
navGraph.addDestination(activityDestination)
}
}
navGraph.startDestination = activityDestinationStart.id
navController.graph = navGraph
sNavController = navController
}
}
private fun getStartDestination(activityNavigator:ActivityNavigator): ActivityNavigator.Destination {
val activityDestination = activityNavigator.createDestination()
activityDestination.id = R.id.bottom_start_activity
activityDestination.setComponentName(
ComponentName(Utils.getApp().packageName, "com.chs.lib_core.navigation.EmptyActivity")
)
return activityDestination
}
}
fun build(toWhere: String) : Builder{
val bundle = Bundle()
return Builder(toWhere,bundle)
}
class Builder(private val toWhere: String,private val bundle: Bundle){
fun withString(key:String,value:String):Builder{
bundle.putString(key, value)
return this
}
fun withInt(key:String,value:Int):Builder{
bundle.putInt(key, value)
return this
}
fun withLong(key:String,value:Long):Builder{
bundle.putLong(key, value)
return this
}
fun withDouble(key:String,value:Double):Builder{
bundle.putDouble(key, value)
return this
}
fun withBoolean(key:String,value:Boolean):Builder{
bundle.putBoolean(key, value)
return this
}
fun withByte(key:String,value:Byte):Builder{
bundle.putByte(key, value)
return this
}
fun withSerializable(key:String,value: Serializable):Builder{
bundle.putSerializable(key, value)
return this
}
fun withParcelable(key:String,value: Parcelable):Builder{
bundle.putParcelable(key, value)
return this
}
private fun getItemId(pageUrl: String): Int {
val destination = NavConfig.getDestinationMap()[pageUrl]
return destination?.id ?: -1
}
fun navigate(){
sNavController?.navigate(getItemId(toWhere),bundle)
}
}
}
直接new一个NavController,和一个新的NavGraph,解析生成的json文件。在注解中添加一个新的属性isBelongTab,是不是主页tab中的页面,不是放到当前导航图中。
每一个导航图要求必须要有一个startDestination(起始页),给NavController设置导航图的时候,会默认显示出起始页面。我们当前的导航图不需要起始页面,可能会随机跳转页面,我们也不知道谁是起始页。所以用一个透明的,空的activity来当起始页,启动之后直接关闭
class EmptyActivity:AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
finish()
}
}
创建出NavController对象就好说了,条用其navigate方法传入要跳转的页面的id就可以完成跳转了。navigate方法有很多重载的方法,我们还可以传入bundle参数,传入切换动画等等。
最终如果我们想要完成一个不同组件之间的activity跳转如下
需要跳转的目标页面添加注解
@ActivityDestination(pageUrl = WanRouterKey.ACTIVITY_MAIN_MINE_RANK)
class RankActivity : BaseActivity() {}
在点击事件中通过如下方式就可以愉快的进行跳转啦。
NavManager.get()
.build(WanRouterKey.ACTIVITY_MAIN_MINE_RANK)
.withString("stringparama","stringparama")
.navigate()
OK,自定义Navigation完成。