【HarmonyOS】关于鸿蒙原生实现红包雨效果的方案

开发语言: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调试,发现红包在下落过程中,FPS60-120之间跳动,通过修改手机设置-显示和亮度-屏幕刷新率设置为固定60Hz120Hz时,可解决此问题。下面是工单官方的回复:

结尾

如大家有更好的实现方案,还请评论回复,一起探讨学习,感谢!

相关推荐
轻口味2 小时前
掌握DevEco Studio这一功能,高效实现ArkTS与C++胶水代码
c++·harmonyos·arkts
鸿蒙自习室7 小时前
鸿蒙UI开发——组件裁剪与遮罩
ui·华为·harmonyos·鸿蒙
play_big_knife9 小时前
鸿蒙项目云捐助第一课鸿蒙环境搭建
华为·华为云·harmonyos·鸿蒙·鸿蒙next·鸿蒙项目·云捐助
Freerain999 小时前
鸿蒙Next下页面级存储LocalStorage用法全解析
华为·harmonyos
鸿蒙开天组●10 小时前
华为OD机考题加答案之输出指定位置的数字
数据结构·华为od·华为·harmonyos·鸿蒙
SameX13 小时前
HarmonyOS Next企业级分布式办公应用实战
harmonyos
zhongcx0120 小时前
鸿蒙NEXT开发案例:九宫格随机
华为·harmonyos·鸿蒙·鸿蒙next
爱笑的眼睛1120 小时前
HarmonyOS Next 元服务新建到上架全流程
程序人生·华为·typescript·harmonyos·鸿蒙
play_big_knife1 天前
鸿蒙项目云捐助第三讲鸿蒙App应用的启动页实现
华为·harmonyos·鸿蒙·云开发·鸿蒙开发·鸿蒙next·华为云开发