import { IBestToast } from '@ibestservices/ibest-ui' import { CellStorage } from '.' import { YTAvoid } from '../../utils/YTAvoid' import { yTRouter } from '../../utils/YTRouter' import { yTToast } from '../../utils/YTToast' import { ReNameInput } from './ReNameInput' import { Cell, Sector } from './Sector' @Component export struct BigWheelView{ @StorageProp(YTAvoid.SAFE_TOP_KEY) safeBottom: number = 0 //持久化的数组,方便下次打开应用直接承接上一次编辑的数据 @StorageLink('bigwheel') DBCells:CellStorage[]=[] //持久化没选中数组id,方便退出应用再次开发还能接着转 @StorageLink('unselectbigwheel') DBUnselectCell:number[]=[] //持久化选中数组id,方便退出应用再次开发还能接着转 @StorageLink('selectbigwheel') DBSelectCell:number[]=[] //转盘操作数组 @State cells: Cell[] = []; // 存储单元格的数组 //随着每次切换操作,需要重置触发画布重新画 @Watch('drawCircleWithCustomRadii') @State changeCanvas:number=0 @State wheelWidth: number = 250; // 转盘的宽度 @State currentAngle: number = 0; // 当前转盘的角度 @State selectedName: string = ""; // 选中的名称 //持久化存储是否重复 @StorageLink('isrepeat') isRepeat:boolean=false //需要操作的数组,选中没选中 @State selected:Cell[]=[] @State UnSelected:Cell[]=[] @State randomAngle:number=0 isAnimating: boolean = false; // 动画状态 colorIndex: number = 0; // 颜色索引 @StorageLink('duration') spinDuration:number=5000 @State spinDurationTime:number=this.spinDuration/1000 //是否显示设置界面 @State isShow:boolean=false private settings: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) //转盘选中判断 @State currSelectNumber:number=1 @State number:number=4 //转盘数量 //打开半模态命名模态 @State isshowRename:boolean=false /** * 后面迭代保障可以更改转盘的初始的颜色 */ colorPalette: string[] = [ // 颜色调色板 "#fff", "#fff", "#fff", "#fff", "#fff", "#fff", ]; filter(){ //每次选中的数组有变化,那就要重新过滤,就要移除没选中的数组里面的数据 //先获取选中id数组 this.DBSelectCell=this.selected.map(item=>item.id) as number[] //再根据选中数组id过滤没选中数组 this.UnSelected = this.cells.filter(item => !this.DBSelectCell.includes(item.id)); //再根据没选中数组,获取没选中数组id this.DBUnselectCell=this.UnSelected.map(item=>item.id) } // 组件即将出现时调用 aboutToAppear() { if(this.DBCells.length==0) { this.cells=[] this.cells.push(new Cell(1, 1, "转盘1",'#fff' )); this.cells.push(new Cell(2, 1, "转盘2", '#fff' )); this.cells.push(new Cell(3, 1, "转盘3",'#fff' )); this.cells.push(new Cell(4, 1, "转盘4", '#fff' )); this.calculateAngles(); // 计算角度 //如果没有之前的记录,存一下没选中的数组和数组id this.UnSelected=this.cells this.DBUnselectCell=this.UnSelected.map(item=>item.id) }else{ //如果有上次记录,需要先获取上一次得轮盘 this.DBCells.forEach((item)=>{ this.cells.push(new Cell(item.id,item.proportion,item.title,item.color)) }) this.calculateAngles(); // 计算角度 //轮盘数 this.number=this.cells.length //再获取没选中的和选中的 this.selected=[] this.UnSelected=[] for (let index = 0; index < this.cells.length; index++) { //遍历扇形,通过选中的数组id,找到选中的数组 this.DBSelectCell.forEach((I)=>{ if(this.cells[index].id==I){ this.selected.push(this.cells[index]) } }) } //再找没选中数组 this.UnSelected=this.cells.filter((item)=>{ return !this.DBSelectCell.includes(item.id) }) this.currSelectNumber=this.number-3 } } //动画 private startAnimation(){ if (this.isAnimating) { // 如果正在动画中,返回 return; } if(this.selected.length==this.cells.length){ yTToast.doubleConfirm({ message: '当前已经转完所有,是否重置', click: () => { //背景色变成白色 this.cells.forEach((item)=>{ item.color='#fff' }) //重置,所有的转盘重新开始 this.selected=[] this.UnSelected=[] this.UnSelected=this.cells this.DBUnselectCell=this.UnSelected.map(item=>item.id) this.DBSelectCell=[] this.DBCells=this.cells.map((item)=>{ return { id:item.id, title:item.title, proportion:item.proportion, color:item.color } as CellStorage }) yTToast.hide() } }) return } //如果都已经被选了,则不能再转了 this.calculateNoSelectedAngles() this.selectedName = ""; // 清空选中的名称 this.isAnimating = true; // 设置动画状态为正在动画 animateTo({ // 开始动画 duration: this.spinDuration, // 动画持续时间为5000毫秒 curve: Curve.EaseInOut, // 动画曲线为缓入缓出 onFinish: () => { // 动画完成后的回调 this.currentAngle %= 360; // 保持当前角度在0到360之间 for (const cell of this.cells) { // 遍历每个单元格 // 检查当前角度是否在单元格的角度范围内 if (360 - this.currentAngle >= cell.angleStart && 360 - this.currentAngle <= cell.angleEnd) { if(!this.isRepeat) { this.selected.push(cell) // this.DBSelectCell.push(cell) //这里需要下次点击的时候 cell.color = '#e5e7ea' this.filter() }else{ // this.selected=[] } this.selectedName = cell.title; // 设置选中的名称为当前单元格的标题 //每次转弯要保存 break; // 找到后退出循环 } } this.DBCells=this.cells.map((item)=>{ return { id:item.id, title:item.title, proportion:item.proportion, color:item.color } as CellStorage }) // promptAction.showToast({ // message:'选中的数组'+JSON.stringify(this.selected)+'没选中的数组'+JSON.stringify(this.UnSelected)+'选中的数组id'+JSON.stringify(this.DBSelectCell)+'没选中的数组id'+JSON.stringify(this.DBUnselectCell) // }) this.isAnimating = false; // 设置动画状态为未动画 }, }, () => { // 动画进行中的回调 //在这里判断 // this.randomAngle=Math.floor(Math.random()*360) // this.currentAngle += (360 * this.spinDuration/1000 + Math.floor(Math.random() * 360)); // 更新当前角度,增加随机旋转 //在这里算已经选过的,不能在指了 // this.currentAngle += (360 * this.spinDuration/1000 + Math.floor(Math.random() * 360)); // 更新当前角度,增加随机旋转 // promptAction.showToast({ // message:this.randomAngle.toString() // }) this.currentAngle += (360 * this.spinDuration/1000)+this.randomAngle }); } // 计算每个单元格的角度 private calculateAngles() { // 根据比例计算总比例,后续迭代需要计算不同比例的角度 const totalProportion = this.cells.reduce((sum, cell) => sum + cell.proportion, 0); this.cells.forEach(cell => { cell.angle = (cell.proportion * 360) / totalProportion; // 计算每个单元格的角度 }); let cumulativeAngle = 0; // 累计角度 this.cells.forEach(cell => { cell.angleStart = cumulativeAngle; // 设置起始角度 cumulativeAngle += cell.angle; // 更新累计角度 cell.angleEnd = cumulativeAngle; // 设置结束角度 cell.rotate = cumulativeAngle - (cell.angle / 2); // 计算旋转角度 }); //手动触发画布,重新画 this.changeCanvas++ } //不允许重复时,需要在没选中的数组里面选,然后计算角度,使其转到没选中数组的区间当中去 private calculateNoSelectedAngles(){ if(this.UnSelected.length!=0) { //随机选取一个没有选中的角度范围扇形 const currangle=this.currentAngle%360 const randomIndex = Math.floor(Math.random() * this.UnSelected.length) as number const ranNumStart = 360-this.UnSelected[randomIndex].angleEnd const ranNumEnd = 360-this.UnSelected[randomIndex].angleStart this.randomAngle =Math.floor(Math.random() * (ranNumEnd - ranNumStart) + ranNumStart)-currangle } } //画线 private drawCircleWithCustomRadii() { // this.context.clearRect(0,0,0,0) this.context.clearRect(0, 0, 250, 250); // 清空Canvas的内容 const centerX = 125 // 圆心x坐标 const centerY = 125 // 圆心y坐标 const radius = 125 // 圆半径 // 根据自定义角度数组绘制半径线 this.cells.forEach(angle => { // 将角度转换为弧度(Canvas使用弧度制) const radians = (angle.angleEnd-90) * Math.PI / 180 // 计算半径线终点坐标 const endX = centerX + radius * Math.cos(radians) const endY = centerY + radius * Math.sin(radians) // 绘制半径线 this.context.beginPath() this.context.moveTo(centerX, centerY) this.context.lineTo(endX, endY) this.context.strokeStyle = '#efd4f9' // 红色半径线 this.context.lineWidth = 1.5 this.context.stroke() }) } build() { Stack({alignContent:Alignment.Center}){ Stack({alignContent:Alignment.Top}){ Column() { // YTHeader({ title: '大转盘', }) Row(){ Image($r('app.media.ic_back')) .width(24) .margin({ left: 16 }) .onClick(()=>{ yTRouter.routerBack() }) Text('大转盘') .fontSize(18) .fontWeight(700) .fontColor(Color.Black) Image($r('app.media.Subtract')) .width(24) .margin({right: 16 }) .onClick(()=>{ this.isShow=true }) }.width('100%') .height(84) .justifyContent(FlexAlign.SpaceBetween) .padding({ top: 44 }) .margin({bottom:20}) Row(){ Column(){}.width(24).height(24) // Image($r('[basic].media.voicemuisc')).width(24) Text('重置').fontColor('rgba(0, 0, 0, 0.65)').onClick(()=>{ //背景色变成白色 this.cells.forEach((item)=>{ item.color='#fff' }) this.selected=[] this.UnSelected=[] this.UnSelected=this.cells this.DBUnselectCell=this.UnSelected.map(item=>item.id) this.DBSelectCell=[] this.DBCells=this.cells.map((item)=>{ return { id:item.id, title:item.title, proportion:item.proportion, color:item.color } as CellStorage }) }) }.width('100%') .justifyContent(FlexAlign.SpaceBetween) .padding({left:30,right:30}) // 显示当前状态 Text(this.isAnimating ? '旋转中' : `${this.selectedName}`) .fontSize(20) .fontColor("#0b0e15") .height(40) .margin({top:100}) Stack() { Stack() { // 遍历每个单元格并绘制扇形 ForEach(this.cells, (cell: Cell) => { Stack() { Sector({ radius: lpx2px(this.wheelWidth) / 2, angle: cell.angle, color: cell.color }); // 创建扇形 Text(cell.title).fontColor(Color.Black).fontWeight(700).margin({ bottom: this.wheelWidth / 1.4 }); // 显示单元格标题 }.width('100%').height('100%').rotate({ angle: cell.rotate }); // 设置宽度和高度,并旋转 }); } .borderRadius('50%') // 设置圆角 // .backgroundColor(Color.Gray) // 设置背景颜色 .width(this.wheelWidth) // 设置转盘宽度 .height(this.wheelWidth) // 设置转盘高度 .rotate({ angle: this.currentAngle }); // 旋转转盘 Column() { Canvas(this.context) .width(250) .height(250) .borderRadius('50%') .backgroundColor(Color.Transparent) .onReady(() => { this.drawCircleWithCustomRadii() }) } .width(this.wheelWidth) // 设置转盘宽度 .height(this.wheelWidth) // 设置转盘高度 .justifyContent(FlexAlign.Center) .rotate({ angle: this.currentAngle }) // 旋转转盘 Image($r('app.media.zhizheng')) .width(63) // 设置按钮宽度 .height(79) // 设置按钮高度 .objectFit(ImageFit.Contain) .clickEffect({ level: ClickEffectLevel.LIGHT }) // 设置点击效果 } .width(this.wheelWidth+15) .height(this.wheelWidth+15) .backgroundImage($r('app.media.xuanzhuankuang')) .backgroundImageSize({width:'100%',height:'100%'}) .backgroundImagePosition(Alignment.Center) Button('转一转').fontColor(Color.White) .backgroundColor('#fd54e3').width(246) .height(44).borderRadius(24) .margin({top:99,bottom:48}) .onClick(()=>{ this.startAnimation() }) Row({space:15}) { ForEach([2,3,4,5],(item:number,index:number)=>{ Text((item+1).toString()) .width(40) .height(40) .textAlign(TextAlign.Center) .border({width:1,color:'#000000'}) .borderRadius('50%') .backgroundColor(this.currSelectNumber==index?'#bff2ff':'#f2f2f2') .onClick(()=>{ this.currSelectNumber=index this.number=item+1 const arr=this.cells this.cells=[] //如果选中的长度比之前要长 if(this.number>arr.length) { for (let i = 0; i < this.number; i++) { if (i < arr.length) { this.cells.push(arr[i]) }else{ this.cells.push(new Cell(i + 1, 1, "转盘" + (i + 1), '#fff')); } } }else{ //短 for (let i = 0; i < this.number; i++) { this.cells.push(arr[i]) } } //要继承之前没选中的,选中的数组根据id this.calculateAngles(); // 重新计算角度 //选中的和没选中的也要重新计算角度 //找到id重新过滤 this.selected=[] this.UnSelected=[] //每次选择扇形数量都要重新更新一下 let a=[] as number[] let b=[] as number[] //先获取选中数组,没选中数组id a=this.DBSelectCell b=this.DBUnselectCell //遍历扇形,获取选中数组和没选中数组 for (let i = 0; i < this.cells.length; i++) { for(let j=0;jitem.id) this.UnSelected=this.cells.filter((item)=>{ return !this.DBSelectCell.includes(item.id) }) this.DBUnselectCell=this.UnSelected.map(item=>item.id) this.DBCells=[] this.DBCells=this.cells.map((item)=>{ return { id:item.id, title:item.title, proportion:item.proportion, color:item.color } as CellStorage }) }) }) Row(){ Image($r('app.media.zidingyi')).width(16) Text('命名').fontSize(12).fontColor(Color.White) }.width(64) .height(40) .borderRadius(20) .backgroundColor('rgba(255, 157, 240, 1)') .justifyContent(FlexAlign.Center) .onClick(()=>{ this.isshowRename=true }) } }.width('100%').padding({ bottom: this.safeBottom }) .justifyContent(FlexAlign.Center).onClick(()=>{ this.isShow=false }) //是否展示选项设置 if(this.isShow) { this.BigWheelManagerBuilder() } }.height('100%') .backgroundImage($r('app.media.backimgNumber')) .backgroundImageSize({width:'100%',height:'100%'}) //重命名设置 if(this.isshowRename) { this.ReNameBuilder() } } } @Builder ReNameBuilder(){ Column() { Column() { Row() { Text('转盘命名').fontSize(20).fontWeight(500).fontColor('#FF1C1C1C').margin({left:110,right:64,top:24}) Column() { Image($r('app.media.quxiaocl')).width(10) }.width(24) .height(24) .backgroundColor(Color.White) .justifyContent(FlexAlign.Center) .borderRadius('50%') .onClick(() => { this.isshowRename = false }) }.width('100%') Column({space:10}){ ForEach(this.cells,(item:Cell,index:number)=>{ ReNameInput({ text:item.title, num:index+1, inputChange:(value:string)=>{ item.title=value } }) }) } }.width(300).height(318) .justifyContent(FlexAlign.Start) .borderRadius(20) .padding({left:16,right:16}) .linearGradient({ angle:135, colors:[ ['rgba(248, 211, 249, 1)',0.2], ['rgba(192, 242, 255, 1)',1] ] }) }.width('100%').height('100%').justifyContent(FlexAlign.Center).backgroundColor('rgba(30, 30, 30,0.5)') } @Builder BigWheelManagerBuilder(){ Column() { //允许结果是否重复 Row() { Row({ space: 10 }) { Image($r('app.media.qiehuan')).width(24) Text('允许结果重复').fontWeight(700) } Row() { Toggle({ type: ToggleType.Switch ,isOn:$$this.isRepeat}) .width(38) .height(20) .selectedColor('rgba(253, 84, 227, 1)') //打开状态下的背景颜色 .switchStyle({ pointRadius: 8, //圆形滑块半径 trackBorderRadius: 14, //滑轨的圆角 pointColor: Color.White, //圆形滑块颜色 switchPointColor不生效 unselectedColor: 'rgba(233, 233, 234, 1)' //关闭状态的背景颜色 }) .onClick(() => { this.isRepeat=!this.isRepeat }) } } .width('100%') .height(40) .backgroundColor(Color.White) .borderRadius(8) .justifyContent(FlexAlign.SpaceBetween) .padding({ left: 12, right: 12 }) .alignItems(VerticalAlign.Center) Row() { Text('每次转动轮盘可能会随机选中相同的选项').fontSize(12).fontColor('rgba(0, 0, 0, 0.45)') }.width('100%') .justifyContent(FlexAlign.Start) .padding({ left: 22 }) .margin({ bottom: 25, top: 10 }) Row() { Row({ space: 10 }) { Image($r('app.media.xuanzhuantime')).width(24) Text('旋转时长').fontWeight(700) } Row() { Counter() { Text(this.spinDurationTime.toString() + 's').border({ width: 0 }) } .onInc(() => { this.spinDurationTime++ this.spinDuration = this.spinDurationTime * 1000 }) .onDec(() => { if(this.spinDurationTime==1){ IBestToast.show({ message:'秒数最低为1秒' }) return } this.spinDurationTime-- this.spinDuration = this.spinDurationTime * 1000 }) } } .width('100%') .height(40) .backgroundColor(Color.White) .borderRadius(8) .justifyContent(FlexAlign.SpaceBetween) .padding({ left: 12, right: 12 }) .alignItems(VerticalAlign.Center) } .width('100%') .height(214) .padding({ left: 22, top: 56, right: 22 }) .borderRadius({ bottomLeft: 20, bottomRight: 20 }) .linearGradient({ angle: 135, colors: [ ['rgba(239, 144, 237, 1)', 0.2], ['rgba(191, 242, 255, 1)', 1] ] }) } }