HarmonyOS4.0快速入门

HarmonyOS4.0快速入门

十二月 22, 2023 评论 10 阅读 1175 点赞 0 收藏 0

了解

组件

样式

事件方法

字体颜色

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor('red')
      }
      .width('100%')
    }
    .height('100%')
  }
}

改变文本

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor('red')
          .onClick((event : ClickEvent)=>{
            this.message = "Hello ArkTS!"
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

ArkUI组件-Image组件


@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor('red')
          .onClick((event : ClickEvent)=>{
            this.message = "Hello ArkTS!"
          })
          Image('https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg')
      }
      .width('100%')
    }
    .height('100%')
  }
}

模拟器图片无法显示
文档解决:https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/accesstoken-guidelines-0000001493744016-V3

找到了module.json5

"requestPermissions": [
      {
        "name" : "ohos.permission.INTERNET",
      }
    ],

本地图片访问

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor('red')
          .onClick((event : ClickEvent)=>{
            this.message = "Hello ArkTS!"
          })
        //Image('https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg')
          Image($r("app.media.icon"))
          .width(250)
      }
      .width('100%')
    }
    .height('100%')
  }
}

让图片清晰

@Entry
@Component
struct Index {
  @State message: string = 'Hello World'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor('red')
          .onClick((event : ClickEvent)=>{
            this.message = "Hello ArkTS!"
          })
        //Image('https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg')
          Image($r("app.media.icon"))
          .width(250)
            // 让图片清晰
            .interpolation(ImageInterpolation.High)
      }
      .width('100%')
    }
    .height('100%')
  }
}

ArkUI组件-Text

ArkUI组件-TextInput

ArkUI组件-Button

ArkUI组件-Slider

ArkUI组件-Column和Row

Column 容器

Row 容器

交叉

demo


@Entry
@Component
struct Index {
  @State imageWidth: number = 150

  build() {
    Column() {
      Row(){
        Image($r('app.media.icon'))
          .width(this.imageWidth)
      }
      .width('100%')
      .height(400)
      .justifyContent(FlexAlign.Center)

      Row(){
        Text($r('app.string.width_label'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

        TextInput({text: this.imageWidth.toFixed(0)})
          .width(150)
          .backgroundColor('#FFF')
          .type(InputType.Number)
          .onChange( value => {
            this.imageWidth = parseInt(value)
          })
      }
      .width('100%')
      .padding({left: 14, right: 14})
      .justifyContent(FlexAlign.SpaceBetween)

      Divider()
        .width('91%')

      Row(){
        Button('缩小')
          .width(80)
          .fontSize(20)
          .onClick(() => {
            if(this.imageWidth >= 10){
              this.imageWidth -= 10
            }
          })

        Button('放大')
          .width(80)
          .fontSize(20)
          .onClick(() => {
            if(this.imageWidth < 300){
              this.imageWidth += 10
            }
          })
      }
      .width('100%')
      .margin({ top: 35, bottom: 35 })
      .justifyContent(FlexAlign.SpaceEvenly)


      Slider({
        min: 100,
        max: 300,
        value: this.imageWidth,
        step: 10,
      })
        .width('100%')
        .blockColor('#36D')
        .trackThickness(5)
        .showTips(true)
        .onChange(value => {
          this.imageWidth = value
        })
    }
    .width('100%')
    .height('100%')
  }
}

ArkUI组件-循环控制

ArkUI组件-List

ArkUI-自定义组件

自定义组件

@Component
export struct Header{
  private title: ResourceStr
  build(){
    // 标题部分
    Row(){
      Image($r('app.media.ic_public_back'))
        .width(30)
      Text(this.title)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      Blank()
      Image($r('app.media.ic_public_refresh'))
        .width(30)
    }
    .width('100%')
    .height(30)
  }
}
class Item {
  name: string
  image: ResourceStr
  price: number
  discount: number

  constructor(name: string, image: ResourceStr, price: number, discount: number = 0) {
    this.name = name
    this.image = image
    this.price = price
    this.discount = discount
  }
}

import {Header} from '../components/CommonComponents'

/*// 全局自定义构建函数
@Builder function ItemCard(item: Item){
  Row({space: 10}){
    Image(item.image)
      .width(100)
    Column({space: 4}){
      if(item.discount){
        Text(item.name)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Text('原价:¥' + item.price)
          .fontColor('#CCC')
          .fontSize(14)
          .decoration({type: TextDecorationType.LineThrough})
        Text('折扣价:¥' + (item.price - item.discount))
          .fontColor('#F36')
          .fontSize(18)
        Text('补贴:¥' + item.discount)
          .fontColor('#F36')
          .fontSize(18)
      }else{
        Text(item.name)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        Text('¥' + item.price)
          .fontColor('#F36')
          .fontSize(18)
      }
    }
    .height('100%')
    .alignItems(HorizontalAlign.Start)
  }
  .width('100%')
  .backgroundColor('#FFF')
  .borderRadius(20)
  .height(120)
  .padding(10)
}*/

/*// 全局公共样式函数
@Styles function fillScreen(){
  .width('100%')
  .height('100%')
  .backgroundColor('#EFEFEF')
  .padding(20)
}*/

// 继承模式,只能写在全局
@Extend(Text) function priceText(){
  .fontColor('#F36')
  .fontSize(18)
}

@Entry
@Component
struct ItemPage {

   // 商品数据
  private items: Array<Item> = [
    new Item('华为Mate60', $r('app.media.mate60'),6999, 500),
    new Item('MateBookProX', $r('app.media.mateBookProX'),13999),
    new Item('WatchGT4', $r('app.media.watchGT4'),1438),
    new Item('FreeBuds Pro3', $r('app.media.freeBudsPro3'),1499),
    new Item('Mate X5', $r('app.media.mateX5'),12999),
    new Item('Mate X5', $r('app.media.mateX5'),12999),
    new Item('Mate X5', $r('app.media.mateX5'),12999),
    new Item('Mate X5', $r('app.media.mateX5'),12999)
  ]

  // 局部公共样式函数
  @Styles fillScreen(){
  .width('100%')
  .height('100%')
  .backgroundColor('#EFEFEF')
  .padding(20)
}

  build() {
    Column({space: 8}){
      // 标题部分
      Header({title: '商品列表'})
        .margin({bottom: 20})

      // 商品列表部分
      List({space: 8}){
        ForEach(
          this.items,
          (item: Item) => {
            ListItem(){
              this.ItemCard(item)
            }
          }
        )
      }
      .width('100%')
      .layoutWeight(1)

    }
    .fillScreen()
  }

  // 局部自定义构建函数
  @Builder ItemCard(item: Item){
    Row({space: 10}){
      Image(item.image)
        .width(100)
      Column({space: 4}){
        if(item.discount){
          Text(item.name)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
          Text('原价:¥' + item.price)
            .fontColor('#CCC')
            .fontSize(14)
            .decoration({type: TextDecorationType.LineThrough})
          Text('折扣价:¥' + (item.price - item.discount))
            .priceText()
          Text('补贴:¥' + item.discount)
            .priceText()
        }else{
          Text(item.name)
            .fontSize(20)
            .fontWeight(FontWeight.Bold)
          Text('¥' + item.price)
            .priceText()
        }
      }
      .height('100%')
      .alignItems(HorizontalAlign.Start)
    }
    .width('100%')
    .backgroundColor('#FFF')
    .borderRadius(20)
    .height(120)
    .padding(10)
  }
}

ArkUI-状态管理@State装饰器

class Person{
  name: string
  age: number
  gf: Person

  constructor(name: string, age: number, gf?: Person) {
    this.name = name
    this.age = age
    this.gf = gf
  }
}

@Entry
@Component
struct StatePage2 {
  idx: number = 1
  @State p: Person = new Person('Jack', 21, new Person('柔丝', 18))
  @State gfs: Person[] = [
    new Person('柔丝', 18),
    new Person('露西', 19),
  ]

  build() {
    Column({space: 10}) {
      Text(`${this.p.name} : ${this.p.age}`)
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.p.age++
        })
      Text(`${this.p.gf.name} : ${this.p.gf.age}`)
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.p.gf.age++
        })
      Button('添加')
        .onClick(() => {
          this.gfs.push(new Person('女友' + this.idx++, 20))
        })

      Text('=女友列表=')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
      ForEach(
        this.gfs,
        (p, index) => {
          Row(){
            Text(`${p.name} : ${p.age}`)
              .fontSize(30)
              .onClick(() => {
                this.gfs[index] = new Person(p.name, p.age+1)
              })
            Button('删除')
              .onClick(() => {
                this.gfs.splice(index, 1)
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
        }
      )

    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}


@Component
struct StatePage {
  @State name: string = 'Jack'
  @State age: number = 21

  build() {
    Column() {
      Text(`${this.name} : ${this.age}`)
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .onClick(() => {
          this.age++
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

ArkUI-状态管理-任务统计案例

ArkUI-状态管理-@Prop@Link@Provide@Consume

单向-拷贝数据

双向-引用数据

ArkUI-状态管理-@Observed和@ObjectLink

状态管理


// 任务类
@Observed
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('#B1B2B1')
}

// 任务统计信息
class StatInfo {
  totalTask: number = 0
  finishTask: number = 0
}

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

  build() {
    Column({space: 10}){
      // 1.任务进度卡片
      TaskStatistics()

      // 2.任务列表
      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)
        }
      }
    }
    .card()
    .margin({top: 20, bottom: 10})
    .justifyContent(FlexAlign.SpaceEvenly)
  }
}

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

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

  build() {
    Column(){
      // 2.新增任务按钮
      Button('新增任务')
        .width(200)
        .margin({bottom: 10})
        .onClick(() => {
          // 1.新增任务数据
          this.tasks.push(new Task())
          // 2.更新任务总数量
          this.handleTaskChange()
        })

      // 3.任务列表
      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.ic_public_delete_filled'))
        .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()
    })
  }
}

@Component
struct TaskItem {
  @ObjectLink item: Task
  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 => {
          // 1.更新当前任务状态
          this.item.finished = val
          // 2.更新已完成任务数量
          this.onTaskChange()
        })
    }
    .card()
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

ArkUI-页面路由


import router from '@ohos.router'
@Component
export struct Header{
  private title: ResourceStr
  @State params: any = router.getParams()
  build(){
    // 标题部分
    Row({space: 5}){
      Image($r('app.media.ic_public_back'))
        .width(30)
        .onClick(() => {
          // 返回前的警告
          router.showAlertBeforeBackPage({
            message: '支付还未完成,确定要返回吗?'
          })

          // 返回上一页
          router.back()
        })
      if(this.params && this.title){
        Text(this.params.id + '.' + this.title)
          .fontSize(28)
          .fontWeight(FontWeight.Bold)
      }
      Blank()
      Image($r('app.media.ic_public_refresh'))
        .width(30)
    }
    .width('98%')
    .height(30)
  }
}
import router from '@ohos.router';
class RouterInfo{
  // 页面路径
  url: string
  // 页面标题
  title: string

  constructor(url: string, title: string) {
    this.url = url;
    this.title = title
  }
}

@Entry
@Component
struct Index {
  @State message: string = '页面列表'

  private routers: RouterInfo[] = [
    new RouterInfo('pages/ImagePage', '图片查看案例'),
    new RouterInfo('pages/ItemPage', '商品列表案例'),
    new RouterInfo('pages/StatePage', 'Jack和他的女友案例'),
    new RouterInfo('pages/PropPage', '任务列表案例'),
    new RouterInfo('pages/AnimationPage', '小鱼动画案例按钮版'),
    new RouterInfo('pages/AnimationPage2', '小鱼动画案例摇杆版'),
    new RouterInfo('pages/LifeCirclePage', '生命周期案例1'),
    new RouterInfo('pages/TestPage1', '生命周期案例2'),
    new RouterInfo('pages/DocumentListPage', 'UIAbility启动模式案例'),
  ]

  tag: string = 'Index Page'

  aboutToAppear(){
    console.log(this.tag, 'about to appear')
  }

  onPageShow(){
    console.log(this.tag, 'on page show')
  }

  onBackPress(){
    console.log(this.tag, 'on back press')
  }

  onPageHide(){
    console.log(this.tag, 'on page hide')
  }

  aboutToDisappear(){
    console.log(this.tag, 'about to disappear')
  }

  build() {
    Column() {
      Text(this.message)
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .height(80)

      List({space: 15}){
        ForEach(
          this.routers,
          (router, index) => {
            ListItem(){
              this.RouterItem(router, index+1)
            }
          }
        )
      }
      .layoutWeight(1)
      .alignListItem(ListItemAlign.Center)
      .width('100%')
    }
    .width('100%')
    .height('100%')
  }

