HarmonyOS开发实例:【菜单app】

 简介

分布式菜单demo 模拟的是多人聚餐点菜的场景,不需要扫码关注公众号等一系列操作,通过分布式数据库可以方便每个人可及时查看到订单详情,数量,总额等;效果如下

  • demo效果

    show

工程目录

完整的项目结构目录如下

├─entry
│  └─src
│      └─main
│          │  config.json  // 应用配置文件
│          │  
│          ├─ets
│          │  └─MainAbility
│          │      │  app.ets  // 应用程序主入口
│          │      │  
│          │      ├─model
│          │      │      CommonLog.ets  // 日志类
│          │      │      MenuData.ets  // 初始化菜单数据类
│          │      │      MenuListDistributedData.ets  // 加入菜单分布式数据库
│          │      │      RemoteDeviceManager.ets  // 分布式拉起设备管理类
│          │      │      SubmitData.ets   // 结算订单分布式数据库
│          │      │      
│          │      └─pages
│          │              detailedPage.ets // 菜品详细页面
│          │              index.ets // 首页
│          │              menuAccount.ets // 订单详情页面
│          │              
│          └─resources
│              ├─base
│              │  ├─element
│              │  │      string.json
│              │  │      
│              │  ├─graphic
│              │  ├─layout
│              │  ├─media   // 存放媒体资源
│              │  │      icon.png
│              │  │      icon_add.png
│              │  │      icon_back.png
│              │  │      icon_cart.png
│              │  │      
│              │  └─profile
│              └─rawfile

鸿蒙开发文档qr23.cn/AKFP8k参考(点击或者复制前往)。

开发步骤

搜狗高速浏览器截图20240326151450.png

1. 新建OpenHarmony ETS项目

在DevEco Studio中点击File -> New Project ->Empty Ability->Next,Language 选择ETS语言,最后点击Finish即创建成功。 

image-20211124092813545

image-20211124092813545

2. 编写商品展示主页面

image-20211124093106260

2.1用户信息

1): 主要用到[Flex]容器[Image]和[Text]组件;

2): 用户名称和头像图标,根据设备序列号不同,可展示不同的名称和图标;

3): 点击右上角分享的小图标,可分布式拉起局域网内的另一台设备;

@Component
struct MemberInfo {
  @Consume userImg: Resource
  @Consume userName: string
  
  aboutToAppear() {
  // 根据设备序列号不同,展示不同的名称和图标
    CommonLog.info('==serial===' + deviceInfo.serial);
    if (deviceInfo.serial == '150100384754463452061bba4c3d670b') {
      this.userImg = $r("app.media.icon_user")
      this.userName = 'Sunny'
    }
    else {
      this.userImg = $r("app.media.icon_user_another")
      this.userName = 'Jenny'
    }
  }

  build() {
    Flex({ direction: FlexDirection.Column }) {
      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
        Image(this.userImg)
          .width('96lpx')
          .height('96lpx')
          .margin({ right: '18lpx' })
        Text(this.userName)
          .fontSize('36lpx')
          .fontWeight(FontWeight.Bold)
          .flexGrow(1)
        Image($r("app.media.icon_share"))
          .width('64lpx')
          .height('64lpx')
      }
      // 打开分布式设备列表
      .onClick(() => {
        this.DeviceDialog.open()
      })
      .layoutWeight(1)
      .padding({ left: '48lpx', right: '48lpx' })

      Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
        Column() {
          Text('124')
            .fontSize('40lpx')
            .margin({ bottom: '24lpx' })
          Text('积分')
            .fontSize('22lpx')
            .opacity(0.4)
        }
        .flexGrow(1)

        Column() {
          Text('0')
            .fontSize('40lpx')
            .margin({ bottom: '24lpx' })
          Text('优惠劵')
            .fontSize('22lpx')
            .opacity(0.4)
        }
        .flexGrow(1)

        Column() {
          Image($r("app.media.icon_member"))
            .width('48lpx')
            .height('48lpx')
            .margin({ bottom: '24lpx' })
          Text('会员码')
            .fontSize('22lpx')
            .fontColor('#000000')
            .opacity(0.4)
        }
        .flexGrow(1)
      }
      .layoutWeight(1)
    }
    .width('93%')
    .height('25%')
    .borderRadius('16lpx')
    .backgroundColor('#FFFFFF')
    .margin({ top: '24lpx', bottom: '32lpx' })
  }
}
2.2列表展示

1): 主要用到[Flex]容器 和[Scroll]容器[Image]和[Text]组件;

2): 从首页点击列表进入菜品详细页面,点菜成功后会自动返回首页,此时列表需要动态更新菜品的数量;

@Component
struct MenuHome {
  private specialty: any[]
  private winterNew: any[]
  private classic: any[]
  private soup: any[]
  private menuItems: MenuData[]
  private titleList = ['招牌菜', '冬季新品', '下饭菜', '汤品']
  @State name: string = '招牌菜'

  build() {
    Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Start }) {
      Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceAround }) {
        ForEach(this.titleList, item => {
          Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Start }) {
            Text(item)
              .fontSize('24lpx')
          }
          .padding({ left: '24lpx' })
          .backgroundColor(this.name == item ? '#1A006A3A' : '#FFFFFF')
          .height('160lpx')
          .onClick(() => {
            this.name = item
            if (this.name == '招牌菜') {
              this.menuItems = initializeOnStartup(this.specialty);
            }
            else if (this.name == '冬季新品') {
              this.menuItems = initializeOnStartup(this.winterNew);
            }
            else if (this.name == '下饭菜') {
              this.menuItems = initializeOnStartup(this.classic);
            }
            else if (this.name == '汤品') {
              this.menuItems = initializeOnStartup(this.soup);
            }
          })
        }, item => item)
      }
      .width('20%')
      .backgroundColor('#FFFFFF')

      Flex({ direction: FlexDirection.Column }) {
        Text(this.name)
          .fontSize('32lpx')
          .fontWeight(FontWeight.Bold)
          .opacity(0.4)
          .height('8%')
        Scroll() {
          Column() {
            List() {
              ForEach(this.menuItems, item => {
                ListItem() {
                  MenuListItem({ menuItem: item })
                }
              }, item => item.id.toString())
            }
          }
        }
        .height('92%')
      }
      .margin({ left: '10lpx' })
      .width('75%')

    }
    .height('50%')
  }
}
2.3底部总额

1): 主要用到[Flex]容器 和[Stack]容器[Image]和[Text]组件;

2): 从首页点击列表进入菜品详细页面,点菜成功后会自动返回首页,更新订单数量和总额;

3): 点击底部总额框,将订单列表加入分布式数据库,@entry模拟监听数据库变化,拉起订单列表详情页面;

@Component
struct TotalInfo {
  @Consume TotalMenu: any[];
  private total: number = 0;
  private amount: number = 0;
  private remoteData: MenuListData

  aboutToAppear() {
    for (var index = 0; index < this.TotalMenu.length; index++) {
      this.total = this.total + this.TotalMenu[index].price * this.TotalMenu[index].quantity
      this.amount = this.amount + this.TotalMenu[index].quantity
    }
  }

  build() {
    Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
      Stack({ alignContent: Alignment.Center }) {
        Image($r("app.media.icon_cart"))
          .width('96lpx')
          .height('96lpx')
          .margin({ left: '22lpx' })
        Text(this.amount.toString())
          .backgroundColor('#F84747')
          .borderRadius('30plx')
          .fontSize('24plx')
          .textAlign(TextAlign.Center)
          .fontColor('#FFFFFF')
          .width('50lpx')
          .height('50lpx')
          .margin({ left: '100lpx', bottom: '85lpx' })
      }
      .width('150lpx')
      .height('150lpx')

      Text('¥')
        .fontSize('22lpx')
        .fontColor('#006A3A')
        .margin({ left: '22lpx' })
      Text(this.total.toString())
        .fontSize('40lpx')
        .fontColor('#006A3A')
        .flexGrow(1)
      Text('点好了')
        .height('100%')
        .width('35%')
        .fontColor('#FFFFFF')
        .backgroundColor('#F84747')
        .textAlign(TextAlign.Center)
    }
    // 将总的订单数据,加入分布式数据库
    .onClick(() => {
      this.remoteData.putData("menu_list", this.TotalMenu)
    })
    .width('100%')
    .height('10%')
    .backgroundColor('#FFFFFF')
  }
}
3. 编写菜单详细页面

