【HarmonyOS】鸿蒙入门学习

一、开发前的准备

(一)HarmonyOS 开发套件介绍

(二)开发者主要使用的核心套件

主要为代码开发阶段会使用到的 DevEco StudioArkTSArkUI三个工具。

(三)熟悉鸿蒙官网

1、网址

https://developer.huawei.com/consumer/cn/

2、开发工具的下载、安装与创建

(1)下载

https://developer.huawei.com/consumer/cn/deveco-studio/

找到如下图所示位置,点击下载对应版本的DevEco Studio安装包:

(2)安装

    1. 双击安装包

     2. 安装引导:下载与安装软件-快速开始-DevEco Studio使用指南-工具

    3. 环境配置:配置开发环境-快速开始-DevEco Studio使用指南-工具

ps: HarmonyOS SDK 路径中不能包含中文字符

(3)创建
  1. 创建一个新项目并运行:创建和运行Hello World-快速开始-DevEco Studio使用指南-工具

二、开发学习

(一)ArkTS 语言

1、了解 ArkTS 语言特点

2、TypeScript 语言

官网地址:https://www.typescriptlang.org/

(1)变量声明

静态类型检查功能:每一个变量都有固定的数据类型。

(2)条件控制

支持基于 if-else 和 switch 的条件控制。

ps:在 TS 中,空字符串、数字0、null、undefined 都被认为是 false ,其他值则为 true。

(3)循环迭代

支持基于 for 和 while 循环,并且为一些内置类型(如 Array)提供快捷迭代语法。

(4)函数

通常利用 function 关键字声明函数,并且支持可选参数、默认参数、箭头函数等特殊语法。

(5)类和接口

具备面向对象编程的基本语法,如: interface、class、enum 等;

具备 封装、继承、多态 等面向对象基本特征;

ps:凡是在 类 和 接口 内部定义的函数不需要加 function 关键字。

(6)模块开发

应用复杂时,可以把通用功能抽取到单独的ts文件中,每个文件都是一个模块(module)。

模块可以相互加载,提高代码复用性。

(二)ArkUI 组件

1、基本信息说明

2、基础组件

用法、resources资源访问

(1)Image 图片显示组件

    1. 声明 Image 组件并设置图片源

Image(src:string|PixelMap|Resource)

      2. 添加图片属性

        组件通用属性:widthheightborderRadius...

        图片插值:interpolation(ImageInterpolation.High)

    3. 申请网络访问权限:访问控制授权申请-访问控制-安全-开发

(2)Text 文本显示组件

  1. 声明 Text 组件并设置文本内容

    Text(content?:string|Resource)
  2. 添加文本属性

(3)TextInput 文本输入框

  1. 声明 Text 组件

    TextInput({placeholder?:ResourceStr, text?:ResourceStr})
  2. 添加属性和事件

(4)Button 按钮组件

  1. 声明 Button 组件,laber 按钮文字

    Button(laber?:ResourceStr)
  2. 添加属性和事件

(5)Slider 滑动条组件

  1. 声明 Silder 组件

    Silder(options?:SilderOptions)
  2. 添加属性和事件

(6)Progress 进度条组件
  1. 声明 Silder 组件

Progress(options: {value: number, total?: number, type?: ProgressType})

// value:当前进度值
// total:进度总长,默认值:100
// type:进度条类型,默认值:ProgressType.Linear 线性

(7)Checkbox 多选框组件
Checkbox(options?: {name?: string, group?: string })

3、容器组件

(1)Column 和 Row 组件

  1. 声明 Column 组件 和 Row 组件

    // 纵向布局使用 Column 容器 
    Column({space?:number}){} 
    
    // 横向布局使用 Row 容器 
    Row({space?:number}){}
  2. 添加属性和事件

(2)List 列表布局组件

    1. 列表项(即 ListItem )特点

        数量过多超出屏幕后,会自动提供滚动功能;

        既可以纵向排列,也可以横向排列;

    2. 代码示例

List({ space: 8 }) {
  ForEach(
    [1,2,3,4],
    (item) => {
      ListItem() {
        Row({ space: 10 }) {
          Image($r('app.media.icon'))
            .width(100)

          Column({ space: 4 }) {
            if (item>4) {
              Text(item)
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
            } else {
              Text(item)
                .fontSize(20)
                .fontWeight(FontWeight.Bold)

              Text('¥' + item)
                .fontSize(18)
                .fontColor('#f36')
            }

          }
          .height('100%')
          .alignItems(HorizontalAlign.Start)
        }
        .width('100%')
        .height(120)
        .padding(10)
        .backgroundColor('#fff')
        .borderRadius(20)
      }
    }
  )
}
.width('100%')
.layoutWeight(1) // 页面中除其他元素外,剩下高度占满
.alignListItem(ListItemAlign.Center) // 列表中的元素左右居中

ps:

  • List 容器内部不能直接跟其他容器的,必须跟 ListItem,ListItem再去套别的元素;

  • ListItem 内部只能有一个根组件,在有多组件的情况下,必须用一个容器组件包起来;

(3)Stack 堆叠容器
Stack(value?: { alignContent?: Alignment })

// alignContent:设置子组件在容器内的对齐方式,默认值:Alignment.Center

3、循环控制:ForEach、if-else

(1)ForEach:循环遍历数组,根据数组内容渲染页面组件
ForEach(
    arr:Array, // 要遍历的数据数组
    (item: any, index?: number) => {
        // 页面组件生成函数
    },
    // 默认自带
    keyGenerator?: (item: any, index?: number): string => {
        // 键生成函数,为数组每一项生成一个唯一标示,组件是否重新渲染的判断标准
    }
)

  示例:

(2)if/else:条件控制,根据数据状态的不同,渲染不同的页面组件
if (判断条件) {
  // 成立时
}else {
  // 不成立时
}

4、自定义组件

(1)自定义组件——可以定义在全局或组件内

    a. 未抽出组件时,完整代码如下:

@Entry
@Component
struct ItemPage {
  build() {
    Column({ space: 8 }) {
      Row() {
        Text('商品列表')
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .margin({ bottom: 20 })
    }
  }
}

    b. 抽出组件后:

        情况一:组件内使用——只有当前页面使用的组件(抽出至当前页面),代码如下:

// 抽出至当前页面最上面
@Component
struct Header {
  private title: ResourceStr

