Skip to content

PDF 查看与渲染

源:PDF 库

在 Android Jetpack 中,处理 PDF 主要有两种方式:使用新的 Jetpack PDF 库 (androidx.pdf) 快速集成 PDF 查看器,或者使用原生的 PdfRenderer 进行底层的页面渲染。

1. Jetpack PDF 库 (推荐)

Android X PDF 库(androidx.pdf:pdf-viewer-fragment)允许你在应用中嵌入一个功能齐全的 PDF 查看器,它支持缩放、滚动、搜索和文本选择等功能。这是在 Android App 中显示 PDF 的最简单方法。

注意:该库目前处于 Alpha 阶段 (截至 2024 年),API 可能会发生变化。它通常要求 API Level 35 (Android 15) 或通过 SDK 扩展支持到更低版本 (Android 11+, API 30+ via SdkExtensions)。

添加依赖

kotlin
[versions]
pdf-viewer = "1.0.0-alpha01"

[libraries]
androidx-pdf-viewer-fragment = { group = "androidx.pdf", name = "pdf-viewer-fragment", version.ref = "pdf-viewer" }
kotlin
dependencies {
    implementation(libs.androidx.pdf.viewer.fragment)
}

使用 PdfViewerFragment

你可以将 PdfViewerFragment 添加到你的 Activity 或 Fragment 中。

kotlin
import androidx.pdf.viewer.fragment.PdfViewerFragment
import android.net.Uri

class PdfActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_pdf)

        // 1. 创建 Fragment 实例
        val pdfFragment = PdfViewerFragment()

        // 2. 将 Fragment 添加到容器中
        supportFragmentManager.beginTransaction()
            .replace(R.id.fragment_container, pdfFragment)
            .commit()

        // 3. 加载 PDF 文档 (必须是本地文件 Uri 或 Content Uri)
        // 假设你已经有了一个 PDF 文件的 Uri
        val fileUri: Uri = ... 
        
        // 注意:DocumentUri 是必须的
        pdfFragment.documentUri = fileUri
    }
}

布局文件示例

xml
<!-- activity_pdf.xml -->
<androidx.fragment.app.FragmentContainerView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2. PdfRenderer (底层渲染)

如果你需要将 PDF 页面渲染为图片(Bitmap)以便在 ImageView 中显示,或者需要完全自定义 UI,可以使用 android.graphics.pdf.PdfRenderer。这是 Android 5.0 (API 21) 引入的原生 API。

使用步骤

  1. 打开 ParcelFileDescriptor: PdfRenderer 需要一个 ParcelFileDescriptor 指向 PDF 文件。
  2. 创建 PdfRenderer: 使用文件描述符初始化。
  3. 打开页面: 使用 openPage(index) 打开特定页面。
  4. 渲染: 使用 render() 方法将页面内容绘制到 Bitmap 上。
  5. 关闭: 使用完后必须关闭页面和渲染器。

代码示例

kotlin
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import android.widget.ImageView
import java.io.File

class SimplePdfRenderer(private val file: File) {

    private var pdfRenderer: PdfRenderer? = null
    private var currentPage: PdfRenderer.Page? = null
    private var parcelFileDescriptor: ParcelFileDescriptor? = null

    init {
        openRenderer()
    }

    private fun openRenderer() {
        parcelFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
        if (parcelFileDescriptor != null) {
            pdfRenderer = PdfRenderer(parcelFileDescriptor!!)
        }
    }

    fun renderPageToBitmap(pageIndex: Int, imageView: ImageView) {
        if (pdfRenderer == null || pdfRenderer!!.pageCount <= pageIndex) return

        // 1. 确保之前的页面已关闭
        currentPage?.close()

        // 2. 打开新页面
        currentPage = pdfRenderer!!.openPage(pageIndex)

        // 3. 创建 Bitmap (宽高根据页面尺寸或 ImageView 尺寸决定)
        val bitmap = Bitmap.createBitmap(
            currentPage!!.width,
            currentPage!!.height,
            Bitmap.Config.ARGB_8888
        )

        // 4. 渲染内容到 Bitmap
        // null 表示渲染整个页面,RENDER_MODE_FOR_DISPLAY 表示为屏幕显示优化
        currentPage!!.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)

        // 5. 显示 Bitmap
        imageView.setImageBitmap(bitmap)
    }

    fun close() {
        currentPage?.close()
        pdfRenderer?.close()
        parcelFileDescriptor?.close()
    }
}

注意事项

  • 线程安全: PdfRenderer 不是线程安全的,建议在后台线程中执行渲染操作。
  • 资源管理: 务必在使用后调用 close() 方法,否则可能会导致内存泄漏或文件锁定。
  • 页面限制: 一次只能打开一个页面。在打开新页面前必须关闭当前页面。