PDF-Viewer implementieren

PdfViewerFragment ist ein spezielles Fragment, mit dem Sie PDF-Dokumente in Ihrer Android-App anzeigen können. PdfViewerFragment vereinfacht das Rendern von PDFs, sodass Sie sich auf andere Aspekte der Funktionalität Ihrer App konzentrieren können.

Ergebnisse

Ein PDF-Dokument, das in einer Android-App mit PdfViewerFragment gerendert wird.
PDF-Dokument in einer App angezeigt.

Versionskompatibilität

Für die Verwendung von PdfViewerFragment muss Ihre Anwendung mindestens auf Android S (API-Level 31) und SDK-Erweiterungslevel 13 ausgerichtet sein. Wenn diese Kompatibilitätsanforderungen nicht erfüllt sind, löst die Bibliothek eine UnsupportedOperationException aus.

Sie können die SDK-Erweiterungsversion zur Laufzeit mit dem Modul SdkExtensions prüfen. So können Sie das Fragment und das PDF-Dokument nur bedingt laden, wenn das Gerät die erforderlichen Anforderungen erfüllt.

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

Abhängigkeiten

Wenn Sie den PDF-Viewer in Ihre Anwendung einbinden möchten, deklarieren Sie die androidx.pdf-Abhängigkeit in der Datei des Moduls Ihrer Appbuild.gradle. Die PDF-Bibliothek ist über das Maven-Repository von Google zugänglich.

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

PdfViewerFragment-Funktionen

PdfViewerFragment präsentiert PDF-Dokumente im paginierten Format, sodass sie einfach zu navigieren sind. Für ein effizientes Laden verwendet das Fragment eine Renderingstrategie mit zwei Durchläufen, bei der die Seitendimensionen progressiv geladen werden.

Um die Arbeitsspeichernutzung zu optimieren, rendert PdfViewerFragment nur die aktuell sichtbaren Seiten und gibt die Bitmaps für Seiten frei, die nicht auf dem Bildschirm angezeigt werden. Außerdem enthält PdfViewerFragment einen unverankerten Aktionsbutton (Floating Action Button, FAB), der Anmerkungen unterstützt, indem er einen impliziten android.intent.action.ANNOTATE-Intent mit dem Dokument-URI auslöst.

Implementierung

Das Hinzufügen eines PDF-Viewers zu Ihrer Android-App ist ein mehrstufiger Prozess.

Aktivitätslayout erstellen

Definieren Sie zuerst das Layout-XML für die Aktivität, die den PDF-Viewer hostet. Das Layout sollte ein FrameLayout für das PdfViewerFragment und Schaltflächen für Nutzerinteraktionen enthalten, z. B. für die Suche im Dokument.

<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>

Aktivität einrichten

Die Aktivität, die PdfViewerFragment hostet, muss AppCompatActivity erweitern. Legen Sie in der Methode onCreate() der Aktivität die Inhaltsansicht auf das von Ihnen erstellte Layout fest und initialisieren Sie alle erforderlichen UI-Elemente.

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)
    }
}

PdfViewerFragment initialisieren

Erstellen Sie eine Instanz von PdfViewerFragment mit einem Fragment-Manager, der von getSupportFragmentManager() abgerufen wurde. Prüfen Sie, ob bereits eine Instanz des Fragments vorhanden ist, bevor Sie eine neue erstellen, insbesondere bei Konfigurationsänderungen.

Im folgenden Beispiel verarbeitet die Funktion initializePdfViewerFragment() die Erstellung und Übertragung der Fragmenttransaktion. Die Funktion ersetzt ein vorhandenes Fragment in einem Container durch eine Instanz von PdfViewerFragment.

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"
    }
}

Funktionalität von PdfViewerFragment erweitern

PdfViewerFragment stellt öffentliche Funktionen zur Verfügung, die Sie überschreiben können, um die Funktionen zu erweitern. Erstellen Sie eine neue Klasse, die von PdfViewerFragment erbt. Überschreiben Sie in Ihrer Unterklasse Methoden wie onLoadDocumentSuccess() und onLoadDocumentError(), um benutzerdefinierte Logik hinzuzufügen, z. B. zum Protokollieren von Messwerten.

@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)
          }
}

PdfViewerFragment enthält zwar kein integriertes Suchmenü, unterstützt aber eine Suchleiste. Sie steuern die Sichtbarkeit der Suchleiste mit der isTextSearchActive-API. Wenn Sie die Dokumentsuche aktivieren möchten, legen Sie die Property isTextSearchActive Ihrer PdfViewerFragment-Instanz fest.

Verwenden Sie WindowCompat.setDecorFitsSystemWindows(), um sicherzustellen, dass WindowInsetsCompat korrekt an Inhaltsansichten übergeben wird. Dies ist für die richtige Positionierung der Suchansicht erforderlich.

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)
    }
}

In die Dateiauswahl einbinden

Wenn Sie Nutzern die Auswahl von PDF-Dateien auf ihrem Gerät ermöglichen möchten, binden Sie PdfViewerFragment in die Android-Dateiauswahl ein. Aktualisieren Sie zuerst das Layout-XML Ihrer Aktivität, um eine Schaltfläche einzufügen, mit der die Dateiauswahl gestartet wird.

<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>

Starten Sie dann in Ihrer Aktivität die Dateiauswahl mit registerForActivityResult(GetContent()). Wenn der Nutzer eine Datei auswählt, stellt der Callback einen URI bereit. Legen Sie dann die Property documentUri Ihrer PdfViewerFragment-Instanz mit diesem URI fest, um das ausgewählte PDF zu laden und anzuzeigen.

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"
        // ...
    }
}

Benutzeroberfläche anpassen

Sie können die Benutzeroberfläche von PdfViewerFragment anpassen, indem Sie XML-Attribute überschreiben, die von der Bibliothek bereitgestellt werden. So können Sie das Erscheinungsbild von Elementen wie der Bildlaufleiste und der Seitenanzeige an das Design Ihrer App anpassen.

Zu den anpassbaren Attributen gehören:

  • fastScrollVerticalThumbDrawable : Legt das Drawable für den Bildlaufleisten-Thumb fest.
  • fastScrollPageIndicatorBackgroundDrawable : Legt das Hintergrund-Drawable für die Seitenanzeige fest.
  • fastScrollPageIndicatorMarginEnd : Legt den rechten Rand für die Seitenanzeige fest. Die Randwerte müssen positiv sein.
  • fastScrollVerticalThumbMarginEnd : Legt den rechten Rand für den vertikalen Bildlaufleisten-Thumb fest. Die Randwerte müssen positiv sein.

Wenn Sie diese Anpassungen anwenden möchten, definieren Sie einen benutzerdefinierten Stil in Ihren XML-Ressourcen.

<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>

Geben Sie dann die benutzerdefinierte Stilressource an PdfViewerFragment weiter, indem Sie PdfStylingOptions verwenden, wenn Sie eine Instanz des Fragments mit PdfViewerFragment.newInstance(stylingOptions) erstellen.

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.
    }
}

Wenn Sie PdfViewerFragment untergeordnet haben, verwenden Sie den geschützten Konstruktor, um die Stiloptionen anzugeben. So wird sichergestellt, dass Ihre benutzerdefinierten Stile korrekt auf Ihr erweitertes Fragment angewendet werden.

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)
        }
    }
}

Vollständige Implementierung

Der folgende Code enthält ein vollständiges Beispiel für die Implementierung von PdfViewerFragment in Ihrer Aktivität, einschließlich Initialisierung, Einbindung der Dateiauswahl, Suchfunktion und Anpassung der Benutzeroberfläche.

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"
    }
}

Wichtige Punkte zum Code

  • Ihr Projekt muss die Mindestanforderungen für das API-Level und die SDK-Erweiterung erfüllen.
  • Die Aktivität, die PdfViewerFragment hostet, muss AppCompatActivity erweitern.
  • Sie können PdfViewerFragment erweitern, um benutzerdefinierte Verhaltensweisen hinzuzufügen.
  • Passen Sie die Benutzeroberfläche von PdfViewerFragment an, indem Sie XML-Attribute überschreiben.