HarmonyOS 鸿蒙Next中如何实现异形Banner

HarmonyOS 鸿蒙Next中如何实现异形Banner 如图, 左右两边都不是正/长方形。


更多关于HarmonyOS 鸿蒙Next中如何实现异形Banner的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

实现细节:

1、我们需要对形状进行裁剪,可以使用 maskShape 为组件上添加指定形状的遮罩。

2、maskShape 支持设置 CircleShape | EllipseShape | PathShape | RectShape 来指定遮罩的形状。

3、我们使用 PathShape ,它通过设置路径的绘制指令commands创建。

4、commands(value: ResourceStr):设置符合SVG路径描述规范的命令字符串,单位为px。

5、那么就简化为通过svg实现特殊的形状。需要使用vp2px进行单位转换。

预览效果:

cke_2120.gif

完整demo代码:

/**
 * @fileName : Irrectangele.ets
 * @author : @cxy
 * @date : 2025/12/20
 * @description : 异形banner
 */

import { PathShape } from "@kit.ArkUI"

@Component
export struct Irrectange {
  @State commands: string = ''
  @State irHeight: number = 0
  @State banners: string[] = [
    'https://picsum.photos/300/150?random=1',
    'https://picsum.photos/300/150?random=2',
    'https://picsum.photos/300/150?random=3',
    'https://picsum.photos/300/150?random=4',
    'https://picsum.photos/300/150?random=5'
  ]

  build() {
    Row({ space: 3 }) {
      Swiper() {
        ForEach(this.banners, (item: string) => {
          Image(item)
            .width('100%')
        }, (item: string) => item)
      }
      .interval(5000)
      .loop(true)
      .indicator(
        new DotIndicator()
          .itemWidth(6)
          .itemHeight(6)
          .selectedItemWidth(12)
          .selectedItemHeight(6)
          .color('#eee')
          .selectedColor('#fff')
      )
      .layoutWeight(1)
      .borderRadius(8)
      .maskShape(new PathShape().commands(this.commands).fill(Color.White))
      .onAreaChange((oldValue: Area, newValue: Area) => {
        const ctx = this.getUIContext()
        this.irHeight = newValue.height as number
        const width = ctx.vp2px(newValue.width as number)
        const height = ctx.vp2px(this.irHeight)
        this.updateCommands(width, height)
      })

      Stack() {
        Image($r('app.media.irrect_right'))
          .width('100%')
          .height('100%')
          .resizable({
            slice: {
              top: 30,
              bottom: 30,
              left: 40,
              right: 40
            }
          })
      }
      .width(105)
      .height(this.irHeight)
    }
    .width('100%')
    .padding({ left: 12, right: 12 })
    .margin({ top: 20 })
  }

  updateCommands(width: number, height: number) {
    const ctx = this.getUIContext()
    const radius = ctx.vp2px(12)
    this.commands =
      `M0,0 L${width - ctx.vp2px(20)},0 A${radius},${radius} 0 0,1 ${width -
      ctx.vp2px(8)},${radius} L${width},${height -
        radius} L${width},${height} L0,${height} Z`
  }
}

仓库链接: https://github.com/iHongRen/harmony-study-demo

更多关于HarmonyOS 鸿蒙Next中如何实现异形Banner的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中实现异形Banner,主要使用Canvas组件进行自定义绘制。通过CanvasRenderingContext2D的API(如arcbezierCurveTo)绘制非矩形路径,并利用clip方法进行裁剪。可将图片或渐变作为填充源。对于轮播效果,结合Swiper组件与Canvas,为每个页面定制异形背景。关键步骤是计算并绘制所需的几何路径。

在HarmonyOS Next中实现异形Banner,核心思路是利用Canvas组件进行自定义绘制,并结合ShapePath来定义非矩形区域。以下是具体实现方法:

1. 使用Canvas绘制自定义形状

通过CanvasRenderingContext2D的路径API(如moveTolineTobezierCurveTo)绘制不规则边界,再通过clip方法裁剪内容区域。

// 示例:绘制左右对称的波浪形Banner
@Entry
@Component
struct IrregularBanner {
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

  build() {
    Column() {
      Canvas(this.context)
        .width('100%')
        .height(200)
        .onReady(() => {
          this.drawBanner()
        })
    }
  }

  private drawBanner() {
    const ctx = this.context
    const width = 360
    const height = 200

    // 创建裁剪路径
    ctx.beginPath()
    ctx.moveTo(0, 20)
    ctx.quadraticCurveTo(width * 0.25, 0, width * 0.5, 20)
    ctx.quadraticCurveTo(width * 0.75, 40, width, 20)
    ctx.lineTo(width, height)
    ctx.lineTo(0, height)
    ctx.closePath()
    ctx.clip() // 应用裁剪

    // 绘制Banner内容(如图片或渐变)
    const gradient = ctx.createLinearGradient(0, 0, width, height)
    gradient.addColorStop(0, '#FF6B8B')
    gradient.addColorStop(1, '#FF8E53')
    ctx.fillStyle = gradient
    ctx.fillRect(0, 0, width, height)
  }
}

2. 使用Shape组件结合蒙版

通过ShapePath命令定义矢量路径,作为Banner的容器:

@Entry
@Component
struct ShapeBanner {
  build() {
    Column() {
      Shape() {
        Path()
          .width('100%')
          .height(200)
          .commands('M0 20 Q90 0 180 20 Q270 40 360 20 L360 200 L0 200 Z')
      }
      .fillLinearGradient({
        angle: 90,
        colors: [['#FF6B8B', 0.0], ['#FF8E53', 1.0]]
      })
    }
  }
}

3. 实现交互与动画

  • 手势滑动:结合Swiper组件,将CanvasShape作为子项。
  • 动态效果:使用animateTo为路径控制点添加动画,实现形状过渡。

注意事项

  1. 性能优化:复杂路径建议预计算或使用OffscreenCanvas
  2. 响应式适配:通过display.getDefaultDisplaySync()获取屏幕尺寸动态计算路径。
  3. 内容叠加:在裁剪区域内使用Stack组件叠加图片、文字等元素。

以上方案可灵活调整路径参数实现各类异形效果,同时保持手势交互的流畅性。

回到顶部