image-20211124093106260

3.1 菜单详情

1): 主要用到[Flex]容器 [Image]和[Text]组件[Button]组件;

2): 辣度可以选择;

3):点击选好了,需要判断该菜品是否已经在总订单里面,并判断是哪一个用户添加,根据判断,做出相应的增加;

@Component
struct detailInfo {
  private menuItem
  private spicyList = ['正常辣', '加辣', '少辣']
  @State spicy: string = '正常辣'
  private TotalMenu: any[]
  private index = 0
  private userName: string

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Start }) {
        Flex({ direction: FlexDirection.Row }) {
          Flex() {
            Image(this.menuItem.imgSrc)
              .objectFit(ImageFit.Contain)
          }

          Flex({ direction: FlexDirection.Column }) {
            Text(this.menuItem.name)
              .fontSize('32lpx')
              .flexGrow(1)
            Text(this.menuItem.remarks)
              .fontSize('22lpx')
              .fontColor('#000000')
              .opacity(0.6)
              .flexGrow(1)
            Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
              Text('¥')
                .fontSize('22lpx')
              Text(this.menuItem.price.toString())
                .fontSize('40lpx')
              Text('/份')
                .fontSize('22lpx')
                .flexGrow(1)
              Image($r("app.media.icon_reduce"))
                .width('44lpx')
                .height('44lpx')
                .onClick(() => {
                  prompt.showToast({
                    message: "Reduce function  to be completed",
                    duration: 5000
                  })
                })
              Text(this.menuItem.quantity.toString())
                .margin({ left: '15lpx', right: '15lpx' })
              Image($r("app.media.icon_add"))
                .width('44lpx')
                .height('44lpx')
                .margin({ right: '15lpx' })
                .onClick(() => {
                  prompt.showToast({
                    message: "Increase function to be completed",
                    duration: 5000
                  })
                })
            }
            .flexGrow(2)
          }
        }
        .height('40%')
        .margin({ top: '40lpx', bottom: '24lpx' })

        Button()
          .backgroundColor('#000000')
          .opacity(0.1)
          .height('2lpx')
          .margin({ left: '24lpx' })
          .width('92%')

        Flex({ direction: FlexDirection.Row }) {
          Button()
            .backgroundColor('#006A3A ')
            .width('8lpx')
            .height('48lpx')
            .margin({ right: '12lpx' })
          Text('辣度')
        }
        .margin({ left: '44lpx', top: '48lpx', bottom: '32lpx' })

        Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceEvenly }) {
          ForEach(this.spicyList, item => {

            Button(item)
              .fontSize('28lpx')
              .height('60lpx')
              .width('156lpx')
              .borderRadius('12lpx')
              .backgroundColor(this.spicy == item ? '#006A3A' : '#0D000000')
              .fontColor(this.spicy == item ? '#FFFFFF' : '#000000')

              .onClick(() => {
                this.spicy = item
              })
          }, item => item)
        }
      }
      .margin({ top: '56lpx' })
      .width('92%')
      .height('50%')
      .borderRadius('16lpx')
      .backgroundColor('#FFFFFF')


      Button('选好了')
        .fontSize('36lpx')
        .width('80%')
        .height('7%')
        .backgroundColor('#F84747')
        .onClick(() => {
          for (this.index = 0; this.index < this.TotalMenu.length; this.index++) {
            if (this.TotalMenu[this.index].name == this.menuItem.name && this.TotalMenu[this.index].spicy == this.spicy) {
              this.TotalMenu[this.index].quantity = this.TotalMenu[this.index].quantity + 1;
              if (this.userName == 'Sunny') {
                this.TotalMenu[this.index].userNumber = this.TotalMenu[this.index].userNumber + 1;
              } else if (this.userName == 'Jenny') {
                this.TotalMenu[this.index].anotherUserNumber = this.TotalMenu[this.index].anotherUserNumber + 1;
              }
              break;
            }
          }
          // 菜名不一样,辣度不一样,都需要重新push到列表里面
          if (this.index == this.TotalMenu.length) {
            this.menuItem.spicy = this.spicy;
            this.menuItem.quantity = 1;
            //根据不用的用户名称,
            if (this.userName == 'Sunny') {
              this.menuItem.userNumber = 1;
            } else if (this.userName == 'Jenny') {
              this.menuItem.anotherUserNumber = 1;
            }
            this.TotalMenu.push(this.menuItem);
          }
          router.push({
            uri: 'pages/index',
            params: { menuItem: this.menuItem, TotalMenu: this.TotalMenu }
          })
        })
        .margin({ top: '10%' })
    }
  }
}
4. 编写订单详情页面

