黑马程序员HarmonyOS4+NEXT星河版入门到企业级实战教程

准备

课程介绍


工具安装


了解arkts


ts基本语法




快速入门



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

    build() {
        Row() {
            Column() {
                Text(this.message)
                    .fontSize(50)
                    .fontWeight(FontWeight.Bold)
                    .fontColor('#36D') // RGB颜色
                    .onClick(()=>{
                        this.message='Hello ArkTS!'
                    })
            }
        .width('100%')
        }
    .height('100%')
    }
}

ArkUI组件

image

网络图片需要权限, 运行在模拟器来实验

// entry/src/main/module.json5
{
  "module": {
    "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('#36D') // RGB颜色
          .onClick(() => {
            this.message = 'Hello ArkTS!'
          })

        // 网络图片
        // Image('https://c-ssl.duitang.com/uploads/blog/202310/06/q4Sy272asxzxN2g.jpeg')
        // resources下的图片
        Image($r('app.media.icon'))
          .width(250) // 可以用数字(默认单位vp)或百分比(字符串)
          .interpolation(ImageInterpolation.High) // 高分辨率
      }
      .width('100%')
    }
    .height('100%')
  }
}

text

点击预览器上的3点, 切换语言环境

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

  build() {
    Row() {
      Column() {
        Image($r('app.media.icon'))
          .width(250) // 可以用数字(默认单位vp)或百分比(字符串)
          .interpolation(ImageInterpolation.High) // 高分辨率

        // 动态文本(国际化)
        Text($r('app.string.width_label'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
  }
}
// entry/src/main/resources/en_US/element/string.json
// entry/src/main/resources/base/element/string.json
{
  "string": [
    // ...
    {
      "name": "width_label",
      "value": "Image Width: "
    }
  ]
}
// entry/src/main/resources/zh_CN/element/string.json
{
  "string": [
    // ...
    {
      "name": "width_label",
      "value": "图片宽度: "
    }
  ]
}

text input

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

  build() {
    Row() {
      Column() {
        Image($r('app.media.icon'))
          .width(this.imageWidth) // 可以用数字(默认单位vp)或百分比(字符串)
          .interpolation(ImageInterpolation.High) // 高分辨率

        // 动态文本(国际化)
        Text($r('app.string.width_label'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

        // placeholder: 提示词   text: 默认值
        // toFixed -> 数字转字符串, 参数为保留几位小数
        TextInput({ placeholder: '请输入图片宽度', text: this.imageWidth.toFixed(0) })
          .width(250)
          .backgroundColor('#36D')
          .type(InputType.Number)
          .onChange(value => {
            this.imageWidth = parseInt(value)
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

button

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

  build() {
    Row() {
      Column() {
        Image($r('app.media.icon'))
          .width(this.imageWidth) // 可以用数字(默认单位vp)或百分比(字符串)
          .interpolation(ImageInterpolation.High) // 高分辨率

        // 动态文本(国际化)
        Text($r('app.string.width_label'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

        // placeholder: 提示词   text: 默认值
        // toFixed -> 数字转字符串, 参数为保留几位小数
        TextInput({ placeholder: '请输入图片宽度', text: this.imageWidth.toFixed(0) })
          .width(250)
          .backgroundColor('#36D')
          .type(InputType.Number)
          .onChange(value => {
            this.imageWidth = parseInt(value)
          })

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

        Button('放大')
          .width(80)
          .fontSize(20)
          .type(ButtonType.Normal)
          .onClick(() => {
            if (this.imageWidth < 300) {
              this.imageWidth += 10
            }
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

slider

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

  build() {
    Row() {
      Column() {
        Image($r('app.media.icon'))
          .width(this.imageWidth) // 可以用数字(默认单位vp)或百分比(字符串)
          .interpolation(ImageInterpolation.High) // 高分辨率

        // 动态文本(国际化)
        Text($r('app.string.width_label'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

        // placeholder: 提示词   text: 默认值
        // toFixed -> 数字转字符串, 参数为保留几位小数
        TextInput({ placeholder: '请输入图片宽度', text: this.imageWidth.toFixed(0) })
          .width(250)
          .backgroundColor('#36D')
          .type(InputType.Number)
          .onChange(value => {
            this.imageWidth = parseInt(value)
          })

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

        Button('放大')
          .width(80)
          .fontSize(20)
          .type(ButtonType.Normal)
          .onClick(() => {
            if (this.imageWidth < 300) {
              this.imageWidth += 10
            }
          })

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

页面布局 column row



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

  build() {
    // Column({ space: 30 }) {
    Column() {
      Row() {
        Image($r('app.media.icon'))
          .width(this.imageWidth) // 可以用数字(默认单位vp)或百分比(字符串)
          .interpolation(ImageInterpolation.High) // 高分辨率
      }
      .width('100%')
      .height(400)
      .justifyContent(FlexAlign.Center)

      Row() {
        // 动态文本(国际化)
        Text($r('app.string.width_label'))
          .fontSize(20)
          .fontWeight(FontWeight.Bold)

        // placeholder: 提示词   text: 默认值
        // toFixed -> 数字转字符串, 参数为保留几位小数
        TextInput({ placeholder: '请输入图片宽度', 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)
          .type(ButtonType.Normal)
          .onClick(() => {
            if (this.imageWidth < 300) {
              this.imageWidth += 10
            }
          })
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceEvenly)
      .margin({ top: 35, bottom: 35 })

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

渲染控制

class Item {
  name: string
  image: ResourceStr
  price: number

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

@Entry
@Component
struct Index {
  // 商品数据
  private items: Array = [
    new Item('图片1', $r('app.media.OIPC'), 1999),
    new Item('图片2', $r('app.media.OIPC1'), 2999),
    new Item('图片3', $r('app.media.OIPC2'), 3999),
    new Item('图片4', $r('app.media.OIPC3'), 4999),
    new Item('图片5', $r('app.media.OIPC4'), 5999),
    new Item('图片6', $r('app.media.OIPC5'), 6999),
  ]

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

      ForEach(
        this.items,
        (item: Item) => {
          Row({ space: 10 }) {
            Image(item.image)
              .width(100)
            Column({ space: 4 }) {
              Text(item.name)
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
              Text('$ ' + item.price)
                .fontSize(18)
                .fontColor('F36')
            }
            .height('100%')
            .alignItems(HorizontalAlign.Start)
          }
          .width('100%')
          .backgroundColor('#fff')
          .borderRadius(20)
          .height(120)
          .padding(10)
        }
      )
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#efefef')
    .padding(20)
  }
}

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
  }
}

@Entry
@Component
struct Index {
  // 商品数据
  private items: Array = [
    new Item('图片1', $r('app.media.OIPC'), 1999, 500),
    new Item('图片2', $r('app.media.OIPC1'), 2999),
    new Item('图片3', $r('app.media.OIPC2'), 3999),
    new Item('图片4', $r('app.media.OIPC3'), 4999),
    new Item('图片5', $r('app.media.OIPC4'), 5999),
    new Item('图片6', $r('app.media.OIPC5'), 6999),
  ]

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

      ForEach(
        this.items,
        (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)
                  .fontSize(14)
                  .fontColor('#CCC')
                  .decoration({ type: TextDecorationType.LineThrough })
                Text('折扣价: $ ' + (item.price - item.discount))
                  .fontSize(18)
                  .fontColor('#F36')
                Text('补贴: $ ' + item.discount)
                  .fontSize(18)
                  .fontColor('#F36')
              } else {
                Text(item.name)
                  .fontSize(20)
                  .fontWeight(FontWeight.Bold)
                Text('$ ' + item.price)
                  .fontSize(18)
                  .fontColor('F36')
              }
            }
            .height('100%')
            .alignItems(HorizontalAlign.Start)
          }
          .width('100%')
          .backgroundColor('#fff')
          .borderRadius(20)
          .height(120)
          .padding(10)
        }
      )
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#efefef')
    .padding(20)
  }
}

List

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
  }
}

@Entry
@Component
struct Index {
  // 商品数据
  private items: Array = [
    new Item('图片1', $r('app.media.OIPC'), 1999, 500),
    new Item('图片2', $r('app.media.OIPC1'), 2999),
    new Item('图片3', $r('app.media.OIPC2'), 3999),
    new Item('图片4', $r('app.media.OIPC3'), 4999),
    new Item('图片5', $r('app.media.OIPC4'), 5999),
    new Item('图片6', $r('app.media.OIPC5'), 6999),
  ]

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

      List({ space: 8 }) {
        ForEach(
          this.items,
          (item: Item) => {
            ListItem() {
              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)
                      .fontSize(14)
                      .fontColor('#CCC')
                      .decoration({ type: TextDecorationType.LineThrough })
                    Text('折扣价: $ ' + (item.price - item.discount))
                      .fontSize(18)
                      .fontColor('#F36')
                    Text('补贴: $ ' + item.discount)
                      .fontSize(18)
                      .fontColor('#F36')
                  } else {
                    Text(item.name)
                      .fontSize(20)
                      .fontWeight(FontWeight.Bold)
                    Text('$ ' + item.price)
                      .fontSize(18)
                      .fontColor('F36')
                  }
                }
                .height('100%')
                .alignItems(HorizontalAlign.Start)
              }
              .width('100%')
              .backgroundColor('#fff')
              .borderRadius(20)
              .height(120)
              .padding(10)
            }
          }
        )
      }
      .width('100%')
      .layoutWeight(1) // 布局权重, 此时上面那行占30, 剩下的高都是该list的

    }
    .width('100%')
    .height('100%')
    .backgroundColor('#efefef')
    .padding(20)
  }
}

自定义组件

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)
//           .fontSize(14)
//           .fontColor('#CCC')
//           .decoration({ type: TextDecorationType.LineThrough })
//         Text('折扣价: $ ' + (item.price - item.discount))
//           .fontSize(18)
//           .fontColor('#F36')
//         Text('补贴: $ ' + item.discount)
//           .fontSize(18)
//           .fontColor('#F36')
//       } else {
//         Text(item.name)
//           .fontSize(20)
//           .fontWeight(FontWeight.Bold)
//         Text('$ ' + item.price)
//           .fontSize(18)
//           .fontColor('F36')
//       }
//     }
//     .height('100%')
//     .alignItems(HorizontalAlign.Start)
//   }
//   .width('100%')
//   .backgroundColor('#fff')
//   .borderRadius(20)
//   .height(120)
//   .padding(10)
// }

// 全局公共样式
// 如果样式需要某种类型的组件才有, 需要用extend继承
// extend不能写在组件内
@Extend(Text) function priceText() {
  .fontColor('#36D')
  .fontSize(18)
}

@Entry
@Component
struct Index {
  // 商品数据
  private items: Array = [
    new Item('图片1', $r('app.media.OIPC'), 1999, 500),
    new Item('图片2', $r('app.media.OIPC1'), 2999),
    new Item('图片3', $r('app.media.OIPC2'), 3999),
    new Item('图片4', $r('app.media.OIPC3'), 4999),
    new Item('图片5', $r('app.media.OIPC4'), 5999),
    new Item('图片6', $r('app.media.OIPC5'), 6999),
  ]

  // 局部自定义构建函数
  @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)
            .fontSize(14)
            .fontColor('#CCC')
            .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)
  }

  // 局部公共样式
  @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() {
              // ItemCard(item) // 全局
              this.ItemCard(item) // 局部
            }
          }
        )
      }
      .width('100%')
      .layoutWeight(1) // 布局权重, 此时上面那行占30, 剩下的高都是该list的

    }
    // .width('100%')
    // .height('100%')
    // .backgroundColor('#efefef')
    // .padding(20)
    .fillScreen()
  }
}
// entry/src/main/ets/components/CommonComponents.ets
@Component
export struct Header {
  private title: ResourceStr

  build() {
    // 标题
    Row() {
      Image($r('app.media.back_64'))
        .width(30)
      Text(this.title)
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      // 这三个元素固定占一定大小, 要让他们分开排列, 不是用justifyContent
      // 而是用blank占位
      Blank() // 占据剩余空间
      Image($r('app.media.refresh'))
        .width(30)
    }
    .width('100%')
    .height(30)
    // .margin({ bottom: 20 })
  }
}

状态管理

@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 Index3 {
  idx: number = 1
  @State p: Person = new Person('Jack', 12)
  @State gfs: Person[] = [
    new Person('gf1', 18),
    new Person('gf2', 17),
  ]

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

      Text('=女友列表=')
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
      ForEach(
        this.gfs,
        (p, idx) => {
          Row() {
            Text(`${p.name} : ${p.age}`)
              .fontSize(30)
              .onClick(() => {
                // p.age++
                // 修改数组(插入/替换/删除...)才能更新视图
                this.gfs[idx] = new Person(p.name, p.age + 1)
              })
            Button('删除')
              .onClick(() => {
                // 从idx位置开始, 删除一个元素
                this.gfs.splice(idx, 1)
              })
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceAround)
        }
      )
    }
    .width('100%')
    .height('100%')
    .padding(20)
  }
}

@Component
struct Index2 {
  @State p: Person = new Person('Jack', 12, new Person('Rose', 18))

  build() {
    Column() {
      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++
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

@Component
struct Index1 {
  // error: Variables decorated by '@State', '@StorageLink', '@StorageProp',
  // and '@Provide' must be initialized locally.
  // @State message: string
  @State name: string = 'Jack'
  @State age: number = 12

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

任务统计案例

// 任务类
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')
}

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

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

  build() {
    Column({ space: 10 }) {
      // 1. 任务进度卡片
      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)
          }
        }
      }
      .card()
      .margin({ top: 20, bottom: 10 })
      .justifyContent(FlexAlign.SpaceEvenly)
      // 2. 新增任务按钮
      Button('新增任务')
        .width(200)
        .onClick(() => {
          // 新增任务
          this.tasks.push(new Task())
          // 更新总任务数
          // this.totalTask = this.tasks.length
          this.handleTaskChange()
        })
      // 3. 任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, idx) => {
            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
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(idx) })
          }
        )
      }
      .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.handleTaskChange()
    })
  }
}

// 任务类
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')
}

// 任务统计信息 (测试object类型)
class StatInfo {
  // 总任务数量
  totalTask: number = 0
  // 已完成任务数量
  finishTask: number = 0
}

@Entry
@Component
struct PropPage {
  // 总任务数量
  // @State totalTask: number = 0
  // 已完成任务数量
  // @State finishTask: number = 0
  // 统计信息
  @State stat: StatInfo = new StatInfo()

  build() {
    Column({ space: 10 }) {
      // 1. 任务进度卡片
      TaskStatistics({
        // 父组件的数据需要传给子组件进行数据同步
        // finishTask: this.finishTask,
        // totalTask: this.totalTask
        finishTask: this.stat.finishTask,
        totalTask: this.stat.totalTask
      })
      // 2. 任务列表
      // TaskList({
      //   // 父组件的数据需要传给子组件进行数据同步
      //   finishTask: $finishTask,
      //   // A '@Link' decorated attribute must be initialized with '$'.
      //   // 双向绑定的数据, 传递的是引用
      //   // totalTask: this.totalTask
      //   totalTask: $totalTask
      // })
      TaskList({
        stat: $stat
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F2F3')
  }
}

@Component
struct TaskStatistics {
  // 总任务数量
  @Prop totalTask: number
  // 已完成任务数量
  @Prop finishTask: number

  build() {
    // 1. 任务进度卡片
    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)
        }
      }
    }
    .card()
    .margin({ top: 20, bottom: 10 })
    .justifyContent(FlexAlign.SpaceEvenly)
  }
}

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

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

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

  build() {
    Column() {
      // 2. 新增任务按钮
      Button('新增任务')
        .width(200)
        .onClick(() => {
          // 新增任务
          this.tasks.push(new Task())
          // 更新总任务数
          // this.totalTask = this.tasks.length
          this.handleTaskChange()
        })
      // 3. 任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, idx) => {
            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
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(idx) })
          }
        )
      }
      .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()
    })
  }
}
// 任务统计信息 (测试object类型)
class StatInfo {
  // 总任务数量
  totalTask: number = 0
  // 已完成任务数量
  finishTask: number = 0
}