  @Builder
  RouterItem(r: RouterInfo, i: number){
    Row(){
      Text(i + '.')
        .fontSize(20)
        .fontColor(Color.White)
      Blank()
      Text(r.title)
        .fontSize(20)
        .fontColor(Color.White)
    }
    .width('90%')
    .padding(12)
    .backgroundColor('#38f')
    .borderRadius(20)
    .shadow({radius: 6, color: '#4F000000', offsetX: 2, offsetY: 4})
    .onClick(() => {
      // router跳转
      router.pushUrl(
        {
          url: r.url,
          params: {id: i}
        },
        router.RouterMode.Single,
        err => {
          if(err){
            console.log(`路由失败,errCode: ${err.code} errMsg:${err.message}`)
          }
        }
      )
    })
  }
}

ArkUI-属性动画和显式动画

ArkUI-组件转场动画

import router from '@ohos.router'
@Entry
@Component
struct AnimationPage {
  // 小鱼坐标
  @State fishX: number = 200
  @State fishY: number = 180
  // 小鱼角度
  @State angle: number = 0
  // 小鱼图片
  @State src: Resource = $r('app.media.fish')

  // 是否开始游戏
  @State isBegin: boolean = false

  build() {
    Row() {
      Stack(){
        // 返回按钮
        Button('返回')
          .position({x:0, y: 0})
          .backgroundColor('#20101010')
          .onClick(() => {
            // 返回上一页
            router.back()
          })

        if(!this.isBegin){
          // 开始按钮
          Button('开始游戏')
            .onClick(() => {
              animateTo(
                {duration: 1000},
                () => {
                  // 点击后显示小鱼
                  this.isBegin = true
                }
              )
            })
        }else{
          // 小鱼图片
          Image(this.src)
            .position({x: this.fishX - 20, y: this.fishY - 20})
            .rotate({angle:this.angle, centerX: '50%', centerY: '50%'})
            .width(40)
            .height(40)
            //.animation({duration: 500, curve: Curve.Smooth})
            .transition({
              type: TransitionType.Insert,
              opacity: 0,
              translate: {x: -250}
            })
        }
        // 操作按钮
        Row(){
          Button('←').backgroundColor('#20101010')
            .onClick(() => {
              animateTo(
                {duration: 500},
                () => {
                  this.fishX -= 20
                  this.src = $r('app.media.fish_rev')
                }
              )
            })
          Column({space: 40}){
            Button('↑').backgroundColor('#20101010')
              .onClick(() => {
                animateTo(
                  {duration: 500},
                  () => {
                    this.fishY -= 20
                  }
                )
              })
            Button('↓').backgroundColor('#20101010')
              .onClick(() => {
                animateTo(
                  {duration: 500},
                  () => {
                    this.fishY += 20
                  }
                )
              })
          }
          Button('→').backgroundColor('#20101010')
            .onClick(() => {
              animateTo(
                {duration: 500},
                () => {
                  this.fishX += 20
                  this.src = $r('app.media.fish')
                }
              )
            })
        }
        .height(240)
        .width(240)
        .justifyContent(FlexAlign.Center)
        .position({x:0,y:120})
      }
      .height('100%').width('100%')
    }
    .height('100%')
    .width('100%')
    .backgroundImage($r('app.media.sea'))
    .backgroundImageSize({height: '105%', width: '100%'})
  }
}

ArkUI-实现摇杆功能

三角函数的介绍

三角函数是数学中一组与三角形相关的函数,它们用于描述和计算三角形中的各种关系。在数学和物理等领域,三角函数被广泛应用于解决各种问题。

常见的三角函数包括正弦(sine)、余弦(cosine)和正切(tangent),它们分别表示一个角对应的比例关系。

  • 正弦(sine):表示某个锐角对边与斜边之比,即 sin(theta) = opposite/hypotenuse。
  • 余弦(cosine):表示某个锐角邻边与斜边之比,即 cos(theta) = adjacent/hypotenuse。
  • 正切(tangent):表示某个锐角对边与邻边之比,即 tan(theta) = opposite/adjacent。

此外,还有反三角函数如反正弦、反余弦和反正切等。它们用于计算给定某个比例关系时所对应的具体角度值。

示例题目

现在我们来解答一些针对三角函数的题目:

  1. 已知一个直角三角形中斜边长为10cm,其中一个锐角为30度。求其对应的邻边长度以及另外两个内部夹着这条邻边和斜线组成的两个尖尖小直线段长度分别是多少?

    解答:
    根据已知条件可得到:

    • 斜边 hypotenuse = 10cm
    • 锐尖30度,则另外一个内部夹着相同端点且垂直hypotenuse小直线段长度为 hypotenuse * sin(30°)
    • 另一条内部夹着相同端点且垂直hypotenuse小直线段长度为 hypotenuse * cos(30°)

    计算结果:

    • 邻居 adjacent = hypotenuse * cos(30°) = 10cm * cos(30°) ≈ 8.6603cm
    • 内部夾持在這個鄰居上面與斜線組成兩個銳尖小線段分別為 hypotenuse * sin(30°) ≈ 5.0000cm
  2. 已知sin(x) = 0.5,并且x是锐航。求x 的值。

    解答:
    根据已知条件可得到sin_inv(0.5)=x,则 x ≈ arcsin(0.5)
    使用计算器或数学库可以得到 x ≈ arcsin(0.5) ≈ 30°

import router from '@ohos.router'
import curves from '@ohos.curves'

@Entry
@Component
struct AnimationPage {
  // 小鱼坐标
  @State fishX: number = 200
  @State fishY: number = 120
  // 小鱼角度
  @State angle: number = 0
  // 小鱼图片
  @State src: Resource = $r('app.media.fish')

  // 是否开始游戏
  @State isBegin: boolean = false

  // 摇杆中心区域坐标
  private centerX: number = 120
  private centerY: number = 120

  // 大、小圆半径
  private maxRadius: number = 100
  private radius: number = 20

  // 摇杆小圆球初始位置
  @State positionX: number = this.centerX;
  @State positionY: number = this.centerY;

  // 角度正弦和余弦
  sin: number = 0
  cos: number = 0

