鸿蒙刮刮乐抽奖效果实现:手把手教你写出流畅的刮擦体验
完整源码LuckyScratchDemo在电商营销活动、积分抽奖等场景中刮刮乐是一种经典且极具趣味性的互动形式。本文将带你使用Canvas 组件从零实现一个支持流畅滑动刮擦、无锯齿、无滞后的刮刮乐抽奖页面。最终效果如下一、需求分析一个完整的刮刮乐界面需要满足底部显示奖品信息一等奖 / 谢谢参与等顶部覆盖一层灰色涂层上面有“刮一刮”提示文字手指在涂层上滑动时涂层被“擦除”露出奖品仅滑动才擦除单纯点击不擦除防止误触擦除轨迹应平滑、无锯齿、不卡顿提供“重置涂层”按钮可重新开始二、技术选型鸿蒙提供了强大的Canvas组件配合CanvasRenderingContext2D的绘图 API 和混合模式globalCompositeOperation可以轻松实现“擦除”效果。核心思路底层Stack 的底层放奖品文字或图片上层用 Canvas 绘制银灰色涂层在onTouch的Move事件中以destination-out混合模式绘制圆形路径使涂层变透明露出底层三、关键实现步骤3.1 页面布局Stack 叠加使用Stack组件将奖品 Column 和涂层 Canvas 重叠Canvas 覆盖在上面。Stack() { // 底层奖品 Column() { Text( 恭喜中奖 ) Text(this.prizeMessage) } .backgroundColor(#FFF9C4) // 顶层涂层 Canvas(this.context) .onTouch(...) } .width(90%) .aspectRatio(3.5)3.2 涂层绘制渐变 文字为了使涂层更逼真使用线性渐变填充背景并绘制“刮一刮”文字。privateinitCoating():void{this.context.clearRect(0,0,this.canvasWidth,this.canvasHeight);constgradientthis.context.createLinearGradient(0,0,this.canvasWidth,this.canvasHeight);gradient.addColorStop(0,#B0BEC5);gradient.addColorStop(0.5,#90A4AE);gradient.addColorStop(1,#B0BEC5);this.context.fillStylegradient;this.context.fillRect(0,0,this.canvasWidth,this.canvasHeight);constfontSizeMath.min(this.canvasWidth,this.canvasHeight)*3.5*0.5;this.context.fontbold${fontSize}px HarmonyOS Sans;this.context.fillStyle#546E7A;this.context.textAligncenter;this.context.fillText(刮一刮,this.canvasWidth/2,this.canvasHeight/2);}3.3 擦除核心destination-out 混合模式globalCompositeOperation destination-out会让新绘制的图形区域变透明从而实现擦除效果。我们用圆形作为“橡皮擦”privatedrawCircle(x:number,y:number,radius:number):void{this.context.save();this.context.beginPath();this.context.arc(x,y,radius,0,Math.PI*2);this.context.closePath();this.context.globalCompositeOperationdestination-out;this.context.fillStylergba(0,0,0,1);this.context.fill();this.context.restore();}3.4 连续刮擦轨迹两点之间插值如果只是在TouchMove时画一个圆快速移动时会产生“断点”不连续。因此需要在上一个点和当前点之间插入多个圆保证轨迹连续。privatedrawLineCircle(x0:number,y0:number,x1:number,y1:number,radius:number):void{constdistanceMath.hypot(x1-x0,y1-y0);if(distance0.1)return;conststepsMath.ceil(distance/(radius*0.7));// 重叠率70%for(leti0;isteps;i){constti/steps;constcxx0(x1-x0)*t;constcyy0(y1-y0)*t;this.drawCircle(cx,cy,radius);}}3.5 触摸事件处理只有滑动才擦除按需求手指点按时不应擦除只有移动距离大于 2px 才触发擦除。.onTouch((event:TouchEvent){constxevent.touches[0].x;constyevent.touches[0].y;if(event.typeTouchType.Down){this.lastXx;this.lastYy;this.isTouchingtrue;}elseif(event.typeTouchType.Movethis.isTouching){if(Math.hypot(x-this.lastX,y-this.lastY)2)return;// 微小移动忽略this.drawLineCircle(this.lastX,this.lastY,x,y,18);this.lastXx;this.lastYy;}elseif(event.typeTouchType.Up){this.isTouchingfalse;}})3.6 重置涂层点击“重置”按钮时重新调用initCoating()清空 Canvas 并重绘涂层。privateresetCoating():void{this.initCoating();promptAction.showToast({message:涂层已重置继续刮奖});}四、完整代码下面是完整的ScratchCardPage.ets可直接复制到你的鸿蒙工程中运行。import{promptAction}fromkit.ArkUI;EntryComponentstruct ScratchCardPage{privatesettings:RenderingContextSettingsnewRenderingContextSettings(true);privatecontext:CanvasRenderingContext2DnewCanvasRenderingContext2D(this.settings);StatecanvasWidth:number0;StatecanvasHeight:number0;StateprizeMessage:string一等奖苹果 手机;privatelastX:number0;privatelastY:number0;privateisTouching:booleanfalse;build(){Column(){Row(){Text(刮刮乐抽奖).fontSize(24).fontWeight(FontWeight.Bold).margin({left:20})Blank()Button(重置涂层).onClick(()this.resetCoating()).margin({right:20})}.width(100%).height(60).backgroundColor(#F5F5F5)Stack(){// 底层奖品Column(){Text( 恭喜中奖 ).fontSize(20).fontWeight(FontWeight.Bold).fontColor(#FF5722).margin({bottom:12})Text(this.prizeMessage).fontSize(23).fontWeight(FontWeight.Bold).fontColor(#D32F2F)}.width(100%).height(100%).justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center).backgroundColor(#FFF9C4).padding(12)// 涂层 CanvasCanvas(this.context).width(100%).height(100%).onAreaChange((_,newValue){this.canvasWidthnewValue.widthasnumber;this.canvasHeightnewValue.heightasnumber;if(this.canvasWidth0this.canvasHeight0){this.initCoating();}}).onTouch((event:TouchEvent){consttouchevent.touches[0];if(!touch)return;constxtouch.x;constytouch.y;if(x0||xthis.canvasWidth||y0||ythis.canvasHeight)return;if(event.typeTouchType.Down){this.lastXx;this.lastYy;this.isTouchingtrue;}elseif(event.typeTouchType.Movethis.isTouching){// 避免微小移动if(Math.hypot(x-this.lastX,y-this.lastY)2)return;// 连续圆形擦除实现涂鸦般平滑效果this.drawLineCircle(this.lastX,this.lastY,x,y,18);this.lastXx;this.lastYy;}elseif(event.typeTouchType.Up){this.isTouchingfalse;}})}.width(90%).aspectRatio(3.5).backgroundColor(#E0E0E0).margin({top:30}).borderRadius(16)Text(手指在灰色涂层上滑动刮奖).fontSize(16).fontColor(#666).margin({top:20})}.width(100%).height(100%).backgroundColor(#FAFAFA)}// 初始化涂层银灰色渐变 文字privateinitCoating():void{if(!this.context||this.canvasWidth0||this.canvasHeight0)return;this.context.clearRect(0,0,this.canvasWidth,this.canvasHeight);constgradientthis.context.createLinearGradient(0,0,this.canvasWidth,this.canvasHeight);gradient.addColorStop(0,#B0BEC5);gradient.addColorStop(0.5,#90A4AE);gradient.addColorStop(1,#B0BEC5);this.context.fillStylegradient;this.context.fillRect(0,0,this.canvasWidth,this.canvasHeight);constfontSizeMath.min(this.canvasWidth,this.canvasHeight)*3.5*0.5;this.context.fontbold${fontSize}px HarmonyOS Sans;this.context.fillStyle#546E7A;this.context.textAligncenter;this.context.textBaselinemiddle;this.context.fillText(刮一刮,this.canvasWidth/2,this.canvasHeight/2);}// 单点圆形擦除privatedrawCircle(x:number,y:number,radius:number):void{if(!this.context)return;this.context.save();this.context.beginPath();this.context.arc(x,y,radius,0,Math.PI*2);this.context.closePath();this.context.globalCompositeOperationdestination-out;this.context.fillStylergba(0,0,0,1);this.context.fill();this.context.restore();}// 两点之间连续绘制圆形实现平滑连续刮擦privatedrawLineCircle(x0:number,y0:number,x1:number,y1:number,radius:number):void{constdistanceMath.hypot(x1-x0,y1-y0);if(distance0.1)return;conststepsMath.ceil(distance/(radius*0.7));for(leti0;isteps;i){constti/steps;constcxx0(x1-x0)*t;constcyy0(y1-y0)*t;this.drawCircle(cx,cy,radius);}}// 重置涂层privateresetCoating():void{if(!this.context)return;this.initCoating();promptAction.showToast({message:涂层已重置继续刮奖});}}五、优化与避坑圆形笔刷 vs 方形笔刷圆形在斜向滑动时边缘平滑无锯齿方形会呈现明显的阶梯感。建议使用圆形。插值步数计算steps distance / (radius * overlapFactor)overlapFactor 越小重叠越多越连续但增加绘制次数。推荐 0.6~0.8。触摸抖动过滤判断Math.hypot(dx, dy) 2可以避免手指轻微抖动产生不必要的圆点。性能考虑drawLineCircle在每一帧绘制多个圆半径过大或步数过多可能掉帧。实际测试半径 15~20、步数系数 0.7 可以流畅运行。涂层重置重置时需要重新计算 canvas 宽高确保initCoating拿到正确的尺寸。所以onAreaChange中调用初始化。六、总结本文介绍了如何使用Canvas 组件和globalCompositeOperation实现一个流畅的刮刮乐效果。关键点在于Stack 布局上下层叠destination-out 混合模式擦除涂层两点之间插值绘制连续轨迹仅滑动时触发擦除提升用户体验希望这篇教程能为你的鸿蒙应用开发带来灵感。如果觉得本文对你有帮助请点赞、收藏、转发支持