  build() {
  // 组件内容如下
    Row() {
        Text('商品列表')
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
     }
     .width('100%')
  }
}

@Entry
@Component
struct ItemPage {
  build() {
    Column({ space: 8 }) {
    // 使用
      Header({ title: '商品列表' })
        .margin({ bottom: 20 })
    }
  }
}

        情况二:全局使用——其他页面也会使用的组件(抽出至组件文件夹),代码如下:

                组件的封装

// 文件路径:entry/srcmain/ets/components/CommonComponents

// 1.封装
@Component
export struct Header {
  private title: ResourceStr

  build() {
  // 组件内容如下
    Row() {
        Text('商品列表')
          .fontSize(30)
          .fontWeight(FontWeight.Bold)
     }
     .width('100%')
  }
}

                封装组件的使用

// 文件路径:entry/srcmain/ets/pages/ItemPage.ets

// 2.引入封装的自定义组件
import { Header } from '../components/CommonComponents'

@Entry
@Component
struct ItemPage {
  build() {
    Column({ space: 8 }) {
      // 3.对应的地方使用组件
      Header({ title: '商品列表' })
        .margin({ bottom: 20 })
    }
  }
}
(2)自定义构建函数——可以定义在全局或组件内
  • 全局定义

// 1、全局自定义构建函数:全局都可以使用
@Builder ItemCardJB(item: string) {
    // UI描述
    Row({ space: 10 }) {
      Text(item)
    }
    .width('100%')
    .height(120)
    .padding(10)
    .backgroundColor('#fff')
    .borderRadius(20)
}

@Entry
@Component
struct ItemPage {
  build() {
    Column({ space: 8 }) {
       // 2、使用——局部写法
      ItemCardJB('111')
    }
  }
}
  • 组件内定义

@Entry
@Component
struct ItemPage {
// 1、局部自定义构建函数:只能该组件内部使用
@Builder ItemCardJB(item: string) {
    // UI描述
    Row({ space: 10 }) {
      Text(item)
    }
    .width('100%')
    .height(120)
    .padding(10)
    .backgroundColor('#fff')
    .borderRadius(20)
}
 
  build() {
    Column({ space: 8 }) {
       // 2、使用——局部写法
      this.ItemCardJB('111')
    }
  }
}

(3)@Styles 装饰器——仅可封装组件通用属性
  • 全局定义

// 1、定义全局公共样式函数
@Styles function fillScreen(){
  .width('100%')
  .height('100%')
  .backgroundColor('#efefef')
  .padding(20)
}

@Entry
@Component
struct ItemPage {
  build() {
    Column({ space: 8 }) {
      Row() {
        Text('商品列表')
      }
      // 2、使用
      .fillScreen()
    }
  }
}
  • 组件内定义

@Entry
@Component
struct ItemPage {
// 1、定义局部公共样式函数
 @Styles fillScreen(){
   .width('100%')
   .height('100%')
   .backgroundColor('#efefef')
   .padding(20)
 }
 
  build() {
    Column({ space: 8 }) {
      Row() {
        Text('商品列表')
      }
      // 2、使用
      .fillScreen()
    }
  }
}
(4)@Extend 装饰器——仅可定义在全局,可以设置组件特有属性
// 1、定义——继承模式
// 1、组件特有属性的样式函数:@Extend(Text),即继承text属性;
// 2、@Extend() 不能写在组件内,即只能写在全局,即页面顶部
@Extend(Text) function priceText(){
  .fontSize(18)
  .fontColor('#f36')
}

@Entry
@Component
struct ItemPage {
  build() {
    Column({ space: 8 }) {
      Row() {
        Text('商品列表')
        // 2、使用
          .priceText()
      }
      .width('100%')
      .margin({ bottom: 20 })
    }
  }
}

(三)状态管理器

1、@State 装饰器

(1)在声明式UI中,以状态驱动视图更新

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

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

(2)代码示例
@Entry
@Component
struct Index {
// 使用
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor('#36D')
          .onClick(()=>{
            this.message = '小明!'
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

ps:

  • @Stste 装饰器 标记的变量必须初始化,不能为空值;

  • @Stste 支持Object、class、string、number、boolean、enum 类型以及这些类型的数据;

  • 嵌套类型以及数组中的对象属性无法触发视图更新

2、@Prop 和 @Link

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

(1)使用说明

@Prop

@Link

同步类型

单向同步

双向同步

允许装饰的

变量类型

  • 支持类型:string、number、boolean、enum;

  • 父组件对象类型,则子组件使用其对象属性;

  • 不可以是数组、any

  • 父子类型一致,且支持类型:string、number、boolean、enum、object、class,及他们的数组;

  • 数组中元素增、删、替换会引起刷新;

  • 嵌套类型及数组中的对象属性无法触发视图更新;

初始化方式

不允许子组件初始化

父组件传递,禁止子组件初始化

 

ps:

  • @Link 和 @State 很像,区别在于:@Link 用于子组件来实现双向同步,@State 用于父组件

(2)代码示例

    a. 传递数据类型——普通类型

// 任务类
class Task {
  static id: number = 1
  //   任务名称
  name: string = `任务${Task.id++}`
  //   任务状态:是否完成
  finished: boolean = false
}

// 统一卡片样式
@Styles function card() {
  .width('90%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({ radius: 6, color: '#1f000000', offsetX: 2, offsetY: 4 })
}

@Entry
@Component
struct PropPage {
  //  总任务数量
  @State totalTask: number = 0
  // 已完成任务数量
  @State finishTask: number = 0

  build() {
    Column({ space: 10 }) {
      // 1、任务进度卡片
      TaskStatistics({ finishTask: this.finishTask, totalTask: this.totalTask })
      //  3、任务列表
      // 传递引用使用 $ ,传递的不是变量值,传递的是引用
      TaskList({ finishTask: $finishTask, totalTask: $totalTask })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f1f2f3')
  }
}

@Component
struct TaskStatistics {
  @Prop finishTask: number
  @Prop totalTask: number

  build() {
    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('#36d')
          Text('/' + this.totalTask.toString())
            .fontSize(24)
            .fontColor('#36d')
        }
      }
    }
    .card()
    .margin({ top: 20, bottom: 20 })
    .justifyContent(FlexAlign.SpaceAround)
  }
}

@Component
struct TaskList {
  //  总任务数量
  @Link totalTask: number
  // 已完成任务数量
  @Link finishTask: number
  // 任务数组
  @State tasks: Task[] = []

  handleTaskChange() {
    // 更新任务总数
    this.totalTask = this.tasks.length
    // 更新已完成任务总数
    this.finishTask = this.tasks.filter(item => item.finished).length
  }

  build() {
    // 子组件只能有一个根元素
    Column() {
      //  2、新增任务按钮
      Button('新增任务')
        .width(200)
        .margin({ bottom: 20 })
        .onClick(() => {
          this.tasks.push(new Task()) // 在数组中添加一条数据
          this.handleTaskChange()
        })
      //  3、任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, index: number) => {
            ListItem() {
              Row() {
                Text(`任务${item.name}`)
                Checkbox()
                  .select(item.finished)
                  .onChange((val) => {
                    item.finished = val //更新任务状态
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(index) })

          }
        )
      }
      .width('100%')
      .alignListItem(ListItemAlign.Center)
      .layoutWeight(1)
    }

  }

  @Builder DeleteButton(index: number) {
    Button('删')
      .width(40)
      .height(40)
      .backgroundColor(Color.Red)
      .margin(10)
      .onClick(() => {
        this.tasks.splice(index, 1)
        this.handleTaskChange()
      })
  }
}

