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间数据通信,原因主要包括以下几个方面:
类型安全: 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: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 源码
相关文章:
Android Jetpack学习系列——Navigation
写在前面 Google在2018年就推出了Jetpack组件库,但是直到今天我才给重视起来,这真的不得不说是一件让人遗憾的事。过去几年的空闲时间里,我一直在尝试做一套自己的组件库,帮助自己快速开发,虽然也听说过Jetpack&#…...
编程语言的新趋势
随着科技的飞速发展,IT行业经历了巨大的变革,其中编程语言作为技术生态的核心要素,其演变趋势对整个行业影响深远。从过去到现在,再到未来,编程语言的发展都呈现出明显的时代特征。本文将探讨当前IT行业的现状…...
C++:类和对象(2)
目录 1.strcut和class的区别 2.将成员属性设置为私有 3.对象的初始化和清理 3.1 构造函数和析构函数 3.1.1 构造函数语法 3.1.2 析构函数语法 3.1.3 检验 3.2 构造函数的分类和调用 3.3 拷贝构造函数调用 1.strcut和class的区别 struct和class的唯一区别在于默认的访问…...
【React系列】网络框架axios库的使用
本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. axios库的基本使用 1.1. 网络请求的选择 目前前端中发送网络请求的方式有很多种: 选择一:传统的Aj…...
pygame学习(二)——绘制线条、圆、矩形等图案
导语 pygame是一个跨平台Python库(pygame news),专门用来开发游戏。pygame主要为开发、设计2D电子游戏而生,提供图像模块(image)、声音模块(mixer)、输入/输出(鼠标、键盘、显示屏)模…...
TCL学习笔记(持续更新)
前言: TCL(tool common language)是一种通用工具语言,很多eda tool都支持tcl,学习了解一些tcl基本语法还是很有必要的。 1:基础概念 解释器: #!/usr/bin/tclsh 打印: puts -> p…...
Xpath的问题:为什么在DOM中确定存在(可见)的元素,用//表达式匹配不到(附解决办法)
今天遇到一个很有意思的问题,我的爬取的目标页面上有时会出现一个弹窗,它挡住我点击其它按钮了,我想找到它的关闭按钮,自动点击一下关闭掉,本来是很简单的事情,但偏偏出问题了,DOM中看到的html是…...
有没有游泳可以戴的耳机?游泳耳机入耳式好,还是骨传导好
游泳是一项既能锻炼身体又能让人放松心情的运动。我们知道,音乐能够为我们的水上时光增添更多的乐趣。那么,在众多游泳耳机中,如何选择一款既适合自己的需求又具备良好性能的产品呢? 首先,我们要了解的是,…...
【绘图软件】自用安装教程
链接:https://pan.baidu.com/s/17r9Pr460FzkULU7fTr91_w?pwdftv7 提取码:ftv7 --来自百度网盘超级会员V6的分享解压并且右键打开set up 解压crack软件前需要退出杀毒软件, 关闭实时保护 域网络关闭,专用网络关闭࿰…...
AIGC时代-GPT-4和DALL·E 3的结合
在当今这个快速发展的数字时代,人工智能(AI)已经成为了我们生活中不可或缺的一部分。从简单的自动化任务到复杂的决策制定,AI的应用范围日益扩大。而在这个广阔的领域中,有两个特别引人注目的名字:GPT-4和D…...
springBoot集成RabbitMQ实现(直连模式\路由模式\广播模式\主题模式)的消息发送和接收
该项目介绍了springboot如何集成rabbitMQ消息中间件,实现(直连模式\路由模式\广播模式\主题模式)的消息发送和接收 pom依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId...
Attention机制
目录 提出背景 Attention原理 Attention机制本质思想 Attention机制总结 改进:Self-Attention 前置知识:RNN,LSTM/GRU 提出背景 Attention模型是基于Encoder-Decoder框架提出的。Encoder-Decoder框架,也就是编码-解码框架&…...
Rust 常用的第三方库
Rust 的标准库虽然已经很强大,但如果恰当地使用第三方库,可以大大改善编程效率。以下是一些常用的 Rust 第三方库。 tokio ---- 最通用的异步编程库,几乎可以说是行业标准了,大量的其它库依赖于tokio。reqwest ---- HTTP 客户端库…...
构建高可用性Java应用:介绍分布式系统设计与开发
构建高可用性Java应用需要考虑许多因素,其中之一是设计和开发分布式系统。分布式系统是由多个独立计算机或节点组成的系统,这些节点通过网络连接,共同完成一项任务。 在分布式系统设计和开发中,有几个关键方面需要考虑࿱…...
x-cmd pkg | gitui - git 终端交互式命令行工具
目录 简介首次用户功能特点类似工具与竞品进一步探索 简介 gitui 由 Stephan D 于 2020 年使用 Rust 语言构建的 git 终端交互式命令行工具,旨在终端界面中便捷管理 git 存储库。 首次用户 使用 x gitui 即可自动下载并使用 在终端运行 eval "$(curl https:/…...
javaWeb案例知识点
一.rest风格编程 二.综合案例结构 三.分页查询 分页插件PageHelper 四.部门管理开发 五.员工管理开发 六.文件上传...
SQL日期列更新操作详解
在实际的数据库管理过程中,有时我们需要对数据库中的日期列进行更新。这篇博客将详细介绍一条 SQL 语句,该语句用于更新 referral_up_order 表中的多个日期列,并将它们的日期部分更改为 2023-10-24,同时保留原始时间部分。 1、背…...
stable diffusion 基础教程-图生图
界面 图生图大概有以下几个功能: 图生图涂鸦绘制局部绘制局部绘制(涂鸦蒙版)其常用的也就上面四个,接下来逐步讲解。 以图反推提示词 图生图可以根据反推提示词来获取相应图片的提示词,目前3种主流方式,如下: CLIP反推提示词:推导出的文本倾向于自然语言的描述方式,…...
如何获取高质量的静态住宅代理?常见问题与误区
静态住宅IP代理在今天的网络营销领域扮演着至关重要的角色,静态住宅IP代理以其稳定性和高匿名性,为互联网业务提供了一个安全的执行环境。通过模拟真实用户的网络行为,这些IP代理降低了企业在网络营销活动中被识别和封禁的风险。它保护了企业…...
基于SpringBoot的旅游网站281
文章目录 项目介绍主要功能截图:部分代码展示设计总结项目获取方式🍅 作者主页:超级无敌暴龙战士塔塔开 🍅 简介:Java领域优质创作者🏆、 简历模板、学习资料、面试题库【关注我,都给你】 🍅文末获取源码联系🍅 项目介绍 基于SpringBoot的旅游网站281,java项目。…...
做外贸没客户就静下来沉淀
最近有人说,现在手上的各种群都变得很安静了,像之前,到各种节日,比如国庆,冬至,元旦这些。群里都非常热闹,不说全部人,总会有几个人发红包,或者发祝贺信息的。今年发现&a…...
网络流总结
网络流总结 基础知识最大流最小割定理 最大流EKdinic模型二分图匹配无源汇上下界可行流有源汇上下界最大、最小流多源汇最大流最大流之关键边最大流之拆点最大流建图实战 最小割模型最大权闭合子图最大密度子图最小点权覆盖集最大点权独立集最小割建图实战 费用流EK模型费用流与…...
安卓11通过脚本修改相应板型系统属性
安卓10以后rk的一套源码兼容很多板型,多种cpu型号都兼容了,这一点相比之前省心了很多,所以就诞生了需要一套代码兼容多种板子的需求,定制修改中需要经常修改系统属性,通过以下脚本一次实现: #!/bin/bashfu…...
网络安全—PKI公钥基础设施
文章目录 前提知识散列函数非对称加密数字签名 PKI受信任的人RA注册CA颁发IKE数字签名认证(交换证书)密钥管理 前提知识 散列函数 散列也可以叫哈希函数,MD5、SHA-1、SHA-2、、(不管叫啥,都记得是同一个东西就行&…...
推荐一款加速器,也可加速github
地址https://github.com/BeyondDimension/SteamTools...
springboot框架,中间库是mognodb,可以写入2个数据库的Demo
1-springboot版本: 2.3.9.RELEASE POM.xml文件: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&quo…...
基于Java SSM框架实现旅游资源网站系统项目【项目源码+论文说明】
基于java的SSM框架实现旅游资源网站系统演示 摘要 本论文主要论述了如何使用JAVA语言开发一个旅游资源网站 ,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述旅…...
015、控制流运算符match
1. 控制流运算符match Rust中有一个异常强大的控制流运算符:match,它允许将一个值与一系列的模式相比较,并根据匹配的模式执行相应代码。模式可由字面量、变量名、通配符和许多其他东西组成;后文会详细介绍所有不同种类的模式及它…...
个人博客主题 vuepress-hope
文章目录 1. 简介2. 配置2.1 个人博客,社媒链接配置 非常推荐vuepress-hope 1. 简介 下面的我的博客文章的截图 通过md写博客并且可以同步到github-page上 2. 配置 2.1 个人博客,社媒链接配置 配置文件 .vuepress/theme.ts blog: {medias: {BiliB…...
【LeetCode-剑指offer】--19.验证回文串II
19.验证回文串II 方法:双指针 首先考虑如果不允许删除字符,如何判断一个字符串是否是回文串。常见的做法是使用双指针。定义左右指针,初始时分别指向字符串的第一个字符和最后一个字符,每次判断左右指针指向的字符是否相同&#…...
wordpress 图片自适应/学前端去哪个培训机构
本文整理匯總了Java中com.android.ddmlib.AndroidDebugBridge.init方法的典型用法代碼示例。如果您正苦於以下問題:Java AndroidDebugBridge.init方法的具體用法?Java AndroidDebugBridge.init怎麽用?Java AndroidDebugBridge.init使用的例子…...
做网站需要哪些成本/广州白云区疫情实时动态
excel怎么冻结前两行不动?其实只要直接使用冻结窗格功能就可以了。下面我们来学习一下。冻结窗口怎么冻结多行步骤1、打开excel文件,例如这个表格。步骤2、只冻结前两行需要选中第三行,选择【视图】-【冻结窗格】;步骤3、在三角处打开&#…...
代替手动修改网站模板标签/seo外链发布平台
一、转换图片格式为SVG 一般APK中都会用到很多图片,一般jpg或者png之类的图片占用的内存都很大,而且为了适配不同大小的屏幕,可能会准备多套内容一样,但是大小不一样的图片,这样就会造成大量内存被浪费。 SVG图片占用…...
光谷做网站推广费用/网站开发公司
#include //引用相关包void display(void){glClear(GL_COLOR_BUFFER_BIT); //清空颜色缓冲区glColor3f(0,1,1); //重置颜色glLoadIdentity(); //清空矩阵glTranslatef(0,0,-5); //将场景中的物体沿z轴负方向移动5个单位长glRotatef(40,0,1,0);//gluLookAt(0,0,5,0,0,0,0…...
可以做游戏可以视频约会的网站/福建百度seo排名点击软件
Free Ur Mind-推荐使用FreeMind工具 什么是MindMap? MindMap(被译成思维导图或心智图)是一种思维工具,由英国的记忆之父托尼-博赞发明。MindMap是一种新的思维模式,它将左脑的逻辑、顺序、条例、文字、数字,以及右脑的…...
金融app开发/安卓优化大师最新版下载
本博客所有内容是原创,未经书面许可,严禁任何形式的转载 http://blog.csdn.net/u010255642 回归分析(英语:Regression Analysis)是一种统计学上分析数据的方法,目的在于了解两个或多个变量间是否相关、相关…...