鸿蒙基础入门与高频知识点梳理

news2025/4/13 12:37:26

介绍鸿蒙高频知识点,持续更新中

一、鸿蒙代码结构

├──entry/src/main/ets        // 代码区
│  ├──common
│  │  └──Constant.ets        // 常量类
│  ├──entryability            
│  │  └──EntryAbility.ts     // 程序入口类
│  ├──pages
│  │  ├──MainPage.ets        // 主页入口文件
│  │  └──WebPage.ets         // 抽奖页入口文件
│  └──viewmodel                          
│     └──NavigatorModel.ets  // 导航model
├──entry/src/main/resources  
│  ├──base
│  │  ├──element             // 尺寸、颜色、文字等资源文件存放位置
│  │  ├──media               // 媒体资源存放位置
│  │  └──profile             // 页面配置文件存放位置
│  ├──en_US                  // 国际化英文
│  ├──rawfile                // 本地html代码存放位置 
│  └──zh_CN                  // 国际化中文
└──HttpServerOfWeb           // 服务端代码

二、配置文件

1、module.json5

用于配置UIAbility页面模块信息。

位置:/entry/src/main/module.json5

{
  "module": {
    "name": "entry",//当前Module的名称
    "type": "entry",//Module的类型(entry:应用的主模块, feature:应用的动态特性模块)
    "description": "$string:module_desc",
    "mainElement": "EntryAbility",//标识当前Module的入口UIAbility名称或者ExtensionAbility名称。
    "deviceTypes": [//运行设备
      "phone",
      "tablet"
    ],
    "deliveryWithInstall": true,//标识当前Module是否在用户主动安装的时候安装,表示该Module对应的HAP是否跟随应用一起安装。
    "installationFree": false,//是否支持免安装特性
    "pages": "$profile:main_pages",//页面配置文件json
    "abilities": [//UIAbility的配置信息
      {
        "name": "EntryAbility",//当前UIAbility组件的名称,该名称在整个应用要唯一
        "srcEntry": "./ets/entryability/EntryAbility.ts",//入口UIAbility的路径
        "description": "$string:EntryAbility_desc",
        "icon": "$media:icon",//app图标
        "label": "$string:EntryAbility_label",//app
        "startWindowIcon": "$media:icon",//当前UIAbility组件启动页面图标(暂时没发现有啥用,与上面保持一致即可)
        "startWindowBackground": "$color:start_window_background",
        "exported": true,//当前UIAbility组件是否可以被其他应用调用
        "skills": [//能够接收的Want的特征集
          {
            "entities": [
              "entity.system.home"
            ],
            "actions": [
              "action.system.home"
            ]
          }
        ]
      }
    ]
  }
}

2、main_pages.json

页面列表json,对应上面module.json5的pages字段。

位置:/entry/src/main/resources/base/profile/main_pages.json

{
  "src": [
    "pages/SecondPage",
    "pages/SimpleVideoPlay",
    "pages/Index"
  ]
}

3、build-profile.json5

定制HAP多目标构建产物。

位置:entry/build-profile.json5

{
  "apiType": 'stageMode',
  "buildOption": {
  },
  "targets": [
    {
      "name": "default",
      "runtimeOS": "HarmonyOS"
    },
    {
      "name": "ohosTest",
    }
  ]
}

例如,以ArkTS Stage模型为例,定义一个免费版和付费版,示例如下。参考资料

{
  "apiType": 'stageMode',
  "buildOption": {
  },
  "targets": [
    {
      "name": "default"  //默认target名称default,未定义deviceType,默认支持config.json或module.json5中定义的设备类型
    },
    {
      "name": "free", //免费版target名称
      "config": {
        "deviceType": [  //定义free支持的设备类型为Tablet
          "tablet"
        ]
      }
    },
    {
      "name": "pay",//付费版target名称
      "config": {
        "deviceType": [  //定义pay支持的设备类型为Tablet
          "tablet"
        ]
      }
    }
  ]
}

4、oh-package.json5

描述项目基础信息

位置:entry/oh-package.json5

{
  "name": "entry",
  "version": "1.0.0",
  "description": "Please describe the basic information.",
  "main": "",
  "author": "",
  "license": "",
  "dependencies": {}
}

三、组件

1、Image

image-20231123184752283
  • 网络图片

需要在module.json5 文件中添加网络访问权限

"module": {
  "requestPermissions": [
    {"name": "ohos.permission.INTERNET"}
  ]
}
Image('https://gitcode.net/liuxingyuzaixian/csdn_img/-/raw/main/pictures/2023/11/17_10_51_42_image-20230518181509168.png')
  .width(78)
  .height(78)
  .objectFit(ImageFit.Cover)//设置缩放类型
  • PixelMap 图片

代码生成的色块图片,需要创建PixelMap对象

@State myPixelmap?: PixelMap = null

onPageShow() {
  // 创建PixelMap图片
  const color = new ArrayBuffer(56);
  let opts = { editable: true, pixelFormat: 3, size: { height: 4, width: 6 } }
  image.createPixelMap(color, opts, (err, pixelmap) => {
    if (pixelmap != undefined) {
      this.myPixelmap = pixelmap;
    }
  })
}

// 使用
if (this.myPixelmap != null)
  Image(this.myPixelmap).width(78).height(78)
  • Resource 图片