    b. 传递数据类型——对象类型

// 任务类
class Task {
  static id: number = 1
  //   任务名称
  name: string = `任务${Task.id++}`
  //   任务状态:是否完成
  finished: boolean = false
}

// 统一卡片样式
@Styles function card() {
  .width('90%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({ radius: 6, color: '#1f000000', offsetX: 2, offsetY: 4 })
}

// 任务统计信息
class StatInfo{
  totalTask: number = 0 //  总任务数量
  finishTask: number = 0 // 已完成任务数量
}

@Entry
@Component
struct PropPage {
  //  统计信息
  @State stat: StatInfo = new StatInfo()

  build() {
    Column({ space: 10 }) {
      // 1、任务进度卡片
      TaskStatistics({ finishTask: this.stat.finishTask, totalTask: this.stat.totalTask })
      //  3、任务列表
      // 传递引用使用 $ ,传递的不是变量值,传递的是引用
      TaskList({ stat: $stat })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f1f2f3')
  }
}

@Component
struct TaskStatistics {
  @Prop finishTask: number
  @Prop totalTask: number

  build() {
    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('#36d')
          Text('/' + this.totalTask.toString())
            .fontSize(24)
            .fontColor('#36d')
        }
      }
    }
    .card()
    .margin({ top: 20, bottom: 20 })
    .justifyContent(FlexAlign.SpaceAround)
  }
}

@Component
struct TaskList {
  //  任务
  @Link stat: StatInfo
  // 任务数组
  @State tasks: Task[] = []

  handleTaskChange() {
    // 更新任务总数
    this.stat.totalTask = this.tasks.length
    // 更新已完成任务总数
    this.stat.finishTask = this.tasks.filter(item => item.finished).length
  }

  build() {
    // 子组件只能有一个根元素
    Column() {
      //  2、新增任务按钮
      Button('新增任务')
        .width(200)
        .margin({ bottom: 20 })
        .onClick(() => {
          this.tasks.push(new Task()) // 在数组中添加一条数据
          this.handleTaskChange()
        })
      //  3、任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, index: number) => {
            ListItem() {
              Row() {
                Text(`任务${item.name}`)
                Checkbox()
                  .select(item.finished)
                  .onChange((val) => {
                    item.finished = val //更新任务状态
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(index) })

          }
        )
      }
      .width('100%')
      .alignListItem(ListItemAlign.Center)
      .layoutWeight(1)
    }

  }

  @Builder DeleteButton(index: number) {
    Button('删')
      .width(40)
      .height(40)
      .backgroundColor(Color.Red)
      .margin(10)
      .onClick(() => {
        this.tasks.splice(index, 1)
        this.handleTaskChange()
      })
  }
}

ps:子组件只能有一个根元素

3、@Provide 和 @Consume

@Provide 和 @Consume 可以跨组件双向同步(类似于 @Prop 和 @Link 的双向同步):

  • 父子间双向同步;

  • 爷孙间双向同步;

(1)使用说明

(2)代码示例
// 任务类
class Task {
  static id: number = 1
  //   任务名称
  name: string = `任务${Task.id++}`
  //   任务状态:是否完成
  finished: boolean = false
}

// 统一卡片样式
@Styles function card() {
  .width('90%')
  .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('#b1b2b1')
  .fontSize(20)
}

// 任务统计信息
class StatInfo{
  totalTask: number = 0 //  总任务数量
  finishTask: number = 0 // 已完成任务数量
}

@Entry
@Component
struct PropPage {
  //  统计信息
  @Provide stat: StatInfo = new StatInfo()

  build() {
    Column({ space: 10 }) {
      // 1、任务进度卡片
      TaskStatistics()
      //  3、任务列表
      TaskList()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f1f2f3')
  }
}

@Component
struct TaskStatistics {
  @Consume stat: StatInfo

  build() {
    Row() {
      Text(`任务进度:`)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      Stack() {
        Progress({
          value: this.stat.finishTask,
          total: this.stat.totalTask,
          type: ProgressType.Ring
        })
          .width(100)
        Row() {
          Text(this.stat.finishTask.toString())
            .fontSize(24)
            .fontColor('#36d')
          Text('/' + this.stat.totalTask.toString())
            .fontSize(24)
            .fontColor('#36d')
        }
      }
    }
    .card()
    .margin({ top: 20, bottom: 20 })
    .justifyContent(FlexAlign.SpaceAround)
  }
}

@Component
struct TaskList {
  @Consume stat: StatInfo
  // 任务数组
  @State tasks: Task[] = []

  handleTaskChange() {
    // 更新任务总数
    this.stat.totalTask = this.tasks.length
    // 更新已完成任务总数
    this.stat.finishTask = this.tasks.filter(item => item.finished).length
  }