  // 小鱼移动速度
  speed: number = 0
  // 任务id
  taskId: number = -1


  build() {
    Row() {
      Stack(){
        // 返回按钮
        Button('返回')
          .position({x:0, y: 0})
          .backgroundColor('#20101010')
          .onClick(() => {
            // 返回上一页
            router.back()
          })

        // 开始按钮和小鱼
        if(!this.isBegin){
          // 开始按钮
          Button('开始游戏')
            .onClick(() => {
              animateTo(
                {duration: 1000},
                () => {
                  // 点击后显示小鱼
                  this.isBegin = true
                }
              )
            })
        }else{
          // 小鱼图片
          Image(this.src)
            .position({x: this.fishX - 20, y: this.fishY - 20})
            .rotate({angle:this.angle, centerX: '50%', centerY: '50%'})
            .width(40)
            .height(40)
            .transition({
              type: TransitionType.Insert,
              opacity: 0,
              translate: {x: -250}
            })
        }

        // 摇杆
        Row(){
          Circle({width: this.maxRadius * 2, height: this.maxRadius * 2})
            .fill('#20101010')
            .position({ x: this.centerX-this.maxRadius, y: this.centerY-this.maxRadius })
          Circle({ width: this.radius * 2, height: this.radius * 2 })
            .fill('#403A3A3A')
            .position({ x: this.positionX - this.radius, y: this.positionY - this.radius})
        }
        .height(240)
        .width(240)
        .justifyContent(FlexAlign.Center)
        .position({x:0,y:120})
        .onTouch(this.handleTouchEvent.bind(this))
      }
      .height('100%').width('100%')
    }
    .height('100%')
    .width('100%')
    .backgroundImage($r('app.media.sea'))
    .backgroundImageSize({height: '105%', width: '100%'})
  }

  // 处理手指移动的事件
  handleTouchEvent(event: TouchEvent){
    switch (event.type){
      case TouchType.Up:
        // 还原小鱼速度
        this.speed = 0
        // 取消定时任务
        clearInterval(this.taskId)
        // 还原摇杆小球的坐标
        animateTo(
          {curve: curves.springMotion()},
          () => {
            this.positionX = this.centerX
            this.positionY = this.centerY

            this.angle = 0
          }
        )
        break
      case TouchType.Down:
        // 开始定时任务
        this.taskId = setInterval(() => {
          this.fishX += this.speed * this.cos
          this.fishY += this.speed * this.sin
        }, 40)
        break

      case TouchType.Move:
        // 1.获取手指位置坐标
        let x = event.touches[0].x
        let y = event.touches[0].y
        // 2.计算手指与中心点的坐标差值
        let vx = x - this.centerX
        let vy = y - this.centerY
        // 3.计算手指与中心点连线和x轴正半轴的夹角,单位是弧度
        let angle = Math.atan2(vy, vx)
        // 4.计算手指与中心点的距离
        let distance = this.getDistance(vx, vy)

        this.sin = Math.sin(angle)
        this.cos = Math.cos(angle)
        animateTo(
          {curve: curves.responsiveSpringMotion()},
          () => {
            // 5.计算摇杆小球的坐标
            this.positionX = this.centerX + distance * this.cos
            this.positionY = this.centerY + distance * this.sin

            // 6.修改小鱼的角度
            if(Math.abs(angle * 2) < Math.PI){
              this.src = $r('app.media.fish')
            }else{
              this.src = $r('app.media.fish_rev')
              angle = angle < 0 ? angle + Math.PI : angle - Math.PI
            }
            this.angle = angle * 180 / Math.PI

            this.speed = 5
          }
        )
        break
    }
  }

  getDistance(x: number, y: number){
    let d = Math.sqrt(x * x + y * y)
    return Math.min(d, this.maxRadius)
  }
}

Stage模型-基本概念

Stage模型-应用配置文件

Stage模型-UIAbility生命周期

Stage模型-页面及组件生命周期

import router from '@ohos.router'
@Entry
@Component
struct TestPage1 {
  @State show: boolean = false
  tag: string = 'TestPage1'

  aboutToAppear(){
    console.log(this.tag, 'about to appear')
  }

  onPageShow(){
    console.log(this.tag, 'on page show')
  }

  onBackPress(){
    console.log(this.tag, 'on back press')
  }

  onPageHide(){
    console.log(this.tag, 'on page hide')
  }

  aboutToDisappear(){
    console.log(this.tag, 'about to disappear')
  }

  build() {
    Row() {
      Column({space: 20}) {
        Text(this.tag)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)

        Row(){
          Button('push page2')
            .onClick(() => {
              router.pushUrl({
                url: 'pages/TestPage2'
              })
            })
          Button('replace page2')
            .onClick(() => {
              router.replaceUrl({
                url: 'pages/TestPage2'
              })
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceAround)
      }
      .width('100%')
    }
    .height('100%')
  }
}
import router from '@ohos.router'
@Entry
@Component
struct TestPage2 {
  tag: string = 'TestPage2'

  aboutToAppear(){
    console.log(this.tag, 'about to appear')
  }

  onPageShow(){
    console.log(this.tag, 'on page show')
  }

  onBackPress(){
    console.log(this.tag, 'on back press')
  }

  onPageHide(){
    console.log(this.tag, 'on page hide')
  }

  aboutToDisappear(){
    console.log(this.tag, 'about to disappear')
  }
  build() {
    Row() {
      Column({space: 20}) {
        Text(this.tag)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)

        Row(){
          Button('push page1')
            .onClick(() => {
              router.pushUrl({
                url: 'pages/TestPage1'
              })
            })
          Button('replace page1')
            .onClick(() => {
              router.replaceUrl({
                url: 'pages/TestPage1'
              })
            })
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceAround)
      }
      .width('100%')

    }
    .height('100%')
  }
}
import { Header } from '../components/CommonComponents'

@Entry
@Component
struct LifeCircle {
  @State show: boolean = false
  @State arr: string[] = []


  tag: string = 'Life Circle Page'

  aboutToAppear(){
    console.log(this.tag, 'about to appear')
  }

  onPageShow(){
    console.log(this.tag, 'on page show')
  }

  onBackPress(){
    console.log(this.tag, 'on back press')
  }

  onPageHide(){
    console.log(this.tag, 'on page hide')
  }

  aboutToDisappear(){
    console.log(this.tag, 'about to disappear')
  }

