Android Jetpack学习系列——Navigation

写在前面

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的整体设计思路为:LoginActivity+MainActivity

其中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.2"implementation "androidx.navigation:navigation-ui-ktx:2.4.2"
}
(2)创建导航文件

步骤如下:

按照这样的步骤,即可创建login_nav_graph.xml和main_nav_graph.xml两个导航文件,如图:

此时,两份文件里内容还是空的,具体内容还需要自行添加

AndroidStudio支持可视化添加fragment以及相互之间的导航关系,这点真的是非常方便

1. 点击加号,可以添加fragment到导航文件

我们首先把使用到的fragment全部添加到导航文件中,如下图:

这里先看一下AndroidStudio自动为我们生成的代码

<fragmentandroid:id="@+id/loginFragment"android:name="com.swy.navigationdemo.login.LoginFragment"android:label="fragment_login"tools:layout="@layout/fragment_login" />

 这个fragment标签,与我们拖进来的三个fragment是一一对应的,它包含了四个参数

  • id:比唯一标识符,可供本文件其他地方调用
  • name:是对应Fragment的路径
  • label:是对应Fragment的一个标签
  • tools:layout:对应Fragment的布局文件

还有另一个属性也是值得关注的,就是最外层navigation标签的

app:startDestination="@id/loginFragment"

这表明了,在LoginActivity中,默认优先显示的是LoginFragment,

说明:我们首次添加的Fragment会被默认为优先显示的Fragment,即如果我这里优先添加LoginFragment到导航图,navigation自动生成app:startDestination="@id/loginFragment",那么如果我首先把RegisterFragment添加进导航图,那么这个属性就会是app:startDestination="@id/registerFragment"

2.增加Fragment间的导航关系 

点LoginFragment,框体右边中部会出现一个圆环

点击该圆环,并拖动,就会出现一条蓝线,将该蓝线指向RegisterFragment后松手

此时就会看到,LoginFragment到RegisterFragment的导航关系被建立了,观察新增的action代码

<actionandroid:id="@+id/action_loginFragment_to_registerFragment"app:destination="@id/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设置为true,Navigation组件也会创建一个新的目标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:
    (1)enterAnim和exitAnim发生于fragment1跳转到fragment2的过程中:
    enterAnim是fragment2的入场动画、exitAnim是fragment1的离场动画
    (2)popEnterAnim和popExitAnim发生于fragment2返回到fragment1的过程中:
    popEnterAnim为返回发生后,fragment1的入场动画
    popExitAnim为返回时,fragment2的离场动画

按照我们最开始设计的跳转关系去完成导航文件,最终是这样的:

即:默认展示登录页面,登录页面可以跳转到注册页面或者是重置密码页面,同时在注册页面和重置密码页面也可以返回到登陆页面

同理,我们完成main_nav_graph的导航关系,如下

因为我们仿照的是微信的底部导航栏,home、contact、find、me四个fragment其实是平级的,它们之间并不存在导航关系。当然我们也可以根据自己的实际业务使用不同的action来设计不同维度和复杂度的导航关系。 

至此,导航文件也有了,剩下的就是创建一个支持导航关系的容器了

(3)导航主机

以LoginActivity为例

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"tools:context=".LoginActivity"><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/login_nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="match_parent"android:layout_height="match_parent"app:defaultNavHost="true"app:navGraph="@navigation/login_nav_graph"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

创建FragmentContainerView作为导航主机(Navigation Host),这里有几个地方需要说明:

  • android:name="androidx.navigation.fragment.NavHostFragment"是固定的写法
  • app:defaultNavHost的作用是将该FragmentContainerView标记为默认的导航主机,这意味着这个FragmentContainerView会拦截系统的后退按钮事件。当用户点击后退按钮时,Navigation组件会按照导航图中的历史记录进行后退操作,而不是直接关闭Activity。并且在一个Activity中,通常只需要一个NavHostFragment作为导航主机。设置app:defaultNavHost="true"可以确保只有这一个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

我相信写到这里,你基本上就能够看出来整个的调用机制了

 (4)Navigation+BottomNavigationView实现底部导航

上面讲了普通的fragment切换,那么关于带底部导航栏的切换,也还是很有必要说明以下的

主界面布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"tools:context=".MainActivity"><androidx.fragment.app.FragmentContainerViewandroid:id="@+id/main_nav_host_fragment"android:name="androidx.navigation.fragment.NavHostFragment"android:layout_width="0dp"android:layout_height="0dp"app:defaultNavHost="true"app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintHorizontal_bias="0.0"app:layout_constraintStart_toStartOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintVertical_bias="0.0"app:navGraph="@navigation/main_nav_graph" /><com.google.android.material.bottomnavigation.BottomNavigationViewandroid:id="@+id/bottomNavigationView"android:layout_width="match_parent"android:layout_height="60dp"app:menu="@menu/main_menu"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintStart_toStartOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout>

显然,页面中只是增加了BottomNavigationView,对应的UI结构如下

 说明:

我这里使用了menu来实现了底部导航栏的几个item内容的导入,代码如下

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"><itemandroid:id="@+id/homeFragment"android:icon="@mipmap/message"android:title="首页"app:showAsAction="ifRoom" /><itemandroid:id="@+id/contactFragment"android:icon="@mipmap/contact"android:title="联系人"app:showAsAction="ifRoom" /><itemandroid:id="@+id/findFragment"android:icon="@mipmap/find"android:title="发现"app:showAsAction="ifRoom" /><itemandroid:id="@+id/meFragment"android:icon="@mipmap/me"android:title="我"app:showAsAction="ifRoom" />
</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方式 

(1)SafeArgs(推荐)

Android官方推荐使用Safe Args来实现Fragment间数据通信,原因主要包括以下几个方面:

  1. 类型安全: Safe Args提供了类型安全的方式来传递参数。在navigation graph XML文件中定义的每个参数都有明确的数据类型(例如字符串、整数、布尔值等)。这将自动为这些参数生成对应的Args类,并提供get和set方法,从而确保在编译时就能捕获到类型不匹配的问题,而不是在运行时才出现崩溃。

  2. 清晰性与可读性: 在navigation graph中直接指定参数及其类型使得整个应用导航结构更加清晰。通过查看XML文件,开发者可以很容易地了解哪些参数在Fragment之间传递,以及它们的类型是什么。

  3. 减少代码量和错误: 使用Safe Args不需要手动创建和解析Bundle对象来传递数据,这大大减少了出错的可能性。自动生成的Args类简化了参数传递过程,使得开发者可以直接操作对象而非键值对,提高了编码效率。

  4. 生命周期感知: Safe Args配合Navigation组件一起使用时,能够更好地适应Android组件的生命周期变化。即使目标Fragment因为配置更改(如屏幕旋转)而重新创建,传递的参数也能得到妥善保存和恢复。

  5. 易于维护: 随着项目规模的增长,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:id="@+id/loginFragment"android:name="com.swy.navigationdemo.login.LoginFragment"android:label="fragment_login"tools:layout="@layout/fragment_login"><actionandroid:id="@+id/action_loginFragment_to_registerFragment"app:destination="@id/registerFragment"><argumentandroid:name="data1"app:argType="string"android:defaultValue=""/></action><actionandroid:id="@+id/action_loginFragment_to_resetPasswordFragment"app:destination="@id/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 = "data3"navController.navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment(data1,data3))}

RegisterFragment

val data1 = arguments?.getString("data1")
val data3 = arguments?.getString("data3")
binding.textData1.text = data1+data3

最终的效果 

可见, 上面说的safeargs的优点,确实是做到了易于维护

易于维护: 随着项目规模的增长,Safe Args能帮助保持代码的整洁和组织有序。当需要修改或添加新的参数时,只需要在navigation graph文件中更新即可,同时会自动反映到相关的Args类中,无需在多个地方手动同步修改。

(2)Bundle

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 源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/596529.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android如何正确使用 Canvas 的 save() 和 restore() 方法

如何正确使用 Canvas 的 save() 和 restore() 方法 在Android的绘图API中&#xff0c;Canvas类提供了一系列强大的功能来绘制自定义视图。为了更高效地管理绘图状态和变换&#xff0c;Canvas类提供了save()和restore()方法。正确使用这些方法是高效绘图和避免常见错误的关键。 …

任务需求分析中的流程图、用例图、er图、类图、时序图线段、图形的作用意义

任务需求分析中的流程图、用例图、er图、类图、时序图线段、图形的作用意义 流程图 流程图中各种图形的含义及用法解析 连接线符号 连接各要素&#xff0c;表示流程的顺序或过程的方向。 批注符号 批注或说明&#xff0c;也可以做条件叙述。 子流程 流程中一部分图形的逻辑…

机器人动力学一些笔记

动力学方程中&#xff0c;Q和q的关系(Q是sita) Q其实是一个向量&#xff0c;q(Q1&#xff0c;Q2&#xff0c;Q3&#xff0c;Q4&#xff0c;Q5&#xff0c;Q6)&#xff08;假如6个关节&#xff09; https://zhuanlan.zhihu.com/p/25789930 举个浅显易懂的例子&#xff0c;你在房…

Windows内核理论基础学习

文章目录 前言Windosw内核 理论基础Windows体系结构CPU权限级别内存空间布局Windows内核结构硬件抽象层&#xff08;HAL&#xff09;内核层执行体层设备驱动程序文件系统/存储管理网络 Windows子系统窗口管理图形设备接口 系统线程和系统进程 内核基本概念处理器模式内存管理进…

项目框架构建之5:日志的构建

本文是“项目框架构建”系列之5&#xff0c;本文介绍日志的构建。 为了做出通用的公共日志模块&#xff0c;我们需要使用微软的Microsoft.Extensions.Logging日志管理模块&#xff0c;该模块提供了灵活且可扩展的日志记录机制&#xff0c;它为整个.net应用程序提供了一致统一的…

02-微服务-Eureka注册中心

