静安网站建设关键词优化seo,农村自建房设计图120平方米三层,完整的社群营销方案,企业网站组网方案写在前面
Google在2018年就推出了Jetpack组件库#xff0c;但是直到今天我才给重视起来#xff0c;这真的不得不说是一件让人遗憾的事。过去几年的空闲时间里#xff0c;我一直在尝试做一套自己的组件库#xff0c;帮助自己快速开发#xff0c;虽然也听说过Jetpack#…写在前面
Google在2018年就推出了Jetpack组件库但是直到今天我才给重视起来这真的不得不说是一件让人遗憾的事。过去几年的空闲时间里我一直在尝试做一套自己的组件库帮助自己快速开发虽然也听说过Jetpack但是压根儿也没去了解但是其实自己已经无形之中用到过很多Jetpack中的库了只是自己不知道比如说databinding、viewmodel、camerax等等
所以我打算推出一个Jetpack的学习记录今天是第一个组件Navigation
老规矩文末有demo的源码永久0积分
demo效果 正文
关于Navigation
据通义千问的说法 Android Jetpack Navigation组件是Google推出的一个用于简化Android应用导航的库。旨在提供一种结构化和统一的方式来管理应用程序中的屏幕切换和导航流程特别是对于基于Fragment的应用。 关于Navigation我觉得可能大家在生活里对于它的功能并不会陌生拿微信来说底部有四个按钮分别是“微信”、“通讯录”、“发现”、“我”如下图 当我们分别点击四个按钮的时候界面区域也会随之切换到对应的页面。这就是一种比较常见的底部导航功能。当然这种结合底部导航栏的fragment切换只是Navigation能够实现的其中一种还有其他的并不需要底部导航栏的比如说登录模块登录模块可能包含着登录、注册和重置密码这三个子模块那么按照UI的设计就需要三张页面去实现我们知道可以使用一个Activity去嵌套三个Fragment去实现那么显然这种纯fragment切换这也是Navigation可以实现的范畴
整体设计思路
今天展示Navigation的使用的demo的整体设计思路为LoginActivityMainActivity
其中LoginActivity包含着LoginFragment、RegisterFragment、ResetPasswordFragment
其中MainActivity包含HomeFragment、ContactFragment、FindFragment、MeFragment
编码开始
我的环境AndroidStudio 4.2.2
1引用
项目级build.gradle
dependencies {...classpath androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0...}
模块级build.gradle
plugins {....id androidx.navigation.safeargs.kotlin
}dependencies {...//Navigationimplementation androidx.navigation:navigation-fragment-ktx:2.4.2implementation androidx.navigation:navigation-ui-ktx:2.4.2
}
2创建导航文件
步骤如下 按照这样的步骤即可创建login_nav_graph.xml和main_nav_graph.xml两个导航文件如图 此时两份文件里内容还是空的具体内容还需要自行添加 AndroidStudio支持可视化添加fragment以及相互之间的导航关系这点真的是非常方便
1. 点击加号可以添加fragment到导航文件
我们首先把使用到的fragment全部添加到导航文件中如下图 这里先看一下AndroidStudio自动为我们生成的代码
fragmentandroid:idid/loginFragmentandroid:namecom.swy.navigationdemo.login.LoginFragmentandroid:labelfragment_logintools:layoutlayout/fragment_login / 这个fragment标签与我们拖进来的三个fragment是一一对应的它包含了四个参数 id比唯一标识符可供本文件其他地方调用name是对应Fragment的路径label是对应Fragment的一个标签tools:layout对应Fragment的布局文件 还有另一个属性也是值得关注的就是最外层navigation标签的
app:startDestinationid/loginFragment
这表明了在LoginActivity中默认优先显示的是LoginFragment
说明我们首次添加的Fragment会被默认为优先显示的Fragment即如果我这里优先添加LoginFragment到导航图navigation自动生成app:startDestinationid/loginFragment那么如果我首先把RegisterFragment添加进导航图那么这个属性就会是app:startDestinationid/registerFragment
2.增加Fragment间的导航关系
点LoginFragment框体右边中部会出现一个圆环 点击该圆环并拖动就会出现一条蓝线将该蓝线指向RegisterFragment后松手 此时就会看到LoginFragment到RegisterFragment的导航关系被建立了观察新增的action代码
actionandroid:idid/action_loginFragment_to_registerFragmentapp:destinationid/registerFragment / id唯一标识符可供Activity调用destination用来表示跳转的目的地可见此时这个action表示的是跳转到registerFragment。需要说明的是这个跳转每次都会新建实例也就是我可以从LoginFragment跳转到LoginFragment但是这两个LoginFragment是不同的实例也就是栈内会同时存在两个不同的LoginFragment。 其他的属性 app:launchSingleTop类似于Android活动中singleTop启动模式当该属性为true时如果目标Fragment已经在回退栈的顶部即用户最近访问的Fragment那么Navigation组件将不会创建新的Fragment实例而是重用已经存在的那个Fragment实例。如果目标Fragment已经在回退栈中但不在栈顶那么app:launchSingleTop属性将不会起作用。在这种情况下即使app:launchSingleTop设置为trueNavigation组件也会创建一个新的目标Fragment实例并将其推送到回退栈的顶部。app:popUpTo出栈直到某个元素。比如目前栈内有fragment1 - fragment2 - fragment3当我在fragment4中定义了app:popUpTo:id/fragment1时那么fragment2和fragment3会被出栈最终栈内情况为fragment1 - fragment4。app:popUpToInclusive这个属性是配合上面的app:popUpTo使用的用来判断到达指定元素时是否把指定元素也出栈。还是以上面的例子来说明如果该值为true那么作为指定元素fragment1也会被出栈最终栈内只剩下fragment4.app:enterAnim、app:exitAnim、app:popEnterAnim、app:popExitAnim这四个属性都是跳转动画相关的前两个用来配置移动到目的地的动画后两个配置离开目的地的动画。 举例说明fragment1跳转到fragment2 1enterAnim和exitAnim发生于fragment1跳转到fragment2的过程中 enterAnim是fragment2的入场动画、exitAnim是fragment1的离场动画 2popEnterAnim和popExitAnim发生于fragment2返回到fragment1的过程中 popEnterAnim为返回发生后fragment1的入场动画 popExitAnim为返回时fragment2的离场动画 按照我们最开始设计的跳转关系去完成导航文件最终是这样的 即默认展示登录页面登录页面可以跳转到注册页面或者是重置密码页面同时在注册页面和重置密码页面也可以返回到登陆页面
同理我们完成main_nav_graph的导航关系如下 因为我们仿照的是微信的底部导航栏home、contact、find、me四个fragment其实是平级的它们之间并不存在导航关系。当然我们也可以根据自己的实际业务使用不同的action来设计不同维度和复杂度的导航关系。
至此导航文件也有了剩下的就是创建一个支持导航关系的容器了
3导航主机
以LoginActivity为例
布局文件
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:layout_widthmatch_parentandroid:layout_heightmatch_parenttools:context.LoginActivityandroidx.fragment.app.FragmentContainerViewandroid:idid/login_nav_host_fragmentandroid:nameandroidx.navigation.fragment.NavHostFragmentandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentapp:defaultNavHosttrueapp:navGraphnavigation/login_nav_graphapp:layout_constraintBottom_toBottomOfparentapp:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toTopOfparent //androidx.constraintlayout.widget.ConstraintLayout
创建FragmentContainerView作为导航主机Navigation Host这里有几个地方需要说明 android:nameandroidx.navigation.fragment.NavHostFragment是固定的写法 app:defaultNavHost的作用是将该FragmentContainerView标记为默认的导航主机这意味着这个FragmentContainerView会拦截系统的后退按钮事件。当用户点击后退按钮时Navigation组件会按照导航图中的历史记录进行后退操作而不是直接关闭Activity。并且在一个Activity中通常只需要一个NavHostFragment作为导航主机。设置app:defaultNavHosttrue可以确保只有这一个NavHostFragment响应导航操作和后退按钮事件避免多个NavHostFragment之间的冲突。 app:navGraph是将导航文件与导航主机相关联
LoginActivity
class LoginActivity : AppCompatActivity() {private lateinit var binding: ActivityLoginBindingprivate lateinit var navController:NavControlleroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding ActivityLoginBinding.inflate(layoutInflater)setContentView(binding.root)val navHostFragment supportFragmentManager.findFragmentById(R.id.login_nav_host_fragment) as NavHostFragmentnavController navHostFragment.navController}public fun getNavController(): NavController {return navController}override fun onSupportNavigateUp(): Boolean {return findNavController(R.id.login_nav_host_fragment).navigateUp()}
}
核心代码获取NavController
说明这里声明了一个方法将获取到的navController返回了出去主要是供Fragment中进行调用因为这里Activity只是容器具体的UI交互是在对应的Fragment上面的以LoginFragment为例
class LoginFragment : Fragment() {private lateinit var binding: FragmentLoginBindingprivate lateinit var navController: NavControlleroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)}override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {binding FragmentLoginBinding.inflate(layoutInflater)val activity requireActivity() as LoginActivitynavController activity.getNavController()initListener()return binding.root}private fun initListener() {binding.login.setOnClickListener {val intent Intent(activity, MainActivity::class.java)startActivity(intent)}binding.register.setOnClickListener {navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)}binding.reset.setOnClickListener {navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)}}}
通过这两行代码fragment获取到了activity的navcontroller val activity requireActivity() as LoginActivitynavController activity.getNavController()
之后就可以操作导航事件了如下
binding.register.setOnClickListener {navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)}binding.reset.setOnClickListener {navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)}
说明这里面的
R.id.action_loginFragment_to_resetPasswordFragment
和
R.id.action_loginFragment_to_resetPasswordFragment
就是login_nav_graph.xml文件中定义的两个action的id
我相信写到这里你基本上就能够看出来整个的调用机制了 4NavigationBottomNavigationView实现底部导航
上面讲了普通的fragment切换那么关于带底部导航栏的切换也还是很有必要说明以下的
主界面布局
?xml version1.0 encodingutf-8?
androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoxmlns:toolshttp://schemas.android.com/toolsandroid:layout_widthmatch_parentandroid:layout_heightmatch_parenttools:context.MainActivityandroidx.fragment.app.FragmentContainerViewandroid:idid/main_nav_host_fragmentandroid:nameandroidx.navigation.fragment.NavHostFragmentandroid:layout_width0dpandroid:layout_height0dpapp:defaultNavHosttrueapp:layout_constraintBottom_toTopOfid/bottomNavigationViewapp:layout_constraintEnd_toEndOfparentapp:layout_constraintHorizontal_bias0.0app:layout_constraintStart_toStartOfparentapp:layout_constraintTop_toTopOfparentapp:layout_constraintVertical_bias0.0app:navGraphnavigation/main_nav_graph /com.google.android.material.bottomnavigation.BottomNavigationViewandroid:idid/bottomNavigationViewandroid:layout_widthmatch_parentandroid:layout_height60dpapp:menumenu/main_menuapp:layout_constraintBottom_toBottomOfparentapp:layout_constraintEnd_toEndOfparentapp:layout_constraintStart_toStartOfparent //androidx.constraintlayout.widget.ConstraintLayout
显然页面中只是增加了BottomNavigationView对应的UI结构如下 说明
我这里使用了menu来实现了底部导航栏的几个item内容的导入代码如下
?xml version1.0 encodingutf-8?
menu xmlns:androidhttp://schemas.android.com/apk/res/androidxmlns:apphttp://schemas.android.com/apk/res-autoitemandroid:idid/homeFragmentandroid:iconmipmap/messageandroid:title首页app:showAsActionifRoom /itemandroid:idid/contactFragmentandroid:iconmipmap/contactandroid:title联系人app:showAsActionifRoom /itemandroid:idid/findFragmentandroid:iconmipmap/findandroid:title发现app:showAsActionifRoom /itemandroid:idid/meFragmentandroid:iconmipmap/meandroid:title我app:showAsActionifRoom /
/menu
重点说明这里面四个item的id并不是随意定义的一定要与main_nav_graph.xml文件中对应的几个fragment的id保持一致否则点击底部导航栏的按钮是无法触发对应的fragment切换的如下 这里面只是UI上对应了如何让bottomnavigationview与navcontroller也关联到一起呢
MainActivity.class
class MainActivity : AppCompatActivity() {private lateinit var binding:ActivityMainBindingprivate lateinit var navController: NavControlleroverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding ActivityMainBinding.inflate(layoutInflater)setContentView(binding.root)val navHostFragment supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragmentnavController navHostFragment.navControllerbinding.bottomNavigationView.setupWithNavController(navController)}override fun onSupportNavigateUp(): Boolean {return findNavController(R.id.main_nav_host_fragment).navigateUp()}}
核心代码就是这一句了 binding.bottomNavigationView.setupWithNavController(navController)
补充内容
Fragment间数据通信的两种方式
先看效果 说明
LoginFragment跳转到RegisterFragment使用SafeArgs方式
LoginFragment跳转到ResetpasswordFragment使用Bundle方式
1SafeArgs推荐 Android官方推荐使用Safe Args来实现Fragment间数据通信原因主要包括以下几个方面 类型安全 Safe Args提供了类型安全的方式来传递参数。在navigation graph XML文件中定义的每个参数都有明确的数据类型例如字符串、整数、布尔值等。这将自动为这些参数生成对应的Args类并提供get和set方法从而确保在编译时就能捕获到类型不匹配的问题而不是在运行时才出现崩溃。 清晰性与可读性 在navigation graph中直接指定参数及其类型使得整个应用导航结构更加清晰。通过查看XML文件开发者可以很容易地了解哪些参数在Fragment之间传递以及它们的类型是什么。 减少代码量和错误 使用Safe Args不需要手动创建和解析Bundle对象来传递数据这大大减少了出错的可能性。自动生成的Args类简化了参数传递过程使得开发者可以直接操作对象而非键值对提高了编码效率。 生命周期感知 Safe Args配合Navigation组件一起使用时能够更好地适应Android组件的生命周期变化。即使目标Fragment因为配置更改如屏幕旋转而重新创建传递的参数也能得到妥善保存和恢复。 易于维护 随着项目规模的增长Safe Args能帮助保持代码的整洁和组织有序。当需要修改或添加新的参数时只需要在navigation graph文件中更新即可同时会自动反映到相关的Args类中无需在多个地方手动同步修改。 具体的使用过程如下
1.引用插件 项目级build.gradle
buildscript {...dependencies {...classpath androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3}
}
模块级build.gradle
plugins {...id androidx.navigation.safeargs.kotlin
}
引用完成之后Sync项目Rebuild项目
2.实际使用修改login_nav_graph.xml文件增加argument参数
fragmentandroid:idid/loginFragmentandroid:namecom.swy.navigationdemo.login.LoginFragmentandroid:labelfragment_logintools:layoutlayout/fragment_loginactionandroid:idid/action_loginFragment_to_registerFragmentapp:destinationid/registerFragmentargumentandroid:namedata1app:argTypestringandroid:defaultValue//actionactionandroid:idid/action_loginFragment_to_resetPasswordFragmentapp:destinationid/resetPasswordFragment/action/fragment
说明我在自己环境上调试的时候发现我定义的argument属性在定义name的时候总是会提示xxx is not a valid destination for tag argument这样的错误网上也没有找到相关的解释和解决方法但是经过实际测试这个地方报红并不影响使用如图 LoginFragment.java
binding.register.setOnClickListener {val data1 这是使用safe args方式从登录界面传递的数据navController.navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment(data1))}
RegisterFragment.java
val data1 arguments?.getString(data1)
binding.textData1.text data1 说明
上面展示的是单一参数多参数也是支持的如下 比如这里我又增加了一个data3那么在LoginFragment中使用逗号隔开两个参数即可如下
binding.register.setOnClickListener {val data1 这是使用safe args方式从登录界面传递的数据val data3 data3navController.navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment(data1,data3))}
RegisterFragment
val data1 arguments?.getString(data1)
val data3 arguments?.getString(data3)
binding.textData1.text data1data3
最终的效果 可见 上面说的safeargs的优点确实是做到了易于维护 易于维护 随着项目规模的增长Safe Args能帮助保持代码的整洁和组织有序。当需要修改或添加新的参数时只需要在navigation graph文件中更新即可同时会自动反映到相关的Args类中无需在多个地方手动同步修改。 2Bundle
LoginFragment
binding.reset.setOnClickListener {val data2 这是使用普通Bundle方式从登录界面传递的数据val bundle Bundle();bundle.putString(data2,data2)navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment,bundle)}
ResetPasswordFragment
val bundle arguments
val data2 bundle?.getString(data2)
binding.textData2.text data2
至此Navigation fragment间数据通信的两种方式的简单介绍就结束了
说明关于safeargs的使用前面讲解的并不是全部的实现方式只是其中的一种就比如我这里从LoginFragment跳转到RegisterFragment我的argument是在LoginFragment里面定义的我看网上还有讲解的是也可以在RegisterFragment里面定义包括Bundle也可以与argument属性有联动关系等等之类的吧大家有兴趣可以都了解一下
到目前为止简单的demo算是初具雏形源码如下
demo 源码