当前位置: 首页 > news >正文

Jetpack Compose实战教程(五)

Jetpack Compose实战教程(五)

第五章 如何在Compose UI中使用基于命令式UI的自定义View


文章目录

  • Jetpack Compose实战教程(五)
  • 一、前言
  • 二、本章目标
  • 三、开始编码
    • 3.1 先让自定义控件能跑起来
    • 3.2给自定义控件使用compose的方式赋值
    • 3.3如何在非composable作用域下使用被记忆的变量
    • 3.4 及时释放资源


一、前言

刚从命令式UI转向compose ui的小伙伴往往会有一个疑问,如果我的项目代码中用到了一些第三方的sdk,它们里面有一些自定义控件,我总不能去改别人的源码,用compose ui 重写一遍吧,成本多大啊。或者我自己项目本身就写了一些自定义控件,功能很多的,全部要用compose重写一遍,成本也很高。不用慌,compose提供了一个支持调用命令式UI的自定义View的组件。

二、本章目标

能完整的把目前项目中暂时无法用compose重写的控件熟练的运用至compose中

友情提醒,如果各位看官有不懂的代码可以先看一下之前的章节,循序渐进,如果还是有不懂的,可以给我留言

三、开始编码

3.1 先让自定义控件能跑起来

这里我以引用的第三方sdk的自定义控件(腾讯自研的pag动画)举例

BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {//AndroidView这个就是支持我们调用命令式UI的组件了,在factory中声明这个控件是什么AndroidView(factory = {PAGView(it).apply {this.composition = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))this.setRepeatCount(0)this.play()}})}}}

运行之后,程序正常运行跑了起来,但它是铺满屏幕的,我们实际使用时,可能需要指定它的位置和大小,这里提供两个方案:

BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {AndroidView(factory = {PAGView(it).apply {//方案一,使用我们熟知的kotlin代码来指定大小val params = ConstraintLayout.LayoutParams(200f.dp2px().toInt(),200f.dp2px().toInt())this.layoutParams = paramsthis.composition = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))this.setRepeatCount(0)this.play()}}, modifier = Modifier //方案二,使用compose 的modifier来指定大小.width(200.dp).height(200.dp))}}}

两个方案都可以将这个PAGView的大小和宽高指定为200dp,但建议使用方案二,因为如果这个AndroidView是需要依赖其它compose控件的位置而发生改变的话,那么方案一就无效了。

3.2给自定义控件使用compose的方式赋值

上述代码我们是写死了动画的执行资源以及执行次数的,那么想要动态改变它,要怎么处理呢?
在实现这个逻辑之前,我需要先解释一个东西,上述代码只贴了compose ui的具体代码,它的完整代码是这样的:

override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent(content = {viewRoot()})}@Composable
fun viewRoot() {BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {AndroidView(factory = {PAGView(it).apply {//方案一,使用我们熟知的kotlin代码来指定大小val params = ConstraintLayout.LayoutParams(200f.dp2px().toInt(),200f.dp2px().toInt())this.layoutParams = paramsthis.composition = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))this.setRepeatCount(0)this.play()}}, modifier = Modifier //方案二,使用compose 的modifier来指定大小.width(200.dp).height(200.dp))}}}}

请留意这个 @Composable注解,它代表了viewRoot 这个函数可以使用compose 的相关代码,这是因为compose有要求,所有compose ui的代码,只能在Composable作用域下执行。这意味着,如果我们是要在kotlin代码中去动态改变值的话,那么我们就不能使用compose ui的相关参数,我们先来看在使用compose ui相关参数的办法:

BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {//这里我们定义了一个用于观察的可变参数var compositionValue = remember {mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901")))}var testBoolean = false //定义一个boolean变量AndroidView(factory = {PAGView(it).apply {this.composition = compositionValue.value //然后将它的值赋值给PAGView使用this.setRepeatCount(0)this.play()}}, modifier = Modifier.width(200.dp).height(200.dp).clickable {testBoolean = !testBoolean //点击一次就改变一次值,从而改变PAGView播放的动画资源if(testBoolean){compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002201"))}else{compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))}})}}}

代码写完了,然后运行一下,发现点击之后并没有任何变化,这里就要提及一下AndroidView的这个组件了,我们先点击进去看一下AndroidView里面都干了啥:

@Composable
@UiComposable
fun <T : View> AndroidView(factory: (Context) -> T,modifier: Modifier = Modifier,update: (T) -> Unit = NoOpUpdate
) {AndroidView(factory = factory,modifier = modifier,update = update,onRelease = NoOpUpdate)
}

如果再点击里面的AndroidView则能看到它的具体实现,但我们目前所引用的这个,就能解决我们的问题,先不做过深讲解,我们先来看上面的几个函数,factory是我们声明了这个自定义View是啥,我们上述的代码是初始化了这个PAGView,并给它赋了值,但如果要它的参数动态改变的话,我们要使用update函数,将代码改成如下:

BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {var compositionValue = remember {mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901")))}var testBoolean = falseAndroidView(factory = {PAGView(it).apply {this.composition = compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier = Modifier.width(200.dp).height(200.dp).clickable {testBoolean = !testBooleanif(testBoolean){compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002201"))}else{compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))}},update = {it.composition = compositionValue.value //将数据的改动设置到update函数中来})}}}

再次运行,得到了我们想要的效果。细心的网友应该会发现一些问题,为什么上面的compositionValue要用remember括起来,并且里面还用了一个mutableStateOf来初始化参数值,直接像testBoolean一样不行吗?
所谓实践出真知,那么我们来修改一下代码,改成如下:

BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {var compositionValue = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))var testBoolean = falseAndroidView(factory = {PAGView(it).apply {this.composition = compositionValuethis.setRepeatCount(0)this.play()}}, modifier = Modifier.width(200.dp).height(200.dp).clickable {testBoolean = !testBooleanif(testBoolean){compositionValue = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002201"))}else{compositionValue = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))}},update = {it.composition = compositionValue})}}}

运行,发现点击并没有改变PAGView的播放资源 这是因为,compose是使用观察者模式,通过记录每个参数的变化来刷新UI的,而我们自行定义的参数,并不能让compose记住它的值的变化。

3.3如何在非composable作用域下使用被记忆的变量

好,接下来,假设我们有这么一个需求,我们要监听手机电量的变化,当电量低的时候,播放一个电量比较低的PAG动画,而监听电量的变化是通过系统的广播来实现的,这就意味着我们需要把 compositionValue这个变量提取出来,变成整个Activity的局部变量,才能在收到电量广播的变化时,修改它的值,于是我们依葫芦画瓢,写出如下代码:

private var compositionValue = remember {mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901")))}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent(content = {viewRoot()})}

然而,当我们刚把这个代码复制上去的时候,android studio就提示编译错误了
在这里插入图片描述
再次提醒我们,@Composable调用只能发生在@Composable函数的上下文中,所以我们需要做一些简单的改动,提示编译错误的是remember这个函数,我们将它干掉即可

private var compositionValue = mutableStateOf(PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901")))override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent(content = {viewRoot()})}@Composable
fun viewRoot() {BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean = falseAndroidView(factory = {PAGView(it).apply {this.composition = compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier = Modifier.width(200.dp).height(200.dp).clickable {testBoolean = !testBooleanif(testBoolean){compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002201"))}else{compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))}},update = {it.composition = compositionValue.value})}}}}

这样编译是没有报错了,但当我们运行时,Logcat抛给了我们另外一个错误:
在这里插入图片描述
很明显,我们在初始化compositionValue的时候,不能在参数那里直接调用PAGFile.Load(assets, Constant.PagConstant.getSourceByName(“002901”)),此时界面还没绘制,读取资源文件会报空指针,然后我们尝试修改一下:

private var compositionValue = mutableStateOf(PAGFile)
@Composableoverride fun viewRoot() {compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean = falseAndroidView(factory = {PAGView(it).apply {this.composition = compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier = Modifier.width(200.dp).height(200.dp).clickable {testBoolean = !testBooleanif(testBoolean){compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002201"))}else{compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))}},update = {it.composition = compositionValue.value})}}}}

此时android studio又报编译错误了
在这里插入图片描述
原来这个PAGFile的构造函数是私有的,于是我们只能改成这样:

private var pagFile:PAGFile?=null //我们定义一个为空的PAGFile,然后赋值给compositionValueprivate var compositionValue = mutableStateOf(pagFile)@Composableoverride fun viewRoot() {//然后尽可能早的给compositionValue赋值一个真实的数据compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean = falseAndroidView(factory = {PAGView(it).apply {this.composition = compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier = Modifier.width(200.dp).height(200.dp).clickable {testBoolean = !testBooleanif(testBoolean){compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002201"))}else{compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))}},update = {it.composition = compositionValue.value})}}}}

运行,程序按我们的要求跑起来了。那么举一反三,设置播放次数 以及何时播放,都可以用外部参数来控制,这里建议大家亲自尝试一下

3.4 及时释放资源

我们再回到刚才AndroidView的实现这里

@Composable
@UiComposable
fun <T : View> AndroidView(factory: (Context) -> T,modifier: Modifier = Modifier,update: (T) -> Unit = NoOpUpdate
) {AndroidView(factory = factory,modifier = modifier,update = update,onRelease = NoOpUpdate)
}

它还有一个函数 onRelease,当我们需要在生命周期结尾的时候释放资源,就需要用到它了,那么我们来编写一下代码

private var pagFile:PAGFile?=nullprivate var compositionValue = mutableStateOf(pagFile)@Composableoverride fun viewRoot() {compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))BaseTheme {Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {ConstraintLayout {var testBoolean = falseAndroidView(factory = {PAGView(it).apply {Log.e("test","初始化资源")this.composition = compositionValue.valuethis.setRepeatCount(0)this.play()}}, modifier = Modifier.width(200.dp).height(200.dp).clickable {testBoolean = !testBooleanif(testBoolean){compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002201"))}else{compositionValue.value = PAGFile.Load(assets, Constant.PagConstant.getSourceByName("002901"))}},update = {Log.e("test","加载资源")it.composition = compositionValue.value},onRelease = {Log.e("test","释放资源")it.freeCache()})}}}}

在这里插入图片描述
本章内容至此结束,各位看官一定要亲自编写代码,才能更加熟练的使用compose ui,祝各位都能更上一层楼

相关文章:

Jetpack Compose实战教程(五)

Jetpack Compose实战教程&#xff08;五&#xff09; 第五章 如何在Compose UI中使用基于命令式UI的自定义View 文章目录 Jetpack Compose实战教程&#xff08;五&#xff09;一、前言二、本章目标三、开始编码3.1 先让自定义控件能跑起来3.2给自定义控件使用compose的方式赋值…...

【vueUse库Watch模块各函数简介及使用方法--上篇】

vueUse库是一个专门为Vue打造的工具库,提供了丰富的功能,包括监听页面元素的各种行为以及调用浏览器提供的各种能力等。其中的Browser模块包含了一些实用的函数,以下是这些函数的简介和使用方法: vueUse库Sensors模块各函数简介及使用方法 vueUseWatch函数1. until2. watc…...

JavaScript中的LHS和RHS

LHS和RHS之前我们先来回忆一下最简单的赋值操作! var test100; console.log(test); 以上代码的意思简单我们理解为把右边的值赋值给左边的test变量,然后输出打印结果。 可是我们要是深入理解你就会发现在这个过程当中&#xff0c;还发生了一些其他的事情 而这些事情就是今天…...

appium 实战问题 播放视频时无法定位到元素

背景 在做UI自动化时&#xff0c;有播放详情页的用例&#xff0c;但是发现视频在播放的时候无法定位到元素或者很慢&#xff0c;了解到appium在动态的页面实时获取布局元素导致定位变慢。所以只能将视频暂停在操作元素&#xff0c;点击到暂停按钮又是个问题&#xff0c;通过ad…...

鸿蒙‘ohpm‘ 不是内部或外部命令,也不是可运行的程序-解决方案

&#x1f525; 博客主页&#xff1a; 小韩本韩&#xff01; ❤️ 感谢大家点赞&#x1f44d;收藏⭐评论✍️ 在鸿蒙的DevEco Studio的终端下输入 ohpm -v 或者 你需要下载第三方ohpm包的时候提示‘ohpm‘ 不是内部或外部命令&#xff0c;也不是可运行的程序- 主要是因为我们…...

方法引用 异常 file

目录 一.方法引用 1.方法引用概述 2.引用静态方法 3.引用成员方法 i.引用其他成员方法 ii.引用本类成员方法 iii.引用父类成员方法 4.引用构造方法 5.其他调用方式 i.使用类名引用成员方法 ii.引用数组的构造方法 二、异常 1.异常的作用 2.异常的处理方式 i.JVM…...

比较(六)利用python绘制径向柱图

比较&#xff08;六&#xff09;利用python绘制径向柱图 径向柱图&#xff08;Circular Barplot&#xff09;简介 径向柱图基于同心圆网格来绘制条形图&#xff0c;虽然不如普通条形图表达准确&#xff0c;但却有抓人眼球的效果。其衍生的南丁格尔玫瑰图则广为人知。 快速绘制…...

为什么需要重写equals和如何重写equals

首先先看Java中的 &#xff0c;比较的两个对象的地址值。 如果是基本数据类型&#xff0c;那么就是比较的是值。 如果是引用数据类型&#xff0c;比较的就是地址. object类中的equals方法也是用的&#xff1b; 所以要比较两个对象的大小&#xff0c;去调用默认的equals方法…...

C#字符串操作:判断一个字符串是否存在于另一个字符串按特定字符分割后的子字符串中的几种方法

要判断一个字符串是否存在于另一个字符串按特定字符分割后的子字符串中&#xff0c;可以使用以下几种方法&#xff1a; 方法一&#xff1a;使用Split和Array.Exists 你可以使用 Split 方法将字符串分割成子字符串数组&#xff0c;然后使用 Exists方法检查目标字符串是否在数组…...

Hi3861 OpenHarmony嵌入式应用入门--MQTT

MQTT 是机器对机器(M2M)/物联网(IoT)连接协议。它被设计为一个极其轻量级的发布/订阅消息传输 协议。对于需要较小代码占用空间和/或网络带宽非常宝贵的远程连接非常有用&#xff0c;是专为受限设备和低带宽、 高延迟或不可靠的网络而设计。这些原则也使该协议成为新兴的“机器…...

[22] Opencv_CUDA应用之 使用背景相减法进行对象跟踪

Opencv_CUDA应用之 使用背景相减法进行对象跟踪 背景相减法是在一系列视频帧中将前景对象从背景中分离出来的过程,它广泛应用于对象检测和跟踪应用中去除背景 背景相减法分四步进行:图像预处理 -> 背景建模 -> 检测前景 -> 数据验证 预处理去除噪声背景建模,以便与…...

Maven在Windows中的配置方法

本文介绍在Windows电脑中&#xff0c;下载、配置Maven工具的详细方法。 Maven是一个广泛使用的项目管理工具&#xff0c;主要针对Java项目&#xff0c;但也可以用于其他类型的项目&#xff1b;其由Apache软件基金会维护&#xff0c;旨在简化和标准化项目构建过程&#xff0c;依…...

一、redis-万字长文读懂redis

高性能分布式缓存Redis `第一篇章`1.1缓存发展史&缓存分类1.1.1 大型网站中缓存的使用带来的问题1.1.2 常见缓存的分类及对比与memcache对比1.2 数据类型选择&应用场景1.2.1 string1.2.2 hash1.2.3 链表1.2.4 set1.2.5 sortedset有序集合类型1.2.6 总结1.3 Redis高级应…...

搞清楚[继承],易如反掌

穷不失义&#xff0c;达不离道。——孔丘《论语》 继承 1、简单理解2、继承2、1、继承的概念2、2、继承定义2、3、基类和派生类对象赋值转换2、4、继承中的作用域2、5、派生类默认成员函数2、6、继承中的特点2、6、1、友元2、6、2、静态成员2、6、3、菱形继承及菱形虚拟继承 3、…...

Perl 语言入门学习指南:探索高效脚本编程的奥秘

引言 Perl&#xff0c;全称Practical Extraction and Report Language&#xff0c;是一种功能强大的编程语言&#xff0c;特别擅长于文本处理、报告生成以及系统自动化管理任务。自1987年诞生以来&#xff0c;Perl凭借其灵活性、强大的内置功能库和广泛的社区支持&#xff0c;…...

【HTML】-解决页面内容无法选择、复制问题

目录 1、网页内容无法选中 1.1、问题原因 1.2、解决脚本 1.2.1、开启控制台窗口 1.2.2、执行脚本命令 2、内容复制弹出阻止框 2.2、解决脚本 1、网页内容无法选中 1.1、问题原因 今天在访问某一网站平台&#xff0c;需要将内容进行选择、复制时发现不可使用。 在使用…...

C#中委托与事件

一、委托 在面向对象中&#xff0c;我们可以将任何数据类型作为参数传递给方法&#xff0c;能否将一个方法作为参数传递给另一个方法&#xff1f;C#中通过委托可以实现将方法作为参数进行传递。 1.1概念 委托是一种引用类型&#xff0c;它可以用于封装并传递方法作为参数。委…...

通用后台管理(二)——项目搭建

目录 前言 一、安装vue-cli依赖 1、使用yarn下载vue-cli 2、使用npm下载 3、检查一下是否下载成功 二、创建项目 1、创建项目&#xff0c;my-app是项目名称 2、 这里选择vue 2&#xff0c;蓝色表示选中的。 3、启动项目 三、下载项目依赖 四、配置项目 1、修改esli…...

多模态大模型之达摩院通义MPLUG

引言 随着人工智能技术的飞速发展&#xff0c;多模态技术逐渐成为研究的热点。它结合了文本、图像、声音等多种数据类型&#xff0c;为机器理解世界提供了更丰富的视角。本文根据严明老师的达摩院通义MPLUG多模态预训练技术分享&#xff0c;及其在电商等行业的应用实践&#x…...

文章翻译记录

以 PINN 为基础&#xff0c;我们开发了一个框架&#xff0c;用于在不同震源位置和速度模型下进行地震建模。本研究的显著贡献包括&#xff1a; 1. 为了提高网络对不同速度模型的泛化能力&#xff0c;必须将速度变量 vp 作为系统的输入参数。本研究从监督学习中汲取灵感&#xf…...

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...

进程地址空间(比特课总结)

一、进程地址空间 1. 环境变量 1 &#xff09;⽤户级环境变量与系统级环境变量 全局属性&#xff1a;环境变量具有全局属性&#xff0c;会被⼦进程继承。例如当bash启动⼦进程时&#xff0c;环 境变量会⾃动传递给⼦进程。 本地变量限制&#xff1a;本地变量只在当前进程(ba…...

golang循环变量捕获问题​​

在 Go 语言中&#xff0c;当在循环中启动协程&#xff08;goroutine&#xff09;时&#xff0c;如果在协程闭包中直接引用循环变量&#xff0c;可能会遇到一个常见的陷阱 - ​​循环变量捕获问题​​。让我详细解释一下&#xff1a; 问题背景 看这个代码片段&#xff1a; fo…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

Objective-C常用命名规范总结

【OC】常用命名规范总结 文章目录 【OC】常用命名规范总结1.类名&#xff08;Class Name)2.协议名&#xff08;Protocol Name)3.方法名&#xff08;Method Name)4.属性名&#xff08;Property Name&#xff09;5.局部变量/实例变量&#xff08;Local / Instance Variables&…...

【Java_EE】Spring MVC

目录 Spring Web MVC ​编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 ​编辑参数重命名 RequestParam ​编辑​编辑传递集合 RequestParam 传递JSON数据 ​编辑RequestBody ​…...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2

每日一言 今天的每一份坚持&#xff0c;都是在为未来积攒底气。 案例&#xff1a;OLED显示一个A 这边观察到一个点&#xff0c;怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 &#xff1a; 如果代码里信号切换太快&#xff08;比如 SDA 刚变&#xff0c;SCL 立刻变&#…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...