Eureka注册中心 假如我们的服务提供者user-service部署了多个实例&#xff0c;如图&#xff1a; 大家思考几个问题&#xff1a; order-service在发起远程调用的时候&#xff0c;该如何得知user-service实例的ip地址和端口&#xff1f;有多个user-service实例地址&#xff0c;…

每日一道算法题day-one(备战蓝桥杯)

从今天开始博主会每天做一道算法题备战蓝桥杯&#xff0c;并分享博主做题的思路&#xff0c;有兴趣就加入我把&#xff01; 算法题目&#xff1a; 有一个长度为 N 的字符串 S &#xff0c;其中的每个字符要么是 B&#xff0c;要么是 E。 我们规定 S 的价值等于其中包含的子…

牛客网面试题知识点记录-03

1.题目讲解重写后子类调用父类的方法总结&#xff1a;当子类重写了父类方法A&#xff0c;父类方法直接调用被重写的父类方法后&#xff0c;调用的是子类的重写的父类方法A。 class Test {public static void main(String[] args) {System.out.println(new B().getValue());}st…

在线负公差测径仪 生产场景智能化

在线负公差测径仪是专为负公差轧制而研发的精密仪器&#xff0c;除检测的外径尺寸外&#xff0c;还能对负公差信息进行展示。让操作工对生产更加得心应手。 负公差测径仪同样采用八轴测头进行非接触式的在线检测&#xff0c;以实现全方位的尺寸检测&#xff0c;并将截面图实时展…

申请域名SSL证书并自动推送至阿里云 CDN

近期国外SSL证书厂商调整了免费证书的续签规则&#xff0c;一年期的证书全部取消&#xff0c;现在只能申请90天有效期的免费证书。普通web站点可以通过宝塔面板或部署acme.sh等证书自动管理工具来实现自动化申请和部署&#xff0c;但是阿里云之类的CDN服务就只能通过手动或Open…

初步认识API安全

一、认识API 1. 什么是API API(应用程序接口)&#xff1a;是一种软件中介&#xff0c;它允许两个不相关的应用程序相互通信。它就像一座桥梁&#xff0c;从一个程序接收请求或消息&#xff0c;然后将其传递给另一个程序&#xff0c;翻译消息并根据 API 的程序设计执行协议。A…

利用阿里云的尖端数据库解决方案增强游戏数据管理

在快节奏和动态的游戏世界中&#xff0c;对于努力为玩家提供无缝体验的公司来说&#xff0c;管理大量数据是一项关键挑战。阿里云是亚太地区的主要参与者&#xff0c;也是全球公认的运营数据库管理系统领导者&#xff0c;提供量身定制的创新解决方案&#xff0c;以应对游戏公司…

C# 全屏label控件实现的贪吃蛇。

C# 全屏label控件实现的贪吃蛇。 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using stat…

案例074:基于微信小程序的儿童预防接种预约管理系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder …

uniCloud 的 schema2code 【实用教程】

schema2code 用于通过 schema 文件&#xff0c;自动生成对表进行增删改查的操作页面。 以 uniCloud-aliyun/database/todo.schema.json 为例 {"bsonType": "object","required": [],"permission": {"read": true,"cr…

Halcon区域的面积和中心点area_center

Halcon区域的面积和中心点 提到区域的特征&#xff0c;最常用的莫过于区域的面积和中心点坐标信息。实际工作中&#xff0c;经常会使用面积或中心点进行特征的选择和定位。Halcon中的area_center算子就是用于实现这一功能的&#xff0c;该算子一次返回以下两个结果。 &#xf…

Vue3-33-路由-路由的别名配置 alias

别名的作用 路由中的别名配置&#xff0c;可以实现 多个路径 对应 同一个路由。 例如 &#xff1a; 路由的路径是 /a; 配置别名为 &#xff1a; /a2; 则 访问 /a 或 /a2 的时候&#xff0c;都可以访问到 同一个组件。 别名的特点 关键字 &#xff1a; alias 当通过别名进行路由…

认识CUDA

CUDA CUDA&#xff08;Compute Unified Device Architecture&#xff09;&#xff0c;是显卡厂商NVIDIA推出的运算平台。 CUDA™是一种由NVIDIA推出的通用并行计算架构&#xff0c;该架构使GPU能够解决复杂的计算问题。 它包含了CUDA指令集架构&#xff08;ISA&#xff09;以及…

抽奖的问题

import randomlucky_num [] # 存放中奖人名单&#xff0c;避免多次中奖 lucky_count 0 # 表示每一种奖品人数够了for time in range(0, 3): # 抽三次奖lucky_count 0 # 每次刷新print(f第一次抽奖现在开始&#xff0c;这次抽的是{3-time}等奖\n)# 判断奖品是哪个if time…

金和OA c6 uploadfileeditorsave接口存在任意文件上传漏洞

产品简介 金和网络是专业信息化服务商&#xff0c;为城市监管部门提供了互联网监管解决方案&#xff0c;为企事业单位提供组织协同OA系统升开发平台&#xff0c;电子政务一体化平台智慧电商平合等服务 漏洞概述 金和-c6 uploadfileeditorsave 任意文件上传&#xff0c;攻击者…