介绍鸿蒙高频知识点,持续更新中
一、鸿蒙代码结构
├──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 图片
代码生成的色块图片,需要创建PixelMap对象
@State myPixelmap?: PixelMap = nullonPageShow() {// 创建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
如果长度超过容器高度,就会滚动
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种启动模式
- singleton(单实例模式)
如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例.
在module.json5配置文件中的"launchType"字段配置为"singleton"即可。
{"module": {// ..."abilities": [{"launchType": "singleton",// ...}]}
}
- standard(标准实例模式)
每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。
"launchType": "standard",
- specified(指定实例模式)
针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)
"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",},// 可选,默认为60sconnectTimeout: 60000,// 可选,默认为60sreadTimeout: 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-鸿蒙系统项目结构解析
鸿蒙开发者学习笔记