@Entry
@Component
struct PropPage {
  // 总任务数量
  // @State totalTask: number = 0
  // 已完成任务数量
  // @State finishTask: number = 0
  // 统计信息
  // @State stat: StatInfo = new StatInfo()
  @Provide stat: StatInfo = new StatInfo()

  build() {
    Column({ space: 10 }) {
      // 1. 任务进度卡片
      TaskStatistics({
        // 父组件的数据需要传给子组件进行数据同步
        // finishTask: this.finishTask,
        // totalTask: this.totalTask
        // @Provider和@Consume会自动传, 不需要显示传递
        // finishTask: this.stat.finishTask,
        // totalTask: this.stat.totalTask
      })
      // 2. 任务列表
      // TaskList({
      //   // 父组件的数据需要传给子组件进行数据同步
      //   finishTask: $finishTask,
      //   // A '@Link' decorated attribute must be initialized with '$'.
      //   // 双向绑定的数据, 传递的是引用
      //   // totalTask: this.totalTask
      //   totalTask: $totalTask
      // })
      TaskList({
        // stat: $stat
        // @Provider和@Consume会自动传, 不需要显示传递
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F2F3')
  }
}

@Component
struct TaskStatistics {
  // 总任务数量
  // @Prop totalTask: number
  // 已完成任务数量
  // @Prop finishTask: number
  @Consume stat: StatInfo

  build() {
    // 1. 任务进度卡片
    Row() {
      Text('任务进度: ')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      // 堆叠容器
      Stack() {
        Progress({
          // value: this.finishTask,
          // total: this.totalTask,
          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 {
  // 总任务数量
  // @Link totalTask: number
  // 已完成任务数量
  // @Link finishTask: number
  // @Link stat: StatInfo
  @Consume stat: StatInfo
  // 任务数组
  @State tasks: Task[] = []

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

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

  build() {
    ///
  }

  ///
}

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

// 任务统计信息 (测试object类型)
class StatInfo {
  // 总任务数量
  totalTask: number = 0
  // 已完成任务数量
  finishTask: number = 0
}

@Entry
@Component
struct PropPage {
  // 总任务数量
  // @State totalTask: number = 0
  // 已完成任务数量
  // @State finishTask: number = 0
  // 统计信息
  // @State stat: StatInfo = new StatInfo()
  @Provide stat: StatInfo = new StatInfo()

  build() {
    Column({ space: 10 }) {
      // 1. 任务进度卡片
      TaskStatistics({
        // 父组件的数据需要传给子组件进行数据同步
        // finishTask: this.finishTask,
        // totalTask: this.totalTask
        // @Provider和@Consume会自动传, 不需要显示传递
        // finishTask: this.stat.finishTask,
        // totalTask: this.stat.totalTask
      })
      // 2. 任务列表
      // TaskList({
      //   // 父组件的数据需要传给子组件进行数据同步
      //   finishTask: $finishTask,
      //   // A '@Link' decorated attribute must be initialized with '$'.
      //   // 双向绑定的数据, 传递的是引用
      //   // totalTask: this.totalTask
      //   totalTask: $totalTask
      // })
      TaskList({
        // stat: $stat
        // @Provider和@Consume会自动传, 不需要显示传递
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F2F3')
  }
}

@Component
struct TaskStatistics {
  // 总任务数量
  // @Prop totalTask: number
  // 已完成任务数量
  // @Prop finishTask: number
  @Consume stat: StatInfo

  build() {
    // 1. 任务进度卡片
    Row() {
      Text('任务进度: ')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      // 堆叠容器
      Stack() {
        Progress({
          // value: this.finishTask,
          // total: this.totalTask,
          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 {
  // 总任务数量
  // @Link totalTask: number
  // 已完成任务数量
  // @Link finishTask: number
  // @Link stat: StatInfo
  @Consume stat: StatInfo
  // 任务数组
  @State tasks: Task[] = []

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

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

  build() {
    Column() {
      // 2. 新增任务按钮
      Button('新增任务')
        .width(200)
        .onClick(() => {
          // 新增任务
          this.tasks.push(new Task())
          // 更新总任务数
          // this.totalTask = this.tasks.length
          this.handleTaskChange()
        })
      // 3. 任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, idx) => {
            ListItem() {
              // 方法传给子组件, 方法里用到的this会改变, 可能出错
              // TaskItem({ item, onTaskChange: this.handleTaskChange })
              TaskItem({ item, onTaskChange: this.handleTaskChange.bind(this) }) // 绑定this
            }
            .swipeAction({ end: this.DeleteButton(idx) })
          }
        )
      }
      .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()
    })
  }
}

@Component
struct TaskItem {
  @ObjectLink item: Task
  onTaskChange: () => void

  build() {
    Row() {
      if (this.item.finished) {
        Text(this.item.name)
          .finishedTask()
      } else {
        Text(this.item.name)
          .fontSize(20)
      }
      Checkbox()
        .select(this.item.finished)
        .onChange(val => {
          // 更新任务状态
          this.item.finished = val
          // 更新完成任务的数量
          // this.handleTaskChange()
          this.onTaskChange()
        })
    }
    .card()
    .justifyContent(FlexAlign.SpaceBetween)
  }
}

页面路由


页面要声明后才能显示和跳转, 可以在新建时直接创建page, 自动加入到main_pages.json

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 msg: string = '页面列表'
  private routers: RouterInfo[] = [
    new RouterInfo('pages/1_components/button', '按钮案例'),
    new RouterInfo('pages/1_components/imagePage', '图片查看案例'),
    new RouterInfo('pages/1_components/slider', '滑动条案例'),
    new RouterInfo('pages/1_components/list', '列表案例'),
    new RouterInfo('pages/1_components/DIYcomponent', '自定义组件案例'),
  ]

  build() {
    Column() {
      Text(this.msg)
        .fontSize(50)
        .fontWeight(FontWeight.Bold)
        .height(80)
        .onClick(() => {
          this.msg = 'hello arkts'
        })

      List({ space: 15 }) {
        ForEach(
          this.routers,
          (r, i) => {
            ListItem() {
              this.RouterItem(r, i + 1)
            }
          }
        )
      }
      .layoutWeight(1)
      .alignListItem(ListItemAlign.Center)
      .width('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.pushUrl(
        {
          url: r.url,
          params: {
            id: i
          }
        },
        router.RouterMode.Single,
        err => {
          if (err) {
            console.log(`路由失败: errCode: ${err.code} errMsg: ${err.message}`)
          }
        }
      )
    })
  }
}

动画

属性动画和显示动画


  目录