HarmonyOS鸿蒙Next应用开发实战-随易App 幸运转盘页面&饼状图组件

HarmonyOS鸿蒙Next应用开发实战-随易App 幸运转盘页面&饼状图组件 介绍
在本次开发实战中,您将熟悉Canvas组件的使用,设计完成一个饼状图组件。再通过Swiper组件ForEach循环渲染来扩展页面。最终添加动画完成“幸运转盘”功能。

通过本案例,您将熟悉Canvas、Swiper组件和ForEach列表渲染的使用,并添加简单的动画

相关技术要素

完整示例代码:

Gitee源码地址(本实战页面位于product/default/src/main/ets/pages/RollPage.ets)

本文以随易App的幸运转盘页面为例:

以下为实现过程:

  1. 首先要完成对目标实现的数据分析,饼状图的数据展示需要数据名、权重、背景颜色,以及饼状图自身的数据名称。考虑到后续的扩展和修改,再为其分别添加一个Index属性,因此设计如下对象数据结构用于饼状图数据:
class RollItem { //饼状图子选项类
  id: string            //选项ID
  text: string          //选项名称
  weight: number        //选项权重
  color: string         //选项背景颜色
  constructor(id: string, text: string, weight: number, color: string) {
    this.id = id
    this.text = text;
    this.weight = weight;
    this.color = color;
  }
}

class Roll {         //饼状图类
  index: string              //饼状图ID
  rollName: string           //饼状图名称
  rollItems: Array<RollItem> //饼状图子选项数组
  constructor(index: string, rollName: string, rollItems: Array<RollItem>) {
    this.index = index;
    this.rollName = rollName;
    this.rollItems = rollItems;
  }
}
//并按照数据结构新建一个示例对象
const test_roll = new Roll('0', '吃什么?', [
  new RollItem('0', '梅菜扣肉', 2, '#ffcba50b'),
  new RollItem('1', '西湖醋鱼', 3, '#ffa87cf4'),
  new RollItem('2', '西红柿炒鸡蛋', 2, '#ffe81b58'),
  new RollItem('3', '海带炖肉', 3, '#ff38ed44'),
  new RollItem('4', '穿过你的黑发我的手', 2, '#fff7942c'),
  new RollItem('5', '蚂蚁上树', 3, '#ff68ead0'),
])
  1. 我们选择Canvas画布组件来完成饼状图的绘制功能,并完成对饼状图组件的封装
  • 首先认识Canvas组件的基本使用方法
@Component
struct PieChart {
  @Prop data: Roll        //用于传入参数
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  //以上两句用于控制Canvas组件
  build(){
    Canvas(this.context)
        .onReady(() => {              //Canvas绘制语句写在此处
          let ctx = this.context;     //获取绑定的Canvas组件
          ctx.beginPath();            //绘制语句示例
          ctx.moveTo();
})
}

以上是Canvas组件的基本用法,注意该组件的特殊的方法onReady(),在ArkUI框架下使用该函数对Canvas画布组件进行绘制控制,在获取到context之后,便可以通过语句控制与该context绑定的Canvas画布。例如:ctx.beginPath()ctx.moveTo()等。

  • 其次设计Canvas组件绘制预期饼状图,在设计时应考虑到以下需求:

为了饼状图组件的适用性和兼容性应该考虑到自适应宽高的问题,因此应该获取宽度根据实际宽度来绘制,可使用context.width获取到宽度。同时组件的宽高比例不应受影响,可使用aspectRatio属性来固定组件宽高比例。

为了饼状图组件的健壮性,weight权重的数据应该足够灵活,方便用户后续的数据输入,根据数据自动计算每个选项权重占总权重的比例。

考虑到数据的数量、权重比例等属性的复杂性,使用forEach函数来循环绘制小扇形以拼接的形式完成饼状图

具体代码实现:

  Canvas(this.context)
    .onReady(() => {
      let ctx = this.context;                                        //获取指定Canvas组件
      const radius =  ctx.width / 2;                           //获取半径(宽度)
      let list: Array<RollItem> = this.data.rollItems    //获取传入数据
      let totalWeight = list.reduce<(acc: number, single: RollItem) => acc + single.weight, number>(acc => acc + single.weight, 0);   //计算出总权重,子选项将根据与总权重的比例计算出角度。
      let startAngle = -list[0].weight / totalWeight * 180 + 90;                                                                           //此处角度控制开始渲染的位置,出于个人习惯我将第一个选项调整为正上方(默认角度为正右)

      list.forEach(single => {  //以下为循环绘制代码,其核心思想是通过绘制小扇形来拼接为圆形,通过权重/总权重*360便可以计算出合适的扇形角度。
        const endAngle: number = startAngle + (single.weight / totalWeight) * 360;    //计算占比角度方便绘制
        const centerAngle = (startAngle + endAngle) / 2;    //计算对称轴角度,以便确定绘制位置
        ctx.beginPath();                        //开始
        ctx.moveTo(radius, radius);   //中心点
        ctx.arc(radius, radius, radius, startAngle * Math.PI / 180, endAngle * Math.PI / 180, false);  //绘制扇形
        ctx.closePath()                    //闭合路径区域
        ctx.fillStyle = single.color; //选定背景颜色
        ctx.fill();                                //绘制背景颜色
        ctx.fillStyle = '#FFFFFF';    //指定文字颜色
        ctx.textAlign = 'center';     //指定对齐方式

        ctx.save()          //以下语句用于绘制扇形上的文字,该方案不适合较长文本,请根据实际需求使用或更改
        ctx.translate(radius, radius)
        ctx.rotate((centerAngle + 90) * Math.PI / 180)
        const texts: Array<string> = single.text.split('');
        const x = 0;
        const y = radius * 0.1 + (radius * 0.1 / texts.length);
        const lineHight = (radius - y) / (texts.length + 1)
        const fontSize = lineHight
        const font = fontSize.toString() + 'vp';
        ctx.font = font;
        for (let n: number = 0; n < texts.length; n++) {
          ctx.fillText(texts[n], x, (-radius + lineHight + y / 2 + n * lineHight), fontSize);
        }
        ctx.restore()       //文字绘制完毕

        startAngle = endAngle;  //每次循环完毕前刷新绘制角度
      })

      ctx.beginPath();  //以下语句用于绘制饼状图中央小圆,可省略
      ctx.moveTo(radius, radius);
      ctx.arc(radius, radius, radius/ 10, -45, 275, false);
      ctx.closePath()
      ctx.fillStyle = 0xf1f3f5;
      ctx.fill();
    })
    .width('100%')
    .aspectRatio(1)
//将以上代码补全进上文PieChart()组件中

以上代码已实现一个饼状图组件PieChart(),传入符合要求的数据项即可复用,开发者也可以定义更多入参来更灵活的修改组件的边框和样式。

  1. 为了展示更多的数据元素,使用轮播图组件和ForEach列表渲染来扩展页面

核心代码:

const rolls: Array<Roll> = [           //创建示例数据
  new Roll('0', '吃什么?', [
    new RollItem('0', '梅菜扣肉', 2, '#ffcba50b'),
    new RollItem('1', '西湖醋鱼', 3, '#ffa87cf4'),
    new RollItem('2', '西红柿炒鸡蛋', 2, '#ffe81b58'),
    new RollItem('3', '海带炖肉', 3, '#ff38ed44'),
  ]),
  new Roll('1', '随机颜色', [
    new RollItem('0', '粉色', 2, '#ffff007a'),
    new RollItem('1', '蓝色', 3, '#ff0cdec5'),
  ]),
  new Roll('2', '抽奖', [
    new RollItem('0', '三等奖', 4, '#ff05f80b'),
    new RollItem('3', '特等奖', 1, '#ffecb609'),
    new RollItem('4', '感谢参与', 4, '#499821'),
    new RollItem('1', '二等奖', 3, '#ff5f16e1'),
    new RollItem('2', '一等奖', 2, '#ffe81b58'),
  ])
]
@Component
struct PieChartsShow {
  [@State](/user/State) dataList: Roll[] = rolls;                  //传入示例数据
  [@State](/user/State) rotateAngle: number = 0                    //预留角度变量
  private swiperController: SwiperController = new SwiperController()  //引入SwiperController
  build() {
      Swiper(this.swiperController) {              //轮播组件,并绑定controller
        ForEach(this.dataList, (item: Roll) => {   //循环渲染,通过传入数据循环渲染
          Row() {
            PieChart({ data: item })                             //调用饼状图组件
              .width('100%')
          }
          .width('90%')
          .margin('5%')
          .aspectRatio(1)
          .rotate({angle: this.rotateAngle})
          .animation({ duration: this.duration, curve: Curve.Ease })
        }, (item: Roll) => item.index)
      }
      .width('100%')
      .aspectRatio(1)
      .loop(true)
      .indicatorInteractive(true)
      .duration(1000)
      .itemSpace(0)
  }
}

为了方便用户切换,您添加按钮并调用this.swiperController.showNext()this.swiperController.showPrevious()等方法来控制轮播图,也可以通过更改Swiper组件的属性更改显示效果。

  1. 添加动画效果

该功能实现较为简单简单,其核心为定义一个@State变量angle为旋转角度,并将该变量加入需要旋转的组件之中。其核心代码如下:

//1.定义角度变量  
[@State](/user/State) rotateAngle: number = 0

//2.为需要旋转的组件添加以下属性,animation用于指定动画效果,详情请查阅官方文档
  .rotate({angle: this.rotateAngle})
  .animation({ duration: this.duration, curve: Curve.Ease })

//3.为指定控件绑定以下函数
  .onClick(()=>{
     this.rotateAngle = Math.floor(Math.random() * 360) + 1;;
  })

通过以上学习,你已经完成了随易APP的幸运轮盘组件,详细代码请查看完整项目代码


更多关于HarmonyOS鸿蒙Next应用开发实战-随易App 幸运转盘页面&饼状图组件的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS鸿蒙Next应用开发中,实现幸运转盘页面和饼状图组件可以通过使用ArkUI框架中的自定义组件和动画功能来完成。首先,幸运转盘页面可以通过Canvas组件绘制转盘,并使用Animation组件实现旋转动画。转盘的每个扇形区域可以通过Path对象绘制,并设置不同的颜色和文本标签。

饼状图组件可以通过Canvas组件绘制,使用Arc方法绘制每个扇形区域,并通过Text组件添加标签。饼状图的数据可以通过@State@Prop装饰器进行动态绑定,实现数据的实时更新。

在实现过程中,可以使用@Component装饰器创建自定义组件,将幸运转盘和饼状图封装为可复用的组件。通过@State@Prop装饰器管理组件的状态和属性,确保组件的响应式更新。

此外,可以通过Gesture组件实现用户交互,例如点击转盘开始旋转,或点击饼状图的某个扇形区域显示详细信息。动画效果可以通过Animation组件实现,设置旋转角度、持续时间和缓动函数,使转盘和饼状图的动画更加流畅。

总之,通过ArkUI框架提供的组件和API,可以高效地实现幸运转盘页面和饼状图组件,提升应用的用户体验。

更多关于HarmonyOS鸿蒙Next应用开发实战-随易App 幸运转盘页面&饼状图组件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next应用开发中,实现“随易App”的幸运转盘页面和饼状图组件,可以按照以下步骤进行:

  1. 页面布局:使用<div><stack>布局组件,设置转盘和饼状图的容器,确保页面结构清晰。

  2. 转盘动画:利用<animation>组件实现转盘的旋转效果,通过rotate属性控制旋转角度和速度,结合onClick事件触发旋转。

  3. 饼状图绘制:使用<canvas>组件绘制饼状图,通过arc方法绘制扇形,结合fill方法填充颜色,动态更新数据展示。

  4. 数据绑定:通过@State@Prop实现数据与UI的绑定,确保转盘和饼状图的数据同步更新。

  5. 交互优化:添加触摸事件和动画过渡效果,提升用户体验,确保页面流畅性和响应速度。

通过这些步骤,可以高效实现幸运转盘页面和饼状图组件,提升应用的用户体验。

回到顶部