需要将图片添加到下面目录:/resources/base/media

// 使用
Image($r('app.media.icon')).width(78).height(78)

2、Text

Text($r('app.string.module_desc'))
  .fontSize(50)
  .fontWeight(FontWeight.Bold)
  .fontColor(0xFF0000)
  .maxLines(1)
  .textOverflow({ overflow: TextOverflow.Ellipsis })//单行...
  .decoration({ type: TextDecorationType.Underline, color: Color.Black })//文本装饰线

1、数字默认单位: vp

2、vp :屏幕密度相关像素

3、sp:文本推荐

3、TextInput

单行文本输入

TextInput({ placeholder: "账号" })
  .maxLength(11)
  .type(InputType.Number)
  .onChange((value: string) => {

  })

4、Button

Button("登录", { type: ButtonType.Capsule })
  .onClick(() => {

  })

5、Column、Row

用法语 flutter 一样,仅仅多了space参数方便添加间距

Column({ space: 10 }) {
  Text("asdf")
  Text("asdf")
}.alignItems(HorizontalAlign.Start)

6、List

Screenshot_2023-11-24T134447

如果长度超过容器高度,就会滚动

private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

List({ space: 10 }) {
  ForEach(this.arr, (item: number) => {
    ListItem() {
      Text(`${item}`)
        .width('100%')
        .height(100)
        .fontSize(20)
        .fontColor(Color.White)
        .textAlign(TextAlign.Center)
        .borderRadius(10)
        .backgroundColor(0x007DFF)
    }
  }, item => item)
}
.height('100%')

7、Grid

构建如下不可滚动网格示例

image-20231124114208959
Grid() {
  ForEach(this.arr, (item: string) => {
    GridItem() {
      Text(item)
        .fontSize(16)
        .fontColor(Color.White)
        .backgroundColor(0x007DFF)
        .width('100%')
        .height('100%')
        .textAlign(TextAlign.Center)
    }
  }, item => item)
}
.columnsTemplate('1fr 2fr 1fr 1fr') // 设置当前网格布局列的数量。
.rowsTemplate('1fr 2fr 1fr 1fr') // 设置当前网格布局行的数量。
.columnsGap(10) // 设置列与列的间距。
.rowsGap(10) // 设置行与行的间距。
.height(300)

如果需要垂直方向滚动,则关闭掉rowsTemplate即可,如下:

Grid() {
  ForEach(this.arr, (item: string) => {
    GridItem() {
      Text(item)
        .fontSize(16)
        .fontColor(Color.White)
        .backgroundColor(0x007DFF)
        .width(50)
        .height(50)
        .textAlign(TextAlign.Center)
    }
  }, item => item)
}
.direction(Direction.Ltr)
.columnsTemplate('1fr 2fr 1fr 1fr') // 设置当前网格布局列的数量。
// .rowsTemplate('1fr 2fr 1fr 1fr') // 设置当前网格布局行的数量。
.columnsGap(10) // 设置列与列的间距。
.rowsGap(10) // 设置行与行的间距。
.height(300)
.onScrollIndex((first: number) => {
  console.info('first:' + first)
})

8、Tabs

使用系统自带的样式:不带图片

Kapture 2023-11-24 at 14.04.14
private controller: TabsController = new TabsController()

Column() {
  Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
    TabContent() {
      Column().width('100%').height('100%').backgroundColor(Color.Green)
    }
    .tabBar('首页')

    TabContent() {
      Column().width('100%').height('100%').backgroundColor(Color.Blue)
    }
    .tabBar('我的')
  }
  .barMode(BarMode.Fixed)//页签比较多的时候,可以设置滑动页签Scrollable
  .barWidth('100%') // 设置TabBar宽度
  .barHeight(60) // 设置TabBar高度
  .width('100%') // 设置Tabs组件宽度
  .height('100%') // 设置Tabs组件高度
  .backgroundColor(0xF5F5F5) // 设置Tabs组件背景颜色
  .vertical(false)//注意:这个表示底部的 tab 排列方向(与页面与 tab 的排列方向刚好相反)
}
.width('100%')
.height('100%')

自定义样式:带图片。tabBar组件支持@Builder装饰器修饰的函数

image-20231124141238336
struct Index {
  @State currentIndex: number = 0;
  private tabsController: TabsController = new TabsController();

  @Builder TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
    Column() {
      Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
        .size({ width: 25, height: 25 })
      Text(title)
        .fontColor(this.currentIndex === targetIndex ? '#1698CE' : '#6B6B6B')
    }
    .width('100%')
    .height(50)
    .justifyContent(FlexAlign.Center)
    .onClick(() => {
      this.currentIndex = targetIndex;
      this.tabsController.changeIndex(this.currentIndex);
    })
  }

  build() {
    Tabs({ barPosition: BarPosition.End, controller: this.tabsController }) {
      TabContent() {
        Column().width('100%').height('100%').backgroundColor('#00CB87')
      }
      .tabBar(this.TabBuilder('首页', 0, $r('app.media.icon'), $r('app.media.test')))

      TabContent() {
        Column().width('100%').height('100%').backgroundColor('#007DFF')
      }
      .c(this.TabBuilder('我的', 1, $r('app.media.icon'), $r('app.media.test')))
    }
    .barWidth('100%')
    .barHeight(50)
    .onChange((index: number) => {
      this.currentIndex = index;
    })
  }
}