  build() {
    Column({space: 20}) {
      // 顶部标题
      Header({ title: '测试组件生命周期' })

      // 按钮
      Button('切换显示')
        .onClick(() => this.show = !this.show)

      Row(){
        if(this.show){
          MyText({message: 'Hello World!'})
        }
      }
      .height(30)

      // 新增按钮
      Button('新增数据')
        .onClick(() => this.arr.push('user_' + this.arr.length))
      ForEach(this.arr, (item, index) => {
        MyText({message: item, index: index, delete: this.delete.bind(this)})
      })
    }
    .width('100%')
    .height('100%')
  }

  delete(index: number = -1){
    if(index > -1){
      this.arr.splice(index, 1)
    }
  }
}

@Component
struct MyText {
  message: string
  index?: number
  delete?: (index: number) => void


  tag: string = 'MyText Page'

  aboutToAppear(){
    console.log(this.tag, 'about to appear')
  }

  onPageShow(){
    console.log(this.tag, 'on page show')
  }

  onBackPress(){
    console.log(this.tag, 'on back press')
  }

  onPageHide(){
    console.log(this.tag, 'on page hide')
  }

  aboutToDisappear(){
    console.log(this.tag, 'about to disappear')
  }


  build() {
    Row(){
      Text(this.message)
        .fontSize(20)
      if(this.delete){
        Image($r('app.media.ic_public_delete'))
          .width(20)
          .onClick(() => this.delete(this.index))
      }
    }
    .width('100%')
    .justifyContent(FlexAlign.SpaceAround)
  }
}

Stage模型-UIAbility的启动模式

import common from '@ohos.app.ability.common'
import Want from '@ohos.app.ability.Want'
import { Header } from '../components/CommonComponents'
@Entry
@Component
struct DocumentListPage {
  private index: number = 1
  @State docs: number[] = []

  private context = getContext(this) as common.UIAbilityContext

  build() {
    Row() {
      Column({space: 10}) {
        Header({title: '文档列表'})
        Divider()
        Button('新建文档')
          .onClick(() => {
            // 添加文档
            this.docs.push(this.index)
            // 跳转到文档编辑的 UIAbility

            // 跳转的目的地want
            let want: Want = {
              deviceId: '',
              bundleName: 'com.example.myapplication',
              moduleName: 'entry',
              abilityName: 'DocumentAbility',
              parameters: {
                instanceKey: 'idx_' + this.index++
              }
            }
            // 跳转
            this.context.startAbility(want)
          })
        ForEach(this.docs, id => {
          Row({space:10}){
            Image($r('app.media.ic_files_doc'))
              .width(20)
            Text(`文档${id}`)
              .fontSize(18)
              .onClick(() => {
                // 跳转到文档编辑的 UIAbility

                // 跳转的目的地want
                let want: Want = {
                  deviceId: '',
                  bundleName: 'com.example.myapplication',
                  moduleName: 'entry',
                  abilityName: 'DocumentAbility',
                  parameters: {
                    instanceKey: 'idx_' + id
                  }
                }
                // 跳转
                this.context.startAbility(want)
              })
          }
          .width('100%')
        })
      }
      .width('100%')
      .height('100%')
      .padding(20)
    }
  }
}

网络连接-Http请求数据

http://localhost:3000/shops/?pageNo=1&pageSize=2

ShopPage.ets

import { Header } from '../common/components/CommonComponents'
import ShopInfo from '../viewmodel/ShopInfo'
import ShopItem from '../views/ShopItem'
import ShopModel from '../model/ShopModel'

@Entry
@Component
struct ShopPage {
  @State shops: ShopInfo[] = []
  isLoading: boolean = false
  isMore: boolean = true

  aboutToAppear() {
    // 加载商品数据
    this.loadShopInfo()
  }

  build() {
    Column({ space: 10 }) {
      Header({ title: '商铺列表' })

      List({ space: 10 }) {
        ForEach(this.shops, shop => {
          ListItem() {
            ShopItem({ shop: shop })
          }
        })
      }
      .width('100%')
      .layoutWeight(1)
      .onReachEnd(() => {
        console.log('触底了!')
        if(!this.isLoading && this.isMore){
          this.isLoading = true
          // 翻页
          ShopModel.pageNo++
          this.loadShopInfo()
        }
      })

    }
    .width('100%')
    .height('100%')
    .padding(10)
    .backgroundColor('#e1e2e3')
  }

  loadShopInfo(){
    // 加载数据
    // http://localhost:3000/shops/?pageNo=1&pageSize=2
    ShopModel.getShopListByAxios()
      .then(shops => {
        // 给图片加上服务器地址前缀
        shops.forEach(s => {
          s.images.forEach((src, i) => {
            s.images[i] = 'http://localhost:3000' + src
          })
        })

        this.shops = this.shops.concat(shops)
        this.isLoading = false
        if(!shops || shops.length === 0){
          this.isMore = false
        }
      })
  }
}

ShopInfo.ts

export default class ShopInfo {
  id: number
  name: string
  images: string[]
  area: string
  address: string
  avgPrice: number
  comments: number
  score: number
  openHours: string
}

ShopItem.ets

import ShopInfo from '../viewmodel/ShopInfo'

@Component
export default struct ShopItem {
  shop: ShopInfo

  build(){
    Column({space: 5}){
      Row(){
        Text(this.shop.name)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .ellipsisTextOverFlow()
      }
      .width('100%')

      Row(){
        Text(this.shop.address)
          .fontColor('#a3a3a3')
          .ellipsisTextOverFlow()
      }.width('100%')

      Row({space: 5}){
        Rating({rating: this.shop.score/10 , indicator: true}).stars(5).stepSize(0.1)
        Text(`${this.shop.score / 10}`).fontColor('#ffb04d')
        Text(`${this.shop.comments}条`).fontColor('#222')
        Blank()
        Text(`¥${this.shop.avgPrice}/人`)
      }.width('100%')

      List({space: 10}){
        ForEach(this.shop.images, (image) => {
          ListItem(){
            Column(){
              Image(image)
                .width(150).aspectRatio(1.1).borderRadius(5)
            }
          }
        })
      }
      .listDirection(Axis.Horizontal)
      .width('100%')
    }
    .width('100%')
    .height(240)
    .padding(12)
    .backgroundColor(Color.White)
    .borderRadius(15)
    .shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
  }
}

// 文本超出时的统一样式处理
@Extend(Text)
function ellipsisTextOverFlow(line: number = 1){
  .textOverflow({overflow: TextOverflow.Ellipsis})
  .maxLines(line)
}

ShopModel.ts

import http from '@ohos.net.http';
import ShopInfo from '../viewmodel/ShopInfo';
import axios from '@ohos/axios'

class ShopModel {
  baseURL: string = 'http://localhost:3000'
  pageNo: number = 1

  /**
   * 基于axios实现异步查询商铺
   * @returns
   */
  getShopListByAxios(): Promise<ShopInfo[]> {
    return new Promise((resolve, reject) => {
      axios.get(
        `${this.baseURL}/shops`,
        {
          params: { pageNo: this.pageNo, pageSize: 3 }
        }
      )
        .then(resp => {
          if (resp.status === 200) {
            // 查询成功
            console.log('testTag',  '查询商铺成功!', JSON.stringify(resp.data))
            resolve(resp.data)
          } else {
            console.log('testTag',  '查询商铺信息失败!error:', JSON.stringify(resp))
            reject('查询商铺失败')
          }
        })
        .catch(error => {
          console.log('testTag',  '查询商铺信息失败!error:', JSON.stringify(error))
          reject('查询商铺失败')
        })
    })
  }

  /**
   * 基于ohos的http模块实现异步查询商铺
   * @returns
   */
  getShopListByHttp(): Promise<ShopInfo[]> {
    return new Promise((resolve, reject) => {
      // 1.创建http的请求对象
      let httpRequest = http.createHttp()
      // 2.发送请求
      httpRequest.request(
        `${this.baseURL}/shops?pageNo=${this.pageNo}&pageSize=3`,
        {
          method: http.RequestMethod.GET
        }
      )
        .then(resp => {
          if (resp.responseCode === 200) {
            // 查询成功
            console.log('testTag',  '查询商铺成功!', resp.result)
            resolve(JSON.parse(resp.result.toString()))
          } else {
            console.log('testTag',  '查询商铺信息失败!error:', JSON.stringify(resp))
            reject('查询商铺失败')
          }
        })
        .catch(error => {
          console.log('testTag',  '查询商铺信息失败!error:', JSON.stringify(error))
          reject('查询商铺失败')
        })
    })
  }

  /**
   * 基于axios实现同步查询商铺
   * @returns
   */
  async getShopListByAxiosAsync(): Promise<ShopInfo[]> {
    // 1.发送请求
    let resp = await axios.get(
      `${this.baseURL}/shops`,
      {
        params: { pageNo: this.pageNo, pageSize: 3 }
      }
    )
    // 2.处理响应
    if (resp.status === 200) {
      // 查询成功
      console.log('testTag', '查询商铺成功!', JSON.stringify(resp.data))
      return resp.data;
    }
    // 查询失败
    console.log('testTag', '查询商铺信息失败!error:', JSON.stringify(resp))
  }

  /**
   * 基于ohos的http模块实现同步查询商铺
   * @returns
   */
  async getShopListByHttpAsync(): Promise<ShopInfo[]> {
    // 1.创建http的请求对象
    let httpRequest = http.createHttp()
    // 2.发送请求
    let resp = await httpRequest.request(
      `${this.baseURL}/shops?pageNo=${this.pageNo}&pageSize=3`,
      {
        method: http.RequestMethod.GET
      }
    )
    // 3.处理响应
    if (resp.responseCode === 200) {
      // 查询成功
      console.log('testTag', '查询商铺成功!', resp.result)
      return JSON.parse(resp.result.toString());
    }
    console.log('testTag', '查询商铺信息失败!error:', JSON.stringify(resp))
  }
}

const shopModel = new ShopModel();

export default shopModel as ShopModel;

网络连接-第三方库axios

数据持久化-关系型数据库

import preferences from '@ohos.data.preferences';

class PreferencesUtil{

  prefMap: Map<string, preferences.Preferences> = new Map()

  async loadPreference(context, name: string){
    try { // 加载preferences
      let pref = await preferences.getPreferences(context, name)
      this.prefMap.set(name, pref)
      console.log('testTag', `加载Preferences[${name}]成功`)
    } catch (e) {
      console.log('testTag', `加载Preferences[${name}]失败`, JSON.stringify(e))
    }
  }

  async putPreferenceValue(name: string, key: string, value: preferences.ValueType){
    if (!this.prefMap.has(name)) {
      console.log('testTag', `Preferences[${name}]尚未初始化!`)
      return
    }
    try {
      let pref = this.prefMap.get(name)
      // 写入数据
      await pref.put(key, value)
      // 刷盘
      await pref.flush()
      console.log('testTag', `保存Preferences[${name}.${key} = ${value}]成功`)
    } catch (e) {
      console.log('testTag', `保存Preferences[${name}.${key} = ${value}]失败`, JSON.stringify(e))
    }
  }

  async getPreferenceValue(name: string, key: string, defaultValue: preferences.ValueType){
    if (!this.prefMap.has(name)) {
      console.log('testTag', `Preferences[${name}]尚未初始化!`)
      return
    }
    try {
      let pref = this.prefMap.get(name)
      // 读数据
      let value = await pref.get(key, defaultValue)
      console.log('testTag', `读取Preferences[${name}.${key} = ${value}]成功`)
      return value
    } catch (e) {
      console.log('testTag', `读取Preferences[${name}.${key} ]失败`, JSON.stringify(e))
    }
  }
}

const preferencesUtil = new PreferencesUtil()

export default preferencesUtil as PreferencesUtil

 async aboutToAppear(){
    this.fontSize = await PreferencesUtil.getPreferenceValue('MyPreferences', 'IndexFontSize', 16) as number
  }

数据持久化-关系型数据库

import relationalStore from '@ohos.data.relationalStore';
import TaskInfo from '../viewmodel/TaskInfo';

class TaskModel {

  private rdbStore: relationalStore.RdbStore
  private tableName: string = 'TASK'