  build() {
    // 子组件只能有一个根元素
    Column() {
      //  2、新增任务按钮
      Button('新增任务')
        .width(200)
        .margin({ bottom: 20 })
        .onClick(() => {
          this.tasks.push(new Task()) // 在数组中添加一条数据
          this.handleTaskChange()
        })
      //  3、任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, index: number) => {
            ListItem() {
              Row() {
                Text(`任务${item.name}`)
                Checkbox()
                  .select(item.finished)
                  .onChange((val) => {
                    item.finished = val //更新任务状态
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(index) })

          }
        )
      }
      .width('100%')
      .alignListItem(ListItemAlign.Center)
      .layoutWeight(1)
    }

  }

  @Builder DeleteButton(index: number) {
    Button('删')
      .width(40)
      .height(40)
      .backgroundColor(Color.Red)
      .margin(10)
      .onClick(() => {
        this.tasks.splice(index, 1)
        this.handleTaskChange()
      })
  }
}

ps:

  • @Provide 和 @Consume 不需要显式传递数据,内部会自己做数据同步,即不用传参(@Provide 提供, @Consume 直接消费);

4、@Observed 和 @ObjectLink

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

(1)使用说明

        a. 嵌套对象-使用说明

        嵌套对象的类型上添加 @Observed 装饰器,不管嵌套几个类型,凡是嵌套的类型都要加上 @Observed 装饰器;

        给嵌套对象内部的对象加上 @ObjectLink 装饰器;

        如果嵌套对象内部的对象是作为一个方法的参数传递的就不能加,解决办法:定义一个组件,然后将内部嵌套对象传给这个组件,那么这个组件可以作为一个变量,就可以加 @ObjectLink 装饰器了

        b. 数组元素对象-使用说明

        如图:gfs: Person[]

(2)代码示例
// 任务类
@Observed
class Task {
  static id: number = 1
  //   任务名称
  name: string = `任务${Task.id++}`
  //   任务状态:是否完成
  finished: boolean = false
}

// 统一卡片样式
@Styles function card() {
  .width('90%')
  .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('#b1b2b1')
  .fontSize(20)
}

// 任务统计信息
class StatInfo {
  totalTask: number = 0 //  总任务数量
  finishTask: number = 0 // 已完成任务数量
}

@Entry
@Component
struct PropPage {
  //  统计信息
  @Provide stat: StatInfo = new StatInfo()

  build() {
    Column({ space: 10 }) {
      // 1、任务进度卡片
      TaskStatistics()
      //  3、任务列表
      TaskList()
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f1f2f3')
  }
}

@Component
struct TaskStatistics {
  @Consume stat: StatInfo

  build() {
    Row() {
      Text(`任务进度:`)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      Stack() {
        Progress({
          value: this.stat.finishTask,
          total: this.stat.totalTask,
          type: ProgressType.Ring
        })
          .width(100)
        Row() {
          Text(this.stat.finishTask.toString())
            .fontSize(24)
            .fontColor('#36d')
          Text('/' + this.stat.totalTask.toString())
            .fontSize(24)
            .fontColor('#36d')
        }
      }
    }
    .card()
    .margin({ top: 20, bottom: 20 })
    .justifyContent(FlexAlign.SpaceAround)
  }
}

@Component
struct TaskList {
  @Consume stat: StatInfo
  // 任务数组
  @State tasks: Task[] = []

  handleTaskChange() {
    // 更新任务总数
    this.stat.totalTask = this.tasks.length
    // 更新已完成任务总数
    this.stat.finishTask = this.tasks.filter(item => item.finished).length
  }

  build() {
    // 子组件只能有一个根元素
    Column() {
      //  2、新增任务按钮
      Button('新增任务')
        .width(200)
        .margin({ bottom: 20 })
        .onClick(() => {
          this.tasks.push(new Task()) // 在数组中添加一条数据
          this.handleTaskChange()
        })
      //  3、任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, index: number) => {
            ListItem() {
            /*
             * 1、传递的是方法: this.handleTaskChange ,因为不是调用方法所以不能写 this.handleTaskChange()。
             * 2、因为this.handleTaskChange这个方法是在子组件里执行,所以这个方法的this就不是父组件的this,而是子组件的this;
             *    因为子组件没有tasks这个数组,所以会找不到;
             * 3、传递的过程中想要保证 this 不要变的办法:
             *    函数在传递时有一个方法 bind(this),即:
             *    把当前父组件中的 this 绑定到这个函数里面,那么这个函数里的 this 就永远都是当前这个父组件
             */
              TaskItem({ item: item, onTaskChange: this.handleTaskChange.bind(this) })
            }
            .swipeAction({ end: this.DeleteButton(index) })

          }
        )
      }
      .width('100%')
      .alignListItem(ListItemAlign.Center)
      .layoutWeight(1)
    }

  }

  @Builder DeleteButton(index: number) {
    Button('删')
      .width(40)
      .height(40)
      .backgroundColor(Color.Red)
      .margin(10)
      .onClick(() => {
        this.tasks.splice(index, 1)
        this.handleTaskChange()
      })
  }
}

@Component
struct TaskItem {
  @ObjectLink item: Task
  /*
   * 1、子组件想要调父组件的方法:
   * 可以将这个父组件的方法作为一个参数传递进来,但是在传递过程中会出现this丢失
   * 2、解决this丢失的办法:
   * 父组件传递参数时使用 .bind(this) ,将父组件的this绑定给子组件,即:this.handleTaskChange.bind(this)
   */
  onTaskChange: () => void // 这是一个变量,变量类型是一个函数

  build() {
    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)
  }
}

ps:

        子组件想要调父组件的方法会造成 this 丢失,解决办法:父组件传递参数时使用 .bind(this) ,将父组件的this绑定给子组件,即:this.handleTaskChange.bind(this)

(四)页面路由

1、基本概念

(1)定义

页面路由:指在应用程序中实现不同页面之间的跳转和数据传递。

(2)基本原理及常见api

a.  页面栈的最大容量上限为32个页面,使用 router.clear() 方法可以清空页面栈,释放内存;

b. Router 有两种页面跳转模式,分别是:

    router.pushUrl():目标页不会替换当前页,而是压入页面栈,因此可以用 router.back() 返回当前页;

    router.replaceUrl():目标页替换当前页,当前页会被销毁并释放资源,无法返回当前页;