9、Swiper

Swiper() {
  Image($r('app.media.video_list0'))
    .borderRadius(12).objectFit(ImageFit.Contain)
  Image($r('app.media.video_list0'))
    .borderRadius(12).objectFit(ImageFit.Contain)
  Image($r('app.media.video_list0'))
    .borderRadius(12).objectFit(ImageFit.Contain)
}
.autoPlay(true)

10、Slider进度条

image-20231127105246602
@State slidingProgress: number = 0;

// 样式 1
Slider({
  value: this.slidingProgress,
  style: SliderStyle.InSet,
})
  .onChange((value: number, mode: SliderChangeMode) => {
    this.slidingProgress = Math.floor(value);
  })
// 样式 2
Slider({
  value: this.slidingProgress,
  style: SliderStyle.OutSet,
})
  .onChange((value: number, mode: SliderChangeMode) => {
    this.slidingProgress = Math.floor(value);
  })

11、Video

1、加载本地

需要先在rawfile中添加videoTest.mp4文件

image-20231128165455140
Video({
  src: $rawfile('videoTest.mp4'),
  previewUri: $r('app.media.icon'),
})

效果图如下

Kapture 2023-11-28 at 16.56.29

2、加载网络视频

src换成网络视频即可,并且添加网络权限。

需要注意的是:

1、目前我使用鸿蒙模拟器对网络视频的加载体验并不好

2、网络加载器点击播放的时候需要一段下载时间,最好加上loading

Video({
  src: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
  previewUri: $r('app.media.icon'),
})
  .objectFit(ImageFit.Contain)
image-20231128174112323

3、自定义Video

Kapture 2023-11-29 at 10.44.23
Button("dianji").onClick(()=>{
  router.pushUrl({
    url: 'pages/SimpleVideoPlay',
    params: { source: $rawfile('videoTest.mp4') }//添加视频资源
  });
})

自定义Video页面SimpleVideoPlay.ets

需要额外不上这些icon
ic_back.png
ic_pause.png
ic_play.png
ic_public_play.png

import router from '@ohos.router';
import { VideoPlayer } from './VideoPlayer';

/**
 * 自定义Video页面
 */
@Entry
@Component
struct Play {
  private source: string = (router.getParams() as Record<string, Object>).source as string;
  private startIconResource: Resource = $r('app.media.ic_public_play');
  private backIconResource: Resource = $r('app.media.ic_back');
  @Provide isPlay: boolean = false;
  @Provide isOpacity: boolean = false;
  controller: VideoController = new VideoController();
  @Provide isLoading: boolean = false;
  @Provide progressVal: number = 0;
  @Provide flag: boolean = false;

  aboutToAppear() {
    this.source;
  }

  onPageHide() {
    this.controller.pause();
  }

  build() {
    Column() {
      Row() {
        Image(this.backIconResource)
          .width(24)
          .height(24)
          .margin({ left: 24 })
          .onClick(() => {
            router.back();
          })
        Text('返回')
          .fontColor(Color.White)
          .fontSize(24)
          .fontWeight(500)
          .margin({ left: 12 })
      }
      .width('100%')
      .margin({
        left: 12,
        top: 12
      })
      .justifyContent(FlexAlign.Start)

      Stack() {
        if (!this.isPlay && !this.isLoading) {
          Image(this.startIconResource)
            .width(50)
            .height(50)
            .zIndex(2)
        }
        if (this.isLoading) {
          Progress({
            value: 0,
            total: 100,
            type: ProgressType.ScaleRing
          })
            .color(Color.Grey)
            .value(this.progressVal)
            .width(80)
            .style({
              strokeWidth: 15,
              scaleCount: 15,
              scaleWidth: 5
            })
            .zIndex(1)
        }
        VideoPlayer({
          source: this.source,
          controller: this.controller
        })
          .zIndex(0)
      }
    }
    .height('100%')
    .backgroundColor(Color.Black)
  }
}

滑块VideoPlaySlider.ets

/**
 * video slider component
 */
@Component
export struct VideoSlider {
  @Consume isOpacity: boolean;
  private controller: VideoController = new VideoController();
  @Consume currentStringTime: string;
  @Consume currentTime: number;
  @Consume durationTime: number;
  @Consume durationStringTime: string;
  @Consume isPlay: boolean;
  @Consume flag: boolean;
  @Consume isLoading: boolean;
  @Consume progressVal: number;

  build() {
    Row({ space: 12 }) {
      Image(this.isPlay ? $r('app.media.ic_pause') : $r('app.media.ic_play'))
        .width(24)
        .height(24)
        .margin({ left: 12 })
        .onClick(() => {
          this.iconOnclick();
        })
      Text(this.currentStringTime)
        .fontSize(16)
        .fontColor(Color.White)
        .margin({ left: 12 })
      Slider({
        value: this.currentTime,
        min: 0,
        max: this.durationTime,
        step: 1,
        style: SliderStyle.OutSet
      })
        .blockColor("#FFFFFF")
        .width('46.7%')
        .trackColor(Color.Gray)
        .selectedColor("#FFFFFF")
        .showSteps(true)
        .showTips(true)
        .trackThickness(this.isOpacity ? 2 : 4)
        .onChange((value: number, mode: SliderChangeMode) => {
          this.sliderOnchange(value, mode);
        })
      Text(this.durationStringTime)
        .fontSize(16)
        .margin({ right: 12 })
        .fontColor(Color.White)
    }
    .opacity(this.isOpacity ? Number.parseFloat('0.2') : 1)
    .width('100%')
    .alignItems(VerticalAlign.Center)
    .justifyContent(FlexAlign.Center)
  }

