鸿蒙APP开发页面组件之间的属性关系

我们将对于多页面以及更多有趣的功能展开叙述,这次我们对于 HarmonyOS 的很多有趣常用组件并引出一些其他概念以及解决方案、页面跳转传值、生命周期、启动模式(UiAbility),样式的书写、状态管理以及动画等方面进行探讨

页面之间的跳转以及数据传递

页面之间的跳转需要用到 router 模块的 pushUrl 方法,所以第一步是要导入 router 模块,然后在用户交互 API 中使用该方法进行页面的跳转(我这里使用的是按钮点击)

import router from '@ohos.router'

router.pushUrl({url: 'pages/Second'
})

然后我们需要将要跳转的到的目标页面进行一个页面路由配置(main_pages.json),忽略掉这一步的话页面跳转时会报错的,报错会提示让你去查看日志,而日志是说路由有问题,没办法跳转,如果你的路径书写的没问题的话,那么就是没有配置了

  • 路由配置
{"src": ["pages/Index","pages/Second"]
}

完整代码如下:

  • 主页面
// 导入 router 路由模块
import router from '@ohos.router'
@Entry
@Component
struct Index {build() {Row() {Column() {Text("主页面").fontSize(32).fontColor('#404040').fontWeight(FontWeight.Bold)Button("点击去往第二个页面").margin(32).padding(12).backgroundColor('#ff21b88d').onClick(()=> {// 路由跳转指定页面router.pushUrl({url: 'pages/Second'})})}.width('100%')}.height('100%')}
}
  • 目标页面
@Entry
@Component
struct Second {build() {Row() {Column() {Text("Hello ArkTs").fontWeight(FontWeight.Bold)}.width('100%')}.height('100%')}
}

页面之间传值

页面之间的传值还是靠 pushUrl 方法的 params 参数来传值

router.pushUrl({url: 'pages/Second',params: {title:'页面之间的传值'}
})

接收方需要使用 router 实例的 getParams 方法来进行接收

@State message:string = router.getParams()?.["title"]

tip: PI9及以上,router.pushUrl()方法新增了mode参数,可以将mode参数配置为router.RouterMode.Single单实例模式和router.RouterMode.Standard多实例模式。

 router.pushUrl({url: 'pages/Second',params: {src: '数据',}}, router.RouterMode.Single)

除了我们上面所说的 pushUrl 方法,其实还有一种方法可以进行页面跳转:router.replaceUrl(),该方法新增了mode参数,可以将mode参数配置为router.RouterMode.Single单实例模式和router.RouterMode.Standard多实例模式。在单实例模式下:如果目标页面的url在页面栈中已经存在同url页面,离栈顶最近同url页面会被移动到栈顶,替换当前页面,并销毁被替换的当前页面,移动后的页面为新建页,页面栈的元素数量会减1;如果目标页面的url在页面栈中不存在同url页面,按多实例模式跳转,页面栈的元素数量不变。

还是那句话,具体问题具体方案,因为应用是有一个页面栈的,如果用户在进入目标页面之后通过一些途径再次重复进入页面,而页面栈或者说我们开发者不进行分辨以及预知的话,就会出现重复跳转、找不到跳转前页面,页面将会出现无法返回或者陷入一个页面的死循环等问题

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

UIAbilIty - 应用程序入口

uiability 就是我们的应用程序入口,是系统调度单元,可以有多个也可以在一个入口中进行所有操作,具体情况具体方案,就比如我们经常使用的聊天工具,里面会内置小游戏等,如果我们通过该聊天工具进入了小游戏,那么该小游戏会重新开一个 uiability,这样我们通过任务管理器即可进行两个应用的互相切换而不影响用户的体验

它的意思相当于 Vue 或者 Uniapp中的 main 程序入口文件,是一个应用程序入口

在这个入口文件中,我们可以通过它的生命周期来做很多的事情,当应用程序打开创建、当他进入了后台、退出后台等等,但WindowStageCreate和WindowStageDestroy也就是使用虚线标明的两个只能算是状态,uiability生命周期只有四个

  • Create:Create状态为在应用加载过程中,UIAbility实例创建完成时触发,系统会调用onCreate()回调。可以在该回调中进行应用初始化操作,例如变量定义资源加载等,用于后续的UI界面展示。
  • WindowStageCreateWindowStageDestroy:UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。WindowStage创建完成后会进入onWindowStageCreate()回调,可以在该回调中设置UI界面加载、设置WindowStage的事件订阅。官网给我们提供了这样一张生命周期图

  • ForegroundBackground:Foreground和Background状态分别在UIAbility实例切换至前台和切换至后台时触发,对应于onForeground()回调和onBackground()回调
  • Destroy:Destroy状态在UIAbility实例销毁时触发。可以在onDestroy()回调中进行系统资源的释放、数据的保存等操作。

我们可以打开应用入口文件查看这些生命周期:

import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';export default class EntryAbility extends UIAbility {onCreate(want, launchParam) {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');}onDestroy() {hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');}onWindowStageCreate(windowStage: window.WindowStage) {// Main window is created, set main page for this abilityhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');windowStage.loadContent('pages/Index', (err, data) => {if (err.code) {hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');return;}hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');});}onWindowStageDestroy() {// Main window is destroyed, release UI related resourceshilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');}onForeground() {// Ability has brought to foregroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');}onBackground() {// Ability has back to backgroundhilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');}
}

UIAbility启动模式

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

  • 在单例模式中,每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例,系统中只存在唯一一个该UIAbility实例。即在最近任务列表中只存在一个该类型的UIAbility实例
  • 在多实例模式中,每次调用startAbility()方法时,都会在应用进程中创建一个该类型的UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例
  • 在指定实例模式中,UIAbility实例新创建之前,允许开发者为该实例创建一个字符串Key,新创建的UIAbility实例绑定Key之后,后续每次调用startAbility方法时,都会询问应用使用哪个Key对应的UIAbility实例来响应startAbility请求。如果匹配有该UIAbility实例的Key,则直接拉起与之绑定的UIAbility实例,否则创建一个新的UIAbility实例。运行时由UIAbility内部业务决定是否创建多实例。

然后我们在module.json5文件中的“launchType”字段配置为对应实例模式即可。

如多实例模式:

{"module": {"abilities": [{"launchType": "multiton"}]}
}

image

单独介绍这个图片的用意就是想要说它的资源引用特点以及和常见前端开发的不同之处

  1. 网络图片引用
Image("https://ts1.cn.mm.bing.net/th/id/R-C.66d7b796377883a92aad65b283ef1f84?rik=sQ%2fKoYAcr%2bOwsw&riu=http%3a%2f%2fwww.quazero.com%2fuploads%2fallimg%2f140305%2f1-140305131415.jpg&ehk=Hxl%2fQ9pbEiuuybrGWTEPJOhvrFK9C3vyCcWicooXfNE%3d&risl=&pid=ImgRaw&r=0").alt($r('app.media.icon'))// 使用alt,在网络图片加载成功前使用占位图.width("264vp")// 像素单位有4中表示单位 vp、px、fp、lpx

网络图片引用官网说是要在module.json5中配置访问网络权限,我就在网上随便找了一张图片,然后我发现没有配置也可以用,这个的话,能用就用,不能用就配置上,后续慢慢了解这个小东西

"requestPermissions": [{"name": "ohos.permission.INTERNET"
}],

2.第二种是使用PixelMap数据加载图片,读取写入图像数据以及获取图像信息

大多用于图形解码等需求的,这个我也不是很懂,我就不阐述了

3.第三种是使用Resource数据加载图片,这个需要将图片放在指定目录下

Image($r("app.media.todolist")).width(64)

resource 可以新建引用资源文件,我们右击 resource 文件夹新建引用资源json文件

这样我们可以写一些常用宽度高度等来提高复用性,便于修改维护

Image($r("app.media.todolist")).width($r("app.float.image"))

harmonyOS的样式怎么使用

对于习惯组件式开发且逻辑层、样式层、架构层分离开发的我来说,这个样式的绘画有点不习惯,写个样式还真得看看官网,所以针对样式的使用我写了一个登陆页面来练习,开发起来倒也是方便,没用多少代码,一个视觉上过的去的页面就开发出来了,尤其是它的 Row 和 Column 容器,掌握了真的很好用,用来做横向竖向布局很舒服

下面的代码没有写注释,但是不难理解,一定可以帮你解决一些样式上的困扰

import router from '@ohos.router'@Builder function ImageBuilder(src) {Image(src).width(32).height(32)
}
@Entry
@Component
struct Index {@State username:string = ''@State password:string = ''build() {Row() {Column() {Image($r("app.media.icon")).width($r("app.float.image_size")).height($r("app.float.image_size")).margin(32)Text("登录系统").fontSize(24).fontColor('#404040').fontWeight(700).margin(12)Text("登录系统以使用更多功能")TextInput({text:this.username,placeholder:'请输入用户名'}).margin(32).padding({top:12,left:24})TextInput({text:this.password,placeholder:'请输入用户密码'}).type(InputType.Password).margin({top:0,left:32,right:32}).padding({top:12,left:24})Row() {Text("验证码登录").fontColor("#ff1a81d0").fontWeight(500)Text("忘记密码").fontColor("#ff1a81d0").fontWeight(500)}.width("75%").margin({top:24}).justifyContent(FlexAlign.SpaceBetween)Button("登录").width("90%").margin(64).padding(12).backgroundColor('#ff1a81d0').fontWeight(700).onClick(()=> {// 路由跳转指定页面router.pushUrl({url: 'pages/Second',params: {title:'页面之间的传值'}})})Row({space: 32}) {ImageBuilder($r("app.media.icon"))ImageBuilder($r("app.media.icon"))ImageBuilder($r("app.media.icon"))}.margin({top:32})Text("其他方式登录").margin({top:24}).fontSize(12).fontColor("#ff1a81d0")}.width('100%')}.height('100%')}
}

获取 input 的用户输入字符可以使用 Input 内置的 onChange 方法,该方法会自己接受 value 也就是用户输入值,然后相应的对其进行操作即可

.onChange((value: string) => {})

顺便说个有趣的加载组件 :

  • LoadingProgress
LoadingProgress().color(Color.Blue).height(60).width(60)

我们把刚才页面的logo图标使用这个加载logo替换一下

这是一个动态的加载图标,我截图所以看不出来,挺好看的,我们可以在登录等待时间使用它撑一段时间,让用户不那么尴尬

harmonyOS的组件还是很多很棒的,其他有趣的组件我们可以去其官网学习

List

HarmonyOS同样也有 list 滚动列表组件便于我们开发

该组件有三个重要可选参数:

  • space:列表间距
  • scroller:控制列表滚动
  • initiallndex:初次加载 list 所显示的 item

@Entry
@Component
struct Second {@Builder listItemComponent(item:string) {Row({space:12}) {Image($r("app.media.icon")).width(32)Text(item).fontWeight(FontWeight.Bold)}.width("100%").justifyContent(FlexAlign.SpaceBetween)}@State list:Array<object> = [{id:0,title:'测试1'},{id:1,title:'测试2'},{id:2,title:'测试3'}]build() {Column() {List({space:12}) {ForEach(this.list,(item:object) => {ListItem() {this.listItemComponent(item["title"])}.width("90%").backgroundColor("#ffffff").padding({top:12,left:24,right:24,bottom:12}).borderRadius(24)},item=> item.id)}.alignListItem(ListItemAlign.Center)}.backgroundColor("#efefef").height("100%").justifyContent(FlexAlign.SpaceEvenly)}
}

Grid

grid 只有一个可选参数 scroller 来控制 grid 的滚动

image.png

以上图可滚动 Grid 为例我们查看其示例代码,想要实现滚动效果,我们只需要给宽高任意一方限定即可八,并配置相应属性,例如仅设置columnsTemplate属性,不设置rowsTemplate属性,就可以实现Grid列表的滚动

  • 组件页面
import GridModel from './grid';
import { GridData } from './gridData';
@Component
export struct GridAssembly {build() {Grid() {ForEach(GridModel.getGridModel(),(item:GridData) => {GridItem() {Column() {Image(item.img).height(52).width(52).margin({bottom:12})Text(item.title).fontWeight(FontWeight.Bold)}}},item => JSON.stringify(item))}.height(124).columnsTemplate('1fr 1fr 1fr').rowsGap(12).columnsGap(12).backgroundColor('#fff').width("90%").borderRadius(24).padding(24)}
}
  • 数据构建
import { GridData } from './gridData';
export default class GridModel {public static getGridModel():Array<GridData> {let data:Array<GridData> = [new GridData("测试文字01",$r("app.media.we_chat")),new GridData("测试文字02",$r("app.media.we_chat")),new GridData("测试文字03",$r("app.media.we_chat")),new GridData("测试文字04",$r("app.media.we_chat")),new GridData("测试文字05",$r("app.media.we_chat")),new GridData("测试文字06",$r("app.media.we_chat")),new GridData("测试文字07",$r("app.media.we_chat"))]return data;}
}
  • 参数类型定义
export class GridData {title:string;img?:Resource;constructor(title:string,img?:Resource) {this.title = title;this.img = img;}
}

无论是 Grid 组件还是 List 组件,我们都可以使用 onScrollIndex 来监听列表的滚动,还有更多 API 我们可以前往其官网查看学习 例如:onScrollStop 等

tabbar

不得不说,harmonyOS 的tabbar 和 uniapp的不同点在于uniapp是统一配置,全局唯一,默认的底部栏不是很灵活多变,uniapp支持的平台太多了,也难怪,不怪它🤗

而 harmonyOS 可以定义其超出滚动、侧边tabbar等等功能,且 tabbar 是一个组件,这样的话,我们即可以无限创建😉

此组件有三个参数:

  • barPosition:指定页签位置来创建Tabs容器组件
  • index:指定初次初始页签索引
  • controller:设置Tabs控制器

属性:

且需要 TabContent 组件来配合

这是一个最简 tabbar,我试了一下…不好看,不过倒也适合那种顶部 navbar 的

所以我决定使用 @Builder 来构建一个稍微好看点的 tabbar

我一开始不了解这个 tabbar 的逻辑,结果配的一塌糊涂了,因为tabbar要跳转页面,跳转页面就要把页面写到 TabContent 里面,这就需要导入其他页面,而我创建的页面需要使用 @Entry 装饰器来声明,这两个直接冲突了,不能导出使用 @Entry 声明的页面

…而且在当前一个页面写 tabbar,本页面跳转本页面且其他页面没有和 tabbar 直接联系,感觉有点不合逻辑,所以我想了一个办法,要不我写一个 专门的 tabbar 页面,然后跳转其他页面,我去试了试,用到是可以用,可是…

是个警告,并且这个警告告诉我这是有安全问题的,可能引起引擎错误…(请不必在意我的项目很乱,我是用来练习的)

想必也不是这样写的,随后我把两个页面的 @Entry 装饰器删了,不警告了…

image.png

我百度出来的一大批都是(包括官网)都是使用一个普通组件来充当页面进行示例的,所以目前就这样写吧,逻辑上也是比较完美的,如果后续了解到了我会回来评论,大家也可以在评论区评论一起探讨,代码我也贴到下面

  • tabbar.ets
import userPage from './user'
import homePage from './Home'
@Entry
@Component
struct tabbarPage{private controller = new TabsController();@State currentIndex:number = 0;@Builder TabbarBuilder(title:string,index:number,selectImg:Resource,Img:Resource) {Column() {Image(this.currentIndex == index ? selectImg : Img).width(32).height(32)Text(title).fontColor(this.currentIndex == index ? "#fff" : "#000")}.onClick(()=> {this.currentIndex = index;this.controller.changeIndex(index);})}build() {Tabs({barPosition:BarPosition.End,controller:this.controller}) {TabContent() {homePage()}.tabBar(this.TabbarBuilder("主页",0,$r("app.media.home"),$r("app.media.home_no")))TabContent() {userPage()}.tabBar(this.TabbarBuilder("用户页面",1,$r("app.media.user"),$r("app.media.user_no")))}.vertical(false).barWidth("100%").barHeight(64).barMode(BarMode.Fixed).onChange((index:number)=> {this.currentIndex = index;})}
}
  • HOME
@Component
export default struct homePage {build() {Text("主页")}
}
  • User
@Component
export default struct userPage {build() {Text("用户页面")}
}

@Watch

这也是一个装饰器,我们在上一章说过 @State、@Prop、@Link、@Component等等

因为要通过组件状态来引出 @Watch ,所以我再大体阐述一下:

  • @State:单个组件中的数据驱动试图
  • @Prop:父组件单向控制子组件数据驱动试图,且该装饰器子组件来使用
  • @Link:父子组件之间的双向数据传递或者说绑定,且该装饰器子组件来使用

而 @Watch 的作用是监听状态值的变化,如果状态值发生变化,那么会触发执行我们定义好的回调函数,实现当前监听数据改变牵动其他状态的改变

以下代码我没有使用组件间的数据传递来配合 @Watch 使用,但是足够描述该装饰器功能,我们通过监听数据的变化,如果数据大于等于5,那么会触发 numMaxFunc 函数来改变其他变量或者状态,且可以与其他装饰器相配合在一起使用

@Component
export default struct homePage {@State flag:boolean = true;@State @Watch("numMaxFunc") num:number = 0;@State text:string = "+ 1";numMaxFunc() {if(this.num >= 5) {this.num--;this.text = "-1";this.flag = !this.flag;}}build() {Column({space:12}) {Text(`${ this.num }`)Button(this.text).onClick(()=> {if(this.flag) {this.num++;} else {this.num--;}})Button("点击切换按钮功能").onClick(()=> {this.flag = !this.flag;if (this.text == "+ 1") {this.text = "- 1";} else {this.text = "+ 1";}})}}
}

跨组件层级双向同步状态:@Provide和@Consume

@Provide和@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于上文提到的父子组件之间通过命名参数机制传递,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递

这是官网给出的定义。不过也确实该有,否则业务需求繁琐的话,需要多页面互相数据共享,一个数据传十来个组件先不说,能乱死

我依稀记得在当初学习 React 的时候,虽然也有对应的传值方法,但是感觉有点BT了,兄弟组件传个值搞得什么状态提升、发布订阅模式、context 状态数传参,不过有时候还是很喜欢 React 的…

这个效果不演示了,很简单,我从官网截了一张图,一看便知

弹窗

警告弹窗

我们可以使用内置 API AlterDialog 的 show 方法来显示警告框,具体配置如下面的代码,效果如上图,和我们在 Uniapp 中使用的方式差不多,一个内置 API,配置即可

@Component
export default struct userPage {// 警告弹框alterFunc() {AlertDialog.show({title:"标题",message:"内容",primaryButton: {value:"关闭操作",action:()=> {// 点击调用回调}},secondaryButton:{value:"确认操作",fontColor:'red',action:()=> {// 点击调用回调}}})}build() {Button("点击出现弹框").onClick(()=> {this.alterFunc();})}
}

这是一个基本的弹框,我们可以根据它的属性来设置不一样的效果,比如显示在底部而不是正中央:

以下是弹框的其他配置,比如我们点击弹框以外的遮罩层是否关闭弹窗

日期弹框

@Component
export default struct userPage {@State dateStr:string = '';// 警告弹框alterFunc() {DatePickerDialog.show({start: new Date('1970-01-01'), // 开始日期end: new Date(), // 结束日期selected: new Date(), // 默认选中日期lunar:false, // 是否为农历// 选中后点击确定onAccept:(val:DatePickerResult)=> {this.dateStr = `-${val.year} -- ${val.month + 1} -- ${val.day}-`}})}build() {Column() {Text(this.dateStr)Button("点击出现弹框").onClick(()=> {this.alterFunc();})}}
}

还有剩下几个弹框样式,我就不一一赘述了,我们可以去官网查看

  • 列表选择弹框
  • 时间滑动选择器弹框
  • 文本滑动选择器弹框

主要写一下自定义弹框,如果这些样式无法满足我们的需求,那么我们可以使用装饰器 @CustomDialog 自定义自己的弹框

  1. 创建自定义弹窗
import { HobbyBean } from './dataObj';
@CustomDialog
@Component
export default struct CustomDialogWidget {@State hobbyBeans:Array<HobbyBean> = [];@Link hobbies: string;private controller: CustomDialogController;// 这是一个生命周期方法,当对话框即将出现时会被调用 (该生命周期被用来初始化数据)aboutToAppear() {// 获取当前弹框组件的上下文// let context: Context = getContext(this);// // 从上下文中获取资源管理器// let manager = context.resourceManager;// // 从资源管理器中获取一个字符串数组,这个字符串数组包含了关于各种爱好的数据// manager.getStringArrayValue($r('app.strarray.hobbies_data'), (error, hobbyResult) => {//   // 字符串数组进行遍历,为每个业余爱好项创建一个新的 HobbyBean 对象//   hobbyResult.forEach((hobbyItem: string) => {//     let hobbyBean:HobbyBean = new HobbyBean();//     hobbyBean.title = hobbyItem;//     hobbyBean.isCheck = false;//     this.hobbyBeans.push(hobbyBean);//   });// });this.hobbyBeans = [{"id":0,"title":"写代码","isCheck": false},{"id":1,"title":"打篮球","isCheck": false},{"id":2,"title":"跑步","isCheck": false}]}// 将数组中所有选中项的标题连接成一个字符串,并将其赋值给hobbies变量setHobbiesValue(hobbyBeans: HobbyBean[]) {let hobbiesText: string = '';hobbiesText = hobbyBeans.filter((isCheckItem: HobbyBean) =>isCheckItem?.isCheck).map((checkedItem: HobbyBean) => {return checkedItem.title;}).join(',');this.hobbies = hobbiesText;}build() {Column({space:12}) {Text("兴趣爱好选择")List() {ForEach(this.hobbyBeans, (item: HobbyBean) => {ListItem() {Row() {Text(item.title)Toggle({ type: ToggleType.Checkbox, isOn: false }).onChange((isCheck) => {item.isCheck = isCheck;})}}}, item => item.id)}Row() {Button("取消").onClick(() => {this.controller.close();})Button("确认").onClick(() => {this.setHobbiesValue(this.hobbyBeans);this.controller.close();})}.width("100%").justifyContent(FlexAlign.SpaceBetween)}.backgroundColor('#fff').padding(32).width('90%').borderRadius(24)}
}

可以看到我注释了一段代码,我本来是写好了数据打算在弹框生命周期中将引用类型数据进行一个处理,然后使用 foreach 遍历一下,结果死活获取不到上下文(应该就是这样获取吧,有朋友知道可以在评论区评论,我们一起探讨研究),先用死数据代替一下将功能写出来

关于上面代码的 hobbyBean 数据类型是我自己定义的,上面也有相关引用

export class  HobbyBean {id:number;title:string;isCheck:boolean;
}

然后我们就可以在页面中使用我们定义好的弹框了

import CustomDialogWidget from './dialog';
@Component
export default struct userPage {@State hobbies:string = '';customDialogController: CustomDialogController = new CustomDialogController({// 弹窗内容构造器builder: CustomDialogWidget({hobbies:$hobbies}),alignment: DialogAlignment.Bottom,customStyle: true,offset: { dx: 0,dy: -20 }});build() {Column({space:12}) {Text(this.hobbies)Button("点击选择你的爱好").onClick(()=> {this.customDialogController.open();})}}
}

Video

视频播放组件,通过 VideoController 对象可以控制一个或多个video的状态或者他们的属性,其他属性、事件、配置以及刚才提到的 VideoController 对象请前往官网学习查阅

我们使用真机模拟吧,我发现 预览模式 不支持…视频组件

这字也是真小,我以为哪里写错了我调试了半天,真服了,真机运行如下:

Video({src:"http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4",// 预览图片previewUri:$r("app.media.app_icon")
}).width("90%").height(240).objectFit(ImageFit.Contain)

动画

属性动画

这是基础页面开发的最后一个内容了,首先我们通过元素属性来实现动画效果

我们给元素组件添加 animation 动画属性即可实现动画效果,下图是 animation 的具体参数配置

@Component
export default struct userPage {@State flag:boolean = false;@State imgWidth:string = "64vp";@State imgUrl:Resource  = $r("app.media.home");build() {Column() {Text(`参数:${this.flag},${this.imgWidth}`)Image(this.imgUrl).width(this.imgWidth).position({x:this.imgWidth,y:this.imgWidth}).animation({duration:1000,tempo:0.8,curve:Curve.LinearOutSlowIn,delay:0,// iterations:-1,playMode:PlayMode.Normal}).onClick(()=> {if(!this.flag) {this.imgWidth = "92vp";this.imgUrl = $r("app.media.we_chat");this.flag = !this.flag;} else {this.imgWidth = "64vp";this.imgUrl = $r("app.media.home");this.flag = !this.flag;}})}}
}


其他动画

除了属性动画,HarmonyOS 还提供了很多的动画,例如 sharedTransition 不同页面同元素的过渡转场动画,transition 组件内转场动画,PageTransitionEnter 页面间转场动画等等

官网根据不同动画的性质做了区分,有兴趣可以查寻对应关键字在官网实现更多有趣的动画,在这里我就不赘诉了,感觉这篇文章稍微有点长了

ArkTs API 文档还有很多功能待我们发现,推荐阅读一遍官方文档,我相信会有一些奇妙的感悟,页面的构建和书写基础篇就先写到这里

码牛课堂也为了积极培养鸿蒙生态人才,让大家都能学习到鸿蒙开发最新的技术,针对一些在职人员、0基础小白、应届生/计算机专业、鸿蒙爱好者等人群,整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线。大家可以进行参考学习:https://qr21.cn/FV7h05

①全方位,更合理的学习路径
路线图包括ArkTS基础语法、鸿蒙应用APP开发、鸿蒙能力集APP开发、次开发多端部署开发、物联网开发等九大模块,六大实战项目贯穿始终,由浅入深,层层递进,深入理解鸿蒙开发原理!

②多层次,更多的鸿蒙原生应用
路线图将包含完全基于鸿蒙内核开发的应用,比如一次开发多端部署、自由流转、元服务、端云一体化等,多方位的学习内容让学生能够高效掌握鸿蒙开发,少走弯路,真正理解并应用鸿蒙的核心技术和理念。

③实战化,更贴合企业需求的技术点
学习路线图中的每一个技术点都能够紧贴企业需求,经过多次真实实践,每一个知识点、每一个项目,都是码牛课堂鸿蒙研发团队精心打磨和深度解析的成果,注重对学生的细致教学,每一步都确保学生能够真正理解和掌握。

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:https://qr21.cn/FV7h05

如何快速入门:

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr21.cn/FV7h05

大厂鸿蒙面试题::https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

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

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

相关文章

mac 教程 终端如何拆墙

一直觉得自己写的不是技术&#xff0c;而是情怀&#xff0c;一个个的教程是自己这一路走来的痕迹。靠专业技能的成功是最具可复制性的&#xff0c;希望我的这条路能让你们少走弯路&#xff0c;希望我能帮你们抹去知识的蒙尘&#xff0c;希望我能帮你们理清知识的脉络&#xff0…

【Paddle】PCA线性代数基础 + 领域应用:人脸识别算法(1.1w字超详细:附公式、代码)

【Paddle】PCA线性代数基础及领域应用 写在最前面一、PCA线性代数基础1. PCA的算法原理2. PCA的线性代数基础2.1 标准差 Standard Deviation2.2 方差 Variance2.3 协方差 Covariance2.4 协方差矩阵 The Covariance Matrix2.5 paddle代码demo①&#xff1a;计算协方差矩阵2.6 特…

4.8 Python dict字典

Python dict字典详解 Python字典&#xff08;dict&#xff09;是一种无序的、可变的序列&#xff0c;它的元素以“键值对&#xff08;key-value&#xff09;”的形式存储。相对地&#xff0c;列表&#xff08;list&#xff09;和元组&#xff08;tuple&#xff09;都是有序的序…

【Android12】Bugreport实现原理

Bugreport实现原理 Bugreport Bugreport介绍 Android Bugreport是一个用于记录和收集 Android设备上系统信息、日志和调试信息的工具。 系统发生某些问题时&#xff0c;可以通过bugreport把系统当前时刻点&#xff08;运行BugRepot的时刻&#xff09;的系统相关的状态和信息…

HarmonyOS 实战开发-MindSpore Lite引擎进行模型推理

场景介绍 MindSpore Lite 是一款 AI 引擎&#xff0c;它提供了面向不同硬件设备 AI 模型推理的功能&#xff0c;目前已经在图像分类、目标识别、人脸识别、文字识别等应用中广泛使用。 本文介绍使用 MindSpore Lite 推理引擎进行模型推理的通用开发流程。 基本概念 在进行开…

YOLOv5利用Labelimg标注自己数据集

目录 一、Labelimg介绍二、Labelimg下载三、Labelimg安装四、Labelimg使用1、准备2、开始标注 一、Labelimg介绍 LabelImg是一款开源的图片标注工具&#xff0c;使用Python编写&#xff0c;基于PyQt5框架。它提供了一个直观的图形用户界面&#xff0c;方便用户对图片进行标注&…

axios——503响应超时重复多次请求——技能提升

今天在写后台管理系统时&#xff0c;遇到一个问题&#xff0c;就是每天早上一启动项目&#xff0c;接口会提示503超时&#xff0c;因此项目运行必须重新刷新请求成功后才可以正常使用。 后端同事说请求超时了&#xff0c;需要前端处理一下&#xff0c;如果是503的状态码&#…

Golang特殊init函数

介绍 init()函数是一个特殊的函数&#xff0c;存在一下特性 不能被其它函数调用&#xff0c;而是子main()函数之前自动调用不能作为参数传入不能有传入参数和返回值 作用&#xff1a; 对变量进行初始化检查/修复程序状态注册运行一次计算 以下是<<the way to go>>…

Web APIs 学习归纳1---Web API概述简单的元素获取

JS基础中我们学习了JS的基本语句&#xff0c;这些是后续学习中的基础。 有了上述JS的基础以后&#xff0c;我们就可以开始学习交互效果的实现方法。这里很多时候直接调用JS封装好的API即可。 一、Web APIs 和 JS 基础关联性 这里有一张图很好的解释了这个问题&#xff1a; 我们…

python高校贫困学生资助奖学金管理系统vue+django

我们根据项目文档&#xff0c;包括规格说明、文档及在设计过程中形成的测试大纲、测试内容及测试的通过准则、再次全面熟悉系统&#xff0c;做好测试准备。为了保证测试的质量&#xff0c;我们将测试过程分为几个阶段。第一阶段&#xff0c;在单元测试阶段我们对每个子模块进行…

可视化+多人协同技术原理和案例分享

前言 hi&#xff0c;大家好&#xff0c;我是徐小夕&#xff0c;之前和大家分享了很多可视化低代码的技术实践&#xff0c;最近也做了一款非常有意思的文档搭建引擎——Nocode/Doc&#xff1a; 也做了一些分享&#xff1a; Nocode/Doc&#xff0c;可视化 零代码打造下一代文件编…

SpringBoot+vue开发记录(二)

说明&#xff1a;本篇文章的主要内容为SpringBoot开发中后端的创建 项目创建: 1. 新建项目&#xff1a; 如下&#xff0c;这样简单创建就行了&#xff0c;JDK什么的就先17&#xff0c;当然1.8也是可以的&#xff0c;后面可以改。 这样就创建好了&#xff1a; 2. pom.xml…

ChatGPT全方位指导:学术论文写作从零开始,轻松搞定高质量论文!

目录 文末福利 一、论文选题的深度探讨 二、撰写摘要的艺术 三、关键词的精选 四、引言的构建 五、正文的结构设计 六、撰写结论的策略 七、致谢的编写 八、附录的有效利用 九、参考文献的整理 文末有福利哦 撰写一篇高质量的学术论文是一项既复杂又耗时的任务。这个…

MongoDB数据恢复—拷贝MongoDB数据库文件后无法启动服务的数据恢复案例

服务器数据恢复环境&#xff1a; 一台Windows Server操作系统服务器&#xff0c;服务器上部署MongoDB数据库。 MongoDB数据库故障&检测&#xff1a; 工作人员在未关闭MongoDB数据库服务的情况下&#xff0c;将数据库文件拷贝到其他分区。拷贝完成后将原MongoDB数据库所在分…

8个拿来即用的Python自动化脚本!

每天你都可能会执行许多重复的任务&#xff0c;例如阅读新闻、发邮件、查看天气、清理文件夹等等&#xff0c;使用自动化脚本&#xff0c;就无需手动一次又一次地完成这些任务&#xff0c;非常方便。而在某种程度上&#xff0c;Python 就是自动化的代名词。 今天分享 8 个非常…

医学影像增强:空间域方法与频域方法等

医学影像图像增强是一项关键技术,旨在改善图像质量,以便更好地进行疾病诊断和评估。增强方法通常分为两大类:空间域方法和频域方法。 一、 空间域方法 空间域方法涉及直接对医学影像的像素值进行操作,以提高图像的视觉质量。以下是一些常用的空间域方法: 对比度调整:通过…

《逍遥游·六十八拐》

五月阳光映大观&#xff0c;艳丽队服身上穿。海埂西门集合后&#xff0c;蓝光城外相谈欢。 松茂水库映蓝天&#xff0c;阳宗镇上舞蹁跹。 六十八拐道崎岖&#xff0c;一鼓作气意志坚。 宜良宿&#xff0c;夜幕深&#xff0c;梦中山水情相牵。待破晓&#xff0c;新日升&#xf…

mongodb 分片集群认证

增加认证 副本间认证外部使用认证 如果是开启状态,先关闭路由,再关闭配置服务,最后关闭分片数据复本集中的每个mongod&#xff0c;从次节点开始。直到副本集的所 有成员都离线&#xff0c;包括任何仲裁者。主节点必须是最后一个成员关闭以避免潜在的回滚.最好通过 db.shutdow…

janus模块介绍-SIP Gateway

模块启动 默认的SIP GateWay也是https协议&#xff0c;端口为8088或者8089 如果需要在自己搭建的测试服务上测试SIP GateWay模块&#xff0c;则也需要修改为wss 具体改动如下: 找到/opt/janus/share/janus/demos/siptest.js var server "wss://" window.location…

比较好的平民衣服品牌有哪些?平价质量好短袖品牌推荐

随着气候变暖&#xff0c;夏天的持续时间似乎越来越长&#xff0c;短袖作为夏季的必备服装&#xff0c;受到了广大男士的青睐。然而&#xff0c;面对市场上众多的短袖品牌和不同的质量&#xff0c;大家都觉得选短袖的时候实在难以找到质量好且合适自己的。 选择合适的短袖确实…