关于 pdf-annotations
面向 Android 开发者的 PDF 注释与页面对象编辑指南,说明如何根据平台版本选择编辑入口。内容涵盖注释添加、更新、删除、渲染及文件写入限制。
主要功能
- 01按 API 与扩展版本选择入口
- 02读取并编辑页面注释
- 03操作页面对象及变换矩阵
- 04配置注释渲染标志
- 05遵循文件描述符与页面生命周期限制
适用场景
- 01在兼容设备上添加 PDF 高亮
- 02更新或移除已有注释
- 03将页面对象用于点击选择和编辑
- 04排查注释未显示或写入失败
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ rcosteira79/ android-skills --skill pdf-annotations安装后在 Claude Code 中输入/pdf-annotations确认已加载。
详细介绍
平台 PDF 注释与页面对象编辑
Android 从 API 36.1 起原生支持编辑 PDF 注释——在此之前,平台仅支持渲染,文档至今仍如此说明:Jetpack 查看器指南中的 PDF 界面是 PdfViewerFragment,它明确不支持编辑模式,并通过 android.intent.action.ANNOTATE intent 将注释操作交给其他应用
[kb://android/develop/ui/views/layout/pdf/implement-pdf-viewer]。在应用下面的版本门槛前,不要得出“注释工作要用第三方库”的结论。
版本门槛(先决定入口)
两个并行的渲染器提供相同的编辑界面
(android.graphics.pdf.component):
| 入口 | 是否支持编辑 | 设备要求 |
|---|---|---|
PdfRenderer.Page | API 36.1+ | 平台版本 |
PdfRendererPreV.Page | SDK extension 18 | API 31+,且已更新 PDF 模块(PreV 本身需要 extension 13) |
在运行时使用 SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) 进行门槛判断
[kb://android/guide/sdk-extensions/index]。通过 mainline 更新,Extension 18 可覆盖 API 31,因此 PdfRendererPreV 是覆盖大多数设备的分支。
工作流:打开 → 编辑 → 写入
// The fd must be seekable — pipes/sockets are rejected at construction.
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).use { fd ->
PdfRenderer(fd).use { renderer -> // or PdfRendererPreV on the ext-18 branch
renderer.openPage(0).use { page ->
// Only SUPPORTED annotation types are listed. The pairs are
// android.util.Pair — NO Kotlin destructuring (no component1/2);
// read them with .first / .second.
val existing = page.getPageAnnotations()
val staleId: Int? = existing.firstOrNull { it.second is FreeTextAnnotation }?.first
val highlight = HighlightAnnotation(boundsList) // List<RectF> quads
val id = page.addPageAnnotation(highlight)
check(id != -1) { "annotation rejected" } // add returns -1, it does NOT throw
highlight.color = newColor
page.updatePageAnnotation(id, highlight) // by id -> Boolean success
staleId?.let { page.removePageAnnotation(it) } // by id, not list position
}
// Write BEFORE close(), to a separate writable fd; the boolean is
// removePasswordProtection (IOException if true and the doc stays encrypted).
ParcelFileDescriptor.open(
dest,
ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE,
).use { out ->
renderer.write(out, false)
}
}
}
约定:
addPageAnnotation返回注释 ID;无法添加注释时返回-1——请检查返回值;只有空值、UNKNOWN类型或已附加的参数才会抛出异常(IllegalArgumentException)。- 一个注释实例在整个文档中只能附加一次:添加已在“此页面或其他页面中添加”的实例会抛出异常。每次添加都要新建组件对象;不要在其他页面中重复使用已移除的对象。
update/remove使用 ID,而非列表位置。getPageAnnotations()只返回 API 可建模的注释类型,因此包含不支持注释的页面会出现 ID 间隔——Pair.firstID 是稳定的句柄。- 一次只能打开一个页面——现有的
PdfRenderer不变式(“任意时刻只能打开一个页面”)仍然适用;打开下一页前,请先关闭当前页面。 - 先调用
write(),再调用close(),目标必须是不同的、可写且可寻址的 fd;关闭后会抛出IllegalStateException。 - 这些 pair 是
android.util.Pair,不是kotlin.Pair——Kotlin 解构(for ((id, a) in …))无法通过编译;请使用.first/.second。
渲染编辑后的内容
只有当 RenderParams.renderFlags 包含相应标志时,注释类型才会被渲染:FLAG_RENDER_TEXT_ANNOTATIONS / FLAG_RENDER_HIGHLIGHT_ANNOTATIONS(以及 36.1 新增的 FLAG_RENDER_FREETEXT_ANNOTATIONS / FLAG_RENDER_STAMP_ANNOTATIONS)。添加的图章或自由文本“未显示”在预览位图中,通常是因为缺少渲染标志,而非添加失败——使用 RenderParams.Builder.setRenderFlags(...) 设置相应标志,并通过 render(bitmap, clip, transform, params) 重载重新渲染。渲染模式常量位于 RenderParams(以及旧版 PdfRenderer.Page)上——PdfRendererPreV.Page 没有这些常量,因此在 PreV 分支中请写作
RenderParams.Builder(RenderParams.RENDER_MODE_FOR_DISPLAY);通过 PdfRendererPreV.Page 限定该常量无法通过编译。
组件词汇(android.graphics.pdf.component)
HighlightAnnotation(List<RectF>)— 四边形列表,每个高亮片段对应一个矩形。FreeTextAnnotation(RectF, String)— 边界 + 文本;可设置文本颜色和背景颜色。StampAnnotation(RectF)— 容器:根据类契约,其子项(通过addObject添加)只能是PdfPagePathObject、PdfPageTextObject或PdfPageImageObject。- 页面对象也可以直接附加到页面(
addPageObject/getPageObjects/updatePageObject/removePageObject— 与注释使用相同的基于 id 的契约),并携带变换矩阵(PdfPageObject.setMatrix/transform)。PdfPagePathObject的描边默认为 sRGB 中的不透明黑色。 Page.getTopPageObjectAtPosition(PointF, …)会对最上层对象进行命中测试 — 可用于在更新或移除对象前,通过点击选中对象。
不在范围内(转到其他入口)
从头创建 PDF(PdfDocument 画布 API)、填写表单(API 35 的 applyEdit)、提取/搜索文本,以及嵌入查看器 UI(PdfViewerFragment — 仅用于显示
[kb://android/develop/ui/views/layout/pdf/implement-pdf-viewer])属于其他领域;只有在低于版本门槛时,或对于组件 API 未建模的注释类型,才考虑使用第三方 SDK。