  /**
   * icon onclick callback
   */
  iconOnclick() {
    if (this.isPlay === true) {
      this.controller.pause()
      this.isPlay = false;
      this.isOpacity = false;
      return;
    }
    if (this.flag === true) {
      this.controller.start();
      this.isPlay = true;
      this.isOpacity = true;
    } else {
      this.isLoading = true;
      // The video loading is not complete. The loading action is displayed.
      let intervalLoading = setInterval(() => {
        if (this.progressVal >= 100) {
          this.progressVal = 0;
        } else {
          this.progressVal += 10;
        }
      }, 100)
      // The scheduled task determines whether the video loading is complete.
      let intervalFlag = setInterval(() => {
        if (this.flag === true) {
          this.controller.start();
          this.isPlay = true;
          this.isOpacity = true;
          this.isLoading = false;
          clearInterval(intervalFlag);
          clearInterval(intervalLoading);
        }
      }, 100);
    }
  }

  /**
   * video slider component onchange callback
   */
  sliderOnchange(value: number, mode: SliderChangeMode) {
    this.currentTime = Number.parseInt(value.toString());
    this.controller.setCurrentTime(Number.parseInt(value.toString()), SeekMode.Accurate);
    if (mode === SliderChangeMode.Begin || mode === SliderChangeMode.Moving) {
      this.isOpacity = false;
    }
    if (mode === SliderChangeMode.End) {
      this.isOpacity = true;
    }
  }
}

Video组件封装VideoPlayer.ets

import prompt from '@ohos.promptAction';
import { VideoSlider } from './VideoPlaySlider';
export function changeSliderTime(value: number): string {
  let second: number = value % 60;
  let min: number = Number.parseInt((value / 60).toString());
  let head = min < 10 ? `${'0'}${min}` : min;
  let end = second < 10 ? `${'0'}${second}` : second;
  let nowTime = `${head}${':'}${end}`;
  return nowTime;
}

/**
 * video controller component
 */
@Component
export struct VideoPlayer {
  private source: string | Resource = '';
  private controller: VideoController = new VideoController();
  private previewUris: Resource = $r('app.media.icon');
  @Provide currentTime: number = 0;
  @Provide durationTime: number = 0;
  @Provide durationStringTime: string = '00:00';
  @Provide currentStringTime: string = '00:00';
  @Consume isPlay: boolean;
  @Consume isOpacity: boolean;
  @Consume flag: boolean;
  @Consume isLoading: boolean;
  @Consume progressVal: number;

  build() {
    Column() {
      Video({
        src: this.source,
        previewUri: this.previewUris,
        controller: this.controller
      })
        .width('100%')
        .height('88%')
        .controls(false)
        .autoPlay(false)
        .objectFit(ImageFit.Contain)
        .loop(false)
        .onUpdate((event) => {
          if (event) {
            this.currentTime = event.time;
            this.currentStringTime = changeSliderTime(this.currentTime);
          }
        })
        .onPrepared((event) => {
          this.prepared(event?.duration);
        })
        .onFinish(() => {
          this.finish();
        })
        .onError(() => {
          prompt.showToast({
            duration: 5000,
            message: '请检查网络'
          });
        })
      VideoSlider({ controller: this.controller })
    }
  }

  /**
   * video component prepared callback
   */
  prepared(duration: number) {
    this.durationTime = duration;
    let second: number = duration % 60;
    let min: number = Number.parseInt((duration / 60).toString());
    let head = min < 10 ? `${'0'}${min}` : min;
    let end = second < 10 ? `${'0'}${second}` : second;
    this.durationStringTime = `${head}${':'}${end}`;
    this.flag = true;
  }

  /**
   * video component finish callback
   */
  finish() {
    this.isPlay = false;
    this.isOpacity = false;
  }
}

12、Web

1、Web组件使用

struct Index {
  controller: WebController = new WebController();

  build() {
    Column() {
      // 加载网页
      Web({ src: 'https://developer.harmonyos.com/', controller: this.controller })
      // 加载本地html
      // Web({ src: $rawfile('index.html'), controller: this.controller })
    }
  }
}

2、Web与js交互

下面示例中:

1、打开App,html回调confirm方法

2、点击按钮,app调用html的test方法

Kapture 2023-12-02 at 18.56.17

鸿蒙页面使用如下

struct Index {
  controller: WebController = new WebController();