  /**
   * 初始化任务表
   */
  initTaskDB(context){
    // 1.rdb配置
    const config = {
      name: 'MyApplication.db',
      securityLevel: relationalStore.SecurityLevel.S1
    }
    // 2.初始化SQL语句
    const sql = `CREATE TABLE IF NOT EXISTS TASK (
                  ID INTEGER PRIMARY KEY AUTOINCREMENT,
                  NAME TEXT NOT NULL,
                  FINISHED bit
                 )`
    // 3.获取rdb
    relationalStore.getRdbStore(context, config, (err, rdbStore) => {
      if(err){
        console.log('testTag', '获取rdbStore失败!')
        return
      }
      // 执行Sql
      rdbStore.executeSql(sql)
      console.log('testTag', '创建task表成功!')
      // 保存rdbStore
      this.rdbStore = rdbStore
    })
  }

  /**
   * 查询任务列表
   */
  async getTaskList(){
    // 1.构建查询条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    // 2.查询
    let result = await this.rdbStore.query(predicates, ['ID', 'NAME', 'FINISHED'])
    // 3.解析查询结果
    // 3.1.定义一个数组,组装最终的查询结果
    let tasks: TaskInfo[] = []
    // 3.2.遍历封装
    while(!result.isAtLastRow){
      // 3.3.指针移动到下一行
      result.goToNextRow()
      // 3.4.获取数据
      let id = result.getLong(result.getColumnIndex('ID'))
      let name = result.getString(result.getColumnIndex('NAME'))
      let finished = result.getLong(result.getColumnIndex('FINISHED'))
      // 3.5.封装到数组
      tasks.push({id, name, finished: !!finished})
    }
    console.log('testTag', '查询到数据:', JSON.stringify(tasks))
    return tasks
  }

  /**
   * 添加一个新的任务
   * @param name 任务名称
   * @returns 任务id
   */
  addTask(name: string): Promise<number>{
    return this.rdbStore.insert(this.tableName, {name, finished: false})
  }

  /**
   * 根据id更新任务状态
   * @param id 任务id
   * @param finished 任务是否完成
   */
  updateTaskStatus(id: number, finished: boolean) {
    // 1.要更新的数据
    let data = {finished}
    // 2.更新的条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    predicates.equalTo('ID', id)
    // 3.更新操作
    return this.rdbStore.update(data, predicates)
  }

  /**
   * 根据id删除任务
   * @param id 任务id
   */
  deleteTaskById(id: number){
    // 1.删除的条件
    let predicates = new relationalStore.RdbPredicates(this.tableName)
    predicates.equalTo('ID', id)
    // 2.删除操作
    return this.rdbStore.delete(predicates)
  }
}

let taskModel = new TaskModel();

export default taskModel as TaskModel;
import TaskModel from '../../model/TaskModel'
import TaskInfo from '../../viewmodel/TaskInfo'

@Component
export default struct TaskItem {
  @ObjectLink item: TaskInfo
  onTaskChange: (item: TaskInfo) => void

  build() {
    Row(){
      if(this.item.finished){
        Text(this.item.name)
          .finishedTask()
      }else{
        Text(this.item.name)
      }
      Checkbox()
        .select(this.item.finished)
        .onChange(async val => {
          // 1.更新当前任务状态
          TaskModel.updateTaskStatus(this.item.id, val)
            .then(() => {
              this.item.finished = val
              // 2.更新已完成任务数量
              this.onTaskChange(this.item)
            })
            .catch(error => console.log('testTag', '更新任务状态失败, id = ', this.item.id, JSON.stringify(error)))

        })
    }
    .card()
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

// 任务完成样式
@Extend(Text) function finishedTask(){
  .decoration({type:TextDecorationType.LineThrough})
  .fontColor('#B1B2B1')
}

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

通知-基础通知

通知-进度条通知

通知-通知行为意图

import notify from '@ohos.notificationManager'
import image from '@ohos.multimedia.image'
import DownloadCard from '../views/notification/DownloadCard'
import { Header } from '../common/components/CommonComponents'

@Entry
@Component
struct NotificationPage {
  // 全局任务id
  idx: number = 100
  // 图象
  pixel: PixelMap

  async aboutToAppear() {
    // 获取资源管理器
    let rm = getContext(this).resourceManager;
    // 读取图片
    let file = await rm.getMediaContent($r('app.media.watchGT4'))
    // 创建PixelMap
    image.createImageSource(file.buffer).createPixelMap()
      .then(value => this.pixel = value)
      .catch(reason => console.log('testTag', '加载图片异常', JSON.stringify(reason)))
  }

  build() {
    Column({space: 20}) {
      Header({title: '通知功能'})

      Button(`发送normalText通知`)
        .onClick(() => this.publishNormalTextNotification())
      Button(`发送longText通知`)
        .onClick(() => this.publishLongTextNotification())
      Button(`发送multiLine通知`)
        .onClick(() => this.publishMultiLineNotification())
      Button(`发送Picture通知`)
        .onClick(() => this.publishPictureNotification())

      // 下载功能卡片
      DownloadCard()
    }
    .width('100%')
    .height('100%')
    .padding(5)
    .backgroundColor('#f1f2f3')
  }

  publishNormalTextNotification() {
    let request: notify.NotificationRequest = {
      id: this.idx++,
      content: {
        contentType: notify.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: '通知标题' + this.idx,
          text: '通知内容详情',
          additionalText: '通知附加内容'
        }
      },
      showDeliveryTime: true,
      deliveryTime: new Date().getTime(),
      groupName: 'wechat',
      slotType: notify.SlotType.SOCIAL_COMMUNICATION
    }
    this.publish(request)
  }

  publishLongTextNotification() {
    let request: notify.NotificationRequest = {
      id: this.idx++,
      content: {
        contentType: notify.ContentType.NOTIFICATION_CONTENT_LONG_TEXT,
        longText: {
          title: '通知标题' + this.idx,
          text: '通知内容详情',
          additionalText: '通知附加内容',
          longText: '通知中的长文本,我很长,我很长,我很长,我很长,我很长,我很长,我很长',
          briefText: '通知概要和总结',
          expandedTitle: '通知展开时的标题' + this.idx
        }
      }
    }
    this.publish(request)
  }

  publishMultiLineNotification() {
    let request: notify.NotificationRequest = {
      id: this.idx++,
      content: {
        contentType: notify.ContentType.NOTIFICATION_CONTENT_MULTILINE,
        multiLine: {
          title: '通知标题' + this.idx,
          text: '通知内容详情',
          additionalText: '通知附加内容',
          briefText: '通知概要和总结',
          longTitle: '展开时的标题,我很宽,我很宽,我很宽',
          lines: [
            '第一行',
            '第二行',
            '第三行',
            '第四行',
          ]
        }
      }
    }
    this.publish(request)
  }

  publishPictureNotification() {
    let request: notify.NotificationRequest = {
      id: this.idx++,
      content: {
        contentType: notify.ContentType.NOTIFICATION_CONTENT_PICTURE,
        picture: {
          title: '通知标题' + this.idx,
          text: '通知内容详情',
          additionalText: '通知附加内容',
          briefText: '通知概要和总结',
          expandedTitle: '展开后标题' + this.idx,
          picture: this.pixel
        }
      }
    }
    this.publish(request)
  }

  private publish(request: notify.NotificationRequest) {
    notify.publish(request)
      .then(() => console.log('notify test', '发送通知成功'))
      .then(reason => console.log('notify test', '发送通知失败', JSON.stringify(reason)))
  }
}
import notify from '@ohos.notificationManager'
import wantAgent, { WantAgent } from '@ohos.app.ability.wantAgent'
import promptAction from '@ohos.promptAction'

enum DownloadState {
  NOT_BEGIN = '未开始',
  DOWNLOADING = '下载中',
  PAUSE = '已暂停',
  FINISHED = '已完成',
}

@Component
export default struct DownloadCard {
  // 下载进度
  @State progressValue: number = 0
  progressMaxValue: number = 100
  // 任务状态
  @State state: DownloadState = DownloadState.NOT_BEGIN