c. Router 有两种页面实例模式,分别是:

    Standard:标准实例模式,每次跳转都会新建一个目标页并压入栈顶,默认模式;

    Single:单实例模式,如果目标页已经在栈中,则离栈顶最近的同Url页面会被移动到栈顶并重新加载;

2、使用方法

(1)第一步:导入 HarmonyOS 提供的 router 模块
// 在页面顶部引入
import router from '@ohos.router';

ps:一般在页面中使用 router 时,会自动引入,所以这一句可以不用自己写,跳过;

(2)第二步:使用 router 的 API
  • 跳转到指定页面

// 跳转到指定路径,并传递参数
router.pushUrl(
  // 1、RouterOptions
  // - url:目标页面路径
  // - params:传递的参数,可选
  {
    url: 'pages/ImagePage',
    params: {id: 1},
  },
  // 2、页面模式:RouterMode 枚举
  router.RouterMode.Single,
  // 3、异常响应的回调函数错误码:
  // 100001:内部错误,可能是渲染失败
  // 100002:路由地址错误
  // 100003:路由栈中页面超过32个
  err => {
    if(err){
      console.log('路由失败')}
  }
)
  • 获取参数,返回上一页或指定页

// 获取传递过来的参数
params: any = router.gerParams()

// router.back()不传参,则返回上一页
router.back()

// router.back()传参,则返回到指定页,并携带参数
router.back(
  {
    url: 'pages/ImagePage',
    params: {id: 10},
  }
)

(1)创建页面,路由配置文件文件自动添加路径的方法

ps:

如果不是上述方法创建页面,则需要在 base/main_pages.json 文件中配置页面路径,格式如下:

{

    "src": [

        "pages/Index",

        "pages/ImagePage", // 对应页面的路径

    ]

}

(2)返回前的二次确认弹窗
// 返回前的二次确认弹窗
router.showAlertBeforeBackPage({
  message:'确认返回吗?'
})
// 返回上一页
router.back()

点击返回后,会前弹出确认弹窗,具体流程如图所示:

  • 点击“确定”,则走 router.back() 返回上一页;

  • 点击“取消”,则停留在当前页面;

 

(五)动画

1、属性动画

属性动画:是通过设置组件的 animation 属性来给组件添加动画,当组件的 width、height、Opacity、backgroundColor、scale、rotate、translate 等属性变更时,可以实现渐变过渡效果。

ps:animation 属性,放在需要变化的样式属性后面,否则不会生效。

2、显示动画

显示动画:是通过全局 animateTo 函数来修改组件属性,实现属性变化时的渐变过渡效果。(使用更多的一种方式)

3、组件转场动画

 

(六)Stage 模型

HarmonyOS为开发者提供了Stage应用模型,是应用程序所需能力的抽象提炼,它提供了应用程序必备的组件和运行机制。有了应用模型,开发者可以基于一套统一的模型进行应用开发,使应用开发更简单、高效。

1、Stage 模型概述

 

(1)项目结构 

Ability Module 可以创建多个,可以将不同的能力放到不同的模块中开发;其中,通用的功能可以抽取出来放至 Library Module 模块中(Library Module 顾名思义:共享依赖类型的模块)。

 

 (2)编译期——项目编译打包的方式
  1. 源码编译——>打包码——>App 安装包;

  2. 所有的 Ability 模块都会被编译成HAP文件(鸿蒙能力类型包),所有的 Library 模块都会被编译成HSP文件(鸿蒙共享类型包);HAP包在运行过程中可以去引用和依赖HSP包;

  • 一个应用内部可能会有很多不同的能力,就会有很多不同的Ability Module,所以会有多个HAP文件;

  • HAP 文件间的差异:

    • Entry类型的HAP,是开发应用的主要能力模块,入口HAP文件,只能有一个

    • Feature类型的HAP,是拓展功能可以有多个

  • 采用这种多HAP文件打包模式的目的:

    • 为了降低不同功能模块间的耦合,每一个模块都可以独立编译和运行

    • 应用在下载安装时,首先安装核心模块Entry,其他Feature可以选择性安装,这样能降低应用安装时的体积

    3. 很多HAP合并在一起叫Bundle,Bundle有一个自己的名字叫 bundleName ,是应用的唯一标识;

    4. Bundle合并打包会变成一个APP,即.app的安装包;

(3)运行期——运行时的一些概念
  1. 每一个HAP在运行时都会创建一个AbilityStage实例,来展示应用能力组件;

  2. 应用能力组件有很多类型,比较常见的有: UIAbility、ExtensionAbility;

  • UIAbility:包含UI界面的应用组件,是系统调度的基本单元。

      在展示组件时,首先会持有一个 WindowStage 实例对象,WindowStage 会持有 Window对象(即:用来绘制UI页面的窗口),窗口里会展示ArkUI Page(UI界面);

  • ExtensionAbility:拓展的能力组件,如应用卡片、输入法;

2、应用及组件配置

 

(1)应用的全局配置信息

 

  • 文件路径:AppScope > app.json5

  • 代码说明:

{
  "app": {
    // 应用唯一标识(在创建应用时就设置了),项目发布、打包、部署时都会用到
    // 格式要求:域名倒置的方式进行定义,类似“包名”
    "bundleName": "com.example.myapplication",
    "vendor": "example",
    // 版本——数字格式,versionName、versionCode需同步变化更新
    "versionCode": 1000000,
    // 版本——字符串格式
    "versionName": "1.0.0",
    // 应用列表中的图标
    "icon": "$media:app_icon",
    // 应用列表中的描述字符
    "label": "$string:app_name"
  }
}
(2)Module的配置信息

 

  • 文件路径:entry > src > main > module.json5

  • 代码说明:

{
  "module": {
    // 申请系统权限
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
      },
    ],
    // 当前模块的名字
    "name": "entry",
    // 当前模块的类型,一共有三种:entry、feature、shared(共享,即Library模块的类型)
    "type": "entry",
    // 当前模块的描述
    "description": "$string:module_desc",
    // 当前模块的入口名字(默认启动的 ability)
    "mainElement": "EntryAbility",
    // 设备类型:如果有多个模块也可以供给不同的设备类型使用
    "deviceTypes": [
      "phone",
      "tablet"
    ],
    // 当前模块是否跟随应用一起安装
    "deliveryWithInstall": true,
    "installationFree": false,
    // 当前模块包含的所有页面
    "pages": "$profile:main_pages",
    // 一个模块下如果有多个ability,则都需要填入
    "abilities": [
      {
        "name": "EntryAbility",
        // 当前 ability对应的源码路径
        "srcEntry": "./ets/entryability/EntryAbility.ts",
        // 当前 ability的描述
        "description": "$string:EntryAbility_desc",
        /* 注意:
              因为:该模块是入口模块,且该ability是入口ability,
              所以:ability的图标和描述(icon、label)就是当前应用的图标和描述。
              =》就是操作系统桌面上的那个应用图标和名称
        */
        // 当前ability的图标
        "icon": "$media:icon",
        // 当前ability的名称
        "label": "$string:EntryAbility_label",
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "exported": true,
        // 当前ability负责的功能需要在skills下指定,与ability之间的跳转有关系
        "skills": [
          // home:代表入口的意思
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}
  • 修改string文件便捷方法:打开编辑器

 

(3)更多详细说明 

官方文档:app.json5配置文件-应用配置文件(Stage模型)-开发基础知识-入门

  3、UIAbility 生命周期

(1)图解

 

(2)详细说明
  • 启动应用的生命周期:onCreate —— onWindowStageCreate —— onForeground —— 加载页面;

  • 将应用程序后台的生命周期:onBackground

  • 将应用程序前台的生命周期:onForeground

  • 清理应用的生命周期:onWindowStageDestroy —— onDestroy

(3)hilog 日志输出使用
// 带上级别的日志
/*
1、16进制的数字,用来标识不同模块的参数
2、tag标记,模块下的功能标识
3、日志输出的内容:线上部署时是否公开(public公开 private不公开)
4、参数3处占位符具体的值是什么,取决于参数4传递的内容
*/
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

4、页面及组件生命周期

 PageA:组成当前页面的入口组件;ComponentA:子组件;

(1)加载组件的步骤:创建组件实例 ——> 执行build函数

ps:

  1. 所有的组件做加载,都是上述步骤;

  2. build函数全部执行完毕后,组件才算绘制完成;

(2)销毁页面 & 销毁组件
  • 只要页面被销毁,里面的组件肯定被销毁;反过来,如果页面还在,里面的子组件不一定还在,因为用户在互动操作中,里面的部分子组件如果不需要显示了就会被销毁(因为组件不存在隐藏,组件不显示就销毁)。

  • 页面隐藏,不一样页面就被销毁,replaceUrl页面被销毁,pushUrl页面没有被销毁。

  • 入口组件被销毁,里面的子组件也会被销毁。

(3)生命周期钩子:组件 & 页面
  1. 组件的生命周期钩子

   aboutToAppear创建组件实例之后,且执行build函数之前触发;

 可以在这个钩子里面进行数据的初始化和准备工作,然后build函数中就可以利用这些数据,完成渲染了;

   aboutToDisappear销毁组件之前触发;

可以在这个钩子里面完成一些数据持久化、数据保存、资源释放等操作;

    2. 页面的生命周期钩子

   onPageShow页面展示之后触发;

   onBackPress点击“返回”按钮时触发;

   onPageHide页面隐藏之前触发;

    3. 注意

        页面的生命周期钩子,只能在加了 @Entry 的入口组件中使用,普通的自定义组件中不能使用;

        组件的生命周期钩子,都可以使用;

    4. 执行顺序验证

        演示步骤——动图

        演示步骤——图示

 

 

        演示步骤——详细说明


  (1)加载IndexPage页面

  触发入口组件的创建 —— 执行 aboutToAppear 函数(执行完毕,即组件创建完毕)—— 执行build函数(执行完毕后,展示page)—— 触发onPageShow

  (2)加载LifeCirclePage页面

ps1:从IndexPage页面,通过pushUrl的方式,跳转到LifeCirclePage页面

ps2:pushUrl方式:即,IndexPage页面是隐藏,不是销毁

  首先创建页面组件,执行aboutToAppear函数 —— 隐藏IndexPage页面——展示LifeCirclePage页面

  (3)LifeCirclePage页面子组件的显示、隐藏:(if/else控制时)

  显示:执行 aboutToAppear 函数 —— 隐藏:执行 aboutToDisappear 函数

  (4)LifeCirclePage页面子组件的新增、删除:(foreach控制时)

  新增1个:执行 aboutToAppear 函数 —— 删除1个:执行 aboutToDisappear 函数

  新增3个:执行 aboutToAppear 函数3次 —— 删除第一个:执行 aboutToDisappear 函数3次,执行 aboutToAppear 函数2次(因为foreach循环渲染有一个特性:当数组中的元素发生变更时,会检查数组中的元素那些发生了变更,对发生了变更的元素进行重新渲染,重新渲染就会把之前的所有元素进行销毁,再重新渲染)

  (5)返回IndexPage页面(通过系统提供的返回按钮返回)

  触发onBackPress函数(只有系统提供的返回按钮才会触发)—— 执行onPageHide函数(隐藏LifeCirclePage页面)—— 执行onPageShow函数(展示IndexPage页面,同时,栈顶页面销毁)—— LifeCirclePage页面执行aboutToDisappear 函数 —— 子组件执行aboutToDisappear 函数(LifeCirclePage页面销毁,里面的子组件也会被销毁)

  (6)跳转到TestPage1页面

5、UIAbility 的启动模式

配置路径:entry\src\main\module.json5

(1)singleton 启动模式
  • 默认启动模式

  • 每一个UIAbility只存在唯一实例;

  • 任务列表中,只会存在一个相同的UIAbility;

(2)standard 启动模式(官网叫:multiton模式)
  • 每次启动UIAbility都会创建一个新的实例;

  • 任务列表中,可能存在一个或多个相同的UIAbility;

ps:

经过实际测试 standard 和 multiton 非常接近,但有不一样的地方:

  • 相同点:每次启动UIAbility都会创建一个新的实例;

  • 不同点:创建新实例,standard模式的旧实例也会并存;multiton模式的旧实例会被移除;

(3)specified 启动模式
  • 每个UIAbility实例可以设置Key标示;

  • 启动UIAbility时,需要指定Key,存在key相同实例直接被拉起,不存在则创建新实例;

  • 在一个Ability中调用另一个Ability的步骤:

        第一步:当前UIAbility方法拉起目标UIAbility

        1.1 获取上下文

    context = getContext(this) as common.UIAbilityContext

        1.2 指定要跳转到的UIAbility的信息

    let want = {
        deviceId:'', // deviceId为空:表示本设备
        bundleName:'com.example.myapplication',
        abilityName:'entry', // moduleName 非必填
        Parameters: {
          // getInstanceKey:自定义方法,生成目标UIAbility实例的key
          instanceKey: this.getInstanceKey()
        }
    }

        1.3 尝试拉起目标UIAbility实例

    this.context.startAbility(want)

        第二步:在AbilityStage的生命周期回调中为目标UIAbility实例生成key

    export default class MyAbilityStage extends AbilityStage{
        onAcceptWant(want: Want): string {
            // 判断当前要拉取的是否是DocumentAbility
            if(want.abilityName === 'DocumentAbility'){
                // 根据参数中的instanceKey参数拼接生成一个key值并返回
                return `DocAbility_${want.parameters.instanceKey}`
            }
            return '';
        }
    }

        第三步:在module.json5配置文件中,通过srcEntry参数指定AbilityStage路径

    {
        "module": {
            "name": "entry",
            "type": "entry",
            "srcEntry": "./ets/myabIlitystage/MyAbIlityStage.ts",
            ...
        }
    }

(七)网络连接

1、Http 请求数据

  (1)导入http模块
import http from '@ohos.net.http'
  (2)发送请求,处理响应
// 1. 创建一个http的请求对象,不可复用
let httpRequest = http.createHttp()
// 2. 发起网络请求
httpRequest.request(
    // 请求url路径
    'http://localhost:3000/users',
    // 请求选项 HttpRequestOptions
    {
        method:http.RequestMethod.GET,
        extraData: {'param1':'value1'} // k1=v1 & k2=v2
    }
)
// Promise:存放未来会完成的结果
// 3. 处理响应结果
.then((resp:http.HttpResponse) => {
    if(resp.responseCode === 200){
        // 请求成功
    }
})
.catch((err: Error) => {
    // 请求失败
})

2、第三方库 axios

(1)下载和安装ohpm(鸿蒙第三方库的包管理工具)
   步骤

        第一步:下载ohpm工具包:DevEco Studio-HarmonyOS SDK下载和升级-华为开发者联盟

        第二步:解压工具包,执行初始化命令

        将压缩包放在自己习惯的安装路径下 —— 解压到当前文件夹 —— 进入文件夹的“ohpm/bin”目录 —— cmd,执行初始化命令“init.bat” —— 验证是否执行成功“ohpm -v”,出现版本号就是成功

 
// windows 环境
init.bat

// Linux 或 Mac 环境
./init.sh

        第三步:将ohpm配置到环境变量

// windows环境,直接在我的电脑配置即可
此电脑 > 属性 > 高级系统设置 > 高级 > 环境变量中:
1. 新建:名称“ OHPM_HOME ”、路径“ 解压文件夹打开到ohpm这一级的对应路径 ”
2. 双击Path —— 新建:添加路径“ %OHPM_HOME%\bin ”

// Linux 或 Mac 环境,其中 OHPM 的路径请替换为 ohpm 的安装路径
export OHPM_HOME=/home/xx/Downloads/ohpm  //OHPM_HOME=ohpm的安装路径
export PATH=${OHPM_HOME}/bin:${PATH}
更多详细说明

  官方文档:ohpm使用指导-命令行工具-DevEco Studio使用指南-工具

(2)下载和安装axios
步骤

        第一步:下载axios

// 进入项目目录,输入命令
ohpm install @ohos/axios
    第二步:开放网络权限
// 在模块的module.json5文件中配置网络权限
{
    "module": {
        "requestPermissions": [
                        {
                "name": "ohos.permission.INTERNET",
            }
        ]
    }
}

更多详细说明

        官方地址:https://ohpm.openharmony.cn/#/cn/home

 

(3)使用axios

导入axios

import axios from '@ohos/axios'

发送请求并处理响应

// 请求方式,不同方式使用不同方法
axios.get(
    'url', // 请求路径
    {
        params: {'param1':'value1'}, // 请求选项
        data: {'param1':'value1'}
    }
)
  // 响应结果
  .then(response => {
    if(response.staus === 200){
        console.log('查询成功')
    }else{
        console.log('查询失败')
    }
    
  })
  .catch(error => {
      console.log('查询失败',JSON.stringify(error))
  })

(八)数据持久化

1、用户首选项

作用:为应用提供 Key-Value 键值型的数据处理能力,支持应用持久化轻量级数据。

2、关系型数据库

1、导入首选项模块

import dataPreference from '@ohos.data.preferences'

2、获取首选项实例,读取指定文件

dataPreference.getPreferences(this.context,'MyAppPreferences')
    .then(preferences => {
        // 获取成功
    })
    .catch(reason=> {
        // 获取失败
    })

3、数据操作

// 1.写入数据,如果已经存在,则会覆盖,可利用.has()判断是否存在
preferences.put('key',val)
    .then(() => preferences.flush()) // 刷到磁盘
    .catch(reason => {}) // 处理异常

// 2.删除数据
preferences.delete('key')
    .then(() => {})
    .catch(reason => {})
    
// 3.查询数据
preferences.get('key','defaultValue')
    .then(value => console.log('查询成功'))
    .catch(reason => console.log('查询失败'))

ps:

key为string类型,要求非空且长度不超过80字节;

value可以是string、number、boolean及以上类型数组,大小不超过8192字节;

(九)通知

1、基础通知

2、进度条通知

3、通知行为意图

三、实战案例

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第19篇 Intel FPGA Monitor Program的使用<二>

Q:Intel FPGA Monitor Program里集成的Computer System是什么架构的呢? A:我们以DE2-115的DE2-115_Computer System为例介绍,简单说DE2-115_Computer System就是一个Qsys系统,该系统包含Nios II处理器以及DE2-115开发…

windows 下 docker 入门

这里只是具体过程,有不清楚的欢迎随时讨论 1、安装docker ,除了下一步,好像也没有其他操作了 2、安装好docker后,默认是运行在linux 下的,这时我们需要切换到windows 环境下, 操作:在右下角d…

C#——方法函数详情

方法(函数) C#是面向对象的,所以C#中的方法也是相对于对象来说的,是指某个对象的行为,比如,有一个动物的类,兔子是这个动物类里的一个对象,那么跳这个行为就是兔子这个对象的方法了.其实也就是C中的函数(C是面向过程的,叫函数). 方法: 就是把一系列相关的代码组织到一块 用于…

人工智能--制造业和农业

欢迎来到 Papicatch的博客 文章目录 🍉人工智能在制造业中的应用 🍈 应用场景及便利 🍍生产线自动化 🍍质量控制 🍍预测性维护 🍍供应链优化 🍈 技术实现及核心 🍍机器学习和…

CSS入门到精通——表格样式

目录 表格边框 任务描述 相关知识 表格边框 折叠边框 编程要求 表格颜色、文字与大小 任务描述 相关知识 表格颜色 表格文字对齐与文字粗细 表格宽度和高度 任务要求 表格边框 任务描述 本关任务:在本关中,我们将学习如何使用CSS设置表格样…

2024年汉字小达人活动还有4个多月开赛:来做18道历年选择题备考

结合最近几年的活动安排,预计2024年第11届汉字小达人比赛还有4个多月就启动,那么孩子们如何利用这段时间有条不紊地准备汉字小达人比赛呢? 我的建议是充分利用即将到来的暑假:①把小学1-5年级的语文课本上的知识点熟悉&#xff0…

【吊打面试官系列-Mysql面试题】MySQL 数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化?

大家好,我是锋哥。今天分享关于 【MySQL 数据库作发布系统的存储,一天五万条以上的增量,预计运维三年,怎么优化?】面试题,希望对大家有帮助; MySQL 数据库作发布系统的存储,一天五万条以上的增量…

【Python】Python 2 测试网络连通性脚本

文章目录 前言1. 命令行传参2. 代码 前言 最近在只有python2的服务器上部署服务,不能用三方类库,这里出于好奇心学习下python。这里简单做个脚本,实现了检验网络连通性的功能(类似于curl)。 1. 命令行传参 使用命令…

Apple ID已成历史,在ios18中正式更名为Apple Account

随着iOS18的首个开发者预览版成功推送,众多热衷于尝鲜的用户已纷纷升级并开启全新体验。在这个版本中,备受瞩目的Apple ID正式迎来了它的进化——更名为Apple Account,并且拥有了中文名称“Apple账户”或简称“苹果账户”。 不过目前官网还称…

桌面应用开发框架比较:Electron、Flutter、Tauri、React Native 与 Qt

在当今快速发展的技术环境中,对跨平台桌面应用程序的需求正在不断激增。 开发人员面临着选择正确框架之挑战,以便可以高效构建可在 Windows、macOS 和 Linux 上无缝运行的应用程序。 在本文中,我们将比较五种流行的桌面应用程序开发框架&…

高效数据架构:分表流程实践

前言 ​ 随着业务的不断扩展,数据量激增成为不可避免的现象。当数据量达到某一临界点时,单一的数据表可能无法承载如此庞大的数据量,此时就需要考虑进行分库分表的策略。尽管业界普遍认为数据量达到1000万时就应考虑分表,但实际上…

HALCON-从入门到入门-阈值分割定位算子综合运用

1.废话 之前我的一个师兄告诉我,针对图像上想要定位的内容,机器视觉中定位的方式有很多种,但是如果用阈值分割定位可以做的,就不要用模板匹配了。因为基于形状的模板匹配始终会存在匹配不到的风险,那如果打光效果可以…

【DevOps】Ubuntu基本使用教程

目录 引言 Ubuntu简介 安装Ubuntu 准备工作 创建启动盘 安装过程 桌面环境 基本操作 定制桌面 文件管理 文件操作 文件权限 软件管理 安装软件 更新软件 系统设置 用户账户 网络设置 电源管理 命令行操作 常用命令 管理权限 安全与维护 系统更新 备份…

C++ 12 之 指针引用

c12指针引用.cpp #include <iostream>using namespace std;struct students12 {int age; };int main() {students12 stu;students12* p &stu; // 结构体指针students12* &pp p; // 结构体指针起别名pp->age 20;// (*pp).age 22;cout << "…

编译原理期末复习

语义分析和中间代码生成 语义检查: 主要进行一致性检查和越界检查。 一致性检查: (1)表达式中操作数是否保持类型一致; (2)赋值语句的左右两边是否类型一致; (3)形、实参数类型是否一致; (4)数组元素与数组说明是否一致。…

基于PID的定速巡航控制系统设计【MATLAB/SIMULINK】

摘要&#xff1a; 本文详细介绍了定速巡航控制是一种车辆驾驶辅助系统&#xff0c;它能让驾驶者在高速或长途驾驶时&#xff0c;设定一个固定的车速&#xff0c;然后车辆会自动维持这个速度行驶&#xff0c;无需驾驶者持续踩油门。这大大减轻了驾驶者的疲劳&#xff0c;同时也…

【Ubuntu双系统】两块硬盘分别安装系统,一块硬盘安装Ubuntu 一块安装Windows

【Ubuntu双系统】两块硬盘分别安装双系统&#xff0c;一块硬盘安装Ubuntu 一块安装Windows 前言安装Ubuntu前置操作安装过程参考文献 前言 机器情况&#xff1a;两块1T的硬盘&#xff0c;其中一块已安装Windows 11现需在另一块硬盘上安装Ubuntu&#xff0c;该硬盘还未初始化Ub…

使用 PyInstaller 将 Python 代码打包成独立可执行文件

大家好&#xff0c;当你完成了一段精彩的 Python 代码&#xff0c;你可能会想要与其他人分享它。但是&#xff0c;你可能担心其他人是否拥有足够的环境来运行你的代码。或者&#xff0c;你可能希望保护你的源代码&#xff0c;以防止他人查看或修改它。在这种情况下&#xff0c;…

Perl 语言入门学习

一、介绍 Perl 是一种高级的、动态的、解释型的通用编程语言&#xff0c;由Larry Wall于1987年开发。它是一种非常灵活和强大的语言&#xff0c;广泛用于文本处理、系统管理、网络编程、图形编程等领域。 Perl 语言的设计理念是“用一种简单的语法&#xff0c;去解决复杂的编…