  build() {
    Column() {
      // 鸿蒙调用html的方法
      Button("鸿蒙按钮").onClick(() => {
        this.controller.runJavaScript({
          script: 'test()',
          callback: (result: string) => {
            prompt.showToast({
              duration: 5000,
              message: result
            });
          } });
      })
      Web({ src: $rawfile('index.html'), controller: this.controller })
        .javaScriptAccess(true)
        // 鸿蒙对外方法
        .onConfirm((event) => {
          AlertDialog.show({
            title: 'title',
            message: event.message,
            confirm: {
              value: 'onAlert',
              action: () => {
                event.result.handleConfirm();
              }
            },
            cancel: () => {
              event.result.handleCancel();
            }
          })
          return true;
        })
        // 输出js的日志
        .onConsole((event) => {
          console.log('getMessage:' + event.message.getMessage());
          console.log('getMessageLevel:' + event.message.getMessageLevel());
          return false;
        })
    }
  }
}

html使用如下

<!DOCTYPE html>
<html>
<meta charset="utf-8">
<body>
</body>
<script type="text/javascript">
  <!--js回调鸿蒙的方法-->
  confirm("confirm message from html")
  <!--js对外方法-->
  function test() {
      return "This value is from index.html"
  }

</script>
</html>

四、鸿蒙api

1、UIAbility启动模式

UIAbility当前支持singleton(单实例模式)、multiton(多实例模式)和specified(指定实例模式)3种启动模式

  • singleton(单实例模式)
img

如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例.

在module.json5配置文件中的"launchType"字段配置为"singleton"即可。

{
  "module": {
    // ...
    "abilities": [
      {
        "launchType": "singleton",
        // ...
      }
    ]
  }
}
  • standard(标准实例模式)
img

每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。

 "launchType": "standard",
  • specified(指定实例模式)
img

针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)

"launchType": "specified",

2、UIAbility组件生命周期

UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态

Ability-Life-Cycle

需要注意的是:UIAbility没有WindowStageCreate、WindowStageDestroy,这两个是WindowStage的生命周期。

UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI界面加载、设置WindowStage的事件订阅。

img

3、toast

import prompt from '@ohos.promptAction';

Button("点击toast").onClick(() => {
  prompt.showToast({
    duration: 5000,
    message: '点击toast'
  });
})

4、Preferences存储

注意:初始化需要await,并且需要context参数,建议在EntryAbility的onCreate方法中

await sharePreferenceUtil.init(this.context);
import dataPreferences from '@ohos.data.preferences';

const KEY_APP_FONT_SIZE = 'appFontSize';
/**
 * SP工具类
 */
export class SharePreferenceUtil {
  preferences: dataPreferences.Preferences;

  // 初始化(注意:初始化是异步方法,需要await)
  async init(context: Context) {
    this.preferences = await dataPreferences.getPreferences(context, 'myPreferences');
  }

  // 存储
  saveDefaultFontSize(fontSize: number) {
    this.preferences.has(KEY_APP_FONT_SIZE).then(async (isExist: boolean) => {
      if (!isExist) {
        await this.preferences.put(KEY_APP_FONT_SIZE, fontSize);
        this.preferences.flush();
      }
    }).catch((err: Error) => {
    });
  }

  // 更新
  async saveChangeFontSize(fontSize: number) {
    await this.preferences.put(KEY_APP_FONT_SIZE, fontSize);
    this.preferences.flush();
  }

  // 获取
  async getChangeFontSize() {
    let fontSize: number = 0;
    fontSize = await this.preferences.get(KEY_APP_FONT_SIZE, fontSize) as number;
    return fontSize;
  }

  // 删除
  async deleteChangeFontSize() {
    let deleteValue = this.preferences.delete(KEY_APP_FONT_SIZE);
    deleteValue.then(() => {
    }).catch((err: Error) => {
    });
  }
}

const sharePreferenceUtil = new SharePreferenceUtil();

export default sharePreferenceUtil;

五、状态管理与数据同步

1、组件状态管理装饰器和@Builder装饰器:

组件状态管理装饰器用来管理组件中的状态,它们分别是:@State、@Prop、@Link。

  • @State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
  • @Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但更改不会通知给父组件,即@Prop属于单向数据绑定。
  • @Link装饰的变量可以和父组件的@State变量建立双向数据绑定,需要注意的是:@Link变量不能在组件内部进行初始化。
  • @Builder装饰的方法用于定义组件的声明式UI描述,在一个自定义组件内快速生成多个布局内容。

组件内的状态管理:@State
从父组件单向同步状态:@Prop
与父组件双向同步状态:@Link
监听状态变化:@Watch
跨组件层级双向同步状态:@Provide和@Consume

1、父页面同步数据给子页面:@Prop

2、子页面同步数据给父页面:@Link

下面示例中

1、父组件把clickIndex通过 Props 传递给子页面

2、点击子组件后,通过 Link 把修改后的clickIndex值传递给页面

3、其余子组件 Watch 了clickIndex,并同时修改组件中的isExpanded值

Kapture 2023-12-03 at 10.04.34

页面Index.ets

import TestItem from './TestItem';

@Entry
@Component
struct Index {
  private arr: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
  @State clickIndex: number = -1;

  build() {
    Column() {
      ForEach(this.arr, (item: number, index: number) => {
        TestItem({
          index: this.arr[index], //@Prop传递给子组件数据
          clickIndex: $clickIndex, //@Link双向绑定数据
        })
      }, item => item)
    }
    .width('100%')
    .height('100%')
  }
}

组件TestItem.ets

@Component
export default struct TestItem {
  @Prop index: number; //当前 item 序号
  @State isExpanded: boolean = false; //当前是否展开
  // @Link修饰是为了同步数据到父组件,@Watch是为了监听回调给onClickIndexChanged
  @Link @Watch('onClickIndexChanged') clickIndex: number; //点击的序号

