这次看一个很常见的 UI 资源:聊天气泡、按钮背景、标签背景里经常出现的 xxx.9.png。
9-patch 在 Android 里由 PNG 和一圈 1px 标记线组成。View 设置成背景后,系统按标记线拉伸图片,并按内容区域安排文字位置。迁到 HarmonyOS 时,不能只把文件复制到 resources/base/media。需要迁的是两个规则:哪里可以被拉伸,内容应该离边缘多远。
示例资源是一张普通气泡 PNG。实际从 Android 迁移时,它应来自去掉 1px 标记边后的 .9.png。

Android:.9.png 把拉伸和内容区域写进图片边框
Android 侧通常是这几个文件:
app/src/main/res/drawable/bg_message_bubble.9.png
app/src/main/res/layout/item_message.xml
布局里只引用背景:
<TextView
android:id="@+id/messageText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_message_bubble"
android:paddingLeft="16dp"
android:paddingTop="10dp"
android:paddingRight="16dp"
android:paddingBottom="10dp"
android:text="Hello 9-patch"
android:textColor="#1F2937"
android:textSize="16sp" />
如果团队把 padding 也画进了 9-patch 的右边线和下边线,XML 里甚至可能看不到 padding。这正是迁移时容易误判的地方:文件名看起来只是一张图片,实际包含了布局信息。
Android 官方文档对 NinePatch 的描述可以拆成三件事:
- 文件是带特殊格式的 PNG,通常放在
res/drawable/,文件名以.9.png结尾。 - 左边和上边的 1px 黑线标记可拉伸区域。
- 右边和下边的 1px 黑线标记内容允许放置的区域,也就是背景图给内容预留的 padding。
所以 Android 这里不是单纯的:
TextView -> background image
更准确的是:
TextView -> NinePatchDrawable
NinePatchDrawable -> bitmap + stretch areas + content padding
HarmonyOS:先把图片还原,再显式写规则
HarmonyOS 里不要保留 bg_message_bubble.9.png 这个资源语义。资源落点可以是:
entry/src/main/resources/base/media/bg_message_bubble.png
处理资源时先去掉 Android 9-patch 外围那一圈 1px 标记边,只保留要显示的气泡图。带黑线的 .9.png 如果被当作普通图片使用,黑线就可能变成可见像素。
页面代码可以这样写:
@Entry
@Component
struct Index {
build() {
Column() {
MessageBubble({ text: 'Hello 9-patch' })
MessageBubble({ text: '这是一段更长的文本,用来验证气泡背景横向拉伸后的边角是否稳定。' })
}
.width('100%')
.height('100%')
.padding(24)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Start)
.backgroundColor('#F4F6FA')
}
}
@Component
struct MessageBubble {
text: string = ''
build() {
Row() {
Text(this.text)
.fontSize(16)
.fontColor('#1F2937')
.lineHeight(22)
}
.constraintSize({ maxWidth: 280 })
.padding({ left: 16, right: 16, top: 10, bottom: 10 })
.backgroundImage($r('app.media.bg_message_bubble'))
.backgroundImageResizable({
slice: { left: 22, right: 22, top: 18, bottom: 18 }
})
.margin({ bottom: 16 })
}
}
这里的迁移关系很直接:
bg_message_bubble.9.png的可见部分变成bg_message_bubble.png。- Android 左/上黑线表达的拉伸区,在 ArkUI 里变成
slice。 - Android 右/下黑线表达的内容区域,在 ArkUI 里变成
.padding(...)。
slice 写的是四边保留区域。上面的配置表示:左右各 22vp、上下各 18vp 不参与中间区域拉伸,气泡边角和边缘质感保留下来,中间区域随内容扩展。
如果只是显示一张可拉伸图片
有些 9-patch 不作为组件背景,而是直接作为图片显示。比如一个可伸缩的面板底图。这个场景可以用 Image().resizable(...):
Image($r('app.media.bg_message_bubble'))
.width(260)
.height(88)
.resizable({
slice: { left: 22, right: 22, top: 18, bottom: 18 }
})
Image.resizable(...) 适合“图片本身就是要显示的对象”。如果图片只是承载文字或子组件的背景,优先用 backgroundImageResizable(...),这样内容仍然按 ArkUI 组件树排版。
如果 9-patch 更像边框图
另一类 9-patch 是边框或面板框:四角、四边很重要,中间区域可以填充,也可以留空。这个时候可以看 borderImage(...):
Column() {
Text('Panel content')
.fontSize(16)
.fontColor('#1F2937')
}
.padding(16)
.borderImage({
source: $r('app.media.bg_message_bubble'),
slice: { left: 22, right: 22, top: 18, bottom: 18 },
width: { left: 22, right: 22, top: 18, bottom: 18 },
repeat: RepeatMode.Stretch,
fill: true
})
borderImage 的处理方式接近 CSS border-image:图片被切成边框和中心区域,再决定边框如何拉伸、中心是否填充。纯边框类资源可以从它开始;普通聊天气泡、按钮背景用 backgroundImageResizable(...) 更直接。
迁移前先把 .9.png 拆成这张表
拿到一个 Android 9-patch 文件时,可以先不要急着写 ArkTS。先把它拆成下面几项:
| Android 9-patch 信息 | 在 HarmonyOS 里放到哪里 |
|---|---|
| 可见图片内容 | 去掉 1px 标记边后,放到 resources/base/media/*.png |
| 左/上黑线:可拉伸区域 | resizable({ slice }) 或 backgroundImageResizable({ slice }) |
| 右/下黑线:内容区域 | .padding(...) |
| 用作 View 背景 | backgroundImage(...) + backgroundImageResizable(...) |
| 用作单张图片 | Image(...) + .resizable(...) |
| 用作边框图 | .borderImage(...) |
多数按钮、气泡、标签背景只需要 slice 就够了。如果 Android .9.png 里有多个分离的可拉伸段,或者对切片规则有更复杂的要求,再看 ArkUI ResizableOptions 里的 lattice。初次迁移不要先碰复杂切片,先用一张真实 UI 图验证边角、边线和内容 padding 是否一致。
常见问题
第一个问题是把 .9.png 原样复制到 HarmonyOS。这样做会丢掉 Android 资源编译阶段对 1px 标记边的解析,黑线也可能进入最终显示。迁移时应该输出一张干净 PNG,再手写拉伸规则。
第二个问题是只迁 slice,忘了迁内容 padding。Android 9-patch 的右边线和下边线经常在控制文字安全区。HarmonyOS 里内容是 ArkUI 子组件,padding 要写在承载内容的 Row、Column 或自定义组件上。
第三个问题是把所有 9-patch 都迁成 Image.resizable(...)。如果这个资源原本是 View 背景,迁到 Image 后,文字和内部布局还要再叠一层 Stack,反而绕远。背景类资源优先从 backgroundImageResizable(...) 开始。
参考
- Android Developers: NinePatch drawables
- Android Developers: Create resizable bitmaps, 9-patch files
- Android Developers: NinePatchDrawable
- HarmonyOS References: Image
- HarmonyOS References: Background
- HarmonyOS References: Border
// Kai@CodeHubble // 观测坐标:Android-HarmonyOS/2026-07-07