一.ImageAnimator组动画,类似于iOS中的组动画组件。
官方解释:“提供帧动画组件来实现逐帧播放图片的能力,可以配置需要播放的图片列表,每张图片可以配置时长”。就是不停的播放展示一组图片。
/// 1.我们事先可以准备好图片资源数组,它是一个ImageFrameInfo类型的数组,里面有各种属性。
export const ImageInfoList: ImageFrameInfo[] = [
{
src: $rawfile('greet/seq_0_0.png'),/// 图片资源
width: 112,/// 宽
height: 112 /// 高
duration: 2000,/// 设置本张图片播放的时长。
},
{
src: $rawfile('greet/seq_0_1.png'),
width: 112,
height: 112
},
]
/// 2.在构建UI时使用此图片资源数组
@State animationImageList: ImageFrameInfo[] = ImageInfoList
/// 控制播放状态(
Initial:动画初始状态;
Running:动画处于播放状态;
Paused:动画处于暂停状态;
Stopped:动画处于停止状态
)
@State animationStatus: AnimationStatus = AnimationStatus.Running
/// 3.ImageAnimator组件.
ImageAnimator()
.images(this.animationImageList)
.state(this.animationStatus)
.duration(2000) /// 注意:设置播放时长。当Images中任意一帧图片设置了单独的duration后,该属性设置无效。
/// 设置播放次数,默认播放一次,设置为-1时表示无限次播放。
.iterations(this.animationRepeatCount)
.width(112)
.aspectRatio(1)
二.@arkts.math.Decimal (高精度数学库Decimal)
官方解释:Decimal用于提供高精度数学库,主要用于提供高精度浮点运算能力。其实就是我们在平时开发中,使用number类型时,对于坐标计算等极有可能精度不准确,我们就可以使用这个Decimal类型。
let convertedSize: number = 3.1415926;
/// 返回一个保留小数点后1位数的Decimal对象,不进行小数的取舍。
let number = new Decimal(convertedSize).toDecimalPlaces(1);
其实还有其他的常用数学方法,不是这个高精度数学库的。
/// 1.Math取绝对值.
Math.abs(-12)
/// 2.Math向下取整.
Math.floor(7.6)
/// 3.Math向上取整.
Math.ceil(7.6)
/// 4.Math就近取整,也叫四舍五入取整.
Math.round(5.8)
三.多线程
Worker的优缺点和替代方案
优点:提供多线程并发能力,适合执行耗时操作,避免主线程阻塞。 缺点:需要手动管理生命周期,不支持设置调度优先级,性能上可能不如TaskPool。 替代方案:在需要高性能和多任务处理的场景下,可以考虑使用TaskPool,它支持负载均衡和自动扩缩容,更适合大多数场景。
3.1 我们常用的Worker可以使用这种方式创建,比较方便。它会自动生成代码和Worker文件夹。
3.2 自动生成的文件会有 onmessage、onmessageerror、onerror三个方法,我们可以在此文件中定义我们需要操作的方法。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。
也就是说子线程和宿主线程通过互相调用关键API postMessage和onmessage,来向对方发出消息和接收到对方发出的消息。
示范:
1.在UI侧
/// 声明阅读文件的worker.
private readWorker?: worker.ThreadWorker
private startWorker() {
/// 启动读取文件的worker,需要传入生成的worker文件的路径
this.readWorker = new worker.ThreadWorker('包名/ets/workers/ReadFileWorker.ets')
/// 接收来自worker侧的消息.
this.readWorker.onmessage = (event: MessageEvents) => {
console.log('接收到的来自worker耗时操作的数据',event.data)
}
// 开启读文件,通过postMessage向worker发出数据消息,ReadParam为定义的传输数据class
this.readWorker.postMessage(json.stringify(new ReadParam(1, this.filePath)))
}
2.在worker侧
/// 我们在自动生成的onmessage方法中进行我们自己的耗时操作,接收来自UI侧的消息.
workerPort.onmessage = (e: MessageEvents) => {
let param = JSON.parse(e.data) as ReadParam
// 读取文件
readFile(param.stringData)
}
/// 耗时异步操作.
async function readFile(filePath: string){
/// 向UI侧发送获取到的文件数据.
workerPort.postMessage(JSON.stringify(sendBytes))
}
四.记录一些我经常忘记的属性意义
- onAreaChange(event: (oldValue: Area, newValue: Area) => void): T
组件区域变化时触发该回调。仅会响应由布局变化所导致的组件大小、位置发生变化时的回调。由绘制变化所导致的渲染属性变化不会响应回调,如translate、offset。 返回的Area对象,里面包含了宽、高、Position信息。
/// 设置背景图片,它是通用属性,都可以用的。
.backgroundImage($r('app.media.icon_voice_assistant_bg'), ImageRepeat.NoRepeat)
/// 设置组件背景图片的宽高。参数为联合类型,既可以设置宽高也可以设置里面的枚举类型ImageSize
.backgroundImageSize(1)
五.动态共享包HSP注意事项.
1.动态共享包(HSP)是可以创建页面并路由过去的,而静态共享包(HAR)却不行。因为HAR并没有像HSP包中的"main_pages.json"这么一个文件用来管理路由路径。
2.创建一个HSP。
2.1 点击New -> 选择Module
2.2 然后选择Shared Library
2.3 创建好后的目录如下
2.4 如果你运行这时肯定报错,说找不到这个包。
这个问题是1.我们创建了但是并没有在整个工程的“oh-package.json5”文件中导入这个包 2.我们没有编辑Edit Configurations导致无法编译。
1.我们先这样
设置完毕别忘了右上角点击同步按钮同步一下生效.
2.然后我们再这样去编辑一下,再运行就可以了,不会报错了。
六.路由
路由的话大部分还是在用系统的router,但是官方已经不推荐使用它。推荐使用Navigation,但是大部分人嫌弃它有点麻烦,所以推荐用HMRouter,这是一个官方插件库。
好的,我们稍微介绍一下使用router,从一个hsp路由到另外一个hasp包的方法。
router.pushUrl({ url: '@bundle:com.xxx.yyyyy/yys_sport_club/ets/pages/SportClubMainPage' })
解释一下:
1.“@bundle:com.xxx.yyyyy”:是你整个App应用的包名。
2.“yys_sport_club”:要路由到的目标hsp包的包名。
3.“ets”:一般我们的代码就是放在这个文件夹里面的。
4."pages/SportClubMainPage":是我们的目标页面的路径,可以在目标hsp包的main_pages.json文件下面找到,呼应上。
七.设置前景色
它是一个公共属性:.colorBlend
官方的解释是:为组件添加颜色叠加效果。其实就是可以给图片设置前景色,给Text设置文字颜色等.
但是有些时候,利用它设置图片的前景色可能会出现色差或者无效,有人说是图片的问题,也有人说是系统版本问题。我实践的经验是:确实是前同事用的图片设置的有效正常,我搞的图片设置就有色差,不正常。。。代码都是一样的,啊哈哈哈哈///
八.获取共享屏幕的上下安全区和全屏等.
这个其实就需要在我们工程的根 EntryAbility 文件下的 onWindowStageCreate 生命周期中作文章了.
import { window } from '@kit.ArkUI';
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
return;
}
let windowClass: window.Window = windowStage.getMainWindowSync();
let isLayoutFullScreen = true;
/// 设置为全屏.
windowClass.setWindowLayoutFullScreen(isLayoutFullScreen);
let indicatorType = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR;
let indicatorArea = windowClass.getWindowAvoidArea(indicatorType);
let safeBottom = indicatorArea.bottomRect.height;
/// 获取到底部安全区并存储
AppStorage.setOrCreate('safeBottom', safeBottom);
let type = window.AvoidAreaType.TYPE_SYSTEM
let statusBarArea = windowClass.getWindowAvoidArea(type);
let safeTop = statusBarArea.topRect.height;
/// 获取到顶部安全区并存储
AppStorage.setOrCreate('safeTop', safeTop);
windowClass.setWindowKeepScreenOn(true);
});
}
好了,这下在全局的其他地方,我们就可以根据AppStorage存储的上下安全区数值布局我们的页面
/// 比如说我们在某个Entry中,通过StorageProp装饰器和safeTop获取到顶部的安全区.
@StorageProp('safeTop') safeTop: number = 0
九.获取到App信息
这里是指获取到App的工程信息,例如版本号,包名等。其实也就是获取到工程根目录下的AppScope文件夹下面的app.json5文件下面的字段信息.
import { bundleManager } from "@kit.AbilityKit";
import { BusinessError } from '@kit.BasicServicesKit';
/// 关键代码.
let bundleFlags = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION | bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA;
try {
bundleManager.getBundleInfoForSelf(bundleFlags).then((data) => {
hilog.info(0x0000, 'testTag', 'getBundleInfoForSelf successfully. Data: %{public}s', JSON.stringify(data));
/// 这里就是示范获取到App的versionName字段信息.
data.versionName
}).catch((err: BusinessError) => {
hilog.error(0x0000, 'testTag', 'getBundleInfoForSelf failed. Cause: %{public}s', err.message);
});
} catch (err) {
let message = (err as BusinessError).message;
hilog.error(0x0000, 'testTag', 'getBundleInfoForSelf failed: %{public}s', message);
}