【HarmonyOS】ArkUI - 状态管理

在声明式 UI 中,是以状态驱动视图更新,如图1所示:
图1

图1

其中核心的概念就是状态(State)和视图(View):

  • 状态(State):指驱动视图更新的数据(被装饰器标记的变量)

    @Entry
    @Component
    struct Index {@State message: string = 'Hello World'build() {Column() {Text(this.message).fontSize(50).onClick(() => {this.message = 'Hello ArkTS'})}.width('100%').height('100%')}
    }
    

    Index 组件里定义了 message 变量,而 message 前面就加了 @State 装饰器,如果没有这个装饰器,message 就是一个普通的变量,但是呢,正是我们给它加上了 @State 装饰器,所以,它就变成了一个状态变量。

  • 视图(View):基于 UI 描述渲染得到的用户界面

    @Entry
    @Component
    struct Index {@State message: string = 'Hello World'build() {Column() {Text(this.message).fontSize(50).onClick(() => {this.message = 'Hello ArkTS'})}.width('100%').height('100%')}
    }
    

    build 函数内部就是 UI 的描述,我们这里就描述了一个列式的容器,容器里有一个普通的文本,文本的内容就是 message 的值,所以最终渲染出来的视图就是在屏幕上显示一个 Hello World。

视图渲染好了以后,用户就可以对视图中的页面元素产生交互,比如去触摸、点击、拖拽等事件。这些互动事件就有可能改变状态变量的值,比如说我们这个示例里,给 Text 绑定了一个点击事件,一旦用户点击,就会修改 message 的值,而在 ArkUI 的内部,有一种机制去监控状态变量的值,一旦发现发生了变更,就会触发视图的重新渲染,所以,按照我们这个示例来看,如果现在去点击这个 Hello World 文字,就会触发点击事件,修改 message 的值,把它变成 Hello ArkTS,而一旦这个变量值发生变更,视图重新渲染,于是,屏幕上显示的文字从 Hello World 变成 Hello ArkTS。

所以像这种状态视图之间相互作用的机制,我们就称之为状态管理机制。有了这种机制以后,我们将来开发的时候,不需要自己操作页面,只需要描述页面的结构,然后定义好对应的事件,在事件里面去操作状态,就可以了,这样每当用户去产生互动时,自然就会引起页面的动态刷新。所以一个动态页面就很容易的实现了。这也就是状态管理的好处。

一、@State 装饰器

  1. @State 装饰器标记的变量必须初始化,不能为空值。

    比如上面的示例代码,message 一声明,就给它初始化了一个 Hello World。

  2. @State 装饰器支持的类型是有限制的。

    支持 Object、class、string、number、boolean、enum 类型以及这些类型的数组。

    注:虽然以上这些类型都是允许的,但是有两个特殊场景:

    1. 嵌套类型:@State 修饰的变量是 Object,如果 Object 里面的属性发生了变更其实是能触发视图的更新,但是如果 Object 里面的某个属性它又是一个 Object,也就是 Object 套 Object,那就是嵌套类型,那么内部嵌套的那个 Object 它里面的属性再发生变更,就无法触发视图更新。

    2. 数组:数组中的元素不是简单类型,而是一个对象,那么对象里面的属性发生变更,同样无法触发视图更新。

