Browse Source

使用canvas绘制渐变文本,闪动,大转盘动画

XUYangWei 2 months ago
parent
commit
af4b38a09c

+ 6 - 0
commons/basic/Index.ets

@@ -75,6 +75,12 @@ export {DrawText} from './src/main/ets/components/animation/draw/DrawText'
 export * from '@mumu/crop'
 
 export * from './src/main/ets/models'
+export * from './src/main/ets/components/bigwheel/BigWheelView'
+export * from './src/main/ets/components/bigwheel/Sector'
+export * from './src/main/ets/components/bigwheel/index'
+export {Draw}from './src/main/ets/components/animation/draw/Draw'
+
+export {AnimaRingCom}from './src/main/ets/components/animation/ring/AnimaRingCom'
 
 
 

+ 187 - 0
commons/basic/src/main/ets/components/animation/ring/AnimaRingCom.ets

@@ -0,0 +1,187 @@
+import { promptAction } from "@kit.ArkUI";
+
+export interface RingInterface{
+  text:string,
+  img:Resource
+}
+
+// export function MyDescriptor(max:number) {
+//   descriptor.value = (args: number) => {
+//     if(args<150) {
+//
+//       return result
+//     }
+//   }
+//   return descriptor;
+// }
+
+// function ValidateNumber(max: number) {
+//   return (target: any, propertyKey: string) => {
+//     const privateKey = `_${propertyKey}`;
+//
+//     Object.defineProperty(target, propertyKey, {
+//       get() {
+//         return this[privateKey];
+//       },
+//       set(value: number) {
+//         if (value >= max) {
+//           console.error(`Error: 数值 ${value} 必须小于 ${max}`);
+//           // 在开发模式下抛出错误
+//           if (process.env.NODE_ENV === 'development') {
+//             throw new Error(`参数验证失败: 数值必须小于 ${max}`);
+//           }
+//         }
+//         this[privateKey] = value;
+//       },
+//       enumerable: true,
+//       configurable: true
+//     });
+//   };
+// }
+
+
+@Component
+export struct AnimaRingCom {
+  private averageAngle: number = 0
+  //选中范围椭圆的离心率
+  /**
+   * 1为左右端点到中心的距离
+   * 2为上下端点到中心的距离
+   */
+
+  @Prop @Watch('checkValue')radius1: number = 100
+  @Prop @Watch('checkValue') radius2: number = 100
+
+  //可以控制是否旋转的
+  private isRotate:boolean = true;//按下暂停转动
+
+  @State private  rotateAngle: number = 0
+
+  //旋转的内容,从外部传入
+  @Prop ringArr:RingInterface[]=[
+    {
+      text:'xxx1',
+      img:$r('app.media.app_icon')
+    },
+    {
+      text:'xxx2',
+      img:$r('app.media.app_icon')
+    },
+    {
+      text:'xxx3',
+      img:$r('app.media.app_icon')
+    },
+    {
+      text:'xxx4',
+      img:$r('app.media.app_icon')
+    },
+    {
+      text:'xxx5',
+      img:$r('app.media.app_icon')
+    },
+    {
+      text:'xxx6',
+      img:$r('app.media.app_icon')
+    }
+  ]
+  @State intervalID:number=0
+  @State length:number=0
+
+  //规定速度最大为5
+  @Prop speed:number=1
+  //图片大小
+  @Prop imgWid:number=25
+  //字体大小
+  @Prop textSize:number=16
+
+  //位置数组
+  @State private  widAr:Array<number> = []
+  @State private heiAr:Array<number> = []
+
+  //抛出异常来规定传入数据的规范
+
+  checkValue(){
+    if (this.radius1 > 150 || this.radius2 > 150) {
+      throw  new Error("传入的radius值不得大于150")
+    }
+  }
+
+
+  //开始旋转
+  startAnima(){
+    this.intervalID = setInterval(()=> {//每0.1秒执行0.5°的转动,动画更平滑
+      for (let index = 0; index < this.length; index++) {
+        let doubleAngle = (this.averageAngle - (360/this.length) * index + 0) * Math.PI / 180
+        let sin = Math.sin(doubleAngle);
+        let cos = Math.cos(doubleAngle);
+        //计算位置
+        this.widAr[index] =this.radius1+this.radius1*cos
+        this.heiAr[index] =this.radius2+this.radius2*sin * Math.cos(Math.PI * 5 / 18)
+      }
+      if (this.isRotate) {
+        if (this.averageAngle<360) {//0~360°循环
+          this.averageAngle+=this.speed
+        }else {
+          this.averageAngle = 0;
+        }
+        this.rotateAngle = 360
+      }
+
+
+    }, 100);
+  }
+
+  aboutToAppear(){
+    this.checkValue()
+    this.length=this.ringArr.length
+    //如果传入是是空,计算会报错
+    if(this.length==0){
+      this.length=6
+    }
+
+    //初始化位置数组
+    for (let index = 0; index < this.length; index++) {
+      let doubleAngle = (this.averageAngle - (360/this.length) * index + 0) * Math.PI / 180
+      let sin = Math.sin(doubleAngle);
+      let cos = Math.cos(doubleAngle);
+      //计算位置
+      this.widAr.push(this.radius1+this.radius1*cos)
+      this.heiAr.push(this.radius2+this.radius2*sin * Math.cos(Math.PI * 5 / 18))
+    }
+    //开始
+    this.startAnima()
+  }
+  aboutToDisappear(): void {
+    clearInterval(this.intervalID)
+  }
+
+  build() {
+    Stack() {
+      RelativeContainer() {
+        ForEach(this.ringArr,(item:RingInterface,index:number)=>{
+          Column(){
+            Image(item.img)
+              .width(this.imgWid)
+              .height(this.imgWid)
+
+            Text(item.text)
+              .fontSize(this.textSize)
+              .fontColor(Color.Black)
+
+          }.position({x:this.widAr[index],y:this.heiAr[index]})//旋转位置
+          .id(item.text+index)
+          .onClick(()=>{
+            promptAction.showToast({
+              message: item.text,
+              duration: 500
+            })
+          })
+        })
+      }.width('100%')
+      .height('100%')
+
+
+    }.width('100%')
+    .height('100%')
+  }
+}

+ 580 - 0
commons/basic/src/main/ets/components/bigwheel/BigWheelView.ets

@@ -0,0 +1,580 @@
+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;j<a.length;j++){
+                      if(a[j]==this.cells[i].id){
+                        this.selected.push(this.cells[i])
+                        continue
+                      }
+                    }
+                  }
+                  //找到选中的id数组
+                  this.DBSelectCell=this.selected.map(item=>item.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]
+      ]
+    })
+  }
+
+}

+ 30 - 0
commons/basic/src/main/ets/components/bigwheel/ReNameInput.ets

@@ -0,0 +1,30 @@
+import { IBestToast } from "@ibestservices/ibest-ui"
+
+@Component
+export struct ReNameInput{
+  @Prop text:string=""
+  @Prop num:number=1
+  inputChange=(value:string)=>{
+  }
+  build() {
+    Row({space:10}){
+      Text(`转盘${this.num}`)
+      TextInput({text:$$this.text})
+        .width(194)
+        .height(30)
+        .padding(0)
+        .border({width:{bottom:1,left:0,right:0,top:0},color:'rgba(0, 0, 0, 0.3)'})
+        .borderRadius(0)
+        .backgroundColor(Color.Transparent)
+        .onChange((value:string)=>{
+          if(value.length>5){
+            this.text = value.slice(0, 5)
+            IBestToast.show({message: '名称最大为5位' })
+            return
+          }
+          this.inputChange(value)
+        })
+    }.width('100%')
+
+  }
+}

+ 86 - 0
commons/basic/src/main/ets/components/bigwheel/Sector.ets

@@ -0,0 +1,86 @@
+// 定义扇形组件
+@Component
+export struct Sector {
+  @Prop radius: number; // 扇形的半径
+  @Prop angle: number; // 扇形的角度
+  @Prop color: string; // 扇形的颜色
+  // 创建扇形路径的函数
+  createSectorPath(radius: number, angle: number): string {
+    const centerX = radius / 2; // 计算扇形中心的X坐标
+    const centerY = radius / 2; // 计算扇形中心的Y坐标
+    const startX = centerX; // 扇形起始点的X坐标
+    const startY = centerY - radius; // 扇形起始点的Y坐标
+    const halfAngle = angle / 4; // 计算半个角度
+    // 计算扇形结束点1的坐标
+    const endX1 = centerX + radius * Math.cos((halfAngle * Math.PI) / 180);
+    const endY1 = centerY - radius * Math.sin((halfAngle * Math.PI) / 180);
+
+    // 计算扇形结束点2的坐标
+    const endX2 = centerX + radius * Math.cos((-halfAngle * Math.PI) / 180);
+    const endY2 = centerY - radius * Math.sin((-halfAngle * Math.PI) / 180);
+
+    // 判断是否为大弧
+    const largeArcFlag = angle / 2 > 180 ? 1 : 0;
+    const sweepFlag = 1; // 设置弧线方向为顺时针
+
+    const pathCommands =
+      `M${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${endX1} ${endY1} L${centerX} ${centerY} L${endX2} ${endY2} A${radius} ${radius} 0 ${largeArcFlag} ${1 -
+        sweepFlag} ${startX} ${startY} Z`;
+    // 生成SVG路径命令
+    // const pathCommands =
+    //   `M${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${startX} ${startY} A${radius} ${radius} 0 ${largeArcFlag} ${1 -
+    //     sweepFlag} ${endX2} ${endY2} Z`;
+    return pathCommands; // 返回路径命令
+  }
+
+
+  // 构建扇形组件
+  build() {
+    Stack() {
+      // 创建第一个扇形路径
+      Path()
+        .width(`${this.radius}px`) // 设置宽度为半径
+        .height(`${this.radius}px`) // 设置高度为半径
+        .commands(this.createSectorPath(this.radius, this.angle)) // 设置路径命令
+        .fillOpacity(1) // 设置填充透明度
+        .fill(this.color) // 设置填充颜色
+        // .stroke(Color.Black)
+        .strokeWidth(0) // 设置边框宽度为0
+        .rotate({ angle: this.angle / 4 - 90 }); // 旋转扇形
+
+      // 创建第二个扇形路径
+      Path()
+        .width(`${this.radius}px`) // 设置宽度为半径
+        .height(`${this.radius}px`) // 设置高度为半径
+        .commands(this.createSectorPath(this.radius, this.angle)) // 设置路径命令
+        .fillOpacity(1) // 设置填充透明度
+        .fill(this.color) // 设置填充颜色
+        // .stroke('#f8edfa')
+        // .stroke(Color.Black)
+        .strokeWidth(0) // 设置边框宽度为0
+        .rotate({ angle: 180 - (this.angle / 4 - 90) }); // 旋转扇形
+    }
+  }
+}
+
+// 定义单元格类
+@ObservedV2
+export class Cell {
+  @Trace id:number=0
+  @Trace angle: number = 0; // 扇形的角度
+  @Trace title: string; // 当前格子的标题
+  @Trace color: string; // 背景颜色
+  @Trace rotate: number = 0; // 在转盘要旋转的角度
+  angleStart: number = 0; // 轮盘所在区间的起始
+  angleEnd: number = 0; // 轮盘所在区间的结束
+  proportion: number = 0; // 所占比例,方便后续迭代,初始每份给1就行
+
+  // 构造函数
+  constructor(id:number,proportion: number, title: string, color: string) {
+    this.proportion = proportion; // 设置比例
+    this.title = title; // 设置标题
+    this.color = color; // 设置颜色
+    this.id=id
+  }
+}
+

+ 6 - 0
commons/basic/src/main/ets/components/bigwheel/index.ets

@@ -0,0 +1,6 @@
+export interface CellStorage{
+  id:number,
+  title: string,
+  color: string,
+  proportion: number
+}

BIN
commons/basic/src/main/resources/base/media/Subtract.png


BIN
commons/basic/src/main/resources/base/media/backimgNumber.png


BIN
commons/basic/src/main/resources/base/media/qiehuan.png


BIN
commons/basic/src/main/resources/base/media/quxiaocl.png


BIN
commons/basic/src/main/resources/base/media/xuanzhuankuang.png


BIN
commons/basic/src/main/resources/base/media/xuanzhuantime.png


BIN
commons/basic/src/main/resources/base/media/zhizheng.png


BIN
commons/basic/src/main/resources/base/media/zidingyi.png


+ 3 - 1
features/feature/src/main/ets/view/SecondView.ets

@@ -1,6 +1,8 @@
+import { BigWheelView } from "basic";
+
 @Component
 export struct SecondView {
   build() {
-    Text('2')
+   BigWheelView()
   }
 }

+ 64 - 1
features/feature/src/main/ets/view/ThirdView.ets

@@ -1,6 +1,69 @@
+import { AnimaRingCom, Draw, DrawText } from "basic"
+
 @Component
 export struct ThirdView {
   build() {
-    Text('3')
+   Column(){
+     Stack() {
+       Column({ space: 30 }) {
+         // AnimaRingCom({
+         //   radius1:151,
+         //   ringArr:this.arr
+         // })
+         //画图形Canvas
+         Draw()
+         Stack() {
+           Draw({
+             drawWidth: 200,
+             drawHeight: 200,
+             drawArr: [[0, 0], [200, 0], [200, 200], [0, 200]]
+           })
+           Draw({
+             drawWidth: 150,
+             drawHeight: 150,
+             drawArr: [[0, 0], [150, 0], [0, 150], [150, 150]]
+           })
+
+         }
+
+         // //上
+         // Button('xxx').onClick(()=>{
+         // // yTRequest.uploadFile('')
+         // })
+         //画文本
+         Row() {
+           DrawText({
+             drawWidth: 100,
+             drawHeight: 100,
+             text: "彩色文本",
+             draw_FontSize: 20,
+             draw_Position: 'center',
+             colors: [
+               ['0', '#64b816'],
+               ['0.2', '#1086ce'],
+               ['0.3', '#1a3b68'],
+               ['0.5', '#ff8080'],
+               ['0.8', '#ffd76a'],
+               ['1', '#fe4377']
+             ]
+
+           })
+           DrawText({
+             drawWidth: 100,
+             drawHeight: 100,
+             text: "彩色文本",
+             draw_FontSize: 20,
+             draw_Position: 'center',
+             // isShow:true
+           })
+         }
+
+       }.width('100%')
+       .height('100%')
+       AnimaRingCom()
+
+
+     }
+   }.backgroundColor(Color.Black)
   }
 }

+ 12 - 0
products/entry/src/main/ets/entryability/EntryAbility.ets

@@ -3,6 +3,7 @@ import { hilog } from '@kit.PerformanceAnalysisKit';
 import { window } from '@kit.ArkUI';
 import {
   AppStorageKeyCollect,
+  CellStorage,
   IBestInit,
   jHStartAd,
   permissionController,
@@ -15,6 +16,7 @@ import {
 import { identifier } from '@kit.AdsKit';
 import { BusinessError } from '@kit.BasicServicesKit';
 
+
 const DOMAIN = 0x0000;
 
 export default class EntryAbility extends UIAbility {
@@ -43,6 +45,16 @@ export default class EntryAbility extends UIAbility {
 
       PersistentStorage.persistProp<string>(AppStorageKeyCollect.TOKEN, '')
 
+      /**
+       * 大转盘
+       */
+      PersistentStorage.persistProp<CellStorage[]>('bigwheel', [])
+      PersistentStorage.persistProp<number[]>('unselectbigwheel', [])
+      PersistentStorage.persistProp<number[]>('selectbigwheel', [])
+      //是否重复和市场
+      PersistentStorage.persistProp<boolean>('isrepeat', false)
+      PersistentStorage.persistProp<number>('duration', 5000)
+
       IBestInit(windowStage, this.context)
       bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_SIGNATURE_INFO)
         .then(res => {

+ 2 - 2
products/entry/src/main/ets/pages/Index.ets

@@ -16,12 +16,12 @@ struct Index {
       acSrc: $r('app.media.app_icon')
     },
     {
-      text: '黄历',
+      text: '大转盘',
       src: $r('app.media.app_icon'),
       acSrc: $r('app.media.app_icon')
     },
     {
-      text: '卡片',
+      text: '动画',
       src: $r('app.media.app_icon'),
       acSrc: $r('app.media.app_icon')
     },