在HarmonyOS ArkUI框架中基于Canvas实现非对称圆角组件的方案 原创

乂心IsOneHeart
发布于 2025-6-11 16:49
浏览
0收藏

在现代的UI界面中,常会出现非常规的圆角样式需求。本文深入解析一种基于Canvas的动态圆角绘制方案,可完美实现正负半径值组合形成的内外圆角混合效果。

在HarmonyOS ArkUI框架中基于Canvas实现非对称圆角组件的方案-鸿蒙开发者社区

核心实现原理

条件判断:

当四个角均为内圆角或均为外圆角时,直接使用ArkUI的通用borderRadius属性:

    if ((this.topRadius >= 0 && this.bottomRadius >= 0) || (this.topRadius < 0 && this.bottomRadius < 0)) {
      Column()
        .height('100%')
        .width('100%')
        .borderRadius(Math.abs(this.topRadius + this.bottomRadius) / 2)
        .backgroundColor(this.active ? this.activeColor : this.inactiveColor)
        .onClick(() => {
          this.action();
        })
    } 

否则使用Canvas绘制:

else {
      Canvas(this.context).height('100%').width('100%')
        .onReady(() => {
          this.drawCanvas();
        })
    }

混合圆角绘制算法

根据参数正负组合,进入两种绘制模式:

模式一:顶部内圆角 + 底部外圆角

    if (this.topRadius >= 0 && this.bottomRadius < 0) {
      let p1: Point = { x: Math.abs(this.bottomRadius) + this.topRadius, y: this.topRadius }
      let p2: Point = { x: this.context.width - Math.abs(this.bottomRadius) - this.topRadius, y: this.topRadius }
      let p3: Point = { x: 0, y: this.context.height - Math.abs(this.bottomRadius) };
      let p4: Point = { x: this.context.width, y: this.context.height - Math.abs(this.bottomRadius) }
      this.context.moveTo(0, this.context.height);
      this.context.arc(p3.x, p3.y, Math.abs(this.bottomRadius), Math.PI / 2, 0, true);
      this.context.lineTo(p1.x - this.topRadius, p1.y);
      this.context.arc(p1.x, p1.y, this.topRadius, Math.PI, -Math.PI / 2);
      this.context.lineTo(p2.x, p2.y - this.topRadius);
      this.context.arc(p2.x, p2.y, this.topRadius, -Math.PI / 2, 0);
      this.context.lineTo(p4.x - Math.abs(this.bottomRadius), p4.y);
      this.context.arc(p4.x, p4.y, Math.abs(this.bottomRadius), Math.PI, Math.PI / 2, true);
      this.context.stroke();
      this.context.fill();
    }

模式二:顶部外圆角 + 底部内圆角

else if (this.topRadius < 0 && this.bottomRadius >= 0) {
      let p1: Point = { x: 0, y: Math.abs(this.topRadius) }
      let p2: Point = { x: this.context.width, y: Math.abs(this.topRadius) }
      let p3: Point = { x: this.bottomRadius + Math.abs(this.topRadius), y: this.context.height - this.bottomRadius };
      let p4: Point = {
        x: this.context.width - this.bottomRadius - Math.abs(this.topRadius),
        y: this.context.height - this.bottomRadius
      }
      this.context.moveTo(0, 0);
      this.context.arc(p1.x, p1.y, Math.abs(this.topRadius), -Math.PI / 2, 0);
      this.context.lineTo(Math.abs(this.topRadius), this.context.height - this.bottomRadius);
      this.context.arc(p3.x, p3.y, this.bottomRadius, Math.PI, Math.PI / 2, true);
      this.context.lineTo(p4.x, this.context.height);
      this.context.arc(p4.x, p4.y, this.bottomRadius, Math.PI / 2, 0, true);
      this.context.lineTo(p2.x - Math.abs(this.topRadius), p2.y);
      this.context.arc(p2.x, p2.y, Math.abs(this.topRadius), Math.PI, -Math.PI / 2);
      this.context.lineTo(0, 0);
      this.context.stroke();
      this.context.fill();
    }

封装完成的组件完整示例代码:

import { Point } from "@ohos.UiTest"

@ComponentV2
export struct XBorderRadiusButtonBackground {
  @Param topRadius: number = 20
  @Param bottomRadius: number = 20
  @Param activeColor: ResourceStr = '#ffffffff'
  @Param inactiveColor: ResourceStr = '#ff8a8a8a'
  @Param action: () => void = () => {
  }
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  @Param active: boolean = true

  @Monitor('topRadius','bottomRadius','activeColor','inactiveColor','active')
  drawCanvas() {
    this.context.clearRect(0, 0, this.context.width, this.context.height);
    this.context.lineWidth = 0;
    this.context.fillStyle = (this.active ? this.activeColor : this.inactiveColor) as string;
    this.context.strokeStyle = (this.active ? this.activeColor : this.inactiveColor) as string;
    if (this.topRadius >= 0 && this.bottomRadius < 0) {
      //      _______
      //     /       \
      //     |        |
      // ___/          \___
      //圆心1
      let p1: Point = { x: Math.abs(this.bottomRadius) + this.topRadius, y: this.topRadius }
      //圆心2
      let p2: Point = { x: this.context.width - Math.abs(this.bottomRadius) - this.topRadius, y: this.topRadius }
      //圆心3
      let p3: Point = { x: 0, y: this.context.height - Math.abs(this.bottomRadius) };
      //圆心4
      let p4: Point = { x: this.context.width, y: this.context.height - Math.abs(this.bottomRadius) }
      this.context.moveTo(0, this.context.height);
      this.context.arc(p3.x, p3.y, Math.abs(this.bottomRadius), Math.PI / 2, 0, true);
      this.context.lineTo(p1.x - this.topRadius, p1.y);
      this.context.arc(p1.x, p1.y, this.topRadius, Math.PI, -Math.PI / 2);
      this.context.lineTo(p2.x, p2.y - this.topRadius);
      this.context.arc(p2.x, p2.y, this.topRadius, -Math.PI / 2, 0);
      this.context.lineTo(p4.x - Math.abs(this.bottomRadius), p4.y);
      this.context.arc(p4.x, p4.y, Math.abs(this.bottomRadius), Math.PI, Math.PI / 2, true);
      this.context.stroke();
      this.context.fill();
    } else if (this.topRadius < 0 && this.bottomRadius >= 0) {
      //圆心1
      let p1: Point = { x: 0, y: Math.abs(this.topRadius) }
      //圆心2
      let p2: Point = { x: this.context.width, y: Math.abs(this.topRadius) }
      //圆心3
      let p3: Point = { x: this.bottomRadius + Math.abs(this.topRadius), y: this.context.height - this.bottomRadius };
      //圆心4
      let p4: Point = {
        x: this.context.width - this.bottomRadius - Math.abs(this.topRadius),
        y: this.context.height - this.bottomRadius
      }
      this.context.moveTo(0, 0);
      this.context.arc(p1.x, p1.y, Math.abs(this.topRadius), -Math.PI / 2, 0);
      this.context.lineTo(Math.abs(this.topRadius), this.context.height - this.bottomRadius);
      this.context.arc(p3.x, p3.y, this.bottomRadius, Math.PI, Math.PI / 2, true);
      this.context.lineTo(p4.x, this.context.height);
      this.context.arc(p4.x, p4.y, this.bottomRadius, Math.PI / 2, 0, true);
      this.context.lineTo(p2.x - Math.abs(this.topRadius), p2.y);
      this.context.arc(p2.x, p2.y, Math.abs(this.topRadius), Math.PI, -Math.PI / 2);
      this.context.lineTo(0, 0);
      this.context.stroke();
      this.context.fill();
    }
  }

  build() {
    if ((this.topRadius >= 0 && this.bottomRadius >= 0) || (this.topRadius < 0 && this.bottomRadius < 0)) {
      Column()
        .height('100%')
        .width('100%')
        .borderRadius(Math.abs(this.topRadius + this.bottomRadius) / 2)
        .backgroundColor(this.active ? this.activeColor : this.inactiveColor)
        .onClick(() => {
          this.action();
        })
    } else {
      Canvas(this.context).height('100%').width('100%')
        .onReady(() => {
          this.drawCanvas();
        })
    }
  }
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