  onClickIndexChanged() {
    this.isExpanded = this.clickIndex == this.index;
  }

  build() {
    Button(this.index + '、是否展开:' + this.isExpanded)
      .width('100%')
      .height(this.isExpanded ? 80 : 40)
      .fontSize(20)
      .fontColor(Color.White)
      .borderRadius(10)
      .backgroundColor(0x007DFF)
      .margin({ top: 10 })
      .onClick(() => {
        this.clickIndex = this.index;
      })
  }
}

2、子组件callback 回调父页面

子组件声明callback 方法

// 组件
@Component
export default struct TestItem {
  callback?: (index: number) => void;

  build() {
    Button('子组件')
      .width('100%')
      .height(40)
      .fontSize(20)
      .fontColor(Color.White)
      .borderRadius(10)
      .backgroundColor(0x007DFF)
      .onClick(() => {
        // this.clickIndex = this.index;
        if (this.callback !== undefined) {
          this.callback(123)
        }
      })
  }
}

父页面传入callback方法

TestItem({
  callback: (index:number): void => {
    console.warn("index:",index)
  }
})

六、弹窗

1、警告弹窗AlertDialog

img
AlertDialog.show(
  {
    title: '删除联系人', // 标题
    message: '是否需要删除所选联系人?', // 内容
    autoCancel: false, // 点击遮障层时,是否关闭弹窗。
    alignment: DialogAlignment.Bottom, // 弹窗在竖直方向的对齐方式
    offset: { dx: 0, dy: -20 }, // 弹窗相对alignment位置的偏移量
    primaryButton: {
      value: '取消',
      action: () => {
      }
    },
    secondaryButton: {
      value: '删除',
      fontColor: '#D94838',
      action: () => {
      }
    },
    cancel: () => { // 点击遮障层关闭dialog时的回调
    }
  }
)

2、文本选择弹窗TextPickerDialog

img
@State select: number = 2;
private fruits: string[] = ['苹果', '橘子', '香蕉', '猕猴桃', '西瓜'];

TextPickerDialog.show({
  range: this.fruits, // 设置文本选择器的选择范围
  selected: this.select, // 设置初始选中项的索引值。
  onAccept: (value: TextPickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调。
    // 设置select为按下确定按钮时候的选中项index,这样当弹窗再次弹出时显示选中的是上一次确定的选项
    this.select = value.index;
    console.info("TextPickerDialog:onAccept()" + JSON.stringify(value));
  },
  onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调。
    console.info("TextPickerDialog:onCancel()");
  },
  onChange: (value: TextPickerResult) => { // 滑动弹窗中的选择器使当前选中项改变时触发该回调。
    console.info("TextPickerDialog:onChange()" + JSON.stringify(value));
  }
})

3、日期滑动选择弹窗DatePickerDialog

image-20231129200521350
selectedDate: Date = new Date("2010-1-1")

DatePickerDialog.show({
  start: new Date("1900-1-1"), // 设置选择器的起始日期
  end: new Date("2023-12-31"), // 设置选择器的结束日期
  selected: this.selectedDate, // 设置当前选中的日期
  lunar: false,
  onAccept: (value: DatePickerResult) => { // 点击弹窗中的“确定”按钮时触发该回调
    // 通过Date的setFullYear方法设置按下确定按钮时的日期,这样当弹窗再次弹出时显示选中的是上一次确定的日期
    this.selectedDate.setFullYear(value.year, value.month, value.day)
    console.info("DatePickerDialog:onAccept()" + JSON.stringify(value))
  },
  onCancel: () => { // 点击弹窗中的“取消”按钮时触发该回调
    console.info("DatePickerDialog:onCancel()")
  },
  onChange: (value: DatePickerResult) => { // 滑动弹窗中的滑动选择器使当前选中项改变时触发该回调
    console.info("DatePickerDialog:onChange()" + JSON.stringify(value))
  }
})

4、自定义弹窗

通过装饰器@CustomDialog定义的组件来实现,然后结合CustomDialogController来控制自定义弹窗的显示和隐藏。

image-20231127102353922

弹窗组件AddTargetDialog.ets绘制

@CustomDialog
export default struct AddTargetDialog {
  @State subtaskName: string = '';
  private controller?: CustomDialogController;
  onClickOk?: (value: string) => void;

  build() {
    Column() {
      Text('添加子目标')
        .width('100%')
        .fontSize('20fp')
        .fontWeight(500)
        .fontColor('#182431')
        .textAlign(TextAlign.Start)
      TextInput({ placeholder: '请输入子目标名称'})
        .placeholderColor(Color.Grey)
        .placeholderFont({ size: '16fp'})
        .caretColor(Color.Blue)
        .backgroundColor('#0D182431')
        .width('100%')
        .height('40%')
        .margin({ top: '6%' })
        .fontSize('16fp')
        .fontColor("#182431")
        .onChange((value: string) => {
          this.subtaskName = value;
        })
      Blank()
      Row() {
        Button('取消')
          .dialogButtonStyle()
          .onClick(() => {
            this.controller?.close();
          })
        Divider()
          .vertical(true)
        Button('确定')
          .dialogButtonStyle()
          .onClick(() => {
            if (this.onClickOk !== undefined) {
              this.onClickOk(this.subtaskName);
            }
          })
      }
      .width('70%')
      .height('10%')
      .justifyContent(FlexAlign.SpaceBetween)
    }
    .padding('24vp')
    .height('168vp')
    .width('90.3%')
    .borderRadius(32)
    .backgroundColor(Color.White)
  }
}

