HarmonyOS实战开发-如何实现一个自定义抽奖圆形转盘

介绍

本篇Codelab是基于画布组件、显式动画,实现的一个自定义抽奖圆形转盘。包含如下功能:

  1. 通过画布组件Canvas,画出抽奖圆形转盘。
  2. 通过显式动画启动抽奖功能。
  3. 通过自定义弹窗弹出抽中的奖品。

相关概念

  • Stack组件:堆叠容器,子组件按照顺序依次入栈,后一个子组件覆盖前一个子组件。
  • Canvas:画布组件,用于自定义绘制图形。
  • CanvasRenderingContext2D对象:使用RenderingContext在Canvas组件上进行绘制,绘制对象可以是矩形、文本、图片等。
  • 显式动画:提供全局animateTo显式动画接口来指定由于闭包代码导致的状态变化插入过渡动效。
  • 自定义弹窗: 通过CustomDialogController类显示自定义弹窗。

环境搭建

软件要求

  • DevEco Studio版本:DevEco Studio 3.1 Release。
  • OpenHarmony SDK版本:API version 9。

硬件要求

  • 开发板类型:润和RK3568开发板。
  • OpenHarmony系统:3.2 Release。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. 获取OpenHarmony系统版本:标准系统解决方案(二进制)。以3.2 Release版本为例:

2.搭建烧录环境。

  1. 完成DevEco Device Tool的安装
  2. 完成RK3568开发板的烧录

3.搭建开发环境。

  1. 开始前请参考工具准备,完成DevEco Studio的安装和开发环境配置。
  2. 开发环境配置完成后,请参考使用工程向导创建工程(模板选择“Empty Ability”)。
  3. 工程创建完成后,选择使用真机进行调测。

代码结构解读

本篇Codelab只对核心代码进行讲解。

├──entry/src/main/ets	            // 代码区
│  ├──common
│  │  ├──constants
│  │  │  ├──ColorConstants.ets      // 颜色常量类
│  │  │  ├──CommonConstants.ets     // 公共常量类 
│  │  │  └──StyleConstants.ets      // 样式常量类 
│  │  └──utils
│  │     ├──CheckEmptyUtils.ets     // 数据判空工具类
│  │     └──Logger.ets              // 日志打印类
│  ├──entryability
│  │  └──EntryAbility.ts            // 程序入口类
│  ├──pages
│  │  └──CanvasPage.ets             // 主界面	
│  ├──view
│  │  └──PrizeDialog.ets            // 中奖信息弹窗类
│  └──viewmodel
│     ├──DrawModel.ets              // 画布相关方法类
│     ├──FillArcData.ets            // 绘制圆弧数据实体类
│     └──PrizeData.ets              // 中奖信息实体类
└──entry/src/main/resources         // 资源文件目录

构建主界面

在这个章节中,我们将完成示例主界面的开发,效果如图所示:

在绘制抽奖圆形转盘前,首先需要在CanvasPage.ets的aboutToAppear()方法中获取屏幕的宽高。

// CanvasPage.ets
// 获取context
let context = getContext(this);aboutToAppear() {// 获取屏幕的宽高window.getLastWindow(context).then((windowClass) => {let windowProperties = windowClass.getWindowProperties();this.screenWidth = px2vp(windowProperties.windowRect.width);this.screenHeight = px2vp(windowProperties.windowRect.height);}).catch((error: Error) => {Logger.error('Failed to obtain the window size. Cause: ' + JSON.stringify(error));})
}

在CanvasPage.ets布局界面中添加Canvas组件,在onReady()方法中进行绘制。

// CanvasPage.ets
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);Stack({ alignContent: Alignment.Center }) {Canvas(this.canvasContext)....onReady(() => {// 通过draw方法进行绘制this.drawModel.draw(this.canvasContext, this.screenWidth, this.screenHeight);})// 开始抽奖图片Image($r('app.media.ic_center'))...
}
...

在DrawModel.ets中,通过draw()方法逐步进行自定义圆形抽奖转盘的绘制。

// DrawModel.ets
// 画抽奖圆形转盘
draw(canvasContext: CanvasRenderingContext2D, screenWidth: number, screenHeight: number) {if (CheckEmptyUtils.isEmptyObj(canvasContext)) {Logger.error('[DrawModel][draw] canvasContext is empty.');return;}this.canvasContext= canvasContext;this.screenWidth = screenWidth;this.canvasContext.clearRect(0, 0, this.screenWidth, screenHeight);// 将画布沿X、Y轴平移指定距离this.canvasContext.translate(this.screenWidth / CommonConstants.TWO,screenHeight / CommonConstants.TWO);// 画外部圆盘的花瓣this.drawFlower();// 画外部圆盘、小圈圈this.drawOutCircle();// 画内部圆盘this.drawInnerCircle();// 画内部扇形抽奖区域this.drawInnerArc();// 画内部扇形区域文字this.drawArcText();// 画内部扇形区域奖品对应的图片this.drawImage();this.canvasContext.translate(-this.screenWidth / CommonConstants.TWO,-screenHeight / CommonConstants.TWO);
}

画外部圆盘

画外部圆盘的花瓣:通过调用rotate()方法,将画布旋转指定角度。再通过调用save()和restore()方法,使画布保存最新的绘制状态。根据想要绘制的花瓣个数,改变旋转角度,循环画出花瓣效果。

// DrawModel.ets
// 画外部圆盘的花瓣
drawFlower() {let beginAngle = this.startAngle + this.avgAngle;const pointY = this.screenWidth * CommonConstants.FLOWER_POINT_Y_RATIOS;const radius = this.screenWidth * CommonConstants.FLOWER_RADIUS_RATIOS;const innerRadius = this.screenWidth * CommonConstants.FLOWER_INNER_RATIOS;for (let i = 0; i < CommonConstants.COUNT; i++) {this.canvasContext?.save();this.canvasContext?.rotate(beginAngle * Math.PI / CommonConstants.HALF_CIRCLE);this.fillArc(new FillArcData(0, -pointY, radius, 0, Math.PI * CommonConstants.TWO),ColorConstants.FLOWER_OUT_COLOR);this.fillArc(new FillArcData(0, -pointY, innerRadius, 0, Math.PI * CommonConstants.TWO),ColorConstants.FLOWER_INNER_COLOR);beginAngle += this.avgAngle;this.canvasContext?.restore();}
}// 画弧线方法
fillArc(fillArcData: FillArcData, fillColor: string) {if (CheckEmptyUtils.isEmptyObj(fillArcData) || CheckEmptyUtils.isEmptyStr(fillColor)) {Logger.error('[DrawModel][fillArc] fillArcData or fillColor is empty.');return;}if (this.canvasContext !== undefined) {this.canvasContext.beginPath();this.canvasContext.fillStyle = fillColor;this.canvasContext.arc(fillArcData.x, fillArcData.y, fillArcData.radius,fillArcData.startAngle, fillArcData.endAngle);this.canvasContext.fill();}
}

画外部圆盘、圆盘边上的小圈圈:在指定的X、Y(0, 0)坐标处,画一个半径为this.screenWidth * CommonConstants.OUT_CIRCLE_RATIOS的圆形。接下来通过一个for循环,且角度每次递增CommonConstants.CIRCLE / CommonConstants.SMALL_CIRCLE_COUNT,来绘制圆环上的小圈圈。

// DrawModel.ets
drawOutCircle() {// 画外部圆盘this.fillArc(new FillArcData(0, 0, this.screenWidth * CommonConstants.OUT_CIRCLE_RATIOS, 0,Math.PI * CommonConstants.TWO), ColorConstants.OUT_CIRCLE_COLOR);let beginAngle = this.startAngle;// 画小圆圈for (let i = 0; i < CommonConstants.SMALL_CIRCLE_COUNT; i++) {this.canvasContext?.save();this.canvasContext?.rotate(beginAngle * Math.PI / CommonConstants.HALF_CIRCLE);this.fillArc(new FillArcData(this.screenWidth * CommonConstants.SMALL_CIRCLE_RATIOS, 0,CommonConstants.SMALL_CIRCLE_RADIUS, 0, Math.PI * CommonConstants.TWO),ColorConstants.WHITE_COLOR);beginAngle = beginAngle + CommonConstants.CIRCLE / CommonConstants.SMALL_CIRCLE_COUNT;this.canvasContext?.restore();}
}

画内部扇形抽奖区域

画内部圆盘、内部扇形抽奖区域:使用fillArc()方法绘制内部圆盘。通过一个for循环,角度每次递增this.avgAngle。然后不断更改弧线的起始弧度this.startAngle * Math.PI / CommonConstants.HALF_CIRCLE和弧线的终止弧度(this.startAngle + this.avgAngle) * Math.PI / CommonConstants.HALF_CIRCLE。最后用fill()方法对路径进行填充。

// DrawModel.ets
// 画内部圆盘
drawInnerCircle() {this.fillArc(new FillArcData(0, 0, this.screenWidth * CommonConstants.INNER_CIRCLE_RATIOS, 0,Math.PI * CommonConstants.TWO), ColorConstants.INNER_CIRCLE_COLOR);this.fillArc(new FillArcData(0, 0, this.screenWidth * CommonConstants.INNER_WHITE_CIRCLE_RATIOS, 0,Math.PI * CommonConstants.TWO), ColorConstants.WHITE_COLOR);
}// 画内部扇形抽奖区域
drawInnerArc() {// 颜色集合let colors = [ColorConstants.ARC_PINK_COLOR, ColorConstants.ARC_YELLOW_COLOR,ColorConstants.ARC_GREEN_COLOR, ColorConstants.ARC_PINK_COLOR,ColorConstants.ARC_YELLOW_COLOR, ColorConstants.ARC_GREEN_COLOR];let radius = this.screenWidth * CommonConstants.INNER_ARC_RATIOS;for (let i = 0; i < CommonConstants.COUNT; i++) {this.fillArc(new FillArcData(0, 0, radius, this.startAngle * Math.PI / CommonConstants.HALF_CIRCLE,(this.startAngle + this.avgAngle) * Math.PI / CommonConstants.HALF_CIRCLE), colors[i]);this.canvasContext?.lineTo(0, 0);this.canvasContext?.fill();this.startAngle += this.avgAngle;}
}

画内部抽奖区域文字:用for循环,通过drawCircularText()方法绘制每组文字。drawCircularText()方法接收三个参数,分别是字符串,起始弧度和终止弧度。绘制文本前需要设置每个字母占的弧度angleDecrement,然后设置水平和垂直的偏移量。垂直偏移量circleText.y - Math.sin(angle) * radius就是朝着圆心移动的距离;水平偏移circleText.x + Math.cos(angle) * radius,是为了让文字在当前弧范围文字居中。最后使用fillText()方法绘制文本。

// DrawModel.ets
// 画内部扇形区域文字
drawArcText() {if (this.canvasContext !== undefined) {this.canvasContext.textAlign = CommonConstants.TEXT_ALIGN;this.canvasContext.textBaseline = CommonConstants.TEXT_BASE_LINE;this.canvasContext.fillStyle = ColorConstants.TEXT_COLOR;this.canvasContext.font = StyleConstants.ARC_TEXT_SIZE + CommonConstants.CANVAS_FONT;}// 需要绘制的文本数组集合let textArrays = [$r('app.string.text_smile'),$r('app.string.text_hamburger'),$r('app.string.text_cake'),$r('app.string.text_smile'),$r('app.string.text_beer'),$r('app.string.text_watermelon')];let arcTextStartAngle = CommonConstants.ARC_START_ANGLE;let arcTextEndAngle = CommonConstants.ARC_END_ANGLE;for (let i = 0; i < CommonConstants.COUNT; i++) {this.drawCircularText(this.getResourceString(textArrays[i]),(this.startAngle + arcTextStartAngle) * Math.PI / CommonConstants.HALF_CIRCLE,(this.startAngle + arcTextEndAngle) * Math.PI / CommonConstants.HALF_CIRCLE);this.startAngle += this.avgAngle;}
}// 绘制圆弧文本
drawCircularText(textString: string, startAngle: number, endAngle: number) {if (CheckEmptyUtils.isEmptyStr(textString)) {Logger.error('[DrawModel][drawCircularText] textString is empty.');return;}class CircleText {x: number = 0;y: number = 0;radius: number = 0;}let circleText: CircleText = {x: 0,y: 0,radius: this.screenWidth * CommonConstants.INNER_ARC_RATIOS};// 圆的半径let radius = circleText.radius - circleText.radius / CommonConstants.COUNT;// 每个字母占的弧度let angleDecrement = (startAngle - endAngle) / (textString.length - 1);let angle = startAngle;let index = 0;let character: string;while (index < textString.length) {character = textString.charAt(index);this.canvasContext?.save();this.canvasContext?.beginPath();this.canvasContext?.translate(circleText.x + Math.cos(angle) * radius,circleText.y - Math.sin(angle) * radius);this.canvasContext?.rotate(Math.PI / CommonConstants.TWO - angle);this.canvasContext?.fillText(character, 0, 0);angle -= angleDecrement;index++;this.canvasContext?.restore();}
}

画内部抽奖区域文字对应图片:使用drawImage()方法绘制抽奖区域文字对应图片,该方法接收五个参数,分别是图片资源、绘制区域左上角的X和Y轴坐标、绘制区域的宽度和高度。

// DrawModel.ets
// 画内部扇形区域文字对应的图片
drawImage() {let beginAngle = this.startAngle;let imageSrc = [CommonConstants.WATERMELON_IMAGE_URL, CommonConstants.BEER_IMAGE_URL,CommonConstants.SMILE_IMAGE_URL, CommonConstants.CAKE_IMAGE_URL,CommonConstants.HAMBURG_IMAGE_URL, CommonConstants.SMILE_IMAGE_URL];for (let i = 0; i < CommonConstants.COUNT; i++) {let image = new ImageBitmap(imageSrc[i]);this.canvasContext?.save();this.canvasContext?.rotate(beginAngle * Math.PI / CommonConstants.HALF_CIRCLE);this.canvasContext?.drawImage(image, this.screenWidth * CommonConstants.IMAGE_DX_RATIOS,this.screenWidth * CommonConstants.IMAGE_DY_RATIOS, CommonConstants.IMAGE_SIZE,CommonConstants.IMAGE_SIZE);beginAngle += this.avgAngle;this.canvasContext?.restore();}
}

实现抽奖功能

在CanvasPage.ets的Canvas组件中添加rotate属性,在Image组件中添加点击事件onClick。点击“开始抽奖”图片,圆形转盘开始转动抽奖。

// CanvasPage.ets
Stack({ alignContent: Alignment.Center }) {Canvas(this.canvasContext)....onReady(() => {this.drawModel.draw(this.canvasContext, this.screenWidth, this.screenHeight);}).rotate({x: 0,y: 0,z: 1,angle: this.rotateDegree,centerX: this.screenWidth / CommonConstants.TWO,centerY: this.screenHeight / CommonConstants.TWO})// 开始抽奖图片Image($r('app.media.ic_center'))....enabled(this.enableFlag).onClick(() => {this.enableFlag = !this.enableFlag;// 开始抽奖this.startAnimator();})
}
...

圆形转盘开始转动抽奖:给转盘指定一个随机的转动角度randomAngle,保证每次转动的角度是随机的,即每次抽到的奖品也是随机的。动画结束后,转盘停止转动,抽奖结束,弹出抽中的奖品信息。

// CanvasPage.ets
dialogController: CustomDialogController = new CustomDialogController({builder: PrizeDialog({prizeData: $prizeData,enableFlag: $enableFlag}),autoCancel: false
});// CanvasPage.ets
// 开始抽奖
startAnimator() {let randomAngle = Math.round(Math.random() * CommonConstants.CIRCLE);// 获取中奖信息this.prizeData = this.drawModel.showPrizeData(randomAngle);animateTo({duration: CommonConstants.DURATION,curve: Curve.Ease,delay: 0,iterations: 1,playMode: PlayMode.Normal,onFinish: () => {this.rotateDegree = CommonConstants.ANGLE - randomAngle;// 打开自定义弹窗,弹出抽奖信息this.dialogController.open();}}, () => {this.rotateDegree = CommonConstants.CIRCLE * CommonConstants.FIVE +CommonConstants.ANGLE - randomAngle;})
}

弹出抽中的奖品信息:抽奖结束后,弹出抽中的文本和图片信息,通过自定义弹窗实现。

// PrizeDialog.ets
@CustomDialog
export default struct PrizeDialog {@Link prizeData: PrizeData;@Link enableFlag: boolean;private controller?: CustomDialogController;build() {Column() {Image(this.prizeData.imageSrc)...Text(this.prizeData.message)...Text($r('app.string.text_confirm'))....onClick(() => {// 关闭自定义弹窗		this.controller?.close();this.enableFlag = !this.enableFlag;})}...}
}

总结

您已经完成了本次Codelab的学习,并了解到以下知识点:

  1. 使用画布组件Canvas,画出抽奖圆形转盘。
  2. 使用显式动画启动抽奖功能。
  3. 使用自定义弹窗弹出抽中的奖品。

为了帮助大家更深入有效的学习到鸿蒙开发知识点,小编特意给大家准备了一份全套最新版的HarmonyOS NEXT学习资源,获取完整版方式请点击→《HarmonyOS教学视频

HarmonyOS教学视频:语法ArkTS、TypeScript、ArkUI等.....视频教程

鸿蒙生态应用开发白皮书V2.0PDF:

获取完整版白皮书方式请点击→《鸿蒙生态应用开发白皮书V2.0PDF》

鸿蒙 (Harmony OS)开发学习手册

一、入门必看

  1. 应用开发导读(ArkTS)
  2. ……

二、HarmonyOS 概念

  1. 系统定义
  2. 技术架构
  3. 技术特性
  4. 系统安全
  5. ........

三、如何快速入门?《做鸿蒙应用开发到底学习些啥?》

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

四、开发基础知识

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

五、基于ArkTS 开发

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

更多了解更多鸿蒙开发的相关知识可以参考:《鸿蒙 (Harmony OS)开发学习手册》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/577487.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DHCP设置二

华为ensp模拟实验 准备工作 需要设备&#xff1a;路由器 一台 交换机 两台 pc两台 ip划分网段 &#xff1a;192.168.10.0 24 192.168.20.0 24 当我们准备好之后就可以开机了 开机实验 点击菜单栏小三角&#xff0c;开启设备。 输入system-view进入系统视图&#x…

C语言结合体和枚举的魅力展现

前言 ✨✨欢迎&#x1f44d;&#x1f44d;点赞☕️☕️收藏✍✍评论 个人主页&#xff1a;秋邱’博客 所属栏目&#xff1a;人工智能 &#xff08;感谢您的光临&#xff0c;您的光临蓬荜生辉&#xff09; 引言: 前面我们已经讲了结构体的声明&#xff0c;自引用&#xff0c;内存…

【全套源码教程】基于SpringBoot+MyBatis框架的智慧生活商城系统的设计与实现

目录 前言 需求分析 可行性分析 技术实现 后端框架&#xff1a;Spring Boot 持久层框架&#xff1a;MyBatis 前端框架&#xff1a;Vue.js 数据库&#xff1a;MySQL 功能介绍 前台功能拓展 商品详情单管理 个人中心 秒杀活动 推荐系统 评论与评分系统 后台功能拓…

【C++】map set

文章目录 1. 关联式容器2. 键值对3. 树形结构的关联式容器3.1 set3.1.1 set 的介绍3.1.2 set 的使用 3.2 map3.2.1 map 的介绍3.2.2 map 的使用 3.3 multiset3.3.1 multuset 的介绍3.3.2 multiset 的使用 3.4 multimap3.4.1 multimap 的介绍3.4.2 multimap 的使用 1. 关联式容器…

Git版本管理使用手册 - 8 - 合并分支、解决冲突

合并整个开发分支 切换到本地test分支&#xff0c;选择右下角远程开发分支&#xff0c;选择Merge into Current。然后提交到远程test仓库。 合并某次提交的代码 当前工作区切换成test分支&#xff0c;选择远程仓库中的dev开发分支&#xff0c;选择需要合并的提交版本右击&a…

github项目名称变更sourcetree如何同步

github项目名称变更sourcetree如何同步 方法1:删除本地仓库 重新从URL克隆 方法2:修改远程地址链接 1.打开项目所在文件夹的终端 2.删除本地关联的这个远程仓库origin git remote rm origin 3.关联修改名字后的远程仓库地址 git remote add origin <新的远程仓库地址&…

CentOS使用Docker部署Halo并结合内网穿透实现公网访问本地博客

文章目录 1. Docker部署Halo1.1 检查Docker版本如果未安装Docker可参考已安装Docker步骤&#xff1a;1.2 在Docker中部署Halo 2. Linux安装Cpolar2.1 打开服务器防火墙2.2 安装cpolar内网穿透 3. 配置Halo个人博客公网地址4. 固定Halo公网地址 本文主要介绍如何在CentOS 7系统使…

网络稳定性(蓝桥省赛)

0网络稳定性 - 蓝桥云课 (lanqiao.cn) 知识点&#xff1a;克鲁斯卡尔生成树&#xff0c;lca&#xff0c;倍增 最小生成树的模板&#xff1a;最小生成树【模板】-CSDN博客 题解代码如下&#xff1a; #include<bits/stdc.h> using namespace std; const int N3e5100; co…

基于java的高校校园点餐系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 系统展示 前…

台灯护眼灯哪个牌子好?护眼灯十大品牌推荐

随着近视率的上升&#xff0c;越来越多的人开始重视眼睛健康&#xff0c;尤其是学生群体面临着巨大的学习压力。家长们也意识到良好的眼睛保护至关重要&#xff0c;开始关注护眼台灯的作用。在选择护眼灯时&#xff0c;家长们常常会陷入犹豫&#xff0c;不知道哪个品牌更可靠。…

Linux安装JDK1.8

前言&#xff1a;本文内容为实操记录&#xff0c;仅供参考&#xff01; Ubuntu配置多版本jdk&#xff1a;http://t.csdnimg.cn/0UcTf 一、下载 官方下载&#xff08;需要注册Oracle账号&#xff09;&#xff1a; Java Downloads | Oracle 国内镜像下载&…

C/C++ 内存管理

1、C/C内存分布 首先我们来了解在一个程序中&#xff0c;代码主要存储在哪些地方&#xff1b; 1.栈&#xff1a;又叫堆栈&#xff0c;其中一般存储非静态局部变量、函数参数、返回值等&#xff0c;栈的增长是向下的。 2.内存映射段&#xff1a;是高效的 I/O 映射方式&#xff0…