  // 下载的文件名
  filename: string = '圣诞星.mp4'

  // 模拟下载的任务的id
  taskId: number = -1

  // 通知id
  notificationId: number = 999

  isSupport: boolean = false

  wantAgentInstance: WantAgent

  async aboutToAppear(){
    // 1.判断当前系统是否支持进度条模板
    this.isSupport = await notify.isSupportTemplate('downloadTemplate')
    // 2.创建拉取当前应用的行为意图
    // 2.1.创建wantInfo信息
    let wantInfo: wantAgent.WantAgentInfo = {
      wants: [
        {
          bundleName: 'com.example.myapplication',
          abilityName: 'EntryAbility',
        }
      ],
      requestCode: 0,
      operationType: wantAgent.OperationType.START_ABILITY,
      wantAgentFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG]
    }
    // 2.2.创建wantAgent实例
    this.wantAgentInstance = await wantAgent.getWantAgent(wantInfo)
  }

  build() {
    Row({ space: 10 }) {
      Image($r('app.media.ic_files_video')).width(50)
      Column({ space: 5 }) {
        Row() {
          Text(this.filename)
          Text(`${this.progressValue}%`).fontColor('#c1c2c1')
        }
        .width('100%')
        .justifyContent(FlexAlign.SpaceBetween)

        Progress({
          value: this.progressValue,
          total: this.progressMaxValue,
        })

        Row({ space: 5 }) {
          Text(`${(this.progressValue * 0.43).toFixed(2)}MB`)
            .fontSize(14).fontColor('#c1c2c1')
          Blank()
          if (this.state === DownloadState.NOT_BEGIN) {
            Button('开始').downloadButton()
              .onClick(() => this.download())

          } else if (this.state === DownloadState.DOWNLOADING) {
            Button('取消').downloadButton().backgroundColor('#d1d2d3')
              .onClick(() => this.cancel())

            Button('暂停').downloadButton()
              .onClick(() => this.pause())

          } else if (this.state === DownloadState.PAUSE) {
            Button('取消').downloadButton().backgroundColor('#d1d2d3')
              .onClick(() => this.cancel())

            Button('继续').downloadButton()
              .onClick(() => this.download())
          } else {
            Button('打开').downloadButton()
              .onClick(() => this.open())
          }
        }.width('100%')
      }
      .layoutWeight(1)
    }
    .width('100%')
    .borderRadius(20)
    .padding(15)
    .backgroundColor(Color.White)
  }


  cancel() {
    // 取消定时任务
    if(this.taskId > 0){
      clearInterval(this.taskId);
      this.taskId = -1
    }
    // 清理下载任务进度
    this.progressValue = 0
    // 标记任务状态:未开始
    this.state = DownloadState.NOT_BEGIN
    // 取消通知
    notify.cancel(this.notificationId)
  }

  download() {
    // 清理旧任务
    if(this.taskId > 0){
      clearInterval(this.taskId);
    }
    // 开启定时任务,模拟下载
    this.taskId = setInterval(() => {
      // 判断任务进度是否达到100
      if(this.progressValue >= 100){
        // 任务完成了,应该取消定时任务
        clearInterval(this.taskId)
        this.taskId = -1
        // 并且标记任务状态为已完成
        this.state = DownloadState.FINISHED
        // 发送通知
        this.publishDownloadNotification()
        return
      }
      // 模拟任务进度变更
      this.progressValue += 2
      // 发送通知
      this.publishDownloadNotification()
    }, 500)
    // 标记任务状态:下载中
    this.state = DownloadState.DOWNLOADING
  }

  pause() {
    // 取消定时任务
    if(this.taskId > 0){
      clearInterval(this.taskId);
      this.taskId = -1
    }
    // 标记任务状态:已暂停
    this.state = DownloadState.PAUSE
    // 发送通知
    this.publishDownloadNotification()
  }

  open() {
    promptAction.showToast({
      message: '功能未实现'
    })
  }

  publishDownloadNotification(){
    // 1.判断当前系统是否支持进度条模板
    if(!this.isSupport){
      // 当前系统不支持进度条模板
      return
    }
    // 2.准备进度条模板的参数
    let template = {
      name: 'downloadTemplate',
      data: {
        progressValue: this.progressValue,
        progressMaxValue: this.progressMaxValue
      }
    }
    let request: notify.NotificationRequest = {
      id: this.notificationId,
      template: template,
      wantAgent: this.wantAgentInstance,
      content: {
        contentType: notify.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
        normal: {
          title: this.filename + ':  ' + this.state,
          text: '',
          additionalText: this.progressValue + '%'
        }
      }
    }
    // 3.发送通知
    notify.publish(request)
      .then(() => console.log('test', '通知发送成功'))
      .catch(reason => console.log('test', '通知发送失败!', JSON.stringify(reason)))
  }
}

@Extend(Button) function downloadButton() {
  .width(75).height(28).fontSize(14)
}
*
*
*