【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时,可解决此问题。下面是工单官方的回复:

结尾

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

相关推荐
zhanshuo8 小时前
在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
harmonyos
zhanshuo8 小时前
在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo
harmonyos
whysqwhw13 小时前
鸿蒙分布式投屏
harmonyos
whysqwhw15 小时前
鸿蒙AVSession Kit
harmonyos
whysqwhw16 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw17 小时前
鸿蒙音频编码
harmonyos
whysqwhw17 小时前
鸿蒙音频解码
harmonyos
whysqwhw18 小时前
鸿蒙视频解码
harmonyos
whysqwhw18 小时前
鸿蒙视频编码
harmonyos
ajassi200018 小时前
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
华为·开源·harmonyos