版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
鸿蒙崛起第6章:UI界面设计延迟符目录content6.1ArkUI概述6.2UI布局概述6.3UI基本语法6.4声明式UI描述6.5自定义组件6.6装饰器6.7状态管理6.8构建布局6.9常用组件6.10章末知识概览目录content延迟符6.1ArkUI概述PART延迟符016.1.1ArkUI的简单介绍ArkUI是鸿蒙操作系统(HarmonyOS)中的重要组成部分,这个框架提供的一个声明式UI开发框架。它旨在简化用户界面的开发过程,为开发者提供一种更高效、更便捷的方式来构建HarmonyOS应用的用户界面,同时它也是构建分布式应用界面的一个UI框架。对于ArkUI来说,它的核心其实是绕着组件、页面、数据绑定和事件进行设计的。UI中的组件可以理解为用户视图页面中的基本组成,这个基本组成是由开发者定义的,比如一个用户输入框和一个密码框共同组成了登录界面的组件,一个“确认”按钮和一个注册链接则组成了另一个组件。组件的作用除了封装UI框架中的基本组件形成一个用户自定义的组件之外,其最重要的意义在于可以进行组件重用。如果在一些场景下需要反复使用相同的UI控件,就可以把这些相同的UI控件打包成一个自己的组件,然后在项目中反复使用即可,尤其是在那些较为复杂的项目中,可以显著提升项目效率。
组件其实就是页面中的一个区域部分,这些区域部分共同组成了一个用户视图页面,而页面其实是应用中的一个独立页面,它一般由一个或几个组件构成。用户可以通过导航在不同的页面之间进行切换。同时在鸿蒙中(与安卓的页面类型),页面有自己的生命周期,包括创建、显示、隐藏和销毁等状态。开发者可以根据页面的生命周期来管理资源,处理逻辑。
页面中有了控件和数据,就自然而言的需要考虑数据绑定的问题,所谓数据绑定就是一种页面中视图和数据的交互机制,它允许开发者将UI组件的属性与后端数据模型中的字段动态关联起来。而数据绑定分为单向数据绑定和双向数据绑定。所谓单项数据绑定就是数据可以从模型流向UI,当模型中的数据发生变化时,UI会自动更新;数据的双向绑定是指数据可以在模型和UI之间双向流动,UI中的变化会立即反映到模型中,反过来,模型中的数据发生了变化也会立刻反应到UI中。在ArkUI中,数据绑定通常通过特定的语法实现,如在属性前加上{{}}来绑定数据。
页面中有了数据和空间以及数据绑定之外,还需要事件来推动应用程序的执行。所谓事件指的是用户与UI交互时发生的交互动作,如点击、滑动、输入等。开发者可以通过指定事件处理函数来监听事件,当事件发生时,相应的处理函数会被调用,事件处理函数通常可以接收事件参数,这些参数包含了事件的相关信息,如事件类型、触发事件的元素等。通过理解这些关键概念,开发者可以更有效地使用ArkUI来构建HarmonyOS应用的用户界面。这些概念是声明式UI框架的核心,它们简化了UI的开发流程,并提供了强大的功能来创建丰富的用户体验。
总之,ArkUI框架为应用的UI开发提供完整的基础设施,包括简介的UI语法、丰富的UI功能(组件、布局、动画及交互事件),以及实时界面预览工具等,可以支持开发者进行可视化界面开发。6.1.2ArkUI中的开发规范此处添加详细文本描述,建议与标题相关并符合整体语言风格,语言描述尽量简洁生动此处添加详细文本描述,建议与标题相关并符合整体语言风格,语言描述尽量简洁生动此处添加详细文本描述,建议与标题相关并符合整体语言风格,语言描述尽量简洁生动在ArkUI中,开发者可以将应用的用户界面拆分为多个功能页面,每个页面都对应一个文件,页面之间通过路由API完成页面间的调度管理,比如跳转、回退等操作,以实现应用内的功能解耦;ArkUI中的“组件”指的是UI构建与显示的最小单位,比如列表、网格、按钮、单选框、进度条、文本等。开发者通过多种组件的组合,构建出满足自身应用诉求的完整界面。另外在ArkUI中有两种开发范式:1.
声明式开发范式:该开发范式采用的是ArkTS语言,ArkTS语言是基于TypeScript声明式UI语法扩展而来的,从组件、动画和状态管理三个维度提供UI绘制能力。2.
类Web开发范式:该开发范式采用了经典的前端web开发技术如HTML、CSS、JS三段式的开发方式,也就是使用html来描述页面中的元素构成,使用css来美化元素,使用js来处理页面的交互逻辑,该范式更符合经常做前端开发的使用者的习惯,可以更加快捷的将已有的web应用改造成ArkUI框架应用。6.1.2ArkUI中的开发规范此处添加详细文本描述,建议与标题相关并符合整体语言风格,语言描述尽量简洁生动此处添加详细文本描述,建议与标题相关并符合整体语言风格,语言描述尽量简洁生动此处添加详细文本描述,建议与标题相关并符合整体语言风格,语言描述尽量简洁生动所以在进行开发时,推荐使用声明式开发范式构建UI,因为声明式开发范式的开发效率相对较高,这种开发范式语法更为简洁、更接近自然语义的编程方式,同时开发者能够直观地描述UI,无需关心UI的绘制和渲染问题。从应用性能上来说,无论是哪种开发范式,它们的UI后端引擎和语言运行时是共用的,声明式开发范式相比于类Web开发范式而言,它并不需要类似于JS框架的DOM管理,因此渲染更新链路更加精简,内存暂用更少,应用的性能会更好一些,如图为方舟UI框架示意图。在官方的鸿蒙版本更新中,声明式开发范式会作为后续主推的开发范式,因此为了更好地掌握后续的开发知识,建议先学习声明式开发范式的语法。6.1.3应用模型的支持情况不同的应用类型所支持的开发范式也有所不同,根据所选用应用模型(Stage模型、FA模型)和页面形态(应用或服务的普通页面、卡片)的不同,对应支持的UI开发范式也有所差异,见下表开发范式的支持情况。应用模型页面形态支持的UI开发范式Stage模型(推荐)应用或服务的页面声明式开发范式(推荐)卡片声明式开发范式(推荐)类Web开发范式FA模型应用或服务的页面声明式开发范式类Web开发范式卡片类Web开发范式需要注意的是,鸿蒙系统最新版本已不再支持使用Java和XML进行应用开发,而JavaScript和类Web开发范式以及FA模型也不再推荐使用。因此,开发者只需要关注和学习ArkTS声明式范式和Stage模型即可。6.1.4ArkUI的整体架构
基于ArkTS的声明式开发范式的方舟开发框架是一套开发极简、高性能、支持跨设备的UI开发框架,提供了构建应用UI所必需的能力。使用ArkUI开发效率会更高,体验更好,这是因为ArkUI可以通过接近自然语义的方式描述UI界面,而不需要关心UI的绘制和实现。
同时它同前端中的Vue框架一样,也是基于数据驱动UI式的UI更新,这种设计方式使得开发者可以更加关注业务逻辑本身而非数据与UI的同步行为。声明式UI前端和UI后端分离,UI后端采用C++语言构建,提供对应前端的基础组件、布局、动效、交互事件、组件状态管理和渲染管线。
另外ArkUI也对语言编译器和运行时的效率进行了优化设计,能够借助主力语言实现技术生态的快速推进。ArkUI整体框架组成
其中的声明式UI前端提供了UI开发范式的基础语言规范,并提供内置的UI组件、布局和动画,也提供了多种状态管理机制,为应用开发者提供了一系列接口支持。在语言运行时,其提供了针对UI范式语法解析的能力、跨语言调用支持的能力和TS语言高性能运行环境。声明式UI后端引擎提供了兼容不同开发范式的UI渲染管线,提供多种基础组件、布局计算、动效、交互事件,提供了状态管理和绘制能力。另外,ArkUI通过渲染引擎进行高效的绘制,也同时具备平台适配性,提供了对系统平台的抽象接口,具备接入不同系统的能力。6.1.5UI开发流程在UI开发过程中涉及到很多过程步骤,大致包括布局设计、组件设计、状态设计、显示设计、交互设计等等,以下为具体的各个开发细节的过程设计:(1)ArkTS的基础知识:因为ArkUI是基于ArkTS的语法的,因此需要对其基本语法有所了解,同时也包括组件状态管理、渲染控制。(2)开发布局:需要使用几种常用的布局,如线性布局、层叠布局、弹性布局、相对布局、栅格布局等等。(3)页面组件管理:给页面进行布局的同时,也需要在其中添加页面组件,可以添加系统组件、自定义组件或者一些其他页面元素。(4)设置组件导航和页面路由:需要对应用程序的页面跳转进行设计,或者是在一个页面内对局部页面的组件跳转进行定义,这时候就需要使用到路由功能。(5)文本的使用:包含简单的文本框输入、富文本和属性字符串等等相关的文本组件使用。(6)弹框的使用:包含内置的模态框和自定义模态框的使用方法。(7)图形的显示:主要是解决图形的显示问题,包括图片的显示设置、自定义几何图形以及使用画布来绘制图形。(8)动画的使用:有时为了页面的UI有更好的交互效果,需要对页面或者组件进行动画的定义。(9)事件的管理:往往需要定义元素与用户的交互,因此需要事件作为驱动,这就涉及到事件的定义、与元素的绑定和事件的触发。(10)自定义节点的设计:可以通过自定义节点与原生控件进行混合显示,同时也可以对已有的UI组件内容进行自定义功能的扩展。(11)UI上下文接口的使用:可以使用UIContext中对应的接口获取与实例绑定的对象。(12)NDK接口的使用:可以使用NDK接口调用C/C++程序来构建UI页面。(13)其他:包括支持适老化、主题设置、镜像能力的使用等等。6.2UI布局概述PART02所谓布局就是如何安排页面中的组件的显示问题,比如对齐问题、宽度高度分配问题、元素显示顺序问题、元素尺寸空间问题等等。合适的UI布局可以让页面中的元素看起来更清晰和有层次。开发者可以根据自身的业务需求,将各个组件放入某一布局设计中,从而完成一个简单的页面设计。在实际的开发过程中,需要遵守一定的UI设计流程,其顺序大致为:首先确定页面整体的布局结构,然后分析页面中的组件或者元素构成,之后选用合适的布局容器组件或者属性控制页面中各个元素的位置和大小。6.2.1布局元素与布局结构页面中的每个组件或元素都可以视为一个容器,每个容器都有一些固定的特性,这里和前端web页面设计中的盒模型类似,可参考图布局元素的组成,详细组成如图所示。6.2.1布局元素与布局结构当每个元素的元素布局组成设定好之后,配合布局就可以完成基本的UI元素显示。再复杂的布局也是由层层布局嵌套而来的,一个常见的页面布局元素组成如图所示。
为实现上述效果,开发者需要在页面中声明对应的元素。其中Page表示页面的根节点,Column/Row等元素为系统组件。针对不同的页面结构,ArkUI提供了不同的布局组件来帮助开发者实现对应布局的效果,例如Row用于实现线性布局。6.2.2常见布局结构在ArkUI中有10种常用的布局,开发者可以根据实际的情况选取合适的页面布局来完成页面设计任务。这些布局包括:线性布局、层叠布局、相对布局、栅格布局、媒体查询、列表、网格、轮播、选项卡等。线性布局层叠布局相对布局栅格布局媒体查询列表网格轮播选项卡6.2.3布局位置与元素约束
每个布局也都可以看成是一个存在一定尺寸的容器,可以使用position、offset等属性来设置该容器相对于自身或者其他容器的位置。比如在绝对定位中,可以使用position属性实现绝对定位,设置元素左上角相对于父容器左上角偏移位置。在布局容器中,设置该属性不影响父容器布局,仅在绘制时进行位置调整,下图为绝对定位演示。但是绝对定位对于不同尺寸设备的场景,其适应性会比较差,在屏幕的适配上会有所缺陷。再比如使用相对定位,利用offset属性能够实现元素相对于自身的偏移量,设置该属性,不影响父容器布局,仅在绘制时进行位置调整。相对定位不脱离文档流,即原位置依然保留,不影响元素本身的特性,仅相对于原位置进行偏移,如图展示的是相对定位。6.2.3布局位置与元素约束在讨论容器和元素时,需要考虑的关键问题是当容器发生了尺寸变化会对子元素产生的影响。这里包括拉伸、缩放、占比和隐藏问题。这些都可以理解为容器对元素的约束。 当元素发生拉伸动作,增加或减小的空间全部分配给容器组件内指定区域,通过两个属性来控制伸缩组件:1.
flexGrow:基于父容器的剩余空间分配来控制组件拉伸。2.
flexShrink:设置父容器的压缩尺寸来控制组件压缩。当元素发生缩放时,子组件的宽高按照预设的比例位置不变,但其大小随容器组件发生变化,可以使用aspectRatio属性指定当前组件的宽高比来控制缩放,公式为:aspectRatio=width/height。6.2.3布局位置与元素约束在行或者列上需要设置元素的空间百分比,这个就是占比问题。随着容器的变化,其内部的组件可以通过占比的设定维持同一个比例。关于组件的占比问题,可以基于通用属性(宽和高)进行两种实现:l子组件的宽高设置为百分比:(这种情况下子组件的百分比所参考的对象有所不同)a)
父组件设置宽,同时高且祖先组件未指定父组件宽或高:参考父组件的宽高。b)
父组件设置宽或高,同时祖先组件指定父组件宽或高:参考祖先组件指定的父组件宽高。c)
父组件未设置宽或高,同时祖先组件指定父组件宽或高:参考祖先组件指定的父组件宽高。d)
父组件未设置宽或高,同时祖先组件未指定父组件宽或高:参考父组件的百分比参照。由于父组件未指定宽高,该百分比参照传递自祖先组件。l使用layoutWeight属性,使得子元素自适应占满剩余空间。当容器尺寸发生变化,也可以设置其子组件的显示和隐藏,并且显示是可以进行优先级设置的,相同优先级的子组件会同时显示或隐藏。6.3UI基本语法PART036.3UI基本语法该例子定义了一个按钮,当用户点击这个按钮,文本内容会从“HelloWorld”变为“HelloArkUI”,如图组件声明示例。代码中的@Entry是一个装饰器,用于标记该组件是应用程序的入口点。@Component也同样是一个装饰器,作用是来自定义一个ArkUI组件。这里使用structHello定义了一个名为Hello的结构体,这个结构体将作为组件的类。通过@State定义了一个状态变量myText,并且@State也同时要说明当前的状态变量是组件的一部分,当这个变量发生改变时,界面会重新渲染以反映新的状态。Build为组件的构建函数,用于描述组件的UI结构,ArkUI会调用这个函数来构建和渲染组件的界面。内部的column是一个系统组件,用户垂直排列子组件,Column中的子组件将按照它们在代码中的顺序垂直堆叠。使用Text关键字声明了一个文本组件,并且在该文本组件中引用了myText状态变量,并设置了字体大小。Driver是一个系统组件,用于在UI中创建一个分隔线。通过使用Button关键字声明一个按钮组件(内置组件),其括号内部可以声明组件要显示的文本,而通过onClick属性可以声明事件的动作,这里使用箭头函数来描述事件行为。同时可以使用height、width、margin等样式属性为该组件声明UI样式。整体来看,上述代码定义了一个简单的ArkUI组件,它包含一个文本显示和一个按钮。当按钮被点击时,文本内容会从'HelloWorld'变为'HelloArkUI'。这个组件展示了ArkTS中组件状态管理、事件处理和UI布局的基本概念。延迟符6.4声明式UI描述PART延迟符04ArkTS以声明方式组合和扩展组件来描述应用程序的UI,同时还提供了基本的属性、事件和子组件配置方法,帮助开发者实现应用交互逻辑。6.4.1组件的创建根据组件构造方法的不同,创建组件包含有参数和无参数两种方式,并且在创建组件时不需要使用new运算符。如果组件的接口定义没有包含必选构造参数,则组件后面的“()”不需要配置任何内容。例如,Divider组件不包含构造参数,示例如图6-8所示。Column(){Text('item1')Divider()Text('item2')}6.4.1组件的创建如果组件的接口定义包含构造参数,则在组件后面的“()”需要配置相应参数,比如Image组件中必须执行src参数,还有Text组件的的非必选参数content一些基本用法,如图6-9为组件的使用示例。Image('https://xyz/test.jpg')//比如文本组件中的非必选参数content,示例如下:Text('test')//string类型的参数//$r形式引入应用资源,可应用于多语言场景Text($r('app.string.title_value'))//无参数形式Text()6.4.1组件的创建变量或表达式也可以用于参数赋值,其中表达式返回的结果类型必须满足参数类型要求,如图。Image(this.imagePath)Image('https://'+this.imageUrl)Text(`count:${this.count}`)6.4.2配置属性属性方法以“.”链式调用的方式配置系统组件的样式和其他属性,建议每个属性方法单独写一行,组件属性配置示例如附录1所示。//组件的多个属性Image('test.jpg').alt('error.jpg').width(100).height(100)//除了直接传递常量参数外,还可以传递变量或表达式Text('hello').fontSize(this.size)Image('test.jpg').width(this.count%2===0?100:200).height(this.offset+100)/*对于系统组件,ArkUI还为其属性预定义了一些枚举类型供开发者调用,枚举类型可以作为参数传递,但必须满足参数类型要求,比如下面的例子就预定义类一些变量应用到了属性中*/Text('hello').fontSize(20).fontColor(Color.Red).fontWeight(FontWeight.Bold)6.4.3配置事件事件方法以“.”链式调用的方式配置系统组件支持的事件,建议每个事件方法单独一行。下面例子声明了一个Button组件,展示文字为Clickme,并且该组件通过onClick绑定了点击事件,该事件通过箭头函数来说明具体触发的执行操作,如图使用箭头函数配置事件。Button('Clickme').onClick(()=>{this.myText='ArkUI';})6.4.3配置事件也可以使用组件的成员函数来作为事件方法,以下例子声明了一个myClickHandler函数,通过语法将其作为事件的触发方法,如图
使用成员函数配置配置事件。myClickHandler():void{this.counter+=2;}...Button('addcounter').onClick(this.myClickHandler.bind(this))6.4.3配置事件还可以在事件方法中使用声明好的箭头函数,如图使用声明的箭头函数配置事件。fn=()=>{(`counter:${this.counter}`)this.counter++}...Button('addcounter').onClick(this.fn)6.4.4配置子组件如果组件支持子组件配置,则需在尾随闭包"{...}"中为组件添加子组件的UI描述。Column、Row、Stack、Grid、List等组件都是容器组件,图6-14是简单的Column组件配置子组件的示例代码。Column(){Text('Hello').fontSize(100)Divider()Text(this.myText).fontSize(100).fontColor(Color.Red)}6.4.4配置子组件容器组件均支持子组件配置,可以实现相对复杂的多级嵌套,如图6-15,Column组件的多级嵌套。Column(){Row(){Image('test1.jpg').width(100).height(100)Button('click+1').onClick(()=>{('+1clicked!');})}}6.5自定义组件PART05在ArkUI中,UI显示的内容均为组件,由框架直接提供的称为系统组件,由开发者定义的称为自定义组件。在进行UI界面开发时,通常不是简单的将系统组件进行组合使用,而是需要考虑代码可复用性、业务逻辑与UI分离,后续版本演进等因素。因此,将UI和部分业务逻辑封装成自定义组件是不可或缺的能力,自定义组件具有以下特点:l可组合:允许开发者组合使用系统组件、及其属性和方法。l可重用:自定义组件可以被其他组件重用,并作为不同的实例在不同的父组件或容器中使用。l数据驱动UI更新:通过状态变量的改变,来驱动UI的刷新。6.5自定义组件PART05//基本用法@ComponentstructHelloComponent{@Statemessage:string='Hello,World!';build(){//HelloComponent自定义组件组合系统组件Row和TextRow(){Text(this.message).onClick(()=>{//状态变量message的改变驱动UI刷新,UI从'Hello,World!'刷新为'Hello,ArkUI!'this.message='Hello,ArkUI!';})}}}6.5自定义组件如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。刚刚声明的HelloComponent组件,可以在其他自定义组件中的build()函数中多次创建,实现自定义组件的重用,如附录3所示。@Entry@ComponentstructParentComponent{build(){Column(){Text('ArkUImessage')HelloComponent({message:'HelloWorld!'});//使用一次
Divider()HelloComponent({message:'你好,世界!'});//使用两次
}}}6.5.1自定义组件的基本结构自定义组件基于关键字struct实现,struct+自定义组件名+{...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new,另外自定义组件名、类名、函数名不能和系统组件名相同。 使用@Component来装饰struct关键字声明的数据结构,则这个自定义组件就具备了组件化的能力,同时需要通过build方法来描述UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的布尔类型参数,这个参数可以控制该组件是否为冻结状态,如图6-16所示。当组件被冻结时,则不会参与UI更新动作,这在一些由很多复杂组件组成的页面中能够有效的提高性能。关于组件冻结这里先不做具体说明,后续会进行详细介绍。@Component({freezeWhenInactive:true})structMyComponent{}注意:从APIversion9开始,该装饰器支持在ArkTS卡片中使用。从APIversion11开始,@Component可以接受一个可选的bool类型参数。6.5.1自定义组件的基本结构build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数,@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件,如图入口组件的基本声明。@Entry@ComponentstructMyComponent{build(){}}可以在@Entry内部配置三个属性,如表6-2@Entry可配置属性。名称类型必填说明routeNamestring否表示作为命名路由页面的名字。storage(API10+)LocalStorage否页面级的UI状态存储。useSharedStorage(API12+)boolean否是否使用LocalStorage.getShared()接口返回的LocalStorage实例对象,默认值false。6.5.1自定义组件的基本结构如果自定义组件需要复用,则需要使用@Reusable来装饰,被@Reusable装饰的自定义组件在每次需要被现实时不需要创建新的实例,这会提高页面的性能,特别是在有列表或者滚动视图的页面中。下图定义了一个可重用的自定义组件。@Reusable@ComponentstructMyComponent{ …}6.5.2成员变量和函数可以在自定义组件中添加本组件的成员变量和成员函数,如附录4所示。该例子中,message是一个使用@State装饰的状态变量,它会在组件中用于显示文本。someValue是一个普通的私有成员变量。handleClick是一个私有成员函数,当按钮被点击时,它会更新message的值。@ComponentstructMyCustomComponent{@Stateprivatemessage:string='Hello,ArkUI!';privatesomeValue:number=42;privatehandleClick(){this.message='ButtonClicked!';}build(){Column(){Text(this.message).fontSize(20).fontWeight(FontWeight.Bold)Button('ClickMe').onClick(()=>this.handleClick())}}}6.5.3自定义组件中的参数
在创建自定义组件时,可以通过参数传递来初始化组件的成员变量。如附录5,自定义组件中的参数传递,MyComponent通过构造函数接收countDownFrom和color两个参数来初始化其成员变量。通过这种方式,便可以在ArkUI的自定义组件中添加成员变量和成员函数,以实现更丰富的功能和逻辑。
在自定义组件的数据传递中,还可以将父组件中的信息传递给子组件,如图附录6,该代码定义了两个组件,parent组件和son组件,并且在parent组件中将成员函数submit通过son组件中的可选函数submitArrow传递给了son组件。6.5.4build函数所有声明在build()函数的语句,统称为UI描述,UI描述需要遵循以下规则:(1)@Entry装饰的自定义组件,其build()函数下的根节点唯一且必要,且必须为容器组件,其中ForEach禁止作为根节点。(2)@Component装饰的自定义组件,其build()函数下的根节点唯一且必要,可以为非容器组件,其中ForEach禁止作为根节点。在上述规则中有一个非常重要的概念:组件分为容器组件和非容器组件两类。容器组件可以包含一个或多个子组件,用于组织和管理其他组件的布局和行为。非容器组件不能包含其他组件,是构成用户界面的基本元素,附录7展示了自定义组件的基本规则。@Entry@ComponentstructMyComponent{build(){//根节点唯一且必要,必须为容器组件
Row(){ChildComponent()}}}
@ComponentstructChildComponent{build(){//根节点唯一且必要,可为非容器组件
Image('test.jpg')}}6.5.4build函数——build使用的反例在build()函数中有一些事情是不能做的:(1)不允许声明本地变量,反例如图6-19所示。(2)不允许在UI描述里直接使用,但允许在方法或者函数里使用,反例如图6-20所示。(3)不允许创建本地的作用域,反例如图6-21所示。build(){//反例:不允许声明本地变量
leta:number=1;}build(){//反例:不允许('printdebuglog');}build(){//反例:不允许本地作用域
{...}}图6-19图6-20图6-216.5.4build函数——build使用的反例(4)不允许调用没有用@Builder装饰的方法,允许系统组件的参数是TS方法的返回值,如附录8所示。(5)不允许使用switch语法,如果需要使用条件判断,请使用if。示例如附录9所示,UI描述中条件判断的使用。(6)不允许使用表达式,请使用if组件,示例如图6-22UI描述中表达式的使用。@ComponentstructParentComponent{doSomeCalculations(){}calcTextValue():string{return'HelloWorld';}@BuilderdoSomeRender(){Text(`HelloWorld`)}build(){Column(){//反例:不能调用没有用@Builder装饰的方法
this.doSomeCalculations();//正例:可以调用
this.doSomeRender();//正例:参数可以为调用TS方法的返回值
Text(this.calcTextValue())}}}build(){Column(){//反例:不允许使用switch语法
switch(expression){case1:Text('...')break;case2:Image('...')break;default:Text('...')break;}//正例:使用ifif(expression==1){Text('...')}elseif(expression==2){Image('...')}else{Text('...')}}}附录8附录9build(){Column(){//反例:不允许使用表达式
(this.aVar>10)?Text('...'):Image('...')//正例:使用if判断
if(this.aVar>10){Text('...')}else{Image('...')}}}图6-226.5.4build函数——build使用的反例(7)不允许直接改变状态变量,反例如附录10所示。@ComponentstructCompA{@Statecol1:Color=Color.Yellow;@Statecol2:Color=Color.Green;@Statecount:number=1;build(){Column(){//应避免直接在Text组件内改变count的值
Text(`${this.count++}`).width(50).height(50).fontColor(this.col1).onClick(()=>{this.col2=Color.Red;})Button("changecol1").onClick(()=>{this.col1=Color.Pink;})}.backgroundColor(this.col2)}}禁止在自定义组件的build()函数直接改变状态变量,主要是因为这种设计可能会造成循环渲染的风险,这样会使得UI渲染进入到一个不可控的死循环中从而引发页面错误。上述代码中变量col1决定了Text组件的字体颜色,col2决定了Column组件的背景颜色,这两个变量只有在更改时会触发对应组件的UI更新,如图6-23组件、变量和事件的关系。附录10图6-236.5.4build函数——build使用的反例附录10图6-23也就是说,当通过Text的触发事件导致col2更改时,Column组件会进行UI更新,Text组件不会更改。当Button的触发事件导致col1更改时,会去更新整个Text组件,其所有属性函数都会执行,按照这个逻辑来看,除了col1变量会更新以外,count变量也会自增1,因此不能在自定义组件的build()函数里直接修改状态变量。6.5.4build函数——build使用的反例当然还有一些更隐蔽的修改行为,比如在@Builder,@Extend或@Styles方法内改变状态变量,在渲染组件时调用函数来改变状态变量,这些都是不允许的,如图6-24其他渲染反例。//反例:渲染组件时调用函数来改变状态变量Text('${this.calcLabel()}')再比如,如果://反例:对数据做出了修改,也会引发状态变量修改@Statearr:Array<...>=[...];ForEach(this.arr.sort().filter(...),item=>{...})//正确的执行方式为:filter返回一个新数组,后面的sort方法才不会改变原数组this.arrForEach(this.arr.filter(...).sort(),item=>{...})图6-24因此在写自定义组件时,要时时刻刻关注该组件是否在渲染时有更改状态变量的行为,包括很多间接的、隐蔽的一些改变行为,否则就会引发循环渲染的异常以及非预期的渲染效果。6.5.5自定义组件通用样式自定义组件通过“.”的方式进行链式调用来设置通用样式,如附录11所示。这里要说明,ArkUI给自定义组件设置样式时,相当于给MyComponent2套了一个不可见的容器组件,而这些样式是设置在容器组件上的,而非直接设置给MyComponent2的Button组件。通过渲染结果我们可以很清楚的看到,背景颜色红色并没有直接生效在Button上,而是生效在Button所处的开发者不可见的容器组件上。@ComponentstructMyComponent2{build(){Button(`HelloWorld`)}}
@Entry@ComponentstructMyComponent{build(){Row(){MyComponent2().width(200).height(300).backgroundColor(Color.Red)}}}6.5.6页面和自定义组件生命周期首先需要先明确页面和自定义组件之间的关系,自定义组件是由@Component修饰的UI单元,可以通过组合实现UI复用,可以调用组件的声明周期;页面可以由一个或多个自定义组件组成,而@Entry修饰的自定义组件是页面入口组件,也就是页面的根节点,一个页面只能有一个这种组件。页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:(1)onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。(2)onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。(3)onBackPress:当用户点击返回按钮时触发。6.5.6页面和自定义组件生命周期组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:(1)aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。(2)onDidBuild:组件build()函数执行完成之后回调该接口,不建议在onDidBuild函数中更改状态变量、使用animateTo等功能,这可能会导致不稳定的UI表现。(3)aboutToDisappear:该函数在自定义组件销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。从上面的生命周期函数可以看出管理页面的生命周期有3个函数,管理自定义组件也有3个函数。它们都是在页面或组件处于某种状态时所触发的钩子函数,这样就可以在组件或页面的各个时间点添加更为灵活的程序设计,右图为生命周期的流程图。6.5.6页面和自定义组件生命周期在了解生命周期的整体知识之前,首先需要了解自定义组件的创建流程,主要分为以下几步:1.
自定义组件的创建:自定义组件的实例由ArkUI框架创建。2.
初始化自定义组件的成员变量:通过本地默认值或者构造方法传递参数来初始化自定义组件的成员变量,初始化顺序为成员变量的定义顺序。3.
如果开发者定义了aboutToAppear,则执行aboutToAppear方法。4.
在首次渲染的时候,执行build方法渲染系统组件,如果子组件为自定义组件,则创建自定义组件的实例。在首次渲染的过程中,框架会记录状态变量和组件的映射关系,当状态变量改变时,驱动其相关的组件刷新。5.
如果开发者定义了onDidBuild,则执行onDidBuild方法。6.5.6页面和自定义组件生命周期当组件创建好之后,当组件中的状态变量发生改变时,需要对组件进行重新渲染,自定义组件的重新渲染,其过程是:当事件句柄被触发(比如设置了点击事件,即触发点击事件)改变了状态变量时,或者LocalStorage/AppStorage中的属性更改,并导致绑定的状态变量更改其值时,此时框架观察到了变化,将重新启动渲染。同时,框架也知道哪些状态变量管理了哪些UI组件,以及这些UI组件对应的更新函数,通过执行这些UI组件的更新函数,实现最小化更新。再来说一下组件的删除问题:如果if组件的分支改变,或者ForEach循环渲染中数组的个数改变,组件将被删除。在删除组件之前,将调用其aboutToDisappear生命周期函数,标记着该节点将要被销毁。6.5.6页面和自定义组件生命周期在ArkUI中,节点删除机制的描述可以分为几个步骤来理解:1.
后端节点摘下:当一个节点被删除时,后端节点(也就是实际的组件或元素)会直接从组件树上移除。这意味着这个节点不再参与渲染和更新过程。2.
后端节点被销毁:在节点被摘下后,后端节点会被销毁。这个过程通常涉及释放与该节点相关的资源,比如事件监听器、内存等,确保不再占用系统资源。3.
前端节点解引用:前端节点是指在JavaScript层面上与后端节点对应的对象。当后端节点被摘下并销毁后,前端节点会解除对后端节点的引用。这样,前端节点就不再能访问到后端节点的任何信息或状态。4.
前端节点的垃圾回收:当前端节点不再有任何引用(即没有其他对象或变量指向它)时,JavaScript虚拟机的垃圾回收机制会将其标记为可回收的对象。最终,这些前端节点会被垃圾回收器回收,释放内存。总结来说,ArkUI的节点删除机制通过这几个步骤确保了节点的有效管理和资源的释放,避免了内存泄漏和不必要的资源占用。这种机制在现代前端框架中是非常常见的,旨在提高性能和维护性。附录12为Index.ets中的MyComponent根组件和Child子组件。(附录12见下页ppt)6.5.6页面和自定义组件生命周期//Index.etsimport{router}from'@kit.ArkUI';@Entry@ComponentstructMyComponent{
@StateshowChild:boolean=true;
@StatebtnColor:string="#FF007DFF";
//只有被@Entry装饰的组件才可以调用页面的生命周期
onPageShow(){
('IndexonPageShow');
}
//只有被@Entry装饰的组件才可以调用页面的生命周期
onPageHide(){
('IndexonPageHide');
}
//只有被@Entry装饰的组件才可以调用页面的生命周期
onBackPress(){
('IndexonBackPress');
this.btnColor="#FFEE0606";
returntrue//返回true表示页面自己处理返回逻辑,不进行页面路由;
//返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理
}
//组件生命周期
aboutToAppear(){
('MyComponentaboutToAppear');
}
//组件生命周期
onDidBuild(){
('MyComponentonDidBuild');
}
//组件生命周期
aboutToDisappear(){
('MyComponentaboutToDisappear');
}
build(){
Column(){
//this.showChild为true,创建Child子组件,执行ChildaboutToAppear
if(this.showChild){
Child()
}
//this.showChild为false,删除Child子组件,执行ChildaboutToDisappear
Button('deleteChild')
.margin(20)
.backgroundColor(this.btnColor)
.onClick(()=>{
this.showChild=false;
})
//push到page页面,执行onPageHide
Button('pushtonextpage')
.onClick(()=>{
router.pushUrl({url:'pages/page'});
})
}
}}附录126.5.6页面和自定义组件生命周期附录13附录13为从主页MyComponent跳转后的页面page.ets。在该示例中,Index.ets页面包含两个自定义组件,一个是被@Entry装饰的MyComponent,它是页面的入口组件,也就是页面的根节点;一个是Child组件,这个组件是MyComponent组件的子组件,并且在首页中有两个button,第一个是用来解绑子组件Child的,第二个是用来跳转到page.ets页。可以看到MyComponent被@Entry所修饰,只有被@Entry装饰的节点才可以使用页面的生命周期方法,因此MyComponent中声明的三个页面生命周期函数(onPageShow/onPageHide/onBackPress)会生效。MyComponent和Child也有其组件的生命周期(aboutToAppear/onDidBuild/aboutToDisappear)。//page.ets@Entry@Componentstructpage{@StatetextColor:Color=Color.Black;@Statenum:number=0;
onPageShow(){this.num=5;}
onPageHide(){console.log("pageonPageHide");}
onBackPress(){//不设置返回值按照false处理
this.textColor=Color.Grey;this.num=0;}
aboutToAppear(){this.textColor=Color.Blue;}
build(){Column(){Text(`num的值为:${this.num}`).fontSize(30).fontWeight(FontWeight.Bold).fontColor(this.textColor).margin(20).onClick(()=>{this.num+=5;})}.width('100%')}}6.5.6页面和自定义组件生命周期图6-26父组件、子组件与生命周期的关系以上示例的生命周期和组件之间的联系,如图6-26父组件、子组件与生命周期的关系。以上示例中,应用冷启动的初始化流程为三个大步骤,先执行MyComponent组件中的生命周期函数,然后执行Child子组件中的生命周期函数,最后在执行页面的onPageShow函数,具体如图6-27生命中周期函数的执行顺序。1.MyComponentaboutToAppear-->MyComponentbuild-->MyComponentonDidBuild2.ChildaboutToAppear-->Childbuild-->ChildonDidBuild3.IndexonPageShow图6-27生命周期函数的执行顺序6.5.6页面和自定义组件生命周期点击“deleteChild”,showChild变成false,删除Child组件,会执行Child中aboutToDisappear方法。点击“pushtonextpage”,调用router.pushUrl接口,跳转到另外一个页面,当前Index页面隐藏,执行页面生命周期IndexonPageHide。此处调用的是router.pushUrl接口,这是因为Index页面被隐藏,并没有销毁,所以只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。如果代码中的方法不是router.pushUrl而是router.replaceUrl接口,则当前的Index页面被销毁。因此这个组件会从组件树上摘下子树,因此执行的生命周期流程将变为:新页面首先进行初始化生命周期流程,然后执行Index中的onPageHide方法,然后是MyComponentaboutToDiappear方法,最后执行得是Child的AboutToDisappear方法。当点击返回按钮的时候,触发页面生命周期IndexonBackPress,且触发返回一个页面后会导致当前Index页面被销毁。最小化应用或者应用进入后台,触发Index的onPageHide。当前Index页面没有被销毁,所以并不会执行组件的aboutToDisappear。应用回到前台,执行Index的onPageShow。退出应用时的执行顺序为:IndexonPageHide-->MyComponentaboutToDisappear-->ChildaboutToDisappear。6.5.7监听页面生命周期可以实现在自定义组件中监听页面的生命周期,见附录14。//Index.etsimport{uiObserver,router,UIObserver}from'@kit.ArkUI';//导入工具对象@Entry@ComponentstructIndex{//定义一个inedex组件
listener:(info:uiObserver.RouterPageInfo)=>void=(info:uiObserver.RouterPageInfo)=>{
letrouterInfo:uiObserver.RouterPageInfo|undefined=this.queryRouterPageInfo();
if(info.pageId==routerInfo?.pageId){
if(info.state==uiObserver.RouterPageState.ON_PAGE_SHOW){
console.log(`IndexonPageShow`);
}elseif(info.state==uiObserver.RouterPageState.ON_PAGE_HIDE){
console.log(`IndexonPageHide`);
}
}
}
aboutToAppear():void{
letuiObserver:UIObserver=this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate',this.listener);
}
aboutToDisappear():void{
letuiObserver:UIObserver=this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate',this.listener);
}
build(){
Column(){
Text(`thispageis${this.queryRouterPageInfo()?.pageId}`)
.fontSize(25)
Button("pushself")
.onClick(()=>{
router.pushUrl({
url:'pages/Index'
})
})
Column(){
SubComponent()
}
}
}}@ComponentstructSubComponent{
listener:(info:uiObserver.RouterPageInfo)=>void=(info:uiObserver.RouterPageInfo)=>{
letrouterInfo:uiObserver.RouterPageInfo|undefined=this.queryRouterPageInfo();
if(info.pageId==routerInfo?.pageId){
if(info.state==uiObserver.RouterPageState.ON_PAGE_SHOW){
console.log(`SubComponentonPageShow`);
}elseif(info.state==uiObserver.RouterPageState.ON_PAGE_HIDE){
console.log(`SubComponentonPageHide`);
}
}
}
aboutToAppear():void{
letuiObserver:UIObserver=this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate',this.listener);
}
aboutToDisappear():void{
letuiObserver:UIObserver=this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate',this.listener);
}
build(){
Column(){
Text(`SubComponent`)
}
}}6.5.7监听页面生命周期在上述代码中,先是通过@kit.ArkUI导入了uiObserver、router和UIObserver这几个对象,后续代码会基于这些导入的内容来实现页面的相关逻辑,比如监听页面状态变化、进行页面跳转等操作。然后定义了一个Index组件,这个Index组件使用@Entry和@Component来修饰,表明其是一个自定义的根组件。然后定义了一个listener函数,它接收一个类型为uiObserver.RouterPageInfo的参数info。这个函数的作用是根据传入的页面信息(info)与当前组件通过queryRouterPageInfo方法获取到的页面信息(routerInfo)进行对比,如果页面
ID
匹配,再根据页面状态(ON_PAGE_SHOW表示页面显示、ON_PAGE_HIDE表示页面隐藏)来在控制台打印相应的日志,用于跟踪页面的显示隐藏情况。而aboutToAppear方法是组件的生命周期钩子方法之一,在组件即将显示时被调用。这里首先通过this.getUIContext().getUIObserver()获取到UIObserver实例,然后调用它的on方法,注册了一个名为routerPageUpdate的事件监听器,当这个事件触发时会执行之前定义的listener函数,以此来监听页面更新相关的事件,比如页面状态变化等情况。与aboutToAppear相对应,这个方法在组件即将消失时被调用。在这里获取UIObserver实例后,调用off方法移除了之前注册的routerPageUpdate事件监听器(对应的执行函数是listener),避免出现不必要的事件处理或者内存泄漏等问题。6.5.7监听页面生命周期Index组件中的build方法用于构建组件界面布局:l外层是一个垂直方向排列的Column容器,里面包含了多个子元素。lColumn中声明了一个Text组件,它显示的文本内容包含了当前页面的
ID(通过queryRouterPageInfo方法获取,这里做了可选链操作以防返回undefined),并且设置了字体大小为25。l声明了一个Button按钮组件,按钮上显示文本“pushself”,并且给按钮添加了点击事件监听器。当按钮被点击时,会通过router.pushUrl方法进行页面跳转,跳转到pages/Index这个页面路径对应的页面,这里实现了页面的自我推送(可能是用于测试页面切换等相关逻辑)。l在内部又嵌套了一个Column容器,里面包含了一个自定义的SubComponent组件,这个组件会按照它自身的定义来进行渲染展示。声明的子组件SubComponent与刚刚介绍的父组件有相同的结构和类似的逻辑,用于构建应用中某个子部分的界面以及处理相关的页面状态变化监听等功能,包括其中声明的listener,只不过打印的日志信息表明是SubComponent这个组件对应的页面显示隐藏情况,用于区分不同组件下的页面状态跟踪,子组件中的aboutToAppear函数与aboutToDisappear函数也与父组件的作用类似,包括子组件的build函数也是用于声明子组件的UI样式组成。6.5.8自定义组件的自定义布局如果需要通过测算的方式布局自定义组件内子组件的位置,建议使用以下接口:(1)onMeasureSize:组件每次布局时触发,计算子组件的尺寸,其执行时间先于onPlaceChildren。(2)onPlaceChildren:组件每次布局时触发,设置子组件的起始位置。如附录15(见后面ppt),自定义组件的布局示例。//xxx.ets@Entry@ComponentstructIndex{
build(){
Column(){
CustomLayout({builder:ColumnChildren})
}
}}6.5.8自定义组件的自定义布局//通过builder的方式传递多个组件,作为自定义组件的一级子组件(即不包含容器组件,如Column)@BuilderfunctionColumnChildren(){
ForEach([1,2,3],(index:number)=>{//遍历数组[1,2,3]
Text('S'+index)
.fontSize(30)
.width(100)
.height(100)
.borderWidth(2)
.offset({x:10,y:20})
})}6.5.8自定义组件的自定义布局@ComponentstructCustomLayout{
@Builder
doNothingBuilder(){
};
@BuilderParambuilder:()=>void=this.doNothingBuilder;
@StatestartSize:number=100;
result:SizeResult={
width:0,
height:0
};
//第一步:计算各子组件的大小
onMeasureSize(selfLayoutInfo:GeometryInfo,children:Array<Measurable>,constraint:ConstraintSizeOptions){
letsize=100;
children.forEach((child)=>{
letresult:MeasureResult=child.measure({minHeight:size,minWidth:size,maxWidth:size,maxHeight:size})
size+=result.width/2;
})
this.result.width=100;
this.result.height=400;
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 强化消防安全思想意识
- 生产准备人员安全讲解
- AI前沿论坛集锦
- 安全生产讲话要点汇编讲解
- AI在农产品加工与质量检测中的应用
- 2026浙教版小学信息科技三年级上册第一单元教学设计
- 员工薪酬及福利管理办法
- 公关服务公司车辆管理制度
- 2026电声工程师面试题及答案
- 第4练《实践是检验真理的唯一标准》课后巩固-语文拓展模块下册(高教版)山东省版《一课一练》答案
- 体育行业体育赛事运营总监岗位招聘考试试卷及答案
- 辐射安全隐患排查
- 2025年六安辅警招聘考试真题完整参考答案详解
- 2025年南通市中考道德与法治试题卷(含标准答案)
- 2024-2025学年四川省成都市成都七中高一(下)期末数学试卷(含答案)
- 2025广东档案培训试题及答案
- 应急救援安全应知应会考试题库及答案
- TCMARQ001-2018膜式燃气表膜片
- 2025-2030中国光伏电站用地政策演变及土地利用效率评估
- 2025年事业单位考试真题答案
- 山东省潍坊市2024-2025学年度高一下学期期末语文试题及参考答案
评论
0/150
提交评论