介绍鸿蒙高频知识点,持续更新中
一、鸿蒙代码结构
- ├──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
需要在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对象
- @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)
复制代码
需要将图片添加到下面目录:/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
如果长度超过容器高度,就会滚动
- 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
构建如下不可滚动网格示例
- 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
使用体系自带的样式:不带图片
- 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装饰器修饰的函数
- 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进度条
- @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文件
- Video({
- src: $rawfile('videoTest.mp4'),
- previewUri: $r('app.media.icon'),
- })
复制代码 效果图如下
2、加载网络视频
src换成网络视频即可,并且添加网络权限。
需要注意的是:
1、现在我使用鸿蒙模仿器对网络视频的加载体验并不好
2、网络加载器点击播放的时间需要一段下载时间,最好加上loading
- Video({
- src: "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",
- previewUri: $r('app.media.icon'),
- })
- .objectFit(ImageFit.Contain)
复制代码 3、自界说Video
- 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方法
鸿蒙页面使用如下
- 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种启动模式
如果应用进程中该类型的UIAbility实例已经存在,则复用体系中的UIAbility实例.
在module.json5设置文件中的"launchType"字段设置为"singleton"即可。
- {
- "module": {
- // ...
- "abilities": [
- {
- "launchType": "singleton",
- // ...
- }
- ]
- }
- }
复制代码
每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。
- "launchType": "standard",
复制代码
针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)
- "launchType": "specified",
复制代码 2、UIAbility组件生命周期
UIAbility的生命周期包罗Create、Foreground、Background、Destroy四个状态
需要注意的是:UIAbility没有WindowStageCreate、WindowStageDestroy,这两个是WindowStage的生命周期。
UIAbility实例创建完成之后,在进入Foreground之前,体系会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI界面加载、设置WindowStage的变乱订阅。
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值
页面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
- 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
- @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
- 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来控制自界说弹窗的表现和隐蔽。
弹窗组件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驱动。
- 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-鸿蒙体系项目结构分析
鸿蒙开辟者学习笔记
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |