Android Jetpack 架构组件之 Navigation

一、关键字段的解释

Kotlin语言环境下在Fragment里面操作控件的注意事项:

onCreateView()方法和onStart()方法里面直接通过id操作控件的话,会要求必须加findViewById()获取控件id,否则就会报"must not be null"这个错误。

但是在onViewCreated()方法里面就不用这样。另外自定义的方法里面也是必须获取控件id。

可以这么理解:
在Fragment里面,除了在onViewCreated()方法里面直接操作控件或者调用操作控件的方法外,其他任何地方操作控件或者调用操作控件的方法都是需要通过findViewById()获取控件id,否者就会报上面的错误。

Navigation各字段含义:

1、Activity对应的xml里面,fragment标签示例以及几个关键字的含义:

<fragment
    android:id="@+id/nav_fragment"    	
    android:layout_width="match_parent"
    android:layout_height=" match_parent "   
    android:name="androidx.navigation.fragment.NavHostFragment"
    app:navGraph="@navigation/nav_host"    		
    app:defaultNavHost="true"/>

android:id:在Activity里面用于通过supportFragmentManager获取NavHostFragment对象。

android:name:是NavHostFragment,它实现了NavHost,这是一个用于放置管理destination的空视图。指向NavHost的实现类NavHostFragment。

app:navGraph:指向Navigation graph配置文件,用于将NavHostFragment和nav_graph.xml关联起来。

app:defaultNavHost:默认值为false,当该值为false的时候,当前Activity中的Fragment使用了Navigation,且使用Navigation跳转到下一个Fragment,在下一个Fragment页面中点击了Back键会退出当前Activity。为true的时候是回到上一个Fragment中,如果上一个Fragment为null才会退出当前Activity。类似于我们处理WebView的back事件。

2、navigation文件夹下nav_host.xml文件示例及里面关键字段的含义:

<navigation xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:app=http://schemas.android.com/apk/res-auto
    xmlns:tools=http://schemas.android.com/tools
    android:id="@+id/nav_host"
    app:startDestination="@id/homeFragment"
    tools:ignore="UnusedNavigation">
    
    <fragment
        android:id="@+id/homeFragment"
        android:name="com.androidjetpack.navigation.HomeFragment"
        android:label="home_dest"
        tools:layout="@layout/home_dest" >
        <action
            android:id="@+id/action_homeFragment_to_oneFragment"
            app:destination="@id/oneFragment" />
    </fragment>
    
    <fragment
        android:id="@+id/oneFragment"
        android:name="com.androidjetpack.navigation.OneFragment"
        android:label="OneFragment"
        tools:layout="@layout/flow_step_one_dest">
        
        <!-- action标签不是必须的,比如后面的fragment标签就没有action标签,尤其是最后一个fragment -->
		<!-- action里面id的规则:action_本fragment的id_to_下一个fragment的id -->		
        <action
            android:id="@+id/action_oneFragment_to_deepLinkFragment"
            app:destination="@+id/deepLinkFragment"/>
    </fragment>
    
    <fragment
        android:id="@+id/deepLinkFragment"
        android:name="com.androidjetpack.navigation.DeepLinkFragment"
        android:label="DeepLinkFragment"
        tools:layout="@layout/deep_link_dest">
        
        <!-- argument标签不是必须的 -->
        <argument
            android:name="flowStepNumber"
            app:argType="integer"
            android:defaultValue="1"/>
    </fragment>
</navigation>

name:指定Fragment的路径。

tools:layout:指定布局文件,如果不配置该属性在Design面板会看不见预览。

action:字面理解就是动作,作用是fragment之间进行切换。

app:startDestination:指定这个Fragment是start-destination。

app:destination:目的地,指定要跳转到Fragment的id。

app:id:定义这个action的id,代码里执行跳转时要用到。

enterAnim、exitAnim、popEnterAnim、popExitAnim:是页面切换和弹窗动画

如果某个Fragment是最后一个Fragment,那么我们就可以直接省略他的action。

二、Navigation的使用步骤

1、app的build.gradle导入依赖:

def nav_version = "2.3.0"
implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"

并在这个build.gradle里面添加:

apply plugin: "androidx.navigation.safeargs"

2、在项目工程的build.gradle添加如下依赖:

classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0"

3、新建几个Fragment并关联布局:

ZeroFragment及对应的xml布局文件:

xml文件布局

<TextView xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:tools=http://schemas.android.com/tools
    android:layout_width="match_parent"     
    android:layout_height="match_parent"
    android:orientation="vertical"    		
    android:text="@string/deep"
    android:textSize="32sp"			
    tools:ignore="HardcodedText" />

kotlin代码实现:

class ZeroFragment :Fragment(){
     
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
     
        return inflater.inflate(R.layout.fragment_zero, container, false)
    }
}

OneFragment及对应的xml布局文件:

xml文件布局:

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:tools=http://schemas.android.com/tools
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:ignore="HardcodedText">
    
    <TextView
        android:layout_width="wrap_content"       
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"        
        android:text="@string/one"
        android:textSize="32sp" />
</LinearLayout>

kotlin代码实现:

class OneFragment :Fragment(){
     
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
     
        return inflater.inflate(R.layout.fragment_one, container, false)
    }
}

HomeFragment及对应的xml布局文件:

xml文件布局:

<LinearLayout xmlns:android=http://schemas.android.com/apk/res/android    
	xmlns:tools=http://schemas.android.com/tools
    android:layout_width="match_parent"    
    android:layout_height="match_parent"
    android:gravity="center"		    
    android:orientation="vertical"
    tools:context="com.androidjetpack.navigation.HomeFragment"    
    tools:ignore="HardcodedText">
    
    <TextView
        android:layout_width="wrap_content"         
        android:layout_height="wrap_content"
        android:layout_marginBottom="50dp"        
        android:text="@string/home"
        android:textSize="32sp" />
        
    <TextView
        android:id="@+id/tvTitle"		        
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"         
        android:textSize="18sp"
        android:textColor="@color/black"	        
        android:padding="12dp"/>
</LinearLayout>

kotlin代码实现:

class HomeFragment :Fragment(){
     
    var mActivity:NavigationPrimaryActivity? = null
    override fun onAttach(context: Context) {
     
        super.onAttach(context)
        mActivity = (context as NavigationPrimaryActivity)
    }
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
     
        return inflater.inflate(R.layout.fragment_home, container, false)
    }
    
    //在onCreateView()方法和onStart()方法里面直接通过id操作控件的话,会要求必须加findViewById()获取控件id,
    //否则就会报"must not be null"这个错误。
    //但是在onViewCreated()方法里面就不用这样。另外自定义的方法里面也是必须获取控件id。
    //可以这么理解:在Fragment里面,除了在onViewCreated()方法里面直接操作控件或者调用操作控件的方法外,
    //其他任何地方操作控件或者调用操作控件的方法都是需要通过findViewById()获取控件id,否者就会报上面的错误。
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
     
        super.onViewCreated(view, savedInstanceState)
        operationView()
    }
    
    fun operationView() {
     
        open1.setOnClickListener {
                 
        	mActivity?.printLoginfo("不能滑动,没什么鸟用")        
        }
        tvTitle.text = "皇者号天令"
    }
}

4、新建Navigation graph资源文件

1)、在工程的res文件夹下创建新的文件夹:navigation
2)、单击navigation文件夹右键菜单选择New->Navigation Resource File,在File name后面输入文件名:nav_host,选择Resource type为Navigation,点击ok即可创建Navigation graph资源文件:nav_host.xml
3)、把新建的Fragment引入到该资源文件内:
Android Jetpack 架构组件之 Navigation_第1张图片
按照上面截图的顺序,在上面第二步创建的nav_host.xml文件里面依次操作。在标记有3的搜索框处搜索要添加的Fragment,然后从Design模式切换到Split模式,在里面编辑功能:跳转的顺序、action行为活动细节等。最终代码如下:

