# Harmony **Repository Path**: erictor_admin/harmony ## Basic Information - **Project Name**: Harmony - **Description**: 我的Harmony学习之路 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 3 - **Created**: 2023-12-11 - **Last Updated**: 2025-04-17 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Harmonyos 官方网站:https://developer.harmonyos.com/cn ## 1.TpyeScript语言 官方网站:https://www.typescriptlang.org/ 在线演练场:https://www.typescriptlang.org/zh/play ### 1.1 变量声明 ![image-20231129100249369](./doc/images/image-20231129100249369.png) ``` //string 字符串 let msg: string = 'hello world!' // number: 数值,整数、浮点数都可以 let age: number = 21 // boolean:布尔 let finished: boolean = true //any: 不确定类型,可以是任意类型 let a:any = 'jack' a= 21 // union: 联合类型,可以是多个指定类型中的一种 let u: string|number|boolean = 'rose' u= 18 // Object: 对象 let p = {name: 'Jack' , age: 21} console.log(p.name) console.log(p['name']) //Array:数组,元素可以是任意其它类型 let names: Array = ['Jack',' Rose' ] let ages: number[] = [21, 18] console.log(names[0]) ``` ### 1.2 条件控制 ![image-20231129111429830](./doc/images/image-20231129111429830.png) ![image-20231129111732740](./doc/images/image-20231129111732740.png) ``` //定义数字 let num:number=-22 //·判断是否是偶数 if(num %2 === 0){ console.log(num + '是偶数' ) }else{ console.log(num + '是奇数') } //·判断是否是正数 if(num > 0){ console.log(num + '是正数') }else if(num < 0){ console.log(num+'是负数') } else { console.log(num+'为0') } //判断变量是否有值 let data: any =100; //let data: any if (data){ console.log('数据存在: '+ data) }else{ console.log('数据不存在') } ``` ``` let grade: string ='A' switch (grade) { case'A':{ console.log('优秀') break } case 'B':{ console.log('合格') break } case'C':{ console.log('不合格') break } default: { console.log('非法输入') break } } ``` ### 1.3 循环迭代 ![image-20231129163308153](./doc/images/image-20231129163308153.png) ``` //定义数组 let names: Array = ['Jack','Rose'] //取角标用 in for (let i in names) { console.log(i + ':' + names[i]) } //取值用 of for (let n of names) { console.log(n) } ``` ### 1.4 函数 ![image-20231207093633148](./doc/images/image-20231207093633148.png) ``` //无返回值函数,返回值void可以省路 function sayHello(name: string): void{ console.log('你好,' + name +'!') } sayHello('Jack') //有返回值函数 function sum(x: number, y: number): number { return x + y } let result = sum(21, 18) console.log('21 + 18 =' + result) //箭头函数 let sayHi = (name: string) =>{ console.log('你好, ' + name +'!') } sayHi(' Rose') ``` ``` //可选参数,在参数名后加 ?,表示该参数是可选的 function sayHello(name?: string){ //判断name是否有值,如果无值则给一个默认值 name = name ? name : '陌生人' console.log('你好,' + name +'!') } sayHello('Jack') sayHello() //参数默认值,在参数后面赋值,表示参数默认值 //如果调用者没有传参,则使用默认值 function sayHello(name: string ='陌生人'){ console.log('你好,' + name +'!') } sayHello('Jack') sayHello() ``` ### 1.5 类和接口 ![image-20231207100209660](./doc/images/image-20231207100209660.png) ``` //定义枚举 enum Msg{ HI = 'Hi' HELLO = 'Hello' } // 定义接口 interface A { say(msg: Msg): void } // 定义类,实现接口 class B implements A { say(msg: Msg): void{ console.log(msg +',I am B') } } // 初始化对象 let a: A = new B() // 调用方法,传递枚举参数 a.say(Msg.HI) ``` ``` //定义矩形类 class Rectangle { // 成员变量 private width: number private length: number // 构造函数 constructor(width: number, length: number) { this.width = width this.length = length } //成员方法 public area(): number{ return this.width * this.length } } // 定义正方形 class Square extends Rectangle{ constructor(side: number) { // 调用父类构造 super(side, side) } } let s = new Square(10) console.log('正方形面积为:'+ s.area()) ``` ### 1.6 模块 ![image-20231207105817981](./doc/images/image-20231207105817981.png) ``` // 定义矩形类,并通过export导出 export class Rectangle { //成员变量 public width: number public length: number //构造函数 constructor(width: number, length: number) { this.width = width this.length = length } } // 定义工具方法,求矩形面积,并通过export导出 export function area(rec: Rectangle): number { return rec.width * rec.length } //通过import语法导入,from后面写文件的地址 import {Rectangle, area} from '·/rectangle' // 创建Rectangle对象 let r = new Rectangle(10, 20) // 调用area方法 console.log('面积为: '+ area(r)) ``` ### 1.7 数组 ![image-20240520094623092](./doc/images/image-20240520094623092.png) ### 1.8 语句 #### if ``` 语法: if(条件1){ 满足条件1代码 }else if(条件2){ 满足条件2代码 }else{ 不满足条件代码 } ``` #### switch ``` switch(表达式){ case 值1: {与值1匹配执行的语句 break} case 值2: {与值2匹配执行的语句 break} default: {以上都未成功匹配执行的代码} ==== let grade: string ='A' switch (grade) { case'A':{ console.log('优秀') break } case 'B':{ console.log('合格') break } case'C':{ console.log('不合格') break } default: { console.log('非法输入') break } } ``` #### 三元条件表达式 ``` 条件?成立表达式:不成立表达式 ``` #### for ``` for(初始值;条件;变化量){ 循环体 } //for of 遍历数值 let songs: string[] = ['aa', 'bb', 'cc'] for (let songsElement of songs) { console.log(songsElement) } ``` #### while ``` while(条件){ 成立表达式 } ``` #### class ``` class Person { name: string constructor(name: string) { this.name = name } sayHi(name: string) { console.log(`Hi,${name},I am:${this.name}`) } } let p: Person = new Person('eric') p.sayHi('yy') ``` ## 2.ArkTS组件 ![image-20231207135425611](./doc/images/image-20231207135425611.png) ![image-20231207140041925](./doc/images/image-20231207140041925.png) ### 2.1 元素组件 #### 1.图片Image ![image-20231207140628847](./doc/images/image-20231207140628847.png) [安装鸿蒙手机模拟器.md](./doc/安装鸿蒙手机模拟器.md) ``` //网络模式,需要申请网络权限 Image('https://shopstatic.vivo.com.cn/vivoshop/commodity/58/10009058_1699426428678_750x750.png.webp') //本地模式 Image($r('app.media.x100pro')) .width(350) //.aspectRatio(1)//长宽等比例 .interpolation(ImageInterpolation.High)//插值,消除图片毛刺 ``` #### 2.文本Text ![image-20231207163034935](./doc/images/image-20231207163034935.png) ``` Text(this.message) .fontSize(20) .fontWeight(FontWeight.Bold) .fontColor('#36D') ``` #### 3.文本输入TextInput ![](./doc/images/image-20231208094257159.png) ``` TextInput({text: this.imageWidth.toFixed(0)}) //默认值 .width(150) .backgroundColor('#ffc2d0bc') //.type(InputType.Password)//密码 .type(InputType.Number) ``` #### 4.按钮Button ![image-20231208105230801](./doc/images/image-20231208105230801.png) ``` Button('缩小') .width(80) .fontSize(20) .onClick(() => { if(this.imageWidth >= 10){ this.imageWidth -= 10 } }) Button('放大') .width(80) .fontSize(20) .type(ButtonType.Normal) .onClick(() => { if(this.imageWidth <= 350){ this.imageWidth += 10 } }) ``` #### 5.滑动条Slider ![](./doc/images/image-20231208111126926.png) ``` Slider({ min: 100, max: 350, value: this.imageWidth, step: 10 }) .width('90%') .blockColor('#36d') .trackThickness(8) .showTips(true) .onChange(value => { this.imageWidth = value }) ``` #### 6.轮播Swiper ``` Swiper() { Image($r('app.media.pg')) Image($r('app.media.hw')) Image($r('app.media.xm')) } .width("100%") .aspectRatio(1.5)//图片比例 .loop(true) //开启循环 .autoPlay(true) //开启自动播放 .interval(4000) //播放时间 .vertical(false) //纵向 //定制小圆点 .indicator( Indicator.dot() .itemWidth(40) .itemHeight(20) .color(Color.Black) .selectedItemWidth(20) .selectedItemHeight(20) .selectedColor(Color.Brown) ) ``` #### 7.层叠组件Stack ``` Stack() { Image($r('app.media.background')).width(300) Image($r('app.media.background')).width(300) Image($r('app.media.background')).width(300) } ``` #### 8.分组布局Grid ``` Grid() { ForEach(this.arr, (item: imageItem, index) => { GridItem() { Image(item.image).width(60) //Text(item.image) } //.backgroundColor('#ffec3232') }) } .columnsTemplate('1fr 1fr 1fr 1fr ')//列数 .rowsTemplate('1fr 1fr 1fr 1fr ')//行数 .columnsGap(5) .rowsGap(5) .width('100%') .height('100%') ``` #### 9.可滑动的布局List ![image-20231211103253461](./doc/images/image-20231211103253461.png) [2-4-list.ets](./entry/src/main/ets/pages/2-4-list.ets) ``` // 滚动条对象,作用: 每次添加信息之后,滚动到最底部 scroller: Scroller = new Scroller() //函数中调用 fun(){ this.scroller.scrollEdge(Edge.Bottom) //当前用户发送完成消息后,滚动到底部 } List({space: 8,scroller: this.scroller }){ //绑定自动滚动 ForEach( this.items, (item: ItemList) =>{ ListItem(){ Row({space:10}) { Image(item.image) .width(130) Column({ space: 8 }) { if(item.discount){ Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('原价:¥' + item.price) .fontColor('#CCC') .decoration({type: TextDecorationType.LineThrough})//中划线 .fontSize(16) Text('折扣价:¥' + (item.price - item.discount )) .fontColor('#F36') .fontSize(18) Text('补贴:¥' + item.discount) .fontColor('#F36') .fontSize(18) }else{ Text(item.name) .fontSize(20) .fontWeight(FontWeight.Bold) Text('¥' + item.price) .fontColor('#F36') .fontSize(18) } } .height('100%') //.margin({right: 20}) // .alignItems(HorizontalAlign.Start) } .width('100%') .height(130) .backgroundColor('#ffa1ae9c') .borderRadius(20) .padding(10)//内边距 //.margin({right: 10})//外边距 //.justifyContent(FlexAlign.SpaceBetween) } } ) } .width('100%') .padding(20) .layoutWeight(1) //滑倒底部加载 .onReachEnd(() => { this.isListReachEnd = true }) .parallelGesture(PanGesture({ direction: PanDirection.Up, distance: 80 }) .onActionStart(() => { //检测上滑操作 if (this.isListReachEnd) { let count = this.arr.length let new_id = count + 1 this.arr.push(new Article(new_id, '第' + new_id + '篇文章', '是时候准备好应对一个40℃极端高温的世界了')) this.isListReachEnd = false } })) ``` #### 10.进度条Progress ``` Stack(){ //堆叠容器 //环进度条 Progress({ value: this.totalTask, total: this.totalTask, type: ProgressType.Ring }) .width(100) //.color(Color.Grey).value(0) //.style({ strokeWidth: 10, scaleCount: 20, scaleWidth: 22 }) Row(){ Text(this.finishTask.toString()) .fontSize(24) .fontColor('#36D') Text(' / ' + this.totalTask.toString()) .fontSize(24) } // xxx.ets @Entry @Component struct ProgressExample { build() { Column({ space: 15 }) { Text('Linear Progress').fontSize(9).fontColor(0xCCCCCC).width('90%') Progress({ value: 10, type: ProgressType.Linear }).width(200) Progress({ value: 20, total: 150, type: ProgressType.Linear }).color(Color.Grey).value(50).width(200) Text('Eclipse Progress').fontSize(9).fontColor(0xCCCCCC).width('90%') Row({ space: 40 }) { Progress({ value: 10, type: ProgressType.Eclipse }).width(100) Progress({ value: 20, total: 150, type: ProgressType.Eclipse }).color(Color.Grey).value(50).width(100) } Text('ScaleRing Progress').fontSize(9).fontColor(0xCCCCCC).width('90%') Row({ space: 40 }) { Progress({ value: 10, type: ProgressType.ScaleRing }).width(100) Progress({ value: 20, total: 150, type: ProgressType.ScaleRing }) .color(Color.Grey).value(50).width(100) .style({ strokeWidth: 15, scaleCount: 15, scaleWidth: 5 }) } // scaleCount和scaleWidth效果对比 Row({ space: 40 }) { Progress({ value: 20, total: 150, type: ProgressType.ScaleRing }) .color(Color.Grey).value(50).width(100) .style({ strokeWidth: 20, scaleCount: 20, scaleWidth: 5 }) Progress({ value: 20, total: 150, type: ProgressType.ScaleRing }) .color(Color.Grey).value(50).width(100) .style({ strokeWidth: 20, scaleCount: 30, scaleWidth: 3 }) } Text('Ring Progress').fontSize(9).fontColor(0xCCCCCC).width('90%') Row({ space: 40 }) { Progress({ value: 10, type: ProgressType.Ring }).width(100) Progress({ value: 20, total: 150, type: ProgressType.Ring }) .color(Color.Grey).value(50).width(100) .style({ strokeWidth: 20, scaleCount: 30, scaleWidth: 20 }) } Text('Capsule Progress').fontSize(9).fontColor(0xCCCCCC).width('90%') Row({ space: 40 }) { Progress({ value: 10, type: ProgressType.Capsule }).width(100).height(50) Progress({ value: 20, total: 150, type: ProgressType.Capsule }) .color(Color.Grey) .value(50) .width(100) .height(50) } }.width('100%').margin({ top: 30 }) } } ``` #### 11.选择框Checkbox&CheckboxGroup ``` CheckboxGroup({group:'all'})//全选 Checkbox({name:'按钮1',group:'all'}) .select(this.name) .onChange(val => { this.name = val }) Checkbox({name:'按钮2',group:'all'}) .select(this.name) .onChange(val => { this.name = val }) ``` #### 12.加载进度条LoadingProgress() ``` LoadingProgress() ``` #### 13.弹框CustomDialog ``` @CustomDialog struct TaskInfoDialog { name: string = '' onTaskConfirm: (name: string) => void controller: CustomDialogController build() { Column({ space: 20 }) { TextInput({ placeholder: '输入任务名' }) .onChange(val => this.name = val) Row() { Button('确定') .onClick(() => { this.onTaskConfirm(this.name) }) Button('取消') .backgroundColor(Color.Grey) .onClick(() => { this.controller.close() }) } .width('100%') .justifyContent(FlexAlign.SpaceEvenly) } .width('100%') .padding(20) } } ``` 使用 ``` // 任务信息弹窗 dialogController: CustomDialogController = new CustomDialogController({ builder: TaskInfoDialog({ onTaskConfirm: this.handleAddTask.bind(this) }), }) this.dialogController.open() this.dialogController.close() ``` 可视弹窗 ``` .onClick(() => { AlertDialog.show({ message: msg }) }) ``` 弹层 ``` import prompt from '@system.prompt' ··· prompt.showToast({ message: '用户名或为空密码' }) ``` #### 14.弹性布局Flex ``` @Entry @Component struct FlexTest { build() { Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center, }) { Text().width('20%').height(60).backgroundColor(Color.Red) Text().width('20%').height(80).backgroundColor(Color.Yellow) Text().width('20%').height(100).backgroundColor(Color.Blue) } .width('90%') .height(200) .padding(10) .backgroundColor(Color.Gray) } } ``` #### 15.栅格布局(GridRow/GridCol) ``` @Entry @Component struct GridRowTest { bgColors: Color[] = [ Color.White, Color.Black, Color.Blue, Color.Brown, Color.Gray, Color.Green, Color.Grey, Color.Orange, Color.Pink, Color.Red, Color.Yellow, Color.Transparent, Color.Orange ] build() { Column() { //指定固定列数 GridRow({ columns: 8, direction: GridRowDirection.RowReverse, gutter: { x: 10, y: 20 } }) { ForEach(this.bgColors, (item: string, index) => { GridCol({ span: 2 }) { Text(index + 1 + '') }.width('100%').backgroundColor(item).border({ width: 2, color: Color.Black }) //.height('100%') }) } .height(300) .backgroundColor(Color.Brown) Divider().strokeWidth(10) //指定区间 GridRow({ columns: { sm: 4, md: 8 } }) { ForEach(this.bgColors, (item: string, index) => { GridCol({ offset: 2 }) { Text(index + 1 + '').backgroundColor(item) } .width('100%') .backgroundColor(item) .border({ width: 2, color: Color.Black }) .width('100%') //.height('100%') }) } .height(300) .backgroundColor(Color.Gray) GridRow() { GridCol({ span: 12 }) { GridRow() { GridCol({ span: 2 }) { Text('left').textAlign(TextAlign.Center) .width('100%') .height('100%') } .height('100%') .backgroundColor(Color.Orange) GridCol({ span: 10 }) { Text('right').textAlign(TextAlign.Center) .width('100%') .height('100%') } .height('100%') .backgroundColor(Color.Brown) } } .height('90%') GridCol({ span: 12 }) { Text('footer') .textAlign(TextAlign.Center) .width('100%') .height('100%') } .width('100%') .height('10%') .backgroundColor(Color.Yellow) } .width('100%') .height(300) .backgroundColor(Color.Gray) } } } ``` #### 16.组件导航Tabs ``` class TabBarItem { title: string img: Resource color: Color context: ResourceStr } @Entry @Component struct TabsTest { @State tabBarList: TabBarItem[] = [ { title: '微信', img: $r('app.media.ic_xiaoxi'), color: Color.Green, context: $r('app.media.wx_wx') }, { title: '通讯录', img: $r('app.media.ic_tongxunlu'), color: Color.Green, context: $r('app.media.wx_txl') }, { title: '发现', img: $r('app.media.ic_faxian'), color: Color.Green, context: $r('app.media.wx_faxian') }, { title: '我的', img: $r('app.media.ic_wode'), color: Color.Green, context: $r('app.media.wx_wode') }, ] @State selectIndex: number = 0 build() { Tabs({ barPosition: BarPosition.End }) { ForEach(this.tabBarList, (item: TabBarItem, index) => { TabContent() { Image(item.context) } .tabBar(this.MyTabBar(item, index)) }) } .onChange((value) => { this.selectIndex = value }) .backgroundColor('#191919') } @Builder MyTabBar(item: TabBarItem, index: number) { Column({ space: 5 }) { Image(item.img) .width(25) .aspectRatio(1) .fillColor(this.selectIndex === index ? Color.Green : Color.White) Text(item.title).fontSize(14).fontColor(Color.White) .fontColor(this.selectIndex === index ? Color.Green : Color.White) } } } @Builder function MyTabBar(item: TabBarItem, index: number) { Column({ space: 5 }) { Image(item.img) .width(25) .aspectRatio(1) Text(item.title).fontSize(14).fontColor(Color.White) } } ``` #### 17.组件导航-Navigation ``` import promptAction from '@ohos.promptAction' @Entry @Component struct NavigationTest { build() { Column() { Navigation() { //内容 TextInput({ placeholder: '搜索笔记' }).width('90%').margin(10) List() { ForEach([1, 1, 1, 1, 1, 1, 1], (item: string, index) => { ListItem() { Text('笔记' + (index+1)) .width('100%') .textAlign(TextAlign.Center) } .width('90%') .height(70) .backgroundColor(Color.White) .margin(10) .borderRadius(10) }) }.margin({bottom:60}) } .title('全部笔记') //标题 .titleMode(NavigationTitleMode.Full) //菜单栏 .menus([ { value: '新增', icon: './images/new.png', action: () => { promptAction.showToast({ message: '新增' }) } }, { value: '历史', icon: './images/history.png' }, { value: '收藏', icon: './images/collect.png' }, ]) //工具栏 .toolBar({ items: [ { value: '笔记', icon: './images/todo.png', action: () => { promptAction.showToast({ message: '笔记' }) } }, { value: '收藏', icon: './images/collect.png', action: () => { promptAction.showToast({ message: '收藏' }) } }, ] }) //.hideTitleBar(true)//隐藏标题栏 //.hideToolBar(true)//隐藏工具栏 } .padding(5) .width('100%') .height('100%') .backgroundColor('#ffcad1d7') } } ``` #### 18.画布组件Canvas ``` @Entry @Component struct CanvasPage { //抗锯齿设置 private setting: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.setting) //相对路径从ets开始 private myImg = new ImageBitmap("images/support.svg") build() { Column() { /*Canvas(this.context) .width('100%') .height('50%') .backgroundColor(Color.Yellow) .onReady(()=>{ //画矩形,坐标x,y ,长 ,宽 this.context.fillRect(0,0,100,100) //边框矩形 this.context.strokeRect(100,100,100,100) //绘制图片 this.context.drawImage(this.myImg,200,200,300,300) })*/ Canvas(this.context) .width('100%') .height('90%') .backgroundColor(Color.Gray) .onReady(()=>{ //以画布顶点旋转45 //this.context.rotate(45*Math.PI/180) //设置旋转中心 this.context.translate(100,100) //旋转180 //this.context.rotate(180*Math.PI/180) //以回50 //this.context.translate(-50,-50) //绘制图片 this.context.drawImage(this.myImg,0,0) }) } .width('100%') .height('100%') } } ``` #### 19.基础手势gesture ##### 4.1 TapGesture-单/双/多次点击手势 ##### 4.2 LongPresGesture-长按手势 ##### 4.3 PanGesture-拖动手势 ``` import promptAction from '@ohos.promptAction' @Entry @Component struct TapGestureTest { @State buttonWidth: number = 80 @State offsetX: number = 0 @State offsetY: number = 190 @State cnt: number = 0 private panOption: PanGestureOptions = new PanGestureOptions({ direction: PanDirection.Left | PanDirection.Right | PanDirection.Up | PanDirection.Down }) build() { Column() { Column({ space: 10 }) { Text('1.TapGesture-单/双/多次点击手势') Button('双击') .width(this.buttonWidth) .gesture( //检测双击,多次点击 TapGesture({ count: 2 }) .onAction(() => { this.buttonWidth = 160 promptAction.showToast({ message: '双击了屏幕' }) }) ) Divider() Text('2. LongPresGesture-长按手势') Button('长按 ' + this.cnt) .width(this.buttonWidth) .gesture( //检测双击,多次点击 LongPressGesture({ repeat: true }) //长按触发 .onAction(() => { this.cnt += 1 promptAction.showToast({ message: '长按了屏幕' }) }) //松开结束 .onActionEnd(() => this.cnt = 0) ) Divider() Text('3 PanGesture-拖动手势') Image($r('app.media.vivoWATCH3')).width(60).position({ x: this.offsetX, y: this.offsetY }) Button('位置:' + ' X:' + this.offsetX + ' Y:' + this.offsetY) .width(150) //.height(200) .gesture( //检测一个手指向右 PanGesture(this.panOption) //长按触发 .onActionStart((event: GestureEvent) => { promptAction.showToast({ message: '开始' }) }) //拖动操作 .onActionUpdate((event: GestureEvent) => { this.offsetX = event.offsetX this.offsetY = event.offsetY promptAction.showToast({ message: '拖动' }) }) //松开结束 .onActionEnd(() => { this.offsetX = 0 this.offsetY = 190 }) ) Divider() } .padding(15) .borderWidth(3) .width(300) .height('90%') .margin(20) } .width('100%') .height('100%') } } ``` ### 2.2 一些属性 #### 1 .position定位 ``` .position({ x: 10, y: 10 }) ``` #### 2 .animation动画效果 ``` .animation({ duration: 500}) animateTo({ duration: 500 }, () => { this.x = this.x - 20 }) ``` #### 3 .stateStyles状态样式 ``` @Entry @Component struct StateStyleTest { @State isDisable: boolean = true build() { Column({ space: 20 }) { Button('按钮1') .stateStyles({ //获取焦点 focused: { .backgroundColor(Color.Brown) }, //按压 pressed: { .backgroundColor(Color.Red) .onClick(() => { this.isDisable = !this.isDisable }) }, //正常状态 normal: { .backgroundColor(Color.Black) } }) Button('按钮2') .stateStyles({ //获取焦点 focused: { .backgroundColor(Color.Brown) }, //按压 pressed: { .backgroundColor(Color.Red) }, //正常状态 normal: { .backgroundColor(Color.Black) }, //不可用 disabled: { .backgroundColor(Color.Gray) } }) Button('按钮3') .enabled(this.isDisable) .stateStyles({ disabled: { .backgroundColor(Color.Gray) } }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } } ``` #### 4 .direction容器组件排序 ``` .direction(Direction.Ltr) // 容器内:Ltr组件的布局从左到右排列 Rtl从右到左排列 ``` #### 5 .focusable聚焦 ``` .focusable(true)//设置是否可以聚焦 .defaultFocus(true)//一打开默认聚焦 .id('my_input') .onSubmit(() => { if (this.textContent) { this.sendTextMessage(this.textContent) // 发送完以后,设置为空 this.textContent = '' focusControl.requestFocus('my_input') //输入完成也聚焦 } }) ``` #### 6 .gesture手势操作 [绑定手势方法-手势处理-组件通用信息-ArkTS组件-ArkUI(方舟UI框架)-应用框架 | 华为开发者联盟 (huawei.com)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-gesture-settings-V5#gesturetype) ``` .gesture(LongPressGesture().onAction(() => { this.showPopupMenu = true })) ``` #### 7 .bindPopup组件绑定popup弹窗 [Popup控制-通用属性-组件通用信息-ArkTS组件-ArkUI(方舟UI框架)-应用框架 | 华为开发者联盟 (huawei.com)](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/ts-universal-attributes-popup-V5#bindpopup) ``` .bindPopup(this.showPopupMenu, { builder: this.getPopupMenus(), //执行的函数 popupColor: $r('app.color.popup_back'), backgroundBlurStyle: BlurStyle.NONE, onStateChange: (event) => { //当手指点了其他位置,关闭状态 this.showPopupMenu = event.isVisible } }) ``` ### 2.3 排版(justifyContent&alignItems) ![1702014709751](./doc/images/1702014709751.png) ![image-20231208135531434](./doc/images/image-20231208135531434.png) ![image-20231208135824218](./doc/images/image-20231208135824218.png) ![image-20231208135903435](./doc/images/image-20231208135903435.png) ![image-20231208135954397](./doc/images/image-20231208135954397.png) ![](./doc/images/image-20231208143932641.png) [2-2-ImagePage2.ets](./entry/src/main/ets/pages/dome/2-2-ImagePage2.ets) ### 2.4 循环控制ForEach ``` ForEach([1, 2, 3, 4], (item: string, index) => { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) }) ``` ![](./doc/images/image-20231208145654747.png) ![image-20231211100717888](./doc/images/image-20231211100717888.png) [2-3-itemPage.ets](./entry/src/main/ets/pages/2-3-itemPage.ets) ### 2.5 自定义组件Component ![image-20231211142408433](./doc/images/image-20231211142408433.png) 自定义组件 [2-5-customComponents.ets](..\MyApplication\entry\src\main\ets\componets\2-5-customComponents.ets) ``` //导入自定义组件 import {Header} from '../componets/2-5-customComponents' ``` ### 2.6 自定义构建函数@Builder [2-6-customFunc.ets](./entry/src/main/ets/pages/2-6-customFunc.ets) #### 2.6.1 全局函数 定义函数 ``` // 全局自定义构建函数 @Builder function Funcname(参数: Item){ } ``` 引用函数 ``` ItemCard(item) ``` #### 2.6.2 局部函数 定义函数(不加function) ``` // 全局自定义构建函数 @Builder Funcname(参数: Item){ } ``` 引用函数(内部需要加this.) ``` this.ItemCard(item) ``` 值传递不能引起ui更新。引用传递可以,比如传对象 ``` @Builder function MoreBuilder(obj: { context: string }) { Row() { Text(obj.context).fontSize(12) Image($r('app.media.ic_more')).width(12).aspectRatio(1) } MoreBuilder({ context: `新品 ${this.count}` }) ``` ### 2.7 自定义公共样式 [2-7-customStyles.ets](./entry/src/main/ets/pages/2-7-customStyles.ets) #### @Extend ``` @Extend(Text) function testFN(ftColor: ResourceColor, msg: string) { .fontSize(50) .fontWeight(FontWeight.Bold) .fontColor(ftColor) .onClick(() => { AlertDialog.show({ message: msg }) }) } ``` #### @Styles ``` @Styles function Stylesfn() { .width(100) .height(100) } ``` 定义 ``` //全局自定义公共样式 @Styles function fillScreen(){ .width('100%') .height('100%') .padding(10) } ``` ``` //局部自定义公共样式(没有function) @Styles fillScreen(){ .width('100%') .height('100%') .padding(10) } ``` 引用(全局和局部一样) ``` //调用公共样式 .fillScreen() ``` 自定义继承组件 定义 ``` //自定义继承组件,只能全局 @Extend(Text) function priceText(){ .fontColor('#F36') .fontSize(18) } ``` 引用 ``` //调用继承组件 .priceText() ``` ![image-20231211155533238](./doc/images/image-20231211155533238.png) ![image-20231211155708532](./doc/images/image-20231211155708532.png) ### 2.8. 传递UI装饰器-@BuilderParam ``` @Entry @Component struct BuilderParamTest { build() { Column() { MyComp({ title: '按钮:' }) { Button('按钮').width(200) } MyComp({ title: '图片:' }) { Image($r('app.media.x100pro')).width(80) } MyComp({ title: '默认的:' }) //可传多个Builder MyComp({ title: '传函数:', MyBuilderParam: MyBuilder }) } } } @Builder function MyBuilder() { TextInput({ placeholder: '请输入' }) } @Component struct MyComp { title: string @Builder defaultBuilder() { Text('我是默认值').fontColor(Color.Orange) } @BuilderParam MyBuilderParam: () => void = this.defaultBuilder build() { Row() { Text(this.title) this.MyBuilderParam() } .width('100%') .height(100) .margin(10) .backgroundColor(Color.Gray) } } ``` ## 3.状态管理 ### 3.1 @State ![image-20231212135121245](./doc/images/image-20231212135121245.png) ``` class Person{ name: string age: number gf: Person constructor(name: string, age:number,gf?:Person) { this.name = name this.age = age this.gf = gf } } @Entry @Component struct StatePage2 { //自定义组件,可复用UI单元 @State p :Person = new Person('gwq',22,new Person('zxy',18)) build() { Row() { Column() { Text(`${this.p.name} : ${this.p.age}`) .fontSize(50) .fontWeight(FontWeight.Bold) .fontColor('#36D') .onClick(() => { this.p.age++ }) Text(`${this.p.gf.name} : ${this.p.gf.age}`) .fontSize(50) .fontWeight(FontWeight.Bold) .fontColor('#36D') .onClick(() => { this.p.gf.age++ }) } .width('100%') } .height('100%') } } @Component struct StatePage { //自定义组件,可复用UI单元 @State name: string = 'eric' @State age: number = 25 build() { Row() { Column() { Text(`${this.name} : ${this.age}`) .fontSize(50) .fontWeight(FontWeight.Bold) .fontColor('#36D') .onClick(() => { this.age++ }) } .width('100%') } .height('100%') } } ``` [3-1-State.ets](entry\src\main\ets\pages\3-1-State.ets) ### 3.2 任务案例 案例: [3-2-Prop.ets](entry\src\main\ets\pages\3-2-Prop.ets) ![image-20231212151142467](./doc/images/image-20231212151142467.png) ``` // 任务类 class Task{ static id: number = 1 // 任务名称 name: string = `任务${Task.id++}` // 任务状态: 是否完成 finished: boolean = false } // 统一的卡片样式 @Styles function card(){ .width('95%') .padding(20) .backgroundColor(Color.White) .borderRadius(15) .shadow({radius: 6, color: '#1f000000', offsetX: 2, offsetY: 4}) } // 任务完成样式 @Extend(Text) function finishedTask(){ .decoration({type:TextDecorationType.LineThrough}) .fontColor('#B1B2B1') } @Entry @Component struct PropPage { //自定义组件,可复用UI单元 // 总任务数量 @State totalTask: number = 0 // 已完成任务数量 @State finishTask: number = 0 // 任务数组 @State tasks: Task[] = [] //修改函数 handleTaskChange(){ this.totalTask = this.tasks.length//新增后,修改最大值为数组长度 this.finishTask = this.tasks.filter(item => item.finished).length//更新进度已完成数量,即完成的数组长度 } build() { Column({space: 10}) { // 任务进度卡片 Row(){ Text('任务进度:') .fontSize(30) .fontWeight(FontWeight.Bold)//加粗 Stack(){ //堆叠容器 //环进度条 Progress({ value: this.totalTask, total: this.totalTask, type: ProgressType.Ring }) .width(100) //.color(Color.Grey).value(0) //.style({ strokeWidth: 10, scaleCount: 20, scaleWidth: 22 }) Row(){ Text(this.finishTask.toString()) .fontSize(24) .fontColor('#36D') Text(' / ' + this.totalTask.toString()) .fontSize(24) } } } .card()//调用统一的卡片样式 .margin({top: 20, bottom: 10})//外边距 .justifyContent(FlexAlign.SpaceEvenly)//平均分布 //新增任务按钮 Button('新增任务') .width(200) .onClick(() =>{ this.tasks.push(new Task()) //点击后新增一条值 // this.totalTask = this.tasks.length//新增后,修改最大值为数组长度 this.handleTaskChange()//调用修改函数 }) //任务列表 List({space: 10}){ ForEach( this.tasks, (item: Task, index)=> { ListItem(){ Row(){ Text(item.name) .fontSize(20) Checkbox() .select(item.finished) .onChange(val =>{ item.finished =val //更新任务状态 this.handleTaskChange()//调用修改函数 //this.finishTask = this.tasks.filter(item => item.finished).length//更新进度已完成数量,即完成的数组长度 }) } .card() .justifyContent(FlexAlign.SpaceBetween)//两端对其 } .swipeAction({end:this.DeleteButton(index)}) //左滑出现框 } ) } .width('100%') .layoutWeight(1) // 剩余空间全给我 .alignListItem(ListItemAlign.Center) } .width('100%') .height('100%') .backgroundColor('#ffd7ccd7') } @Builder DeleteButton(index: number){ Button(){ Image($r('app.media.delete')) .fillColor(Color.White) .width(20) } .width(40) .height(40) .type(ButtonType.Circle)//圆形 .backgroundColor(Color.Red) .margin(5) .onClick(() =>{ this.tasks.splice(index,1)//点击删除角标 this.handleTaskChange() }) } } ``` ### 3.3@Prop和@Link装饰器 [3-3-PropLink.ets](entry\src\main\ets\pages\3-3-PropLink.ets) ![image-20231214135111305](./doc/images/image-20231214135111305.png) ### 3.4@Provde和@Consume装饰器 [3-4-PtovideConsume.ets](entry\src\main\ets\pages\dome\3-4-PtovideConsume.ets) ### 3.5@ObjectLink和Observed装饰器 ![image-20231214140243261](./doc/images/image-20231214140243261.png) [3-5-ObjectLinkObserved.ets](entry/src/main/ets/pages/dome/3-5-ObjectLinkObserved.ets) ### 3.6@Watch监听装饰器 ``` import promptAction from '@ohos.promptAction' @Entry @Component struct WatchTest { @State count: number = 0 build() { Column() { Button('加1') .onClick(() => { this.count +=1 }) CompB({ count: this.count }) } } } @Component struct CompB { @Prop @Watch('onChange') count: number onChange(a: string): void { console.log('testTag','调用了吗?') AlertDialog.show({ message: '点我了吗?' + this.count }) promptAction.showToast({ message: '点我了吗?' + this.count }) } build() { } } ``` ## 4.页面路由router ![image-20240109160956687](./doc/images/image-20240109160956687.png) ![image-20240109161407661](./doc/images/image-20240109161407661.png) [Index.ets](entry\src\main\ets\pages\Index.ets) ![image-20240110110319129](./doc/images/image-20240110110319129.png) ``` //去消息 onPageShow() { let acc = router.getParams() as Record if (acc) { this.account = acc['sendMsg'] } } //跳转时放消息 router.pushUrl({ url: "view/MySetting", params:{ sendMsg:'传消息' } }) ``` ## 5.页面动画 ### 5.1 属性动画 ![image-20240110111003877](./doc/images/image-20240110111003877.png) ![image-20240110111049222](./doc/images/image-20240110111049222.png) ### 5.2 显示动画 ![image-20240110144623141](./doc/images/image-20240110144623141.png) ### 5.3 转场动画 ![image-20240110150806848](./doc/images/image-20240110150806848.png) ``` .transition({ type: TransitionType.Insert,//入场 opacity: 0,//透明到有 translate: {x: -250}//外面进来 }) ``` ### 5.4.共享元素转场动画sharedTransition ``` import router from '@ohos.router' @Entry @Component struct SharedTransitionTest1 { build() { Row(){ Column({space:15}){ Image($r("app.media.img2")) .width('100%') .aspectRatio(1) .sharedTransition("img2",{duration:200}) Text('贵州茅台2016年飞天茅台-53度500ml酱香型白酒') .sharedTransition("text",{duration:200}) }.width('50%') .onClick(()=>{ router.pushUrl({ url:'pages/animatetest/SharedTransitionTest2' }) }) } } ``` ``` import router from '@ohos.router' @Entry @Component struct SharedTransitionTest2 { build() { Row(){ Column({space:15}){ Image($r("app.media.img2")) .width('100%') .aspectRatio(1) .sharedTransition("img2",{duration:200}) Text('贵州茅台2016年飞天茅台-53度500ml酱香型白酒') .sharedTransition("text",{duration:200}) }.width('100%') } } } ``` ### 5.5.小鱼游戏案例 案件版: [Animation.ets](entry\src\main\ets\pages\Animation.ets) ``` import router from '@ohos.router' @Entry @Component struct AnimationPage { //小鱼坐标 @State fishX: number = 200 @State fishY: number = 180 //是否开始游戏 @State isBegin: boolean = false //小鱼图片 @State src: Resource = $r('app.media.fishy') //小鱼角度 @State angle: number = 0 build() { Row() { Stack() { //Image($r('app.media.background')) Button('返回') .position({x:0,y:0}) .backgroundColor('#20cdc1c1') .onClick(() => { router.back() }) if(!this.isBegin){ Button('开始游戏') .onClick(() => { animateTo({duration:800},() =>{ this.isBegin = true }) }) }else { //小鱼图片 Image(this.src) .position({x:this.fishX - 40,y: this.fishY -40}) .rotate({angle:this.angle,centerX: '50%',centerY: '50%'}) .width(80) .width(80) .transition({ type: TransitionType.Insert,//入场 opacity: 0,//透明到有 translate: {x: -250},//外面进来 scale: {x:0,y:0}//从小到默认 }) //.animation({ duration:1000 })//全局动画,以上生效 //操作按钮 Row(){ Button('←').backgroundColor('#20e9dede') .onClick(() => { animateTo({duration:500},() =>{ this.fishX -= 20 this.src = $r('app.media.fishz') }) }) Column({space:40}){ Button('↑').backgroundColor('#20eadfdf') .onClick(() => { animateTo({duration:500},() =>{ this.fishY -= 20 }) }) Button('↓').backgroundColor('#20dbd1d1') .onClick(() => { animateTo({duration:500},() =>{ this.fishY += 20 }) }) } Button('→').backgroundColor('#20e0d8d8') .onClick(() => { animateTo({duration:500},() =>{ this.fishX += 20 this.src = $r('app.media.fishy') }) }) } .height(240) .width(240) .position({x:50,y:150}) } } .width('100%') .height('100%') } .width('100%') .height('100%') .backgroundImage($r('app.media.background')) .backgroundImageSize({ height: '105%', width: '100%' }) } } ``` 摇杆版: [Animation2.ets](entry\src\main\ets\pages\Animation2.ets) ``` import router from '@ohos.router' import curves from '@ohos.curves' import Brightness from '@system.brightness' @Entry @Component struct AnimationPage2 { //小鱼坐标 @State fishX: number = 250 @State fishY: number = 200 //是否开始游戏 @State isBegin: boolean = false //小鱼图片 @State src: Resource = $r('app.media.fishy') //小鱼角度 @State angle: number = 0 //摇杆中心区域位置 private centerX: number = 120 private centerY: number = 120 //圆圈半径 private maxRadius: number = 100 private radius: number = 20 //摇杆小圆初始位置 @State positionX: number = this.centerX; @State positionY: number = this.centerY; //角度正弦和余弦 sin: number = 0 cos: number = 0 //小雨一定速度 speed: number = 0 //任务id taskId: number = -1 build() { Row() { Stack() { //Image($r('app.media.background')) Button('返回') .position({ x: 0, y: 0 }) .backgroundColor('#20cdc1c1') .onClick(() => { router.back() }) if (!this.isBegin) { Button('开始游戏') .onClick(() => { animateTo({ duration: 800 }, () => { this.isBegin = true }) }) } else { //小鱼图片 Image(this.src) .position({ x: this.fishX - 40, y: this.fishY - 40 }) .rotate({ angle: this.angle, centerX: '50%', centerY: '50%' }) .width(80) .width(80) .transition({ type: TransitionType.Insert, //入场 opacity: 0, //透明到有 translate: { x: -250 }, //外面进来 scale: { x: 0, y: 0 } //从小到默认 }) //.animation({ duration:1000 })//全局动画,以上生效 } //操作按钮 Row() { Circle({ width: this.maxRadius * 2, height: this.maxRadius * 2 }) .fill('#11ffffff') .position({ x: this.centerX - this.maxRadius, y: this.centerY - this.maxRadius }) Circle({ width: this.radius * 2, height: this.radius * 2 }) .fill('#ff1f1a1a') .position({ x: this.centerX - this.radius, y: this.centerY - this.radius }) } .onTouch(this.handleTouchEvent.bind(this)) .height(240) .width(240) .justifyContent(FlexAlign.Center) .position({ x: 0, y: 120 }) } .height('100%') .width('100%') } .width('100%') .height('100%') .backgroundImage($r('app.media.background')) .backgroundImageSize({ height: '105%', width: '100%' }) } //处理手指移动事件 handleTouchEvent(event: TouchEvent) { switch (event.type) { case TouchType.Up: //还原小鱼速度 this.speed = 0 //取消定时任务 clearInterval() animateTo({ curve: curves.springMotion() }, () => { // 还原摇杆坐标 this.positionX = this.centerX this.positionY = this.centerY this.angle = 0 }) break; case TouchType.Down: //设置定时任务 this.taskId = setInterval(() =>{ this.fishX += this.speed * this.cos this.fishY += this.speed * this.sin },40) break; case TouchType.Move: //1.获取手指位置 let x = event.touches[0].x let y = event.touches[0].y //2.计算手指与中心点的差值 let vx = x - this.centerX let vy = y - this.centerY //3.计算手指与中心连线的和X轴的夹角,单位弧度 let angle = Math.atan2(vy, vx) //4.计算手指与中心点的距离 let distance = this.getDistance(vx, vy) //调用计算距离方法 this.sin = Math.sin(angle) this.cos = Math.cos(angle) animateTo({ curve: curves.responsiveSpringMotion() }, () => { //5.计算摇杆小球的坐标 this.positionX = this.centerX + distance * this.cos this.positionY = this.centerY + distance * this.sin //6.修改小鱼坐标和角度 if(Math.abs(angle * 2) < Math.PI){ this.src = $r('app.media.fishy') }else { this.src = $r('app.media.fishz') angle = angle < 0 ? angle + Math.PI: angle - Math.PI } this.angle = angle * 180 /Math.PI//转换弧度为角度 this.speed = 4 } ) break; } } //计算距离方法 getDistance(x: number, y: number) { let d = Math.sqrt(x * x + y * y) return Math.min(d, this.maxRadius) } } ``` ## 6.Stage模型 ### 6.1 Stage模型概述 ![image-20240115095945644](./doc/images/image-20240115095945644.png) ### 6.2 应用配置文件 [应用配置文件概述(Stage模型)-应用配置文件(Stage模型)-开发基础知识-入门 | 华为开发者联盟 (huawei.com)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/application-configuration-file-overview-stage-0000001428061460-V2) ### 6.3 UIAbility生命周期 ![image-20240116090511095](./doc/images/image-20240115111747503.png) ### 6.4 页面及组件生命周期 ![image-20240116091453478](./doc/images/image-20240116091453478.png) ### 6.5 UIAblility的启动模式 ![image-20240116105544835](./doc/images/image-20240116105544835.png) ![image-20240116105630930](./doc/images/image-20240116105630930.png) ## 7.网络连接 [网络管理开发概述-网络管理-网络与连接-开发 | 华为开发者联盟 (huawei.com)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/net-mgmt-overview-0000001478341009-V2) ![image-20240117135412539](./doc/images/image-20240117135412539.png) ### 7.1 http 数据请求 ![image-20240117140255016](./doc/images/image-20240117140255016.png) 准备模拟服务端 [shopServer](shopServer) 启动: ``` cd .\shopServer\ PS D:\code\Harmony\MyApplication\shopServer> npm install PS D:\code\Harmony\MyApplication\shopServer> npm start > shopserver@0.0.0 start > node ./bin/www ``` 访问:[localhost:3000/shops?pageNo=1&pageSize=2](http://localhost:3000/shops?pageNo=1&pageSize=2) ![image-20240119103535991](./doc/images/image-20240117141633550.png) 安装第三方库 [ohpm使用指导-命令行工具-DevEco Studio使用指南-工具 | 华为开发者联盟 (huawei.com)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/ide-command-line-ohpm-0000001490235312-V2) ![image-20240119153300544](./doc/images/image-20240119153300544.png) [OpenHarmony三方库中心仓](https://ohpm.openharmony.cn/#/cn/home) ![image-20240119154617571](./doc/images/image-20240119154308546.png) ``` import http from '@ohos.net.http' import promptAction from '@ohos.promptAction' import axios, { AxiosResponse } from '@ohos/axios' @Entry @Component struct HttpTestPage { @State message: string = 'Hello World' url:string='http://525bcda5.r20.cpolar.top/persons' build() { Column() { Text(this.message) .fontSize(18) Button('获取数据') .onClick(async () => { let response: AxiosResponse = await axios.get('https://apis.tianapi.com/fanyi/index?key=0a7083e6db3b7f824099f793abb06869&text=hallo') let src: string = this.message let dst: string = response.data.result.dst let from: string = response.data.result.from let to: string = response.data.result.to this.message = `${from},${to},${dst}` /*const response = http.createHttp() response.request('http://525bcda5.r20.cpolar.top/persons') .then(response => { const data = response.result.toString() promptAction.showToast({ message: data }) this.message = data }) .catch(err => { console.log('err:', err) })*/ }) } .width('100%') .height('100%') } } ``` ## 8.数据持久化 ### 8.1 用户首选项 ![image-20240119155545985](./doc/images/image-20240119155545985.png) ![image-20240119160043570](./doc/images/image-20240119160043570.png) ### 8.2 关系型数据库 ![image-20240612141833717](./doc/images/image-20240612141833717.png) ![image-20240612093611842](./doc/images/image-20240612093611842.png) ![image-20240612093912685](./doc/images/image-20240612093912685.png) ``` import relationalStore from '@ohos.data.relationalStore'; import TaskInfo from '../viewmodel/TaskInfo'; class TaskModel { private rdbStore: relationalStore.RdbStore private tableName: string = 'TASK' //初始化数据 initTaskDB(context) { const config = { name: 'my.db', securityLevel: relationalStore.SecurityLevel.S1 } const sql = `CREATE TABLE IF NOT EXISTS TASK (ID INTEGER PRIMARY KEY,NAME TEXT NOT NULL,FINISHED bit)` relationalStore.getRdbStore(context, config, (err, rdbStore) => { if (err) { console.log('testTag', '初始化失败') return } rdbStore.executeSql(sql) console.log('testTag', '初始化成功') this.rdbStore = rdbStore }) } //查询 async getTask() { let predicates = new relationalStore.RdbPredicates(this.tableName) //predicates.equalTo(0),不加条件为全部 let result = await this.rdbStore.query(predicates, ['ID', 'NAME', 'FINISHED']) //定义数组 let tasks: TaskInfo[] = [] //循环添加 while (result.isAtLastRow) { result.goToNextRow() //调至下一行 let id = result.getLong(result.getColumnIndex('ID')) let name = result.getString(result.getColumnIndex('NAME')) let finished = result.getLong(result.getColumnIndex('FINISHED')) //添加到数组 tasks.push({ id, name, finished: !!finished }) //!!两次取反将number转换为bool } return tasks } //新增 addTask(name: string): Promise { return this.rdbStore.insert(this.tableName, { name, finished: false }) } //更新 updateTaskStatus(id: number, finished: boolean) { //更新的数据 let data = { finished } //查询的条件 let predicates = new relationalStore.RdbPredicates(this.tableName) predicates.equalTo('ID', id) //更新后返回 return this.rdbStore.update(data, predicates) } //删除 delTaskDB(id: number) { //查询的条件 let predicates = new relationalStore.RdbPredicates(this.tableName) predicates.equalTo('ID', id) //删除 return this.rdbStore.delete(predicates) } } const taskModel = new TaskModel(); export default taskModel as TaskModel; ``` ## 9.管理应用状态 ### 9.1LocalStorage-页面级UI状态存储 ``` class person { name: string age: number } let storage = new LocalStorage({ age: 10, person: { name: 'zs', age: 5 } }) @Entry(storage) @Component struct LocalStorageTest { //@LocalStorageProp('age')//单项同步 @LocalStorageLink('age') //双向同步 fAge: number = 2 @LocalStorageLink('person') //双向同步 prs: person = { name: 'aa', age: 100 } build() { Column() { Button(`父组件${this.fAge}`) .onClick(() => { this.fAge += 1 }) Button(`父组件对象${this.prs.age}`) .onClick(() => { this.fAge += 1 this.prs.age+=1 }) Comp() }.width('100%') .padding(100) } } @Component struct Comp { @LocalStorageProp('age') sAge: number = 3 @LocalStorageLink('person') //双向同步 prs: person = { name: 'aa', age: 100 } build() { Column() { Text(this.sAge.toString()) Text(this.prs.age.toString()) } } } ``` 页面之间需要配置EntryAbility文件 ``` ... onDestroy() { hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); } //创建LocalStorage storageGlobal = new LocalStorage({ person: { name: 'eric', age: 22 } }) onWindowStageCreate(windowStage: window.WindowStage) { // Main window is created, set main page for this ability hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); //添加this.storageGlobal传递给页面 windowStage.loadContent('pages/Index', this.storageGlobal, (err, data) => { ... ``` ### 9.2.AppStorage-应用全局UI状态存储 ``` import { person } from '../../viewmodel/DataItem' //创建AppStorage AppStorage.SetOrCreate('person', { name: 'eric', age: 23 }) @Entry @Component struct AppStorageTest { @StorageLink('person') person: person = {} build() { Column() { Text(`根组件${this.person.age}`) .onClick(() => { this.person.age += 1 }) Divider() Comp() } } } @Component struct Comp { @StorageLink('person') person: person = {} build() { Column({ space: 10 }) { Text(`子组件${this.person.age}`) .onClick(() => { this.person.age += 1 }) Button('获取AppStorage的值') .onClick(() => { const p1: person = AppStorage.Get('person') AlertDialog.show({ message: p1.name }) }) Divider() Button('设置AppStorage的值') .onClick(() => { AppStorage.Set('person', { name: 'gwq', age: 2 }) }) Divider() Button('Link方式获取/置AppStorage的值') .onClick(() => { const link: SubscribedAbstractProperty = AppStorage.Link('person') //获取 AlertDialog.show({ message: link.get().name }) //设置 link.set({ name: 'yy', age: 18 }) }) } } } ``` ### 9.3 PersistentStorage-持久化存储UI状态 ``` import { person } from '../viewmodel/DataItem' //简单类型持久化 PersistentStorage.PersistProp('cnt', 10) //对象持久化(转换为string) PersistentStorage.PersistProp('obj', `{"name":"zs","age":10}`) @Entry @Component struct PersistentStoragePage { @StorageLink('cnt') count: number = 0 @StorageLink('obj') obj: string = "" @State person: person = {} build() { Row() { Column() { Text(this.count.toString()) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick(() => { this.count++ }) Text(`obj:${this.obj}`) .onClick(() => { this.person = JSON.parse(this.obj) this.person.age++ AppStorage.Set('obj', JSON.stringify(this.person)) }) } .width('100%') } .height('100%') } } ``` ## 10.通知 ### 10.1 发送通知 ![image-20240124135636815](./doc/images/image-20240124135636815.png) ![image-20240124135926203](./doc/images/image-20240124135926203.png) ![image-20240124143550046](./doc/images/image-20240124143550046.png) ![image-20240124143509204](./doc/images/image-20240124143509204.png) ![image-20240124143409025](./doc/images/image-20240124143409025.png) ![image-20240124143742557](./doc/images/image-20240124143742557.png) ![image-20240124143953830](./doc/images/image-20240124143953830.png) ![image-20240124145745919](./doc/images/image-20240124145745919.png) ![image-20240124154528928](./doc/images/image-20240124154528928.png) ``` import notify from '@ohos.notificationManager' import image from '@ohos.multimedia.image' import DownloadCard from '../views/notification/DownloadCard' import { Header } from '../common/components/CommonComponents' import notificationManager from '@ohos.notificationManager' @Entry @Component struct NotificationPage { // 全局任务id idx: number = 100 // 图象 pixel: PixelMap async aboutToAppear() { // 获取资源管理器 let rm = getContext(this).resourceManager; // 读取图片 let file = await rm.getMediaContent($r('app.media.vivoWATCH3')) // 创建PixelMap image.createImageSource(file.buffer).createPixelMap() .then(value => this.pixel = value) .catch(reason => console.log('testTag', '加载图片异常', JSON.stringify(reason))) } build() { Column({ space: 20 }) { Header({ title: '通知功能' }) Button(`发送normalText通知`) .onClick(() => this.publishNormalTextNotification()) Button(`发送longText通知`) .onClick(() => this.publishLongTextNotification()) Button(`发送multiLine通知`) .onClick(() => this.publishMultiLineNotification()) Button(`发送Picture通知`) .onClick(() => this.publishPictureNotification()) // 下载功能卡片 DownloadCard() //发送 Button('发送通知') .onClick(() => { this.publishNotification() }) //取消 Button('取消通知') .onClick(() => { notificationManager.cancel(this.idx) }) } .width('100%') .height('100%') .padding(5) .backgroundColor('#f1f2f3') } //发送消息 publishNotification() { let myRequest: notificationManager.NotificationRequest = { id: this.idx, content: { contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: '温馨提示', text: '今天要下大学,记得骑车哦' } } } notificationManager.publish(myRequest) console.log('testTag', '发送通知成功') } //发送normalText通知 publishNormalTextNotification() { let request: notify.NotificationRequest = { id: this.idx++, content: { contentType: notify.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: '通知标题' + this.idx, text: '通知内容详情', additionalText: '通知附加内容' } }, showDeliveryTime: true, deliveryTime: new Date().getTime(), groupName: 'wechat', slotType: notify.SlotType.SOCIAL_COMMUNICATION } this.publish(request) } //发送longText通知 publishLongTextNotification() { let request: notify.NotificationRequest = { id: this.idx++, content: { contentType: notify.ContentType.NOTIFICATION_CONTENT_LONG_TEXT, longText: { title: '通知标题' + this.idx, text: '通知内容详情', additionalText: '通知附加内容', longText: '通知中的长文本,我很长,我很长,我很长,我很长,我很长,我很长,我很长', briefText: '通知概要和总结', expandedTitle: '通知展开时的标题' + this.idx } } } this.publish(request) } //发送multiLine通知 publishMultiLineNotification() { let request: notify.NotificationRequest = { id: this.idx++, content: { contentType: notify.ContentType.NOTIFICATION_CONTENT_MULTILINE, multiLine: { title: '通知标题' + this.idx, text: '通知内容详情', additionalText: '通知附加内容', briefText: '通知概要和总结', longTitle: '展开时的标题,我很宽,我很宽,我很宽', lines: [ '第一行', '第二行', '第三行', '第四行', ] } } } this.publish(request) } //发送Picture通知 publishPictureNotification() { let request: notify.NotificationRequest = { id: this.idx++, content: { contentType: notify.ContentType.NOTIFICATION_CONTENT_PICTURE, picture: { title: '通知标题' + this.idx, text: '通知内容详情', additionalText: '通知附加内容', briefText: '通知概要和总结', expandedTitle: '展开后标题' + this.idx, picture: this.pixel } } } this.publish(request) } private publish(request: notify.NotificationRequest) { notify.publish(request) .then(() => console.log('notify test', '发送通知成功')) .then(reason => console.log('notify test', '发送通知失败', JSON.stringify(reason))) } } ``` ### 10.2 跳转应用 ``` async publishNotification() { //准备跳转应用信息 let want = await WantAgent.getWantAgent({ wants: [{ bundleName: 'com.eric.myapplication', abilityName: 'EntryAbility' }], operationType: WantAgent.OperationType.START_ABILITY, requestCode: 0 }) //准备发送消息内容 let myRequest: notificationManager.NotificationRequest = { id: this.idx, content: { contentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, normal: { title: '温馨提示', text: '今天要下大学,记得骑车哦' } }, // 跳转应用的 WantAgent 对象 wantAgent: want } //发送 notificationManager.publish(myRequest) console.log('testTag', '发送通知成功') } ``` ## 11 .后台任务-代理提醒 ## 13.媒体查询 [媒体查询(mediaquery)-构建布局-开发布局-基于ArkTS的声明式开发范式-UI开发-开发 | 华为开发者联盟 (huawei.com)](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-layout-development-media-query-0000001454445606-V2) ![image-20240219154922909](./doc/images/image-20240219154922909.png) ## 14.数据类型 ![image-20240221150802730](./doc/images/image-20240221150802730.png) ## 15.设置避让模式 在EntryAbility.ets中添加代码: ``` windowStage.getMainWindowSync().getUIContext().setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE) //默认OFFSET=0表示:上移,RESIZE = 1表示压缩 ``` ## 问题汇总: ### 1.下面目录一般放那类文件,有规定,还是自己定义的分类。 在鸿蒙(HarmonyOS)端云一体化项目中,目录的命名和组织方式通常是基于项目需求、开发习惯以及团队规范来定义的,但也有一些常见的最佳实践和约定俗成的规则可以参考。以下是对你提到的目录一般放置哪类文件的解答: views: 通常用于存放项目的视图层文件。在鸿蒙项目中,这可能包括页面的布局文件(XML或JSON格式)以及对应的视图逻辑代码(如使用Java/Kotlin/JS/Dart等语言编写的代码文件)。这些文件定义了用户界面的外观和行为。 viewmodel: 这是一个较为现代的命名,尤其在MVVM(Model-View-ViewModel)架构模式中较为常见。在鸿蒙项目中,如果采用这种架构,viewmodel 目录可能会包含用于连接视图(View)和模型(Model)的ViewModel类。ViewModel负责处理业务逻辑、数据转换以及视图状态管理等。 models: 用于存放数据模型(Model)的类文件。这些模型通常代表应用程序中的数据实体,如用户、订单、产品等。它们定义了数据的结构和行为。 common: 这个目录用于存放项目中通用的代码和资源,如配置文件、常量定义、工具类、样式文件等。这些资源或代码可能在整个项目中被多个模块或组件共享。 components: 用于存放可复用的组件。在鸿蒙项目中,这些组件可能包括自定义的UI组件、业务逻辑组件等。组件化的开发有助于提升代码复用性和维护性。 utils: 包含工具类文件,这些工具类提供了一系列静态方法或工具函数,用于执行常见的、与具体业务逻辑无关的任务,如日期处理、字符串操作、网络请求封装等。 services: 存放服务相关的代码,这些服务可能包括网络请求服务、本地数据存储服务、远程调用服务等。服务层通常负责处理数据的获取、处理和持久化,以及与其他系统或服务的交互。 需要注意的是,这些目录的命名和组织方式并不是固定不变的,你可以根据项目的实际情况和团队的开发习惯进行调整。重要的是保持代码的组织结构清晰、易于理解和维护。同时,确保团队成员之间对项目的目录结构有共同的理解和约定。