/**
 * Custom button style.
 */
@Extend(Button) function dialogButtonStyle() {
  .fontSize('16fp')
  .height('32vp')
  .width('96vp')
  .backgroundColor(Color.White)
  .fontColor('#007DFF')
}

页面使用

@Entry
@Component
struct Index {
  dialogController: CustomDialogController = new CustomDialogController({
    builder: AddTargetDialog({
      onClickOk: (value: string): void => {
        console.warn("value:",value)
        this.dialogController.close();// 关闭
      }
    }),
    alignment: DialogAlignment.Bottom,
    offset: {
      dx: 0,
      dy: '-16vp'
    },
    customStyle: true,
    autoCancel: false
  });

  build() {
    Button("点击打开弹窗").onClick(()=>{
      this.dialogController.open()// 打开
    })
  }
}

七、动画

添加animation属性就好,由State驱动。

Kapture 2023-11-30 at 20.19.59
struct Index {
  @State iconWidth: number = 30;

  onPageShow() {
    this.iconWidth = 90;
  }

  build() {
    Column() {
      Image($r('app.media.icon'))
        .width(this.iconWidth)
        .margin(10)
        .objectFit(ImageFit.Contain)
        .animation({
          duration: 2000,
          tempo: 3.0, //动画的播放速度
          delay: 0,
          curve: Curve.Linear,
          playMode: PlayMode.Normal,
          iterations: -1, //播放次数,默认一次,设置为-1时表示无限次播放。
        })
    }
  }
}

八、网络请求

注意:多个请求可以使用同一个httpRequest对象,httpRequest对象不能复用,因为它支持request、destroy、on和off方法,例如取消网络请求httpRequest.destroy();

import http from '@ohos.net.http';

let httpRequest = http.createHttp();
let promise = httpRequest.request(
  "http://www.baidu.com",
  {
    // 请求方式
    method: http.RequestMethod.POST,
    // 请求的额外数据。
    extraData: {
      "param1": "value1",
      "param2": "value2",
    },
    // 可选,默认为60s
    connectTimeout: 60000,
    // 可选,默认为60s
    readTimeout: 60000,
    // 开发者根据自身业务需要添加header字段
    header: {
      'Content-Type': 'application/json'
    }
  });
promise.then((data) => {
  if (data.responseCode === http.ResponseCode.OK) {
    console.info('Result:' + data.result);
    console.info('code:' + data.responseCode);
  }
}).catch((err) => {
  console.info('error:' + JSON.stringify(err));
});

九、路由

在如下目录下注册页面

/entry/src/main/resources/base/profile/main_pages.json

跳转代码

import router from '@ohos.router';

router.pushUrl({
  url: 'pages/SecondPage',
  params: {
    src: 'Index页面传来的数据',
  }
}, router.RouterMode.Single)

鸿蒙参考资料

鸿蒙第一课视频,对应代码Codelabs

完整版的功能demo

官方文档

HarmonyOS点石成金

鸿蒙系统系列教程6-鸿蒙系统项目结构解析

鸿蒙开发者学习笔记

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

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

相关文章

学习-java多线程面试题

为什么不建议用Executors启动线程池 *队列LinkedBlockingQueue是没有边界的队列,请求多会造成OOM *建议使用ThreadPoolExecutors 线程池中提交一个任务的流程&#xff1f; 1先判断线程池是否有线程&#xff0c;如果与就直接执行&#xff0c;没有就放队列 2如果队列满了&#…

【ArcGIS Pro微课1000例】0041:Pro强大的定位搜索功能、定位窗格、地图上查找地点

一谈到搜索,你是不是还停留在矢量数据的属性表中呢?今天给大家介绍ArcGIS Pro中定位搜索强大功能的使用,可以基于在线地图、矢量数据等多种数据源,进行地址、地名、道路、坐标等的查找。 文章目录 一、定位工具介绍二、在线地图搜索三、本地矢量数据搜索四、无地图搜索五、…

Makefile初学之谜之隐式规则

刚开始学习Make教程&#xff1a;https://makefiletutorial.vercel.app/#/docs/fancy-rules&#xff0c;里面有个sample: objects foo.o bar.o all.o all: $(objects)# These files compile via implicit rules foo.o: foo.c bar.o: bar.c all.o: all.call.c:echo "int…

分布式事务有哪些解决方案?

本文我们来讨论下分布式事务的相关知识点。 分布式事务是分布式系统中非常重要的一部分&#xff0c;最典型的例子是银行转账和扣款&#xff0c;A 和 B 的账户信息在不同的服务器上&#xff0c;A 给 B 转账 100 元&#xff0c;要完成这个操作&#xff0c;需要两个步骤&#xff0…

java蚁群算法的物流管理系统eclipse定制开发mysql数据库BS模式java编程百度地图

一、源码特点 java 基于蚁群算法的物流管理系统是一套完善的web设计系统 &#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0&a…

Debian下载安装教程

目录 一.前言二.下载三.安装 一.前言 这篇文章展示如何使用VMware Workstation Player安装Debian12虚拟机。 二.下载 官网地址&#xff1a;官网 进入官网之后可以直接点击下载Debian选项&#xff0c;这样下载的是最新版的网络安装镜像。 三.安装 使用VMware Workstation P…

听GPT 讲Rust源代码--src/tools(5)

File: rust/src/tools/rust-analyzer/crates/hir-ty/src/mir/lower.rs 在Rust源代码中&#xff0c;lower.rs文件位于Rust Analyzer项目的hir-ty子库中&#xff0c;其目的是将高级中间表示&#xff08;HIR&#xff09;降低为中间表示&#xff08;MIR&#xff09;。下面对文件及其…

一、Zookeeper基本知识

目录 1、ZooKeeper概述 2、ZooKeeper特性 3、ZooKeeper集群角色 ​​​​​​​1、ZooKeeper概述 Zookeeper是一个分布式协调服务的开源框架。主要用来解决分布式集群中应用系统的一致性问题。 ZooKeeper本质上是一个分布式的小文件存储系统。提供基于类似于文件系统的目录…

3D模型材质编辑

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 如今&#xff0c;3D 纹理、打印和建模都非常流行。使用可用的高级工具&#xff0c;创建 3D 模型…

vscode插件离线下载

离线下载插件地址&#xff1a;https://marketplace.visualstudio.com/VSCode

SmartSoftHelp8,json格式化,校验工具

json格式化&#xff0c;校验工具 json 校验 json 格式化 本地校验 本地格式化 不需要联网 下载地址&#xff1a; https://pan.baidu.com/s/1zBgeYsqWnSlNgiKPR2lUYg?pwd8888​​​​​​​

前后端数据传输格式(上)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 作为后端&#xff0c;写…

矩阵代数与MATLAB实现(特征值、广义特征值、酋矩阵、奇异值、托普利兹矩阵、汉克尔矩阵、范德蒙矩阵、)

矩阵代数的相关知识 目录 一、特征值与特征向量 1、特征值与特征向量 2、MATLAB计算 二、广义特征值与广义特征向量 1、广义特征值与广义特征向量 2、MATLAB计算 三、酋矩阵 1、酋矩阵 2、MATLAB计算 四、矩阵的奇异值分解 1、奇异值 2、MATLAB计算 五、托普利兹矩…

Spring事务管理介绍

文章目录 Spring事务管理1 Spring事务简介【重点】问题导入1.1 Spring事务作用1.2 需求和分析1.3 代码实现【前置工作】环境准备【第一步】在业务层接口上添加Spring事务管理【第二步】设置事务管理器(将事务管理器添加到IOC容器中)【第三步】开启注解式事务驱动【第四步】运行…

阿里系列-淘宝接口抓取及相关问题

阿里系-淘宝接口抓取 一、安装charlse抓包工具 官方下载地址 安装证书 二、安装xposed hook框架 Xponsed简介 具体安装步骤 三、安装模块 关闭阿里系ssl验证 开启http模式 支持支付宝、淘宝、淘宝直播各个接口抓取 四、效果如下 接下去一段时间更新阿里系相关接口 文章目录 一、…

WIN10 WIN11 关闭更新的绝佳办法(极简单无副作用)

WIN10 WIN11 关闭更新的绝佳办法&#xff08;极简单无副作用&#xff09; 极其简单用实用可以关闭更新20年 winr&#xff0c;输入regedit 打开注册表打开注册表的这个路径&#xff1a; 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\WindowsUpdate\UX\Settings 右键空白的地方…

Redis 入门、基础。(五种基本类型使用场景)

文章目录 1. 概况1.1 认识 NoSQL1.1.1 查询方式1.1.2 事务1.1.3 总结 2. 认识 Redis4. Redis 常见命令4.1 Redis 数据结构介绍4.2 Redis 通用命令4.3 Redis 命令之 String 命令4.4 Redis 命令的层级结构4.5 Redis 命令之 Hash 命令4.6 Redis 命令之 List 命令4.7 set 唯一不排序…

蓝桥杯第1037题子串分值和 C++ 字符串 逆向思维 巧解

题目 思路和解题方法 方案一——遍历哈希表 仅能过60%样例,大多数同学都用的该方法&#xff0c;就不过多赘述 #include <iostream> #include <unordered_map> using namespace std; int main() {string s;cin >> s;int n s.size();int res n;for (int i 0…

字符指针变量数组指针变量

字符指针变量 在指针的类型中&#xff0c;我们知道有一种指针叫做字符指针 一般的使用情况如下&#xff1a; #include<stdio.h> int main() {char ch w;char* pa &ch;*pa h;printf("%c", *pa);return 0; } 还有一种使用方法如下&#xff1a; #incl…

CRM在设备制造行业的应用,优化资源配置

设备制造业竞争激烈&#xff0c;公司要以客户为中心&#xff0c;搞好售后服务。CRM管理软件是设备制造业客户关系管理的重要工具。以下是CRM在设备制造业里的典型应用。 1.营销管理 制订市场策略&#xff1a;设备制造通常涉及较长的决策周期和销售周期。客户可能会在多家供货商…