<navigation xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:app=http://schemas.android.com/apk/res-auto
    xmlns:tools=http://schemas.android.com/tools
    android:id="@+id/nav_host"
    app:startDestination="@id/homeFragment"
    tools:ignore="UnusedNavigation">
    
    <fragment
        android:id="@+id/homeFragment"   
        android:name="com.androidjetpack.navigation.HomeFragment"
        android:label="home_dest"		    
        tools:layout="@layout/fragment_home" >
        
        <action
            android:id="@+id/action_homeFragment_to_oneFragment"
            app:destination="@id/oneFragment" />
    </fragment>
    
    <fragment
        android:id="@+id/oneFragment"
        android:name="com.androidjetpack.navigation.OneFragment"
        android:label="OneFragment"           
        tools:layout="@layout/fragment_one">
        
        <!--action标签不是必须的,比如后面的fragment标签就没有action标签,尤其是最后一个fragment-->
        <!--action里面id的规则:action_本fragment的id_to_下一个fragment的id-->
        <action
            android:id="@+id/action_oneFragment_to_deepLinkFragment"
            app:destination="@+id/deepLinkFragment"/>
    </fragment>
    
    <fragment
        android:id="@+id/deepLinkFragment"
        android:name="com.androidjetpack.navigation.ZeroFragment"
        android:label="DeepLinkFragment"           
        tools:layout="@layout/fragment_zero">
        
        <!-- 下面的argument标签不是必须的 -->
        <!--argument:类似于Activity的跳转传参,只不过传参取参更加方便简单,
        	如果某个Fragment要接收从别的Fragment传过来的参数,那么可以通过argument标签处理,
        	如果我们在kotlin代码里面没有传参,那么我们获取到的就是通过argument标签设定的默认值,具体见下面。-->
		<!--下面defaultValue的类型必须与字段argType必须保持一致,比如下面argType="integer",defaultValue="1"-->
        <argument
            android:name="flowStepNumber"            
            app:argType="integer"            
            android:defaultValue="1"/>
    </fragment>
</navigation>

argument
类似于Activity的跳转传参,只不过传参取参更加方便简单,如果某个Fragment要接收从别的Fragment传过来的参数,那么可以通过argument标签处理,如果我们在kotlin代码里面没有传参,那么我们获取到的就是通过argument标签设定的默认值,具体如下:

//传值且跳转
val flowStepNumberArg=1
val action = HomeFragmentDirections.nextAction(flowStepNumberArg)
findNavController().navigate(action)

//取值
val safeArgs: FlowStepFragmentArgs by navArgs()
val flowStepNumber = safeArgs.flowStepNumber

5、创建menu菜单:bottom_nav_menu.xml

<menu xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <!--这里的id对应着nav_host.xml里面的fragment标签的id-->
    <!--这里的item的顺序对应着nav_host.xml里面的fragment的顺序-->
    <!--showsAction属性解释:
        1、alaways:这个值会使菜单项一直显示在ActionBar上。
        2、ifRoom:如果有足够的空间,这个值会使菜单显示在ActionBar上。
        3、never:这个值菜单永远不会出现在ActionBar是。
        4、withText:这个值使菜单和它的图标,菜单文本一起显示。
        5、collapseActionView 声明了这个操作视窗应该被折叠到一个按钮中,
           当用户选择这个按钮时,这个操作视窗展开。否则,这个操作视窗在默认的情况下是可见的,
           并且即便在用于不适用的时候,也要占据操作栏的有效空间。-->
    <item
        android:id="@+id/homeFragment"        
        android:icon="@mipmap/home"
        android:title="home"        		
        app:showAsAction="ifRoom"/>
        
    <item
        android:id="@+id/oneFragment"        
        android:icon="@mipmap/focus"        
        android:title="one" />
        
    <item
        android:id="@+id/deepLinkFragment"   
        android:icon="@mipmap/fire"        
        android:title="deep" />
</menu>

注意:

这里的item.id和Navigation graph资源文件中的fragment.id必须保持一致,否则,恭喜入坑。

6、最后是Fragment的寄主NavigationPrimaryActivity和activity_navigation_primary.xml的处理:

<LinearLayout
    xmlns:android=http://schemas.android.com/apk/res/android
    xmlns:app=http://schemas.android.com/apk/res-auto
    xmlns:tools=http://schemas.android.com/tools
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/container"
    android:orientation="vertical"
    tools:context=".navigation.NavigationPrimaryActivity">
    
	<!--android:name:是NavHostFragment,它实现了NavHost,这是一个用于放置管理destination的空视图。指向NavHost的实现类NavHostFragment-->
	<!--app:navGraph:指向Navigation graph配置文件,用于将NavHostFragment和nav_graph.xml关联起来。-->
	<!--app:defaultNavHost:默认值为false,当该值为false的时候,当前Activity中的Fragment使用了Navigation,
		且使用Navigation跳转到下一个Fragment,在下一个Fragment页面中点击了Back键会退出当前Activity。
		为true的时候是回到上一个Fragment中,如果上一个Fragment为null才会退出当前Activity。类似于我们处理WebView的back事件。-->
		
    <fragment
        android:id="@+id/nav_fragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:navGraph="@navigation/nav_host"
        app:defaultNavHost="true"/>
        
    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottom_nav_view"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@color/write"
        app:menu="@menu/bottom_nav_menu"/>
</LinearLayout>

上面fragment标签的id在Activity里面将会用到,这一点要特别注意,其余注意事项以及说明,代码里面解释的很清楚了。

接下来就是Activity里面的逻辑处理,一共分四步

class NavigationPrimaryActivity : BaseActivity() {
     
	
    override fun onCreate(savedInstanceState: Bundle?) {
     
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_navigation_primary)
        
        //第一步获取到NavHostFragment,这里的nav_fragment就是本Activity对应的xml里面Fragment标签的id。
        val host: NavHostFragment = supportFragmentManager.findFragmentById(R.id.nav_fragment) as NavHostFragment? ?: return
        
        //第二步获取到NavController,这一步的内容几乎都是死的。
        val navController = host.navController
        
        //第三步配置BottomNavigationView,这里的bottom_nav_view就是Activity对应的xml里面BottomNavigationView控件的id。
        val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
        //这里,将导航控件BottomNavigationView与navController关联起来。
        bottomNav?.setupWithNavController(navController)
        
        //第四步添加路由监听,这一步的内容几乎都是死的。
        navController.addOnDestinationChangedListener {
      _, destination, _ ->
            val dest: String = try {
        
            	resources.getResourceName(destination.id)   
            } catch (e: Resources.NotFoundException) {
     
                destination.id.toString()
            }
            toast("Navigated to $dest")
        }
    }
}

要注意一个细节:

第三部其实分两个细节,一是初始化BottomNavigationView的id,二是将导航控件BottomNavigationView与navController关联起来。运行效果如下:
Android Jetpack 架构组件之 Navigation_第2张图片
Android Jetpack 架构组件之 Navigation_第3张图片
Android Jetpack 架构组件之 Navigation_第4张图片

7、跳转传参

虽然Fragment之间传递参数的几率比较低,但是多少还是会用到。

1)、无参跳转

在HomeFragment中的onViewCreated(view: View, savedInstanceState: Bundle?)函数中添加以下代码:

//其中,id值action_homeFragment_to_oneFragment是我们在nav_host.xml资源内添加的action属性。
//这里是定义了一个无参跳转:homeFragment_to_oneFragment
open1.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.action_homeFragment_to_oneFragment))

上面定义了一个从homeFragment跳转到oneFragment的动作。
其中R.id. action_homeFragment_to_oneFragment是我们在nav_host资源内添加的action属性的id

R.id. action_homeFragment_to_oneFragment中:
action:意指活动;
homeFragment_to_oneFragment:从homeFragment到oneFragment

2)、带参跳转

这个目前尚未研究成功,无法编译生成带参跳转的重载方法,待后面成功了在完成这部分内容。

最后来说一个关于带参跳转的问题的解决方案,问题如下:

Cannot inline bytecode built with JVM target 1.8 into bytecode that is being built with JVM target 1.6. Please specify proper '-jvm-target' option.
android {
     
    ......
    compileOptions {
             
	    sourceCompatibility = 1.8        
	    targetCompatibility = 1.8    
    }
    
    kotlinOptions {
             
    	jvmTarget = "1.8"    
    }
}

添加上面加粗标红的代码。然后按下图操作即可完成:
Android Jetpack 架构组件之 Navigation_第5张图片

你可能感兴趣的:(Jetpack组件,Android,Jetpack,Jetpack组件,安卓组件Navigation,Jetpack,组件,导航)