二、@Prop 和 @Link 装饰器

  1. 首先看一段代码,这是实现任务统计的示例代码:

     // 任务类class Task {static id: number = 1// 任务名称name: string = `任务${Task.id++}`// 任务状态finished: boolean = false}// 统一的卡片样式@Styles function card() {.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({ radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4 })}// 任务完成样式@Extend(Text) function finishedTask() {.decoration({ type: TextDecorationType.LineThrough }).fontColor('#B2B2B1')}@Entry@Componentstruct PropPage {// 总任务数量@State totalTask: number = 0// 已完成任务数量@State finishTask: number = 0// 任务数组@State tasks: Task[] = []build() {Column({ space: 10 }) {// 任务进度卡片Row() {Text('任务进度:').fontSize(30).fontWeight(FontWeight.Bold)Stack() {Progress({value: this.finishTask,total: this.totalTask,type: ProgressType.Ring}).width(100)Row() {Text(this.finishTask.toString()).fontSize(24).fontColor('#0000FF')Text(' / ' + this.totalTask.toString()).fontSize(24)}}}.card().margin({ top: 20, bottom: 10 }).justifyContent(FlexAlign.SpaceEvenly)// 新增任务按钮Button('新增任务').width(200).margin({ top: 10 }).onClick(() => {// 新增任务数据this.tasks.push(new Task())// 更新任务总数量this.totalTask = this.tasks.length})// 任务列表List({ space: 10 }) {ForEach(this.tasks,(item: Task, index) => {ListItem() {Row() {Text(item.name).fontSize(20)Checkbox().select(item.finished).onChange(val => {// 更新当前任务状态item.finished = val// 更新已完成任务数量this.finishTask = this.tasks.filter(item => item.finished).length})}.card().justifyContent(FlexAlign.SpaceBetween)}.swipeAction({ end: this.DeleteButton(index) })})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}.width('100%').height('100%').backgroundColor('#F1F2F3')}@Builder DeleteButton(index: number) {Button() {Image($r('app.media.delete')).fillColor(Color.White).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(() => {this.tasks.splice(index, 1)this.totalTask = this.tasks.lengththis.finishTask = this.tasks.filter(item => item.finished).length})}}
    
  2. 概念

    当父子组件之间需要数据同步时,可以使用 @Prop 和 @Link 装饰器。

    Q:什么是父子组件?什么又是数据同步

    A:看上面这段示例代码,我们会发现代码是从上到下一股脑写的,写了上百行代码,整个代码的可读性是比较差的。要解决这个问题,可以把整个功能分成几个模块,然后按模块封装成一个一个的组件,这样在入口组件(@Entry)当中就不用写太多代码,而是去引用其他模块对应的组件。整个代码结构会更加清晰,复用性也会更好。所以,入口组件就是一个父组件,它引用了其他的组件,那么这些被引用的组件就是子组件。所以这时候组件之间就出现了这种引用关系,而组件之间引用的过程中可能就会有数据传递的需求。比如在父组件里定义了一些数据,然后在子组件里需要用,这时候就需要把父组件的数据传给子组件,单纯的传递还不够,每当数据发生变更,还要去通知子组件,这就叫数据同步。数据同步利用 @State 装饰器是实现不了的,那就需要用 @Prop 和 @Link 装饰器来实现。

  3. @Prop 和 @Link 装饰器对比

    @Prop@Link
    同步类型单向同步双向同步
    允许装饰的变量类型· 父子类型一致:string、number、boolean、enum
    · 父组件是对象类型,子组件是对象属性
    · 不可以是数组、any
    · 父子类型一致:string、number、boolean、enum、object、class,以及它们的数组
    · 数组中元素增、删、替换会引起刷新
    · 嵌套类型以及数组中的对象属性无法触发视图更新
    初始化方式允许子组件初始化父组件传递,禁止子组件初始化
  4. 使用 @Prop 对示例代码进行封装和改造

    假设父组件中的变量采用 @State 装饰器,与之对应的子组件采用 @Prop 装饰器,那这时候就可以实现单项同步,当父组件对 @State 装饰的变量进行任意的修改时,就会立刻把这个数据传递给子组件,但反过来,子组件如果对这个数据进行了修改,是不会反向传递到父组件那里。所以,这种同步被称之为单向同步。实现原理就是拷贝

     ...@Entry@Componentstruct PropPage {// 总任务数量@State totalTask: number = 0// 已完成任务数量@State finishTask: number = 0// 任务数组@State tasks: Task[] = []build() {Column({ space: 10 }) {// 任务进度卡片TaskStatistics({ finishTask: this.finishTask, totalTask: this.totalTask })...}.width('100%').height('100%').backgroundColor('#F1F2F3')}...}@Componentstruct TaskStatistics {@Prop finishTask: number@Prop totalTask: number...}
    
  5. 使用 @Link 对示例代码进行封装和改造

    假设父组件中的变量采用 @State 装饰器,与之对应的子组件采用 @Link 装饰器,此时就是双向同步,当父组件对 @State 装饰的变量进行任意的修改时,就会立刻把这个数据传递给子组件,反过来,子组件如果对这个数据进行了修改,也会把这个数据传递给父组件。所以,这种同步被称之为双向同步。实现原理就是引用

     ...@Entry@Componentstruct PropPage {// 总任务数量@State totalTask: number = 0// 已完成任务数量@State finishTask: number = 0build() {Column({ space: 10 }) {// 任务进度卡片...// 任务列表TaskList({ finishTask: $finishTask, totalTask: $totalTask })}.width('100%').height('100%').backgroundColor('#F1F2F3')}}...@Componentstruct TaskList {// 总任务数量@Link totalTask: number// 已完成任务数量@Link finishTask: number// 任务数组@State tasks: Task[] = []...}
    
  6. 使用数组对示例代码进行封装和改造

     ...// 任务统计信息class StatisticsInfo {totalTask: number = 0finishTask: number = 0}@Entry@Componentstruct PropPage {// 任务统计信息@State info: StatisticsInfo = new StatisticsInfo()build() {Column({ space: 10 }) {// 任务进度卡片TaskStatistics({ finishTask: this.info.finishTask, totalTask: this.info.totalTask })// 任务列表TaskList({ info: $info })}.width('100%').height('100%').backgroundColor('#F1F2F3')}}@Componentstruct TaskStatistics {@Prop finishTask: number@Prop totalTask: number...}@Componentstruct TaskList {@Link info: StatisticsInfo...}
    

    结论:@Prop 不支持对象类型,@Link 支持对象类型。@Prop 和 @Link 该怎么选?如果子组件拿到父组件的值以后,只是用来展示,不做修改,用 @Prop,如果子组件需要修改父组件的值,用 @Link。

四、@Provide 和 @Consume

@Provide 和 @Consume 可以跨组件提供类似于 @State 和 @Link 的双向同步。

使用 @Provide@Consume 对示例代码进行封装和改造:

...// 任务统计信息
class StatisticsInfo {totalTask: number = 0finishTask: number = 0
}@Entry
@Component
struct PropPage {// 任务统计信息@Provide info: StatisticsInfo = new StatisticsInfo()build() {Column({ space: 10 }) {// 任务进度卡片TaskStatistics()// 任务列表TaskList()}.width('100%').height('100%').backgroundColor('#F1F2F3')}
}@Component
struct TaskStatistics {@Consume info: StatisticsInfo...
}@Component
struct TaskList {@Consume info: StatisticsInfo...
}

结论:@Provide@Consume 不需要显示的传参,内部会帮你去实现,但是代价是资源上面的损耗,所以,多数情况下,能用 @State@Prop@Link 就不要用 @Provide@Consume 了,除非是跨组件那种的场景。

五、@Observed 和 @ObjectLink

作用:@Observed 和 @ObjectLink 装饰器用于在涉及嵌套对象数组元素为对象的场景中进行双向数据同步。

  1. 嵌套对象

     class Person {name: stringage: numberfriend: Personconstructor(name: string, age: number, friend?: Person) {this.name = namethis.age = agethis.friend = friend}}
    
     @Entry@Componentstruct Parent {@State p: Person = new Person('Xxx', 20, new Person('Yyy', 20))build() {Column() {Text(`${this.p.friend.name} : ${this.p.friend.age},`).onClick(() => this.p.friend.age++)}}}
    

    通过上面这两段代码可以发现 Xxx 这个对象持有了 Yyy 对象,这就是嵌套对象。利用 Text 去渲染 Xxx 的 Friend 的 name 和 age,当发生点击事件时,去修改 Yyy 的 age,但是我们知道嵌套对象它的属性变更是无法被感知到,因此就无法触发视图的更新。要解决这个问题,需要做两件事:

    (1)需要给嵌套对象它所对应的类型上面加上 @Observed 装饰器

     @Observedclass Person {...}
    

    (2)需要给嵌套对象内部的对象加上 @ObjectLink 装饰器

     @Componentstruct Child {@ObjectLink p: Personbuild() {Column() {Text(`${this.p.name} : ${this.p.age}`)}}}@Entry@Componentstruct Parent {@State p: Person = new Person('Xxx', 20, new Person('Yyy', 20))build() {Column() {Child({ p: this.p.friend }).onClick(() => this.p.friend.age++)}}}
    
  2. 数组元素为对象

     @Observedclass Person {name: stringage: numberfriend: Personconstructor(name: string, age: number, friend?: Person) {this.name = namethis.age = agethis.friend = friend}}@Componentstruct Child {@ObjectLink p: Personbuild() {Column() {Text(`${this.p.name} : ${this.p.age}`)}}}@Entry@Componentstruct Parent {@State p: Person = new Person('Xxx', 20, new Person('Yyy', 20))@State ps: Person[] = [new Person('Aaa', 20), new Person('Bbb', 20)]build() {Column() {Child({ p: this.p.friend }).onClick(() => this.p.friend.age++)Text('==== 朋友列表 ====')ForEach(this.ps,p => {Child({ p: p }).onClick(() => p.age++)})}}}
    

    只要有了 @Observed,然后传递子组件的属性时,加上 @ObjectLink,那么,也能够触发视图的更新了。

  3. 示例代码

     // 任务类@Observedclass Task {static id: number = 1// 任务名称name: string = `任务${Task.id++}`// 任务状态finished: boolean = false}// 统一的卡片样式@Styles function card() {.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({ radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4 })}// 任务完成样式@Extend(Text) function finishedTask() {.decoration({ type: TextDecorationType.LineThrough }).fontColor('#B2B2B1')}// 任务统计信息class StatisticsInfo {totalTask: number = 0finishTask: number = 0}@Entry@Componentstruct PropPage {// 任务统计信息@Provide info: StatisticsInfo = new StatisticsInfo()build() {Column({ space: 10 }) {// 任务进度卡片TaskStatistics()// 任务列表TaskList()}.width('100%').height('100%').backgroundColor('#F1F2F3')}}@Componentstruct TaskStatistics {@Consume info: StatisticsInfobuild() {Row() {Text('任务进度:').fontSize(30).fontWeight(FontWeight.Bold)Stack() {Progress({value: this.info.finishTask,total: this.info.totalTask,type: ProgressType.Ring}).width(100)Row() {Text(this.info.finishTask.toString()).fontSize(24).fontColor('#0000FF')Text(' / ' + this.info.totalTask.toString()).fontSize(24)}}}.card().margin({ top: 20, bottom: 10 }).justifyContent(FlexAlign.SpaceEvenly)}}@Componentstruct TaskList {@Consume info: StatisticsInfo// 任务数组@State tasks: Task[] = []handleTaskChange() {// 更新任务总数量this.info.totalTask = this.tasks.length// 更新已完成任务数量this.info.finishTask = this.tasks.filter(item => item.finished).length}build() {Column() {// 新增任务按钮Button('新增任务').width(200).margin({ top: 10, bottom: 10 }).onClick(() => {// 新增任务数据this.tasks.push(new Task())// 更新任务总数量this.handleTaskChange()})// 任务列表List({ space: 10 }) {ForEach(this.tasks,(item: Task, index) => {ListItem() {TaskItem({ item: item, onTaskChange: this.handleTaskChange.bind(this) })}.swipeAction({ end: this.DeleteButton(index) })})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}}@Builder DeleteButton(index: number) {Button() {Image($r('app.media.delete')).fillColor(Color.White).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(() => {this.tasks.splice(index, 1)this.handleTaskChange()})}}@Componentstruct TaskItem {@ObjectLink item: TaskonTaskChange: () => voidbuild() {Row() {if (this.item.finished) {Text(this.item.name).finishedTask()} else {Text(this.item.name)}Checkbox().select(this.item.finished).onChange(val => {// 更新当前任务状态this.item.finished = val// 更新已完成任务数量this.onTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}}
    
  4. 运行效果,如图2所示:
    图2

    图2

  5. 总结

    @Observed 和 @ObjectLink 主要用来解决嵌套对象里面,对象属性变更无法触发数组刷新和数组里的元素式对象属性变更无法触发视图更新的问题。解决方案是给对象上面添加 @Observed 装饰器,同时给嵌套的对象或数组元素对象的变量上加 @ObjectLink 装饰器;当子组件调用父组件方法,我们的办法是把父组件的方法作为参数传递进来,但是传递过程中会有 this 的丢失,解决办法是传递这个函数过程当中,用 bind 把这个 this 绑定进去。

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

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

相关文章

第十一届蓝桥杯大赛第二场省赛试题 CC++ 研究生组-子串分值和

solution1&#xff08;通过40%&#xff09; 依次求子串并统计出现过的字母个数 #include<iostream> #include<string> #include<set> using namespace std; int main(){string s, subs;cin >> s;int len s.size(), ans 0;for(int j 1; j < len…

【LabVIEW FPGA入门】FPGA寄存器(Register)

当您需要从多个时钟域或设计的不同部分访问数据&#xff0c;并且需要编写可重复使用的代码时&#xff0c;可使用寄存器项来存储数据。与 FIFO 相比&#xff0c;寄存器项消耗的 FPGA 逻辑资源更少&#xff0c;而且不消耗块存储器&#xff0c;而块存储器是最有限的 FPGA 资源类型…

2024阿里云2核2G服务器租用价格99元和61元一年

阿里云2核2G服务器配置优惠价格61元一年和99元一年&#xff0c;61元是轻量应用服务器2核2G3M带宽、50G高效云盘&#xff1b;99元服务器是ECS云服务器经济型e实例ecs.e-c1m1.large&#xff0c;2核2G、3M固定带宽、40G ESSD entry系统盘&#xff0c;阿里云活动链接 aliyunfuwuqi.…

微光图像增强算法学习记录(一)

微光图像增强&#xff08;LLIE&#xff09;旨在恢复照明并提高微光图像的可见性&#xff0c;本文对阅读的文献进行记录和分享&#xff0c;帮助回顾和大家建立学习资料。 文献一摘要及前沿摘选主要贡献网络结构实验结论 文献二摘要 文献三摘要主要贡献网络架构实验 文献四摘要实…

机器学习K-means算法

K-Means 算法&#xff08;K-Means算法、K-Means 中心值计算、K-Means 距离计算公式、K-Means 算法迭代步骤、K-Means算法实例&#xff09; 问题引入 给你如下两种图片&#xff0c;快读回答2个问题&#xff0c;问 图1 中有几类五谷杂粮&#xff1f;问 图2 中有几类五谷杂粮&…

linux源配置:ubuntu、centos;lspci与lsmod命令区别

1、ubuntu源配置 1&#xff09;先查电脑版本型号: lsb_release -c2&#xff09;再编辑源更新&#xff0c;源要与上面型号对应 参考&#xff1a;https://midoq.github.io/2022/05/30/Ubuntu20-04%E6%9B%B4%E6%8D%A2%E5%9B%BD%E5%86%85%E9%95%9C%E5%83%8F%E6%BA%90/ /etc/apt/…

基于SpringBoot+MyBatis框架的智慧生活商城系统的设计与实现(源码+LW+部署+讲解)

目录 前言 需求分析 可行性分析 技术实现 后端框架&#xff1a;Spring Boot 持久层框架&#xff1a;MyBatis 前端框架&#xff1a;Vue.js 数据库&#xff1a;MySQL 功能介绍 前台功能拓展 商品详情单管理 个人中心 秒杀活动 推荐系统 评论与评分系统 后台功能拓…

分布式之网关介绍

一、网关简介 1、网关背景 由于微服务“各自为政的特性”使微服务的使用非常麻烦。通常公司会有一个“前台小姐姐”作为统一入口&#xff0c;这就是网关 2、网关作用 统一入口&#xff1a;为服务提供一个唯一的入口&#xff0c;网关起到外部和内部隔离的作用&#xff0c; 保…

阿里云幻兽帕鲁4核16G和8核32G服务器优惠价格

2024阿里云幻兽帕鲁专用服务器价格表&#xff1a;4核16G幻兽帕鲁专用服务器26元一个月、149元半年&#xff0c;默认10M公网带宽&#xff0c;8核32G幻兽帕鲁服务器10M带宽价格90元1个月、271元3个月。阿里云提供的Palworld服务器是ECS经济型e实例&#xff0c;CPU采用Intel Xeon …

C#,图论与图算法,用于检查给定图是否为欧拉图(Eulerian Graph)的算法与源程序

1 欧拉图 欧拉图是指通过图(无向图或有向图)中所有边且每边仅通过一次通路, 相应的回路称为欧拉回路。具有欧拉回路的图称为欧拉图(Euler Graph), 具有欧拉通路而无欧拉回路的图称为半欧拉图。 对欧拉图的一个现代扩展是蜘蛛图,它向欧拉图增加了可以连接的存在点。 这给…

AJAX-综合

文章目录 同步代码和异步代码回调函数地狱解决回调函数地狱Promise-链式调用async函数和awaitasync函数和await-捕获错误 事件循环宏任务与微任务Promise.all静态方法 同步代码和异步代码 同步代码&#xff1a;逐步执行&#xff0c;需原地等待结果后&#xff0c;才继续向下执行…

阿里云效流水线—发布公用jar到Maven私仓

后端项目发布 1.选择流水线 2.新建流水线 3.选择模板 4.选择代码仓库 5.调整构建命令 添加mvn install 重新构建项目 6.添加镜像 在wms-app目录下新建Dockerfile文件(Dockerfile文件名中的D一定要是大写的&#xff09;文件&#xff0c;重新推送项目 #基础镜像 FROM openjd…

【力扣hot100】1. 两数之和 49.字母异位词分组 128. 最长连续序列

目录 1. 两数之和题目描述做题思路参考代码 49.字母异位词分组题目描述做题思路参考代码 128. 最长连续序列题目描述做题思路参考代码 1. 两数之和 题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数…

EPO企业生产运营数智化平台助力制造企业迈向智能制造

随着“中国制造2025”和工业4.0的不断推进&#xff0c;越来越多的制造企业准备迈入智能制造和智慧制造领域&#xff0c;实现数智化管理。企业通过搭建EPO企业生产运营平台&#xff0c;结合自身业务现状和数字化需求&#xff0c;从各个业务场景、部门人员、产品组成等方面进行分…

Django缓存(二)

一、视图缓存 Django的缓存可以设置缓存指定的视图,具体方式使用django.views.decorators.cache.cache_page, 方法有2种方式: 装饰器:以方法以装饰器的方式使用 from django.views.decorators.cache import cache_page@cache_page(60 * 15,cache="default") def…

【算法每日一练]-图论(保姆级教程篇16 树的重心 树的直径)#树的直径 #会议 #医院设置

目录 树的直径 题目&#xff1a;树的直径 &#xff08;两种解法&#xff09; 做法一&#xff1a; 做法二&#xff1a; 树的重心&#xff1a; 题目&#xff1a; 会议 思路&#xff1a; 题目&#xff1a;医院设置 思路&#xff1a; 树的直径 定义&#xff1a;树中距离最…

科技革新背后:码垛机器人在不同领域的实践应用

随着科技的进步&#xff0c;机器人技术已经渗透到各个行业之中&#xff0c;成为提高生产效率、减少人工成本的重要工具。码垛机器人作为自动化技术的杰出代表&#xff0c;其在各个行业中的应用场景日益广泛&#xff0c;从食品饮料到化工产品&#xff0c;再到物流仓储&#xff0…

睿考网:注册会计师考试有年龄限制吗?

参加注册会计师考试是否有年龄限制&#xff1f;现行的规定并没有要求考生的年龄&#xff0c;所以只要符合既定的报名条件任何人都是可以参加的。 报名资格要求如下&#xff1a; 1. 报考者须为中国国籍公民&#xff0c;并且具备完全的民事行为能力。 2. 学历要求方面&#xf…

CSS的特殊技巧

1.精灵图 使用精灵图核心总结&#xff1a; 1. 精灵图主要针对于小的背景图片使用。 2. 主要借助于背景位置来实现--- background-position 。 3. 一般情况下精灵图都是负值。&#xff08;千万注意网页中的坐标&#xff1a; x轴右边走是正值&#xff0c;左边走是负值&#xf…

海康威视-AIOT的业务转型

海康威视的转型和定位为智能物联网&#xff08;AIoT&#xff09;解决方案和大数据服务的提供商。 公司不仅仅聚焦于其核心的视频监控业务&#xff0c;而且正在积极拓展到新的技术领域和市场。通过专注于物联感知、人工智能、大数据等技术的创新&#xff0c;对未来技术发展方向的…