Menerapkan penampil PDF

PdfViewerFragment adalah Fragment khusus yang dapat Anda gunakan untuk menampilkan dokumen PDF dalam aplikasi Android. PdfViewerFragment menyederhanakan rendering PDF, sehingga Anda dapat berkonsentrasi pada aspek lain dari fungsi aplikasi.

Hasil

Dokumen PDF yang dirender dalam aplikasi Android menggunakan PdfViewerFragment.
Dokumen PDF yang ditampilkan dalam aplikasi.

Kompatibilitas versi

Untuk menggunakan PdfViewerFragment, aplikasi Anda harus menargetkan minimal Android S (level API 31) dan level ekstensi SDK 13. Jika persyaratan kompatibilitas ini tidak terpenuhi, library akan menampilkan UnsupportedOperationException.

Anda dapat memeriksa versi ekstensi SDK saat runtime menggunakan modul SdkExtensions. Hal ini memungkinkan Anda memuat fragmen dan dokumen PDF secara kondisional hanya jika perangkat memenuhi persyaratan yang diperlukan.

if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 13) {
    // Load the fragment and document.
}

Dependensi

Untuk menyertakan penampil PDF ke dalam aplikasi, deklarasikan dependensi androidx.pdf di filebuild.gradle modul aplikasi Anda. Library PDF dapat diakses dari repositori Google Maven.

dependencies {
    val pdfVersion = "1.0.0-alpha0X"
    implementation("androidx.pdf:pdf:pdf-viewer-fragment:$pdfVersion")
}

Fitur PdfViewerFragment

PdfViewerFragment menampilkan dokumen PDF dalam format yang diberi nomor halaman, sehingga mudah dinavigasi. Untuk pemuatan yang efisien, fragmen menggunakan strategi rendering dua langkah yang secara progresif memuat dimensi halaman.

Untuk mengoptimalkan penggunaan memori, PdfViewerFragment hanya merender halaman yang saat ini terlihat dan merilis bitmap untuk halaman yang berada di luar layar. Selain itu, PdfViewerFragment menyertakan tombol tindakan mengambang (FAB) yang mendukung anotasi dengan memicu intent android.intent.action.ANNOTATE implisit yang berisi URI dokumen.

Penerapan

Menambahkan penampil PDF ke aplikasi Android Anda adalah proses multi-langkah.

Membuat tata letak aktivitas

Mulailah dengan menentukan XML tata letak untuk aktivitas yang menghosting penampil PDF. Tata letak harus menyertakan FrameLayout untuk memuat PdfViewerFragment dan tombol untuk interaksi pengguna, seperti penelusuran dalam dokumen.

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/pdf_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fragment_container_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/search_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/search_string"
        app:strokeWidth="1dp"
        android:layout_marginStart="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Menyiapkan aktivitas

Aktivitas yang menghosting PdfViewerFragment harus memperluas AppCompatActivity. Dalam metode onCreate() aktivitas, tetapkan tampilan konten ke tata letak yang Anda buat, dan inisialisasi elemen UI yang diperlukan.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val getContentButton: MaterialButton = findViewById(R.id.launch_button)
        val searchButton: MaterialButton = findViewById(R.id.search_button)
    }
}

Menginisialisasi PdfViewerFragment

Buat instance PdfViewerFragment menggunakan pengelola fragmen yang diperoleh dari getSupportFragmentManager(). Periksa apakah instance fragmen sudah ada sebelum membuat yang baru, terutama selama perubahan konfigurasi.

Dalam contoh berikut, fungsi initializePdfViewerFragment() menangani pembuatan dan penerapan transaksi fragmen. Fungsi ini mengganti fragmen yang ada dalam penampung dengan instance PdfViewerFragment Anda.

class MainActivity : AppCompatActivity() {
    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
    private var pdfViewerFragment: PdfViewerFragment? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        if (pdfViewerFragment == null) {
            pdfViewerFragment =
                supportFragmentManager
                    .findFragmentByTag(PDF_VIEWER_FRAGMENT_TAG) as PdfViewerFragment?
        }

    }

    // Used to instantiate and commit the fragment.
    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
    private fun initializePdfViewerFragment() {
        // This condition can be skipped if you want to create a new fragment every time.
        if (pdfViewerFragment == null) {
            val fragmentManager: FragmentManager = supportFragmentManager

          // Fragment initialization.
          pdfViewerFragment = PdfViewerFragmentExtended()
          val transaction: FragmentTransaction = fragmentManager.beginTransaction()

          // Replace an existing fragment in a container with an instance of a new fragment.
          transaction.replace(
              R.id.fragment,4_container_view,
              pdfViewerFragment!!,
              PDF_VIEWER_FRAGMENT_TAG
          )
          transaction.commitAllowingStateLoss()
          fragmentManager.executePendingTransactions()
        }
    }

    companion object {
        private const val MIME_TYPE_PDF = "application/pdf"
        private const val PDF_VIEWER_FRAGMENT_TAG = "pdf_viewer_fragment_tag"
    }
}

Memperluas fungsi PdfViewerFragment

PdfViewerFragment mengekspos fungsi publik yang dapat Anda ganti untuk memperluas kemampuannya. Buat class baru yang mewarisi PdfViewerFragment. Di subclass Anda, ganti metode seperti onLoadDocumentSuccess() dan onLoadDocumentError() untuk menambahkan logika kustom, seperti metrik logging.

@RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
class PdfViewerFragmentExtended : PdfViewerFragment() {
          private val someLogger : SomeLogger = // ... used to log metrics

          override fun onLoadDocumentSuccess() {
                someLogger.log(/** log document success */)
          }

          override fun onLoadDocumentError(error: Throwable) {
                someLogger.log(/** log document error */, error)
          }
}

Meskipun PdfViewerFragment tidak menyertakan menu penelusuran bawaan, penelusuran ini mendukung kolom penelusuran. Anda mengontrol visibilitas kolom penelusuran menggunakan isTextSearchActive API. Untuk mengaktifkan penelusuran dokumen, Anda menetapkan properti isTextSearchActive dari instance PdfViewerFragment.

Gunakan WindowCompat.setDecorFitsSystemWindows() untuk memastikan WindowInsetsCompat diteruskan dengan benar ke tampilan konten, yang diperlukan untuk penempatan tampilan penelusuran yang tepat.

class MainActivity : AppCompatActivity() {
    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        searchButton.setOnClickListener {
            pdfViewerFragment?.isTextSearchActive =
                pdfViewerFragment?.isTextSearchActive == false
        }

        // Ensure WindowInsetsCompat are passed to content views without being
        // consumed by the decor view. These insets are used to calculate the
        // position of the search view.
        WindowCompat.setDecorFitsSystemWindows(window, false)
    }
}

Mengintegrasikan dengan pemilih file

Untuk mengizinkan pengguna memilih file PDF dari perangkat mereka, integrasikan PdfViewerFragment dengan pemilih file Android. Pertama, perbarui XML tata letak aktivitas Anda untuk menyertakan tombol yang meluncurkan pemilih file.

<androidx.constraintlayout.widget.ConstraintLayout
    ...
    tools:context=".MainActivity">

    <FrameLayout
        ...
        app:layout_constraintBottom_toTopOf="@+id/launch_button"/>

    // Adding a button to open file picker.
    <com.google.android.material.button.MaterialButton
        android:id="@+id/launch_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/launch_string"
        app:strokeWidth="1dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/search_button"/>

    <com.google.android.material.button.MaterialButton
        ...
        app:layout_constraintStart_toEndOf="@id/launch_button" />

</androidx.constraintlayout.widget.ConstraintLayout>

Selanjutnya, di aktivitas Anda, luncurkan pemilih file menggunakan registerForActivityResult(GetContent()). Saat pengguna memilih file, callback akan memberikan URI. Kemudian, tetapkan properti documentUri dari instance PdfViewerFragment dengan URI ini untuk memuat dan menampilkan PDF yang dipilih.

class MainActivity : AppCompatActivity() {
    // ...

    private var filePicker: ActivityResultLauncher<String> =
        registerForActivityResult(GetContent()) { uri: Uri? ->
            uri?.let {
                initializePdfViewerFragment()
                pdfViewerFragment?.documentUri = uri
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
    }

    private fun initializePdfViewerFragment() {
        // ...
    }

    companion object {
        private const val MIME_TYPE_PDF = "application/pdf"
        // ...
    }
}

Menyesuaikan UI

Anda dapat menyesuaikan antarmuka pengguna PdfViewerFragment dengan mengganti atribut XML yang diekspos library. Hal ini memungkinkan Anda menyesuaikan tampilan elemen seperti scrollbar dan indikator halaman agar sesuai dengan desain aplikasi.

Atribut yang dapat disesuaikan meliputi:

  • fastScrollVerticalThumbDrawable — Menetapkan drawable untuk thumb scrollbar.
  • fastScrollPageIndicatorBackgroundDrawable — Menetapkan drawable latar belakang untuk indikator halaman.
  • fastScrollPageIndicatorMarginEnd — Menetapkan margin kanan untuk indikator halaman. Pastikan nilai margin positif.
  • fastScrollVerticalThumbMarginEnd — Menetapkan margin kanan untuk thumb scrollbar vertikal. Pastikan nilai margin positif.

Untuk menerapkan penyesuaian ini, tentukan gaya kustom di resource XML Anda.

<resources>
    <style name="pdfContainerStyle">
        <item name="fastScrollVerticalThumbDrawable">@drawable/custom_thumb_drawable</item>
        <item name="fastScrollPageIndicatorBackgroundDrawable">@drawable/custom_page_indicator_background</item>
        <item name="fastScrollVerticalThumbMarginEnd">8dp</item>
    </style>
</resources>

Kemudian, berikan resource gaya kustom ke PdfViewerFragment menggunakan PdfStylingOptions saat Anda membuat instance fragmen dengan PdfViewerFragment.newInstance(stylingOptions).

private fun initializePdfViewerFragment() {
    // This condition can be skipped if you want to create a new fragment every time.
    if (pdfViewerFragment == null) {
      val fragmentManager: FragmentManager = supportFragmentManager

      // Create styling options.
      val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)

      // Fragment initialization.
      pdfViewerFragment = PdfViewerFragment.newInstance(stylingOptions)

      // Execute fragment transaction.
    }
}

Jika Anda telah membuat subclass PdfViewerFragment, gunakan konstruktor yang dilindungi untuk memberikan opsi gaya. Hal ini memastikan gaya kustom Anda diterapkan dengan benar ke fragmen yang diperluas.

class StyledPdfViewerFragment: PdfViewerFragment {

    constructor() : super()

    private constructor(pdfStylingOptions: PdfStylingOptions) : super(pdfStylingOptions)

    companion object {
        fun newInstance(): StyledPdfViewerFragment {
            val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)
            return StyledPdfViewerFragment(stylingOptions)
        }
    }
}

Penerapan lengkap

Kode berikut memberikan contoh lengkap cara menerapkan PdfViewerFragment dalam aktivitas Anda, termasuk inisialisasi, integrasi pemilih file, fungsi penelusuran, dan penyesuaian UI.

class MainActivity : AppCompatActivity() {

    private var pdfViewerFragment: PdfViewerFragment? = null
    private var filePicker: ActivityResultLauncher<String> =
        registerForActivityResult(GetContent()) { uri: Uri? ->
            uri?.let {
                initializePdfViewerFragment()
                pdfViewerFragment?.documentUri = uri
            }
        }

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

        if (pdfViewerFragment == null) {
            pdfViewerFragment =
                supportFragmentManager
                   .findFragmentByTag(PDF_VIEWER_FRAGMENT_TAG) as PdfViewerFragment?
        }

        val getContentButton: MaterialButton = findViewById(R.id.launch_button)
        val searchButton: MaterialButton = findViewById(R.id.search_button)

        getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
        searchButton.setOnClickListener {
            pdfViewerFragment?.isTextSearchActive = pdfViewerFragment?.isTextSearchActive == false
        }
    }

    private fun initializePdfViewerFragment() {
        // This condition can be skipped if you want to create a new fragment every time.
        if (pdfViewerFragment == null) {
            val fragmentManager: FragmentManager = supportFragmentManager

          // Create styling options.
          // val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)

          // Fragment initialization.
          // For customization:
          // pdfViewerFragment = PdfViewerFragment.newInstance(stylingOptions)
          pdfViewerFragment = PdfViewerFragmentExtended()
          val transaction: FragmentTransaction = fragmentManager.beginTransaction()

          // Replace an existing fragment in a container with an instance of a new fragment.
          transaction.replace(
              R.id.fragment_container_view,
              pdfViewerFragment!!,
              PDF_VIEWER_FRAGMENT_TAG
          )
          transaction.commitAllowingStateLoss()
          fragmentManager.executePendingTransactions()
        }
    }

    companion object {
        private const val MIME_TYPE_PDF = "application/pdf"
        private const val PDF_VIEWER_FRAGMENT_TAG = "pdf_viewer_fragment_tag"
    }
}

Poin utama tentang kode

  • Pastikan project Anda memenuhi persyaratan level API minimum dan ekstensi SDK.
  • Aktivitas yang menghosting PdfViewerFragment harus memperluas AppCompatActivity.
  • Anda dapat memperluas PdfViewerFragment untuk menambahkan perilaku kustom.
  • Sesuaikan UI PdfViewerFragment dengan mengganti atribut XML.