Реализовать средство просмотра PDF-файлов,Реализовать средство просмотра PDF-файлов

PdfViewerFragment — это специализированный Fragment , который можно использовать для отображения PDF-документов в вашем Android-приложении. PdfViewerFragment упрощает рендеринг PDF-файлов, позволяя вам сосредоточиться на других аспектах функциональности вашего приложения.

Результаты

PDF-документ, отображаемый в Android-приложении с помощью PdfViewerFragment.
PDF-документ отображается в приложении.

Совместимость версий

Для использования PdfViewerFragment ваше приложение должно быть ориентировано как минимум на Android S (уровень API 31) и иметь уровень расширения SDK 13. Если эти требования совместимости не выполняются, библиотека выдаст исключение UnsupportedOperationException .

Проверить версию расширения SDK во время выполнения можно с помощью модуля SdkExtensions . Это позволяет загружать фрагмент и PDF-документ только при условии, что устройство соответствует необходимым требованиям.

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

Зависимости

Чтобы интегрировать программу для просмотра PDF-файлов в ваше приложение, укажите зависимость androidx.pdf в файле build.gradle модуля вашего приложения. Библиотека для работы с PDF доступна в репозитории Google Maven.

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

Функции PdfViewerFragment

PdfViewerFragment отображает PDF-документы в постраничном формате, что упрощает навигацию по ним. Для эффективной загрузки фрагмент использует двухпроходную стратегию рендеринга, которая постепенно загружает размеры страниц.

Для оптимизации использования памяти PdfViewerFragment отображает только видимые в данный момент страницы и освобождает растровые изображения для страниц, находящихся за пределами экрана. Кроме того, PdfViewerFragment включает плавающую кнопку действия (FAB), которая поддерживает аннотации, запуская неявный интент android.intent.action.ANNOTATE , содержащий URI документа.

Выполнение

Добавление программы для просмотра PDF-файлов в ваше Android-приложение — это многоэтапный процесс.

Создайте макет действия

Для начала определите XML-макет для активности, которая содержит средство просмотра PDF-файлов. Макет должен включать FrameLayout для размещения PdfViewerFragment и кнопок для взаимодействия с пользователем, например, для поиска внутри документа.

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

Настройте мероприятие

Активность, в которой размещен PdfViewerFragment должна наследовать класс AppCompatActivity . В методе onCreate() активности установите представление содержимого в соответствии с созданным вами макетом и инициализируйте все необходимые элементы пользовательского интерфейса.

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

Создайте экземпляр PdfViewerFragment , используя менеджер фрагментов, полученный из getSupportFragmentManager() . Перед созданием нового экземпляра проверьте, существует ли уже существующий, особенно при изменении конфигурации.

В следующем примере функция initializePdfViewerFragment() обрабатывает создание и подтверждение транзакции фрагмента. Функция заменяет существующий фрагмент в контейнере экземпляром вашего 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"
    }
}

Расширить функциональность PdfViewerFragment

PdfViewerFragment предоставляет общедоступные функции, которые можно переопределить для расширения его возможностей. Создайте новый класс, наследующий от PdfViewerFragment . В вашем подклассе переопределите такие методы, как onLoadDocumentSuccess() и onLoadDocumentError() чтобы добавить собственную логику, например, запись метрик в лог.

@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 не имеет встроенного меню поиска, он поддерживает строку поиска. Вы управляете видимостью строки поиска с помощью API isTextSearchActive . Чтобы включить поиск по документу, необходимо установить свойство isTextSearchActive экземпляра PdfViewerFragment .

Используйте WindowCompat.setDecorFitsSystemWindows() , чтобы убедиться, что WindowInsetsCompat корректно передается в представления содержимого, что необходимо для правильного позиционирования представления поиска.

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

Интеграция с файловым редактором

Чтобы пользователи могли выбирать PDF-файлы со своих устройств, интегрируйте PdfViewerFragment с Android-файловым виджетом. Сначала обновите XML-файл разметки вашего Activity, добавив кнопку, которая запускает файловый виджет.

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

Далее, в вашем Activity запустите средство выбора файлов, используя registerForActivityResult(GetContent()) . Когда пользователь выберет файл, функция обратного вызова предоставит URI. Затем вы устанавливаете свойство documentUri вашего экземпляра PdfViewerFragment с этим URI, чтобы загрузить и отобразить выбранный PDF-файл.

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

Настройте пользовательский интерфейс

Вы можете настроить пользовательский интерфейс PdfViewerFragment , переопределив XML-атрибуты, предоставляемые библиотекой. Это позволяет адаптировать внешний вид таких элементов, как полоса прокрутки и индикатор страницы, в соответствии с дизайном вашего приложения.

К числу настраиваемых атрибутов относятся:

  • fastScrollVerticalThumbDrawable — Задает изображение для ползунка полосы прокрутки.
  • fastScrollPageIndicatorBackgroundDrawable — Задает фоновое изображение для индикатора страницы.
  • fastScrollPageIndicatorMarginEnd — Задает правый отступ для индикатора страницы. Убедитесь, что значения отступов положительные.
  • fastScrollVerticalThumbMarginEnd — Задает правый отступ для ползунка вертикальной полосы прокрутки. Убедитесь, что значения отступов положительные.

Для применения этих настроек определите пользовательский стиль в ваших XML-ресурсах.

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

Затем, при создании экземпляра фрагмента с помощью метода PdfViewerFragment.newInstance(stylingOptions) , укажите пользовательский ресурс стиля для PdfViewerFragment , используя PdfStylingOptions .

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

Если вы создали подкласс PdfViewerFragment , используйте защищенный конструктор для указания параметров стиля. Это гарантирует правильное применение ваших пользовательских стилей к расширенному фрагменту.

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

Полная реализация

Приведённый ниже код представляет собой полный пример реализации PdfViewerFragment в вашей активности, включая инициализацию, интеграцию средства выбора файлов, функцию поиска и настройку пользовательского интерфейса.

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

Основные моменты, касающиеся кода.

  • Убедитесь, что ваш проект соответствует минимальным требованиям к уровню API и расширениям SDK.
  • Активность, в которой размещен PdfViewerFragment должна наследовать AppCompatActivity .
  • Вы можете расширить функциональность PdfViewerFragment , чтобы добавить пользовательские функции.
  • Настройте пользовательский интерфейс PdfViewerFragment , переопределив XML-атрибуты.