HarmonyOS应用开发——页面

我们将对于多页面以及更多有趣的功能展开叙述,这次我们对于 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 文档还有很多功能待我们发现,推荐阅读一遍官方文档,我相信会有一些奇妙的感悟,页面的构建和书写基础篇就先写到这里

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

《鸿蒙 (Harmony OS)开发学习手册》

入门必看:https://qr21.cn/FV7h05

  1. 应用开发导读(ArkTS)
  2. 应用开发导读(Java)

HarmonyOS 概念:https://qr21.cn/FV7h05

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全

如何快速入门:https://qr21.cn/FV7h05

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

开发基础知识: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. ……

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

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

相关文章

项目进度已经落后了,项目经理该怎么办?

进度管理是项目管理的核心工作之一&#xff0c;通过可续的进度计划与控制管理&#xff0c;最终实现项目按照目标交付。 进度管理的两大核心工作&#xff1a;计划制定、过程管控。 项目管理过程中难免会遇到工作进度和计划不一致的情况&#xff0c;有效管理项目进度&#xff…

springboot简单集成jwt

springboot简单集成jwt 参考&#xff1a;https://blog.csdn.net/gjtao1130/article/details/111658060 大佬的源码是可以运行的&#xff0c;我写这个文章的目的是添加一些注释来辅助理解 源码 JwtInterceptor.Java package com.xxh.jwt1.interceptor;import com.auth0.jwt.J…

Redis安装和使用(基于windows)

Redis是一个使用C语言编写的开源、高性能、非关系型的键值对存储数据库。它支持多种数据结构&#xff0c;包括字符串、列表、集合、有序集合、哈希表等。Redis的内存操作能力极强&#xff0c;其读写性能非常优秀&#xff0c;且支持持久化&#xff0c;可以将数据存储到磁盘上&am…

使用 React 和 ECharts 创建地球模拟扩散和飞线效果

在本博客中&#xff0c;我们将学习如何使用 React 和 ECharts 创建一个酷炫的地球模拟扩散效果。我们将使用 ECharts 作为可视化库&#xff0c;以及 React 来构建我们的应用。地球贴图在文章的结尾。 最终效果 准备工作 首先&#xff0c;确保你已经安装了 React&#xff0c;并…

大语言模型有那些能力和应用

目录 能力 应用 能力 理解语义的能力&#xff1a;LLM 具有强大的语义理解能力&#xff0c;能够理解大部分文本&#xff0c;包括不同语言&#xff08;人类语言或计算机语言&#xff09;和表达水平的文本&#xff0c;即使是多语言混杂、语法用词错误&#xff0c;也在多数情况下…

关于Java中list三个实现类区别

1. 前言&#xff1a; List实现Collection接口&#xff0c;它的数据结构是有序可以重复的结合&#xff0c;该结合的体系有索引&#xff1b;它有三个实现类&#xff1a;ArrayList、LinkList、Vector三个实现类。 2. 三个实现类的基本区别&#xff1a; 2.1 ArrayList: 底层数据…

基于python实现人脸识别登录系统

一、图片管理系统亮点&#xff1a;本系统注重登录方式 1.1 登录方式一&#xff1a; 运用本地摄像头进行实时拍照登录&#xff0c;拍照得到的图片识别获取人脸与文件库里的人脸进行对比登录。 1.2 登录方式二&#xff1a; 登录者上传图片给系统&#xff0c;然后系统识别图片中的…

智能安全芯片ACH512芯片描述及功能

ACH512 芯片是一款基于安全算法的高性能 SOC 芯片&#xff0c; 主要应用于 eMMC/SD/Nandflash 大容量存储设备、加密 U 盘、指纹识别等市场。 芯片采用 32 位内核&#xff0c;片内集成多种安全密码模块&#xff0c;包括SM1、 SM2、 SM3、 SM4、 SSF33 算法以及RSA/ECC、 ECDSA、…

torch 打印网络参数、结构

要打印网络结构&#xff0c;可以使用print或print(model)语句&#xff0c;其中model是定义的神经网络模型对象。这将输出整个网络的结构信息&#xff0c;包括每个层的名称、输入和输出尺寸以及参数量等。 要打印网络参数&#xff0c;可以使用以下代码&#xff1a; for name, …

io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTI

在使用redisTemplate的事务功能时&#xff0c;代码运行抛出异常&#xff1a; io.lettuce.core.RedisCommandExecutionException: ERR EXEC without MULTIat io.lettuce.core.internal.ExceptionFactory.createExecutionException(ExceptionFactory.java:147)at io.lettuce.cor…

Vue封装组件 父子组件相互传值

在Vue中&#xff0c;父子组件通信是非常常见的场景。以下是使用场景以及优缺点&#xff1a; 使用场景&#xff1a; 父组件需要向子组件传递数据&#xff1a;父组件需要将某些数据传递给子组件&#xff0c;以便子组件能够根据这些数据进行展示或执行某些操作。子组件需要向父组…

数据结构 | 二叉树的各种遍历

数据结构 | 二叉树的各种遍历 文章目录 数据结构 | 二叉树的各种遍历创建节点 && 创建树二叉树的前中后序遍历二叉树节点个数二叉树叶子节点个数二叉树第k层节点个数二叉树查找值为x的节点二叉树求树的高度二叉树的层序遍历判断二叉树是否是完全二叉树 我们本章来实现二…

同调群的维度 和 同调群的秩

同调群的维度是指同调群中非零元素的最小阶数。与线性代数中对向量空间的维度的理解类似。对同调群&#xff0c;k维同调群的维度是k。 同调群的秩是指同调群中的自由部分的维度。同调群通常包含自由部分和挠部分。同调群的秩是指同调群中自由部分的维度。对同调群&#xff0c;…

SQL SERVER 设置权限和隐藏其他数据库

一、创建用户名&#xff0c;选择默认数据库 二、分配权限 --对用户EAM分配 View_1视图 只有 只读select权限 GRANT select on View_1 to EAM --对用户分配指定表权限&#xff08;读写删&#xff09; GRANT SELECT , INSERT , UPDATE , DELETE ON table1 TO [用户名] --对用户分…

更改 Mac 所使用网络服务的顺序

如果以多种不同的方式&#xff08;例如使用 Wi-Fi 或以太网&#xff09;接入互联网或网络&#xff0c;你可以更改连接时电脑所尝试的网络连接顺序。 如果有多个活跃的连接&#xff0c;电脑会首先尝试列表顶部的连接&#xff0c;然后按降序尝试其他连接。 你不能更改虚拟专用网…

详解Python 迭代器介绍及作用

文章目录 迭代器&#xff1a;初探什么是迭代器&#xff1f;通过迭代器进行迭代迭代器 for 循环的工作构建自定义迭代器Python 无限迭代器Python 迭代器的好处总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包…

Linux下安装MySQL 5.6

1、下载二进制安装文件 使用wget下载MySQL 5.6.35二进制安装文件并存放在/root目录下。 wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz ll mysql-5.6.35-linux-glibc2.5-x86_64.tar.gz 2、创建mysql用户 先创建mysql…

【c语言指针详解】复杂数据结构的指针用法

目录 一、动态内存分配 1.1 使用malloc和free函数进行内存的动态分配和释放 1.2 内存泄漏和野指针的概念和解决方法 二、复杂数据结构的指针用法 2.1 结构体指针和成员访问操作符 2.2 指针数组和指向指针的指针 2.2.1 指针数组 2.2.2 指向指针的指针 2.3 动态内存分配与结构体指…

Vue项目解决van-calendar 打开下拉框显示空白(白色),需滑动一下屏幕,才可正常显示

问题描述&#xff0c;如图 ipad(平板&#xff09;或者 H5移动端引入Vant组件的日历组件&#xff08;van-calendar&#xff09;&#xff0c;初始化显示空白&#xff0c;需滚动一下屏幕&#xff0c;才可正常显示 解决方法 需在van-calendar上绑定open"openCalendar"事件…

基于chatGPT全程辅助的Python教程(逆向教学设计)——使用pyqt5开发

安装 https://zhuanlan.zhihu.com/p/162866700 一些资源推荐 当成手册&#xff0c;现用现场查。 【2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~】 这个优点是用的pycharm &#xff0c;符合自己习惯 这个控件比较全&#xff0c;可以直接查【PyQt5从入门到…