开发语言:ArkTs
开发工具:DevEco Studio 5.0.0 Release
API版本:API 12
demo演示Gitee:harmony-red-packet-rain.git
最近项目在做鸿蒙化,按照原需求需要实现红包雨的活动效果,下面介绍一下实现红包雨效果的思路:
需求:全屏展示,每隔1s下落3个红包,每个红包的下落时长随机生成,随机点击1~5次后中奖。
一、全屏展示实现:
使用window.createWindow()
实现全屏半透明的弹窗,将红包雨页面page作为window的内容视图。
ts
showWindowDialog(callback?: ()=>void): void {
let date = new Date()
this.dialogConfig = {
name: this.name + date.getMinutes().toString(),
windowType: window.WindowType.TYPE_DIALOG,
ctx: getContext()
}
window.createWindow(this.dialogConfig, (string, newWindow: window.Window) => {
if (!newWindow) {
return;
}
this.dialogWindow = newWindow;
newWindow.setWindowTouchable(true);
newWindow.setUIContent('pages/HMRedPacketRainPage').then(()=>{
newWindow.setWindowBackgroundColor(this.winBgColor);
})
newWindow.showWindow(()=>{
if (callback !== undefined) {
callback();
}
})
})
}
二、红包雨实现
方案一:
红包开始下落前一次性创建所有红包,并添加到数据源数组中,所有红包每3个一组,分别给每组红包设置延迟下落的时间,第一组延迟0s,第二组延迟1s,第三组延迟2s,以此类推,从而实现红包雨的效果。
1、创建红包数据模型
每个红包对象属性:宽、高、位置x坐标、位置y坐标、开始位置、结束位置、下落时长、红包间距、延迟下落时间。
ts
import display from "@ohos.display"
@Observed
export class HMRedPacketModel {
width: number = 76.5;
height: number = 84;
x: number = 0;
y: number = -85;
start: number = -85;
end: number = this.getDeviceHeight();
duration: number = 2000 + (HMRedPacketModel.getRandomNumber(0, 255) % 250 / 100) * 1000;
space: number = (this.getDeviceWidth()-84*3-20)/4.0;
delay: number = 0;
constructor(index: number, time?: number) {
this.x = 10 + this.height * (index-1) + this.space * index;
if (delay) {
this.delay = delay * 1000;
}
}
/**
* 获取随机数
* @param min 最小值
* @param max 最大值
* @returns
*/
static getRandomNumber(min: number, max: number) {
let num = Math.floor(Math.random() * (max - min + 1)) + min;
return num;
}
/**
* 获取屏幕宽度
* @returns
*/
getDeviceWidth(): number {
return px2vp(display.getDefaultDisplaySync().width);
}
/**
* 获取屏幕高度
* @returns
*/
getDeviceHeight(): number {
return px2vp(display.getDefaultDisplaySync().height);
}
}
2、创建红包雨组件,添加下落动画
使用帧动画AnimatorResult
实现红包的下落效果,可以实现监听动画过程onFrame()
、动画结束onFinish()
、动画取消onCancel()
等事件,并且提供开始动画play()
、暂停动画pause()
等方法。
ts
// 红包对象
@ObjectLink model: HMRedPacketModel;
// 下落动画是否完成
@State downFinish: boolean = false;
// 帧动画
animatorResult: AnimatorResult | undefined = undefined;
aboutToAppear(): void {
// 帧动画
let animatorOption: AnimatorOptions = {
duration: this.model.duration,
delay: this.model.delay,
easing: 'linear',
iterations: 1,
fill: 'forwards',
direction: 'normal',
begin: this.model.start,
end: this.model.end
}
this.animatorResult = this.getUIContext().createAnimator(animatorOption);
this.animatorResult.onFrame = (progress: number) => {
this.model.y = progress;
// 动画执行结束释放
if (progress == this.model.end) {
console.info('HMRedPacketWidget', '执行到结束为止'+progress.toString())
this.downFinish = true;
this.animatorResult?.pause();
clearTimeout(this.startTimeout);
}
};
this.animatorResult.onCancel = () => {
console.info("HMRedPacketWidget", '动画取消');
};
this.animatorResult.onFinish = () => {
console.info("HMRedPacketWidget", '动画完成');
this.downFinish = true;
};
this.animatorResult.onRepeat = () => {
console.info("HMRedPacketWidget", '动画重复播放');
};
// 开始动画
this.animatorResult?.play();
}
3、创建红包雨数据源
ts
@State redPacketModels: HMRedPacketModel[] = [];
/**
* 创建红包雨对象数组
*/
createAllRedRainModels() {
for (let index = 0; index < 15; index++) {
this.redPacketModels.push(new HMRedPacketModel(1, index));
this.redPacketModels.push(new HMRedPacketModel(2, index));
this.redPacketModels.push(new HMRedPacketModel(3, index));
}
}
4、展示红包雨
ts
@Entry
@Component
struct HMRedPacketRainPage {
// 红包雨下落总时长
static RED_PACKET_DOWN_TOTAL_TIME: number = 15;
@State marginTop: number = 0;
@State redPacketModels: HMRedPacketModel[] = [];
@State showResult: boolean = false;
private interval: number = 0;
private timer: number = 0;
private clickRedPacketCount: number = 0;
private showResultCount: number = HMRedPacketModel.getRandomNumber(1, 5);
/**
* 创建红包雨对象数组
*/
createAllRedRainModels() {
for (let index = 0; index < HMRedPacketRainPage.RED_PACKET_DOWN_TOTAL_TIME; index++) {
this.redPacketModels.push(new HMRedPacketModel(1, index));
this.redPacketModels.push(new HMRedPacketModel(2, index));
this.redPacketModels.push(new HMRedPacketModel(3, index));
}
}
/**
* 页面展示
*/
onPageShow(): void {
// 一次性创建所有红包雨对象
this.createAllRedRainModels();
// 定时器
this.timer = setTimeout(() => {
this.closePage();
}, HMRedPacketRainPage.RED_PACKET_DOWN_TOTAL_TIME * 1000);
}
onPageHide(): void {
this.closePage();
}
/**
* 关闭当前页面
*/
closePage() {
HMWindowDialog.getInstance().closeWindowDialog();
clearInterval(this.interval);
clearTimeout(this.timer)
}
build() {
Stack() {
// 红包雨
ForEach(this.redPacketModels, (model: HMRedPacketModel) => {
// 红包雨
HMRedPacketWidget({model: model, onClickRedPacket: () => {
this.clickRedPacketCount++;
if (this.clickRedPacketCount === this.showResultCount) {
this.showResult = true;
clearInterval(this.interval)
clearTimeout(this.timer);
}
}})
})
if (this.showResult) {
HMRedPacketResultWidget();
}
// 关闭按钮
Row() {
Image($rawfile('redPacketRain/redRain_close.png'))
.width(36)
.height(36)
.margin({right: 15})
.onClick(() => {
this.closePage();
})
}
.width('100%')
.height(40)
.margin({top: 50})
.justifyContent(FlexAlign.End)
.alignItems(VerticalAlign.Center)
}
.height('100%')
.width('100%')
.clip(true)
.alignContent(Alignment.TopStart)
.backgroundColor(Color.Transparent)
}
}
方案二
定义红包数据源数组,每隔1s创建3个红包,添加到红包数据源中,通过数据改变驱动UI刷新,由于鸿蒙的UI刷新逻辑是状态改变驱动UI刷新,当第2组红包下落的时,由于数据源发生改变,会驱动整体UI刷新,导致前面的红包回到初始状态再次下落,该问题无法解决。
该问题也给华为提过工单,收到的回复是暂无更好的实现方式:
问题:
问题一:在方案一中,一次性创建那么多红包,会不会影响性能? 由于单个红包图片很小,占用内存也不会太大,在开发中验证了一次性创建上百的红包,暂时未出现性能问题;并且由于方案二无法实现,所以暂时没有发现更好的实现方案。相信我们的"遥遥领先",性能绝对没问题。
问题二:在方案一中,红包下落过程中,偶尔会有卡顿的情况? 该问题使用开发工具的Profiler
调试,发现红包在下落过程中,FPS
在60-120
之间跳动,通过修改手机设置-显示和亮度-屏幕刷新率
设置为固定60Hz
或120Hz
时,可解决此问题。下面是工单官方的回复:
结尾
如大家有更好的实现方案,还请评论回复,一起探讨学习,感谢!