JS布局开发指南:
在上一次https://i.cnblogs.com/posts/edit-done;postId=15339184咱们学习了鸿蒙Java布局开发的一些基础,而对于鸿蒙来说它是还支持用JS语言来进行开发的,所以这里也从基础开始了解一下。
JS UI特性:
上官网https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js-overview-0000000000500376可以看到,对于JS语言的开发官方有一个专有名词:
其中可以看到可以基于JS(JavaScript),也可以基于TS(TypeScript),这里重点是看JS这块的,先来了解一下它的特点:
JS UI的整体架构:
这块官网也有解释,这里贴出来:
使用基于JS扩展的类Web开发范式的方舟开发框架,包括应用层(Application)、前端框架层(Framework)、引擎层(Engine)和平台适配层(Porting Layer)。
- Application
- Framework
前端框架层主要完成前端页面解析,以及提供MVVM(Model-View-ViewModel)开发模式、页面路由机制和自定义组件等能力。
- Engine
引擎层主要提供动画解析、DOM(Document Object Model)树构建、布局计算、渲染命令构建与绘制、事件管理等能力。
- Porting Layer
适配层主要完成对平台层进行抽象,提供抽象接口,可以对接到系统平台。比如:事件对接、渲染管线对接和系统生命周期对接等。
开发指南:
JS UI框架支持纯JavaScript、JavaScript和Java混合语言开发。JS FA是指基于JavaScript或JavaScript和Java混合开发的FA【Feature Ability】,FA在HarmonyOS上运行时需要的基类AceAbility。
AceAbility基类:
它是JS FA在HarmonyOS上运行环境的基类,继承自Ability。通过JS来开发HarmonyOS需要继承此类来定义自己的Ability,这块在上一次学习中已经使用到了:
如何加载JS FA?
我们知道可以新建不同的JS Component:
而对于想要加载指定的JS Component,我们需要在这块进行这么一个调用对吧:
对于如何加载JS FA这里再来探讨一下:
JS FA生命周期事件分为应用生命周期和页面生命周期,应用通过AceAbility类中的setInstanceName()接口设置该Ability的实例资源,并通过AceAbility窗口进行显示以及全局应用生命周期管理。
setInstanceName()的参数"name"指实例名称,实例名称与config.json文件中js.name的值对应:
若未修改实例名,而使用了缺省值default,则无需调用此接口。若修改了实例名,则需要在应用Ability实例的onStart()中调用此接口,并将参数“name”设置为修改后的实例名称。其中需要注意setInstanceName()方法需要在MainAbility的onStart()中super.onStart()前调用此接口才行。
布局单位:
在上一次学习Java布局时学习过相关的布局单位,这里回忆一下:
也就是会使用vp的虚拟像素单位,那在JS模式下布局单位又是怎样呢?在HarmonyOS的JS UI框架中为不同类型的设备提供了一个基准宽度【可以参考官网:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-js-building-ui-layout-intro-0000000000513453】:
- 智慧屏的基准宽度为720px(px指逻辑像素,非物理像素);
- 穿戴设备的基准宽度为454px;
如何理解基准宽度呢?以智慧屏为例,720px的基准宽度,如果你在代码设置了某个布局或控件的宽度为720px,那么无论是它运行在1440px的设备上还是运行在其他分辨率的设备上都能实现占满整个屏幕宽度的效果;另外,我们不妨计算一下,720px的基准宽度,运行在1440px的设备上它的1px代表2px的设备像素,所以对于视觉同学出视觉稿的时候就可以按照720px的宽度标准来进行视觉标注了。
实战:基于JS UI实现计算器界面布局
效果:
接下来则使用JS来操练一下布局,最终样子长这样:
其实是来源于mac上的计算器布局:
实现:
1、创建工程:
此时运行看一下效果,启动穿戴设备模拟器:
其中创建的工程中有两个java类:
默认对于前端的小伙伴通常这俩java代码不需要动,直接编写js代码既可。
2、页面布局编写
接下来则来进行页面的布局,先来对这两行进行处理:
接下来定义一下css样式:
.container { flex-direction: column; justify-content: center; align-items: center; background-color: #bebebe; } .result { text-align: right; color: black; padding-right: 15px; width: 454px;/* 由于是可穿戴设备,则基准宽度为454px */ } .row { flex-direction: row; } .cell-normal { background-color: #a9a9a9; margin: 1px; flex: 1;/*占满整个屏幕的宽度*/ color: #000; text-align: center; }
此时运行看一下:
感觉字体太大了一点,改小一下:
接下来需要将最后一列弄成黄颜色对吧,所以再修改一下样式:
再运行:
接下来再来添加剩余的行:
<div class="container"> <text class="result">0</text> <div class="row"> <text class="cell-normal">AC</text> <text class="cell-normal">+/-</text> <text class="cell-normal">%</text> <text class="cell-normal cell-special">÷</text> </div> <div class="row"> <text class="cell-normal">7</text> <text class="cell-normal">8</text> <text class="cell-normal">9</text> <text class="cell-normal cell-special">x</text> </div> <div class="row"> <text class="cell-normal">4</text> <text class="cell-normal">5</text> <text class="cell-normal">6</text> <text class="cell-normal cell-special">-</text> </div> <div class="row"> <text class="cell-normal">1</text> <text class="cell-normal">2</text> <text class="cell-normal">3</text> <text class="cell-normal cell-special">+</text> </div> <div class="row"> <text class="cell-normal">0</text> <text class="cell-normal">.</text> <text class="cell-normal cell-special">=</text> </div> </div>
运行:
对于最后一行,应该是0占两个元素的宽度才行:
这里给它再增加另一个样式:
运行:
通过这个小小的案例可以发现,用JS来开发鸿蒙,Java代码没有动过,只需要编写JS相当的代码既可。
页面生命周期解析:
和Android开发一样,HarmonyOS也有自己的生命周期,了解它们有助于我们更好的来开发app,由于它有Java和JS两种开发方式,所以下面分别进行学习。
Java开发模式下Ability和AbilitySlice的生命周期:
在HarmonyOS中Ability(可类比Android的Activity)和AbilitySlice(可类比Android的的Fragment)是页面的基本单元,用户操作或系统管理等行为均会引起页面实例在其生命周期的不同状态之间进行转换。Ability类提供的回调机制能够让页面及时感知外界变化,从而正确地应对状态变化(比如释放资源)。关于这块可以参考官网:https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ability-page-lifecycle-0000000000029840。
页面(Ability)生命周期回调:
Page生命周期的不同状态转换及其对应的回调,如下所示。
关于各状态的介绍这里将官网的贴出来,如果熟悉Android的生命周期的话对于它理解非常容易。
- onStart()
当系统首次创建Page实例时,触发该回调。对于一个Page实例,该回调在其生命周期过程中仅触发一次【类似于Android的onCreate()】,Page在该逻辑后将进入INACTIVE状态。开发者必须重写该方法,并在此配置默认展示的AbilitySlice。
@Override public void onStart(Intent intent) { super.onStart(intent); super.setMainRoute(FooSlice.class.getName()); }
- onActive()
Page会在进入INACTIVE状态后来到前台,然后系统调用此回调。Page在此之后进入ACTIVE状态,该状态是应用与用户交互的状态。Page将保持在此状态,除非某类事件发生导致Page失去焦点,比如用户点击返回键或导航到其他Page。当此类事件发生时,会触发Page回到INACTIVE状态,系统将调用onInactive()回调。此后,Page可能重新回到ACTIVE状态,系统将再次调用onActive()回调。因此,开发者通常需要成对实现onActive()和onInactive(),并在onActive()中获取在onInactive()中被释放的资源。
- onInactive()
当Page失去焦点时,系统将调用此回调,此后Page进入INACTIVE状态。开发者可以在此回调中实现Page失去焦点时应表现的恰当行为。
- onBackground()
如果Page不再对用户可见,系统将调用此回调通知开发者用户进行相应的资源释放,此后Page进入BACKGROUND状态。开发者应该在此回调中释放Page不可见时无用的资源,或在此回调中执行较为耗时的状态保存操作。
- onForeground()
处于BACKGROUND状态的Page仍然驻留在内存中,当重新回到前台时(比如用户重新导航到此Page),系统将先调用onForeground()回调通知开发者,而后Page的生命周期状态回到INACTIVE状态。开发者应当在此回调中重新申请在onBackground()中释放的资源,最后Page的生命周期状态进一步回到ACTIVE状态,系统将通过onActive()回调通知开发者用户。
- onStop()
系统将要销毁Page时,将会触发此回调函数,通知用户进行系统资源的释放【类似于Android的onDestroy()】。销毁Page的可能原因包括以下几个方面:
- 用户通过系统管理能力关闭指定Page,例如使用任务管理器关闭Page。
- 用户行为触发Page的terminateAbility()方法调用,例如使用应用的退出功能。
- 配置变更导致系统暂时销毁Page并重建。
- 系统出于资源管理目的,自动触发对处于BACKGROUND状态Page的销毁。
AbilitySlice生命周期:
AbilitySlice作为Page的组成单元,其生命周期是依托于其所属Page生命周期的。AbilitySlice和Page具有相同的生命周期状态和同名的回调,当Page生命周期发生变化时,它的AbilitySlice也会发生相同的生命周期变化。此外,AbilitySlice还具有独立于Page的生命周期变化,这发生在同一Page中的AbilitySlice之间导航时,此时Page的生命周期状态不会改变。
AbilitySlice生命周期回调与Page的相应回调类似,因此不再赘述。由于AbilitySlice承载具体的页面,开发者必须重写AbilitySlice的onStart()回调,并在此方法中通过setUIContent()方法设置页面,如下所示:
@Override protected void onStart(Intent intent) { super.onStart(intent); setUIContent(ResourceTable.Layout_main_layout); }
AbilitySlice实例创建和管理通常由应用负责,系统仅在特定情况下会创建AbilitySlice实例。例如,通过导航启动某个AbilitySlice时,是由系统负责实例化;但是在同一个Page中不同的AbilitySlice间导航时则由应用负责实例化。
实践:
接下来回到代码上,通过给Ability增加一些生命周期的日志来实际观测一下整个生命周期的过程,这里还是以上面操练JSLayout这个工程为例:
package com.example.jslayout; import ohos.aafwk.content.Intent; import ohos.ace.ability.AceAbility; public class MainAbility extends AceAbility { @Override public void onStart(Intent intent) { super.onStart(intent); log("onStart"); } @Override public void onActive() { super.onActive(); log("onActive"); } @Override public void onInactive() { super.onInactive(); log("onInactive"); } @Override public void onBackground() { super.onBackground(); log("onBackground"); } @Override public void onForeground(Intent intent) { super.onForeground(intent); log("onForeground"); } @Override public void onStop() { super.onStop(); log("onStop"); } private void log(String log) { System.out.println("----;Ability log:" + log); } }
运行,在启动时会走这俩方法:
此时咱们将它切到后台:
接下来则从后台再切回前台,那对于鸿蒙模拟器如何进行切换呢?
这个跟Android的图标有点不一样,通常Android切进程是用的那个正方形的图标,下面来试一下:
接着再退出app:
其中发现居然退了两次才全部退出,这个应该跟Ability的模式设置有关,就类似于Activity的launchMode设置一样,关于这块的内容之后再研究。
JS开发模式下页面的生命周期:
概述:
应用生命周期主要有两个:应用创建时调用的onCreate()和应用销毁时触发的onDestroy。
一个应用中可能会有多个页面,一个页面一般包括onInit、onReady、onShow和onDestroy等在页面创建、显示和销毁时会触发调用的事件:
- onInit():表示页面的数据已经准备好,可以使用js文件中的data数据。
- onReady():表示页面已经编译完成,可以将界面显示给用户。
- onShow():JS UI只支持应用同时运行并展示一个页面,当打开一个页面时,上一个页面就销毁了。当一个页面显示的时候,会调用onShow()。
- onDestroy():页面销毁时被调用。
关于应用生命周期和页面生命周期的关系可以看下图:
关于这块还可以参考官网的说明:https://developer.harmonyos.com/cn/docs/documentation/doc-references/js-framework-lifecycle-0000001113708038, 其中有一个更加详细的图:
其中App生命周期指的是:
而页面A的生命周期接口的调用顺序如下:
- 打开页面A:onInit() -> onReady() -> onShow()
- 在页面A打开页面B:onHide()
- 从页面B返回页面A:onShow()
- 退出页面A:onBackPress() -> onHide() -> onDestroy()
- 页面隐藏到后台运行:onInactive() -> onHide()
- 页面从后台运行恢复到前台:onShow() -> onActive()
而具体各生命周期出现的时机可以参考官方的说明。
实践:
接下来则回到js页面中针对不同的回调打印一些日志,然后运行看一下:
此时在html中将日志数据输出出来:
此时运行发现显示的状态有问题。。
这是因为这里忘了声明了:
修改一下:
再运行:
此时将它切到后台,再切前台看一下:
可以看到从后台切回前台又开了一个新页面,按一下back键此时就可以看到之前显示的页面里面多了一个onShow()了。
如何进行页面间跳转和传参?
对于页面之间的跳转少不了是要进行参数传递的对吧,同样的这里也是分别看一下在Harmony OS中Java和JS两者页面是如何传参的。
页面跳转和传参(Java):
页面间跳转分为Page(Ability)内跳转和Page(Ability)外跳转两种场景:两种场景跳转都需要借助Intent,另外传递参数也可以借助Intent来携带参数(这点和Android相似)。
Page(Ability)内跳转:
这种场景的跳转类似于Android应用内的跳转,在同一个Page(Ability)内跳转时,当发起跳转的AbilitySlice和跳转目标的AbilitySlice处于同一个Page时,可以通过present()方法实现跳转:
如果在跳转返回时需要获得其返回结果,则可以使用presentForResult()实现跳转。用户从跳转目标AbilitySlice返回时,系统将回调onResult()来接收和处理返回结果,此时需要重写该方法。返回结果由跳转目标AbilitySlice在其生命周期内通过setResult()进行设置。
从上面的代码来看,其使用方式基本跟Android雷同,也很好理解,所以这里就不演示了。
这里需要注意的:系统为每一个Page维护了一个AbilitySlice实例的栈,每个进入前台的AbilitySlice实例均会入栈。当开发者在调用present()或presentForResult()时指定的AbilitySlice实例已经在栈中存在时,则栈中位于此实例之上的AbilitySlice均会出栈并被销毁。
Page(Ability)外跳转:
这种场景类似于Android的应用间的跳转,由于不同page中的AbilitySlice相互不可见,因此无法通过present()或presentForResult()方法直接导航到其它Page的AbilitySlice,AbilitySlice作为Page的内部单元,以Action的形式对外暴露,因此可以通过配置Intent的Action导航到目标AbilitySlice。Page间的导航可以使用startActivity()或startAbilityForResult()方法,获得返回结果的回调为onAbilityResult()。在Ability中调用setResult()可以设置返回结果。另外,能够跳转到其它Page(Ability)的前提是要跳转到的这个Page(Ability)需要配置对应的action【这个也跟Android很类似】:
第一步:配置action
1、首先需要在配置文件中声明对外提供的能力,以便系统据此找到自身并作为候选的请求处理者,比如:
2、在Ability中配置路由以便支持以此action导航到对应的AbilitySlice:
这样,当其它应用跳转到这个action时,就会自动打开DemoSlice这个Page了。
3、在Ability中处理请求,并调用setResult()方法暂存返回结果:
第二步:进行页面间跳转
此时我们就可以这样来调用了:
然后在这个回调中进行结果的处理:
非常容易理解,还是跟Android神似~~
页面跳转和传参(JS):
为了方便开发者在JS中进行页面跳转,HarmonyOS提供了router API,通过该API可以完成页面之间的导航:
注意:页面路由需要在页面渲染完成后才能调用,在onInit和onReady生命周期中页面还处于渲染阶段,在这两个生命周期方法中调用页面路由方法。
通过router.push()跳转到指定页面:
方法原型:router.push(OBJECT)
跳转到应用内的指定页面。
参数说明:
举例说明一下:
也就是要跳转到routerpage2这个页面,其携带了一些参数,接着在routerpager2就可以进行参数的接收了,如下:
实践:
接下来回到代码来使用一下在JS页面之间的跳转传参。
1、创建一个新的用来跳转的page:
这里用IDE的这个功能来进行JS Page的创建:
你会发现创建的目录有点问题:
很明显这样目录结构是不合理的,所以这里把代码还原一下,应该切一个视图这样来创建:
这样目录就和index页面在同一个层级了。
2、在page1中添加跳转到page2的逻辑:
注意:此时需要导一下包:
3、在page2中接收参数:
运行:
4、Page2增加back的功能:
目前已经跳转到了Page2了,可以按返回软键盘返回对吧,那如果用代码怎么返回呢?如下:
另外page2的文本大写得调小一点:
运行:
5、Page2返回时给Index传递参数:
这里就可以使用另一个api了,如下:
运行:
其中发现个小细节,就是back两次才退出,这个还是跟ability的launchType有关:
关于这块的东东,这里就不过多说明了,懂Android的都比较好理解。
结语:
关于页面跳转及数据传递这块还可以参考官网https://developer.harmonyos.com/cn/docs/documentation/doc-guides/ui-ts-page-redirection-data-transmission-0000001146626072,官网中给了一个很好的例子:
那下次准备从0来照着官方的源代码撸一遍,一是为了提高鸿蒙开发的码感,二是对于界面的数据传递知识进行巩固。