image-20211124093106260

4.1 订单列表

1): 主要用到[Flex]容器[Image]和[Text]组件[Button]组件;

2): 点击下单,将"submitOk" 加入分布式数据库,监听数据库变化后,弹出自定义对话框;

@Component
struct TotalItem {
  private totalMenu: MenuData

  build() {
    Flex({ direction: FlexDirection.Column }) {
      Flex({ direction: FlexDirection.Row, alignContent: FlexAlign.Start, justifyContent: FlexAlign.Start }) {

        Image(this.totalMenu.imgSrc)
          .width('210lpx')
          .height('100%')
        Flex({ direction: FlexDirection.Column }) {
          Text(this.totalMenu.name)
            .fontSize('32lpx')
            .flexGrow(1)
          Text(this.totalMenu.spicy)
            .fontSize('22lpx')
            .fontColor('#000000')
            .opacity(0.6)
            .flexGrow(1)
          Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
            Text('¥')
              .fontSize('22lpx')
            Text(this.totalMenu.price.toString())
              .fontSize('40lpx')
            Text('/份')
              .fontSize('22lpx')
              .flexGrow(1)
            Text(this.totalMenu.quantity.toString())
              .fontColor("#F84747")
              .fontSize('40lpx')
          }
          .flexGrow(2)
        }
        .padding({ left: '5%', top: '6%' })
        .width('70%')
      }
      .height('180lpx')

      Button()
        .backgroundColor('#000000')
        .opacity(0.1)
        .height('2lpx')
        .margin({ top: '20lpx' })
        .width('100%')


      if (this.totalMenu.userNumber > 0) {
        Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
          Image(this.totalMenu.userImg)
            .width('96lpx')
            .height('96lpx')
          Text(this.totalMenu.userName)
            .fontSize('36lpx')
            .fontWeight(FontWeight.Bold)
            .margin({ left: '12lpx' })
            .flexGrow(1)
          Text(this.totalMenu.userNumber.toString())
            .fontSize('32lpx')
            .margin({ right: '11plx' })

        }
        .height('150lpx')
      }
      if (this.totalMenu.anotherUserNumber > 0) {
        Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
          Image(this.totalMenu.anotherUserImg)
            .width('96lpx')
            .height('96lpx')
          Text(this.totalMenu.anotherUserName)
            .fontSize('36lpx')
            .fontWeight(FontWeight.Bold)
            .margin({ left: '12lpx' })
            .flexGrow(1)
          Text(this.totalMenu.anotherUserNumber.toString())
            .fontSize('32lpx')
            .margin({ right: '11plx' })

        }
        .height('150lpx')
      }
    }
    .margin({ top: '12lpx' })
    .borderRadius('16lpx')
    .padding({ left: '3%', right: '3%', top: '2%' })
    .backgroundColor('#FFFFFF')
  }
}
4.2自定义弹框

1)通过**@CustomDialog**装饰器来创建自定义弹窗,使用方式可参考 [自定义弹窗];

2)规则弹窗效果如下,弹窗组成由一个[Image]和两个[Text]竖向排列组成;

所有我们可以在build()下使用[Flex]容器来包裹,组件代码如下:

@CustomDialog
struct SubmitDialog {
  private controller: CustomDialogController

  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
      Flex({ justifyContent: FlexAlign.Center }) {
        Image($r("app.media.icon_success"))
          .width('100lpx')
          .height('80lpx')
      }
      .flexGrow(1)

      Text('下单成功')
        .fontSize('36lpx')
        .fontColor('#000000')
        .flexGrow(1)
      Text('*温馨提示:菜品具体售卖情况请以店面实际情况为准哦~')
        .fontSize('22lpx')
        .opacity(0.6)
        .fontColor('#000000')
        .padding({ left: '10lpx', right: '10lpx' })
    }
    .height('300lpx')
    .width('100%')
    .padding({ top: '50lpx', bottom: '20lpx' })

  }
}

​ 3)在@entry创建CustomDialogController对象并传入弹窗所需参数,设置点击允许点击遮障层退出,通过open()方法,显示弹窗;

SubmitDialog: CustomDialogController = new CustomDialogController({
    builder: SubmitDialog(),
    autoCancel: true
  })
aboutToAppear() {
  
    this.remoteData.createManager(() => {
      let self = this;
      var data;
      if (JSON.stringify(self.remoteData.dataItem).length > 0) {
      	data = self.remoteData.dataItem;
        CommonLog.info("======submit==" + data[0].submit);
        if (data[0].submit == "submitOk") {
          this.SubmitDialog.open()
        }
      }
    }, "com.distributed.order", "submit")
  }
5. 添加分布式流转

分布式流转需要在同一网络下通过 [DeviceManager组件]进行设备间发现和认证,获取到可信设备的deviceId调用 [featureAbility].startAbility ,即可把应用程序流转到另一设备。

1)创建DeviceManager实例;

2)调用实例的startDeviceDiscovery(),开始设备发现未信任设备;

3)设置设备状态监听on('deviceFound',callback),获取到未信任设备,并用discoverList变量进行维护;

4)传入未信任设备参数,调用实例authenticateDevice方法,对设备进行PIN码认证;

5)若是已信任设备,可通过实例的getTrustedDeviceListSync()方法来获取设备信息;

6)将设备信息中的deviceId传入[featureAbility].startAbility方法,实现流转;

7)流转接收方可通过[featureAbility].getWant()获取到发送方携带的数据;

项目中将上面设备管理封装至RemoteDeviceManager,通过RemoteDeviceManager的四个方法来动态维护deviceList设备信息列表,实现分布式流转只需要在deviceList中获取deviceId,然后调用featureAbility.startAbility并携带数据,即可实现分布式流转。

RemoteDeviceManager

6.分布式数据管理

[分布式数据管理]要求两个或多个设备在同一网络,才能监听到数据库的改变,从而渲染页面;开发步骤:

1)创建一个KVManager对象实例,用于管理数据库对象;

2)通过指定Options和storeId,创建并获取KVStore数据库,如下是参数说明;需要先通过createKVManager构建一个KVManager实例;

参数名类型必填说明
storeIdstring数据库唯一标识符,长度不大于[MAX_STORE_ID_LENGTH]。
options[Options]创建KVStore实例的配置信息。

3)KVStore数据库实例, KVStore.put提供增加数据的方法,如下是参数说明;

参数名类型必填说明
keystring要添加数据的key,不能为空且长度不大于[MAX_KEY_LENGTH]。
valueUint8Arraystringnumberboolean要添加数据的value,支持Uint8Array、number 、 string 、boolean,Uint8Array、string 的长度不大于[MAX_VALUE_LENGTH]。
callbackAsyncCallback回调函数。

4) KVStore数据库实例,KVStore.on订阅指定类型的数据变更通知;一般监听远端设备变化,再进行相应操作达到分布式数据共享的效果;

本项目通过storeId 值不同,创建了两个数据库,分别是MenuListDistributedData类和SubmitData类;

MenuListDistributedData是将完整订单添加到分布式数据库

@Component
struct TotalInfo {
  @Consume TotalMenu: any[];
  private total: number = 0;
  private amount: number = 0;
  private remoteData: MenuListData

  aboutToAppear() {
    for (var index = 0; index < this.TotalMenu.length; index++) {
      this.total = this.total + this.TotalMenu[index].price * this.TotalMenu[index].quantity
      this.amount = this.amount + this.TotalMenu[index].quantity
    }
  }

  build() {
    Flex({ direction: FlexDirection.Row, alignItems: ItemAlign.Center }) {
      Stack({ alignContent: Alignment.Center }) {
        Image($r("app.media.icon_cart"))
          .width('96lpx')
          .height('96lpx')
          .margin({ left: '22lpx' })
        Text(this.amount.toString())
          .backgroundColor('#F84747')
          .borderRadius('30plx')
          .fontSize('24plx')
          .textAlign(TextAlign.Center)
          .fontColor('#FFFFFF')
          .width('50lpx')
          .height('50lpx')
          .margin({ left: '100lpx', bottom: '85lpx' })
      }
      .width('150lpx')
      .height('150lpx')

      Text('¥')
        .fontSize('22lpx')
        .fontColor('#006A3A')
        .margin({ left: '22lpx' })
      Text(this.total.toString())
        .fontSize('40lpx')
        .fontColor('#006A3A')
        .flexGrow(1)
      Text('点好了')
        .height('100%')
        .width('35%')
        .fontColor('#FFFFFF')
        .backgroundColor('#F84747')
        .textAlign(TextAlign.Center)
    }
    .onClick(() => {
      this.remoteData.putData("menu_list", this.TotalMenu)
    })
    .width('100%')
    .height('10%')
    .backgroundColor('#FFFFFF')
  }
}

SubmitData在订单结算是点击下单,将submitOk 添加到数据库;

@Component
struct SubmitList {
  private remoteData: SubmitData
  private SubmitOK: any[] = [
    {
      submit: "submitOk"
    }
  ];

  build() {
    Flex({ alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
      Text('下单')
        .fontSize('36lpx')
        .fontColor('#FFFFFF')
    }
    .width('100%')
    .height('10%')
    .backgroundColor('#F84747')
    .onClick(() => {
      this.remoteData.putData("submit", this.SubmitOK)
    })
    .margin({ top: '5%' })
  }
}

鸿蒙开发岗位需要掌握那些核心要领?

目前还有很多小伙伴不知道要学习哪些鸿蒙技术?不知道重点掌握哪些?为了避免学习时频繁踩坑,最终浪费大量时间的。

自己学习时必须要有一份实用的鸿蒙(Harmony NEXT)资料非常有必要。 这里我推荐,根据鸿蒙开发官网梳理与华为内部人员的分享总结出的开发文档。内容包含了:【ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战】等技术知识点。

废话就不多说了,接下来好好看下这份资料。

如果你是一名Android、Java、前端等等开发人员,想要转入鸿蒙方向发展。可以直接领取这份资料辅助你的学习。鸿蒙OpenHarmony知识←前往。下面是鸿蒙开发的学习路线图。

针对鸿蒙成长路线打造的鸿蒙学习文档。鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,帮助大家在技术的道路上更进一步。

其中内容包含:

《鸿蒙开发基础》鸿蒙OpenHarmony知识←前往

  1. ArkTS语言
  2. 安装DevEco Studio
  3. 运用你的第一个ArkTS应用
  4. ArkUI声明式UI开发
  5. .……

《鸿蒙开发进阶》鸿蒙OpenHarmony知识←前往

  1. Stage模型入门
  2. 网络管理
  3. 数据管理
  4. 电话服务
  5. 分布式应用开发
  6. 通知与窗口管理
  7. 多媒体技术
  8. 安全技能
  9. 任务管理
  10. WebGL
  11. 国际化开发
  12. 应用测试
  13. DFX面向未来设计
  14. 鸿蒙系统移植和裁剪定制
  15. ……

《鸿蒙开发实战》鸿蒙OpenHarmony知识←前往

  1. ArkTS实践
  2. UIAbility应用
  3. 网络案例
  4. ……

最后

鸿蒙是完全具备无与伦比的机遇和潜力的;预计到年底将有 5,000 款的应用完成原生鸿蒙开发,这么多的应用需要开发,也就意味着需要有更多的鸿蒙人才。鸿蒙开发工程师也将会迎来爆发式的增长,学习鸿蒙势在必行!

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

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

相关文章

代码随想录第38天| 509. 斐波那契数 70. 爬楼梯

理论基础 刷题大纲&#xff1a; 动态规划5步曲&#xff1a; 1、确定dp数组以及下标的含义 2、确定递推公式 3、dp数组如何初始化 4、确定遍历顺序 5、举例推导dp数组 509. 斐波那契数 509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; 代码随想录 (programmercarl.co…

SpringBoot学习笔记二

SpringBoot学习笔记二 1.SpringBoot配置加载顺序1.1 内部配置加载顺序1.2 外部配置加载顺序 2. SpringBoot整合其他框架2.1 SpringBoot整合Test2.2 SpringBoot整合Redis 1.SpringBoot配置加载顺序 1.1 内部配置加载顺序 同理可知&#xff0c;父项目中的confg下的配置优先级最…

Bitmap OOM

老机器Bitmap预读仍然OOM&#xff0c;无奈增加一段&#xff0c;终于不崩溃了。 if (Build.VERSION.SDK_INT < 21)size 2; 完整代码&#xff1a; Bitmap bitmap; try {//Log.e(Thread.currentThread().getStackTrace()[2] "", surl);URL url new URL(surl);…

【数据结构与算法】之8道顺序表与链表典型编程题心决!

个人主页&#xff1a;秋风起&#xff0c;再归来~ 数据结构与算法 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 目录 1、顺序表 1.1 合并两个有序数组 1.2 原地移除数组中所有的元素va…

2024/4/5—力扣—下一个排列

代码实现&#xff1a; 思路&#xff1a;两遍扫描 void swap(int *a, int *b) {int t *a;*a *b;*b t; }void reverse(int *nums, int l, int r) {while (l < r) {swap(nums l, nums r);l;r--;} }void nextPermutation(int *nums, int numsSize) {int i numsSize - 2;wh…

手把手从零搭建ChatGPT网站midjourney-AI绘画系统,附详细搭建部署教程文档

一、系统前言 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支持…

前端开发攻略---用JavaScript打造炫酷数字变化动画效果:手写实现你的自定义动画函数!支持更改任意数字、动画速度

1、演示 2、介绍 这篇文章将向您展示如何使用JavaScript来创建一个自定义的动画函数&#xff0c;以实现数字变化效果。我们将深入了解前端动画的本质&#xff0c;并通过手写代码来实现这个炫酷的数字变化动画效果。您将学到如何利用JavaScript来操作DOM元素&#xff0c;控制动画…

知名专业定制线缆源头工厂推荐-精工电联:智能制造线缆的前沿技术探索

优质定制线缆源头厂家推荐-精工电联&#xff1a;智能制造线缆的前沿技术探索 知名专业定制线缆源头工厂推荐-精工电联&#xff1a;智能制造线缆的前沿技 在科技飞速发展的当今时代&#xff0c;智能制造已成为工业4.0的核心驱动力。精工电联&#xff0c;作为智能制造领先的高品质…

Pycharm中 Console 打不开

引言&#xff1a; 近年来&#xff0c;越来越多的高校洞察到了Pycharm为代表的编程IDE软件的重要性&#xff0c;已经购买了对应的版权。对于这些软件的使用&#xff0c;许多本科生可能还比较陌生&#xff0c;这系列博客主要总结了一些常见的BUG及对应的解决方案。本篇博客主要总…

核心api实操-Activiti7从入门到专家(5)

背景 上一节已经搭建了&#xff0c;具体的开发环境&#xff0c;数据库&#xff0c;并且找了一个可以用bpmnjs流程设计器&#xff0c;这一些&#xff0c;我们对核心api做个基础的实操&#xff0c;有个感性的认知&#xff0c;另外对数据库和基本数据流动有个理解。 部署 模板部…

Java | Leetcode Java题解之第17题电话号码的字母组合

题目&#xff1a; 题解&#xff1a; class Solution {public List<String> letterCombinations(String digits) {List<String> combinations new ArrayList<String>();if (digits.length() 0) {return combinations;}Map<Character, String> phoneM…

open-sora

Open-Sora&#xff0c;高效复现类Sora视频生成方案开源&#xff01;魔搭社区最佳实践教程来啦&#xff01;https://mp.weixin.qq.com/s/WMQIDgZs2MBPGtx18XSXgw Open-Sora开源方案讲解开源但“平替”的方案。https://mp.weixin.qq.com/s/nPYCzgBA7hIsPZ6PCyXxKQOpen-Sora/docs…

Qt信号与槽

我们在使用Qt的时候&#xff0c;不使用Qt Designer 的方式进行开发&#xff0c;使用ui文件&#xff0c;信号与槽的连接方式是生成代码之后才能在setupUi函数里才能看到&#xff0c;或者需要进入Ui设计器里的信号槽模式里才能看到信号槽的连接。所以我们最好使用代码绘制界面。 …

CCD机器视觉在工业生产中起到什么作用?

CCD机器视觉尺寸测量是基于相对测量方法&#xff0c;通过可追溯性、放大校准、自动边缘提升和屏幕图像测量来计算实际尺寸。在精密测量中&#xff0c;放大倍数必须达到35倍或更高&#xff0c;才能达到微米级的精度。此时&#xff0c;视线宽度小于5mm。对于大于5mm的物体&#x…

游戏提示找不到steam_api64.dll,无法继续执行代码的解决方法

在我们日常沉浸在电脑世界中&#xff0c;尽情享受各类电子游戏带来的精彩与刺激时&#xff0c;偶尔会遭遇一些令人困扰的技术问题。这次&#xff0c;当您正全神贯注地启动心仪的游戏&#xff0c;期待着新一局冒险或竞技的开始&#xff0c;电脑屏幕上却冷不防地弹出一条警示信息…

--每周分享--

分享内容&#xff1a; 1.单链表的归并排序 2.一道有趣的思考题 分享细节&#xff1a; 单链表的归并排序 主要思想&#xff1a;递归 怎么理解&#xff1f;下面具体说明&#xff1a; 1.首先&#xff0c;我从整体的思考步骤说明&#xff1a;先分区&#xff0c;再排序&#…

游标的定义和类型

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 游标的基本概念 游标从字面上理解为游动的光标&#xff0c;可以使用 Excel 表格来想象游标的作用&#xff0c;游标指向每一行&#xff0c;通过游标访问每行数据。 在 Orac…

二维相位解包理论算法和软件【全文翻译- 菲林(Flynn)最小不连续性方法(4.5)】

4.5 菲林最小不连续性方法 在迄今为止对路径跟踪算法的讨论中,我们忽略了一种非常自然的方法,现在我们将对其进行描述。如果我们仔细观察图 4.42(a)中包裹相位数据中的条纹图案,就会发现 "条纹线 "或最亮像素和最暗像素之间的边界标志着从 0 到 2π 的过渡,它们…

【LAMMPS学习】八、基础知识(1.5) LAMMPS 库接口

8. 基础知识 此部分描述了如何使用 LAMMPS 为用户和开发人员执行各种任务。术语表页面还列出了 MD 术语&#xff0c;以及相应 LAMMPS 手册页的链接。 LAMMPS 源代码分发的 examples 目录中包含的示例输入脚本以及示例脚本页面上突出显示的示例输入脚本还展示了如何设置和运行各…

react17中配置webpack:使用@代表src目录

在vue的项目中可以使用表示src目录&#xff0c;使用该符号表示绝对路径&#xff0c;那么在react中想要使用怎么办呢&#xff1f; 在react中使用表示src目录是需要在webpack中配置的&#xff0c;在核心模块node_modules-》react-scripts-》config-》webpack.config.js中搜索找到…