在HarmonyOS 6购物比价或电商客服类应用中,聊天气泡(Bubble)/商品价格标签/优惠券角标​ 常需要用一张带圆角和尖角的 PNG 作为背景,文字长短不一希望背景自动撑开且图案(如边框、气泡嘴)不变形。很多同学直接给 TextbackgroundImage发现文字一长背景就被强制拉伸失真,或点九图(.9.png)不生效。

官方明确说明:HarmonyOS 不支持 Android 式 .9.png资源,但可通过 Image组件的 resizable({slice})属性或 borderImage实现同等效果。本文将完整讲解两种平替方案。


一、问题现象:Text设背景图文字长后变形

1. 错误尝试

// ❌ 问题:背景图被整体等比拉伸,气泡嘴/边框图案扭曲
Text('满199减50 限今日使用')
  .backgroundImage($r('app.media.bubble_left'))
  .backgroundImageSize(ImageSize.FILL)
  .padding(10)

当文字变长 → Column/Row撑宽 → backgroundImageSize(FILL)把整张 PNG 横向拉伸 → 左侧气泡尖角变扁、边框变粗,视觉失真

2. 根因揭秘

普通 backgroundImage对图片做整体缩放,无法区分"可拉伸区"和"固定区"。Android 的点九图思路是:图片指定上下左右各 N px 为拉伸区,其余区域保持原样不被缩放——HarmonyOS 用以下两种等价方式实现。


二、方案一(推荐):容器 + Image.resizable 做背景层

Column/RowText,给容器设 .background(builder)其中放一个 Image并配 resizable({slice:{top,bottom,left,right}})

slice 参数含义(单位:vp 或 px,与图片原始尺寸对应)

slice: { top: T, bottom: B, left: L, right: R }
  • top/bottom:距图片上/下边缘多少像素内的横向条带可被纵向拉伸

  • left/right:距图片左/右边缘多少像素内的纵向条带可被横向拉伸

  • 四个角区域(左上/右上/左下/右下)永不拉伸,保持原比例 → 气泡嘴、圆角不变形

例:一张 60×40 气泡图,上下各留 10vp 拉伸区、左右各留 15vp 拉伸区 → slice:{top:10,bottom:10,left:15,right:15}

完整代码

// utils/BubbleBg.ets
import { ImageFit } from '@kit.ArkUI';

/**
 * 聊天气泡背景 Builder
 * @param res 点九风格 PNG资源
 * @param sliceTop 上边缘不拉伸区高度(vp)
 * @param sliceBottom 下边缘不拉伸区高度(vp)
 * @param sliceLeft 左边缘不拉伸区宽度(vp)
 * @param sliceRight 右边缘不拉伸区宽度(vp)
 */
export function bubbleBackground(
  res: Resource,
  sliceTop: number = 8,
  sliceBottom: number = 8,
  sliceLeft: number = 16,
  sliceRight: number = 16
) {
  return () => {
    Image(res)
      .objectFit(ImageFit.Fill)
      .resizable({
        slice: {
          top: sliceTop,
          bottom: sliceBottom,
          left: sliceLeft,
          right: sliceRight
        },
        resizableEdges: { top: true, bottom: true, left: true, right: true }
      })
      .width('100%')
      .height('100%')
  };
}
// pages/ChatBubblePage.ets
import { bubbleBackground } from '../utils/BubbleBg';

@Entry
@Component
struct ChatBubblePage {
  build() {
    Column({ space: 16 }) {
      // 左气泡(对方)
      Row() {
        Column() {
          Text('你好,这款耳机支持主动降噪吗?')
            .fontColor('#333')
        }
        .padding({ horizontal: 12, vertical: 8 })
        .background(
          bubbleBackground(
            $r('app.media.bubble_left'),  // 左侧带尖角气泡
            8, 8, 16, 16
          )
        )
      }

      // 右气泡(自己)
      Row() {
        Column() {
          Text('支持的哦~满199减50券可以用')
            .fontColor('#FFF')
        }
        .padding({ horizontal: 12, vertical: 8 })
        .background(
          bubbleBackground(
            $r('app.media.bubble_right'), // 右侧尖角气泡
            8, 8, 16, 16
          )
        )
      }
      .alignSelf(HorizontalAlign.End)
    }
    .padding(24)
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

要点

  • Image必须设 .width('100%').height('100%')铺满容器

  • objectFit(ImageFit.Fill)确保 resizable 生效(不保持原图宽高比)

  • 容器 Column/Row不设固定宽 → 随 TextlayoutWeight或内容自适应


三、方案二:Text 直接设 borderImage(无额外容器)

若不想包一层容器,可直接对 TextborderImage+ slice,效果等价:

Text('满5000减4000 活动仅今日')
  .fontColor('#0A59F7')
  .textAlign(TextAlign.Center)
  .padding({ horizontal: 12, vertical: 8 })
  .borderImage({
    source: $r('app.media.bubble_left'),
    slice: { top: 8, bottom: 8, left: 16, right: 16 },
    width: { top: 8, bottom: 8, left: 16, right: 16 },  // 边框宽度=slice值
    repeat: RepeatMode.Stretch,
    fill: true
  })

fill: true使图片内容填充满 border 区域(类似 background),repeat: Stretch拉伸指定区。

适用场景:单个 Text/Button独立做气泡标签时省一层布局;复杂嵌套内容(多行+图标)仍推荐方案一(Column.background(builder))更灵活。


四、避坑指南

问题

原因

修复

背景图仍整体拉伸

Image.resizable({slice})或 slice 值不符图片实际留边区

用画图工具量 PNG 四边固定区像素 → vp 换算后填入 slice

气泡尖角变形

slice 把尖角区域也纳入了拉伸区

确保尖角落在四角"不拉伸区"内(slice 不覆盖尖角所在边区域)

borderImage 显示不全

未设 fill:trueborderWidth不够

fill:true+ width同 slice 值

容器背景盖住内容

Image 盖在内容上层(z序问题)

.background(builder)而非 .overlay;builder 中 Image 放底层

想用 .9.png 文件

HarmonyOS 不支持 .9.png解析

转普通 PNG + resizable({slice})平替


五、总结:聊天气泡自适应背景 SOP

  1. 准备普通 PNG:在 PS/Figma 留上下左右各 N px 纯色/渐变拉伸区,四角图案放中心不拉伸区

  2. 测 slice 值:距上/下/左/右边缘多少 vp 是可拉伸带 → 填入 resizable({slice:{top,bottom,left,right}})

  3. 容器包 TextColumn.background(bubbleBackground($r(...), t,b,l,r))+ Imagewidth/height 100%+ objectFit(Fill)

  4. 简单标签可用 borderImage平替(单组件无嵌套)

核心法则:HarmonyOS 6 中"点九图平替 = Image.resizable({slice})borderImage({slice,fill:true})",不支持 .9.png但效果完全等价且可控。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