PdfViewerFragment es un Fragment especializado que puedes usar para mostrar documentos PDF en tu aplicación para Android.
PdfViewerFragment simplifica la renderización de PDF, lo que te permite concentrarte en otros aspectos de la funcionalidad de tu app.
Resultados
Compatibilidad de versiones
Para usar PdfViewerFragment, tu aplicación debe tener como objetivo un nivel de API 31 (Android S) y un nivel de extensión del SDK 13 como mínimo. Si no se cumplen estos requisitos de compatibilidad, la biblioteca arroja un UnsupportedOperationException.
Puedes verificar la versión de la extensión del SDK en el tiempo de ejecución con el módulo SdkExtensions. Esto te permite cargar el fragmento y el documento PDF de forma condicional solo si el dispositivo cumple con los requisitos necesarios.
if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 13) {
// Load the fragment and document.
}
Dependencias
Para incorporar el visor de PDF en tu aplicación, declara la dependencia androidx.pdf en el archivo build.gradle del módulo de tu app. Se puede acceder a la biblioteca de PDF desde el repositorio de Google Maven.
dependencies {
val pdfVersion = "1.0.0-alpha0X"
implementation("androidx.pdf:pdf:pdf-viewer-fragment:$pdfVersion")
}
Funciones PdfViewerFragment
PdfViewerFragment presenta los documentos PDF en formato paginado, lo que facilita la navegación. Para una carga eficiente, el fragmento emplea una estrategia de renderización de dos pasos que carga progresivamente las dimensiones de la página.
Para optimizar el uso de memoria, PdfViewerFragment solo renderiza las páginas visibles actualmente y libera los mapas de bits de las páginas que no están en pantalla.
Además, PdfViewerFragment incluye un botón de acción flotante (BAF) que admite anotaciones activando un intent android.intent.action.ANNOTATE implícito que contiene el URI del documento.
Implementación
Agregar un visor de PDF a tu aplicación para Android es un proceso de varios pasos.
Cómo crear el diseño de la actividad
Comienza por definir el diseño XML para la actividad que aloja el visor de PDF. El diseño debe incluir un FrameLayout para contener el PdfViewerFragment y botones para las interacciones del usuario, como la búsqueda dentro del documento.
<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>
Configura la actividad
La actividad que aloja PdfViewerFragment debe extender AppCompatActivity. En el método onCreate() de la actividad, establece la vista de contenido en el diseño que creaste y, luego, inicializa los elementos de la IU necesarios.
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)
}
}
Inicializa PdfViewerFragment
Crea una instancia de PdfViewerFragment con un administrador de fragmentos obtenido de getSupportFragmentManager(). Verifica si ya existe una instancia del fragmento antes de crear una nueva, en especial durante los cambios de configuración.
En el siguiente ejemplo, la función initializePdfViewerFragment() controla la creación y la confirmación de la transacción de fragmento. La función reemplaza un fragmento existente en un contenedor por una instancia de tu 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"
}
}
Extiende la funcionalidad de PdfViewerFragment
PdfViewerFragment expone funciones públicas que puedes anular para extender sus capacidades. Crea una clase nueva que herede de PdfViewerFragment. En tu subclase, anula métodos como onLoadDocumentSuccess() y onLoadDocumentError() para agregar lógica personalizada, como el registro de métricas.
@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)
}
}
Habilita la búsqueda de documentos
Si bien PdfViewerFragment no incluye un menú de búsqueda integrado, admite una barra de búsqueda. Controlas la visibilidad de la barra de búsqueda con la API de isTextSearchActive. Para habilitar la búsqueda de documentos, debes establecer la propiedad isTextSearchActive de tu instancia de PdfViewerFragment.
Usa WindowCompat.setDecorFitsSystemWindows() para garantizar que WindowInsetsCompat se pase correctamente a las vistas de contenido, lo que es necesario para el posicionamiento adecuado de la vista de búsqueda.
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)
}
}
Integración con el selector de archivos
Para permitir que los usuarios seleccionen archivos PDF desde sus dispositivos, integra PdfViewerFragment con el selector de archivos de Android. Primero, actualiza el diseño XML de tu actividad para incluir un botón que inicie el selector de archivos.
<...>
<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>
A continuación, en tu actividad, inicia el selector de archivos con registerForActivityResult(GetContent()). Cuando el usuario selecciona un archivo, la devolución de llamada proporciona un URI. Luego, establece la propiedad documentUri de tu instancia PdfViewerFragment con este URI para cargar y mostrar el PDF seleccionado.
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"
// ...
}
}
Personaliza la IU
Puedes personalizar la interfaz de usuario de PdfViewerFragment anulando los atributos XML que expone la biblioteca. Esto te permite adaptar la apariencia de elementos como la barra de desplazamiento y el indicador de página para que coincidan con el diseño de tu app.
Los atributos personalizables incluyen los siguientes:
fastScrollVerticalThumbDrawable: Establece el elemento de diseño para el control deslizante de la barra de desplazamiento.fastScrollPageIndicatorBackgroundDrawable: Establece el elemento de diseño dibujable de fondo para el indicador de página.fastScrollPageIndicatorMarginEnd: Establece el margen derecho del indicador de página. Asegúrate de que los valores de margen sean positivos.fastScrollVerticalThumbMarginEnd: Establece el margen derecho del control deslizante de la barra de desplazamiento vertical. Asegúrate de que los valores de margen sean positivos.
Para aplicar estas personalizaciones, define un estilo personalizado en tus recursos 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>
Luego, proporciona el recurso de diseño personalizado a PdfViewerFragment con PdfStylingOptions cuando crees una instancia del fragmento con 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.
}
}
Si creaste una subclase de PdfViewerFragment, usa el constructor protegido para proporcionar las opciones de diseño. Esto garantiza que tus estilos personalizados se apliquen correctamente a tu fragmento extendido.
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)
}
}
}
Implementación completa
El siguiente código proporciona un ejemplo completo de cómo implementar PdfViewerFragment en tu actividad, incluida la inicialización, la integración del selector de archivos, la funcionalidad de búsqueda y la personalización de la IU.
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"
}
}
Puntos clave sobre el código
- Asegúrate de que tu proyecto cumpla con los requisitos mínimos de nivel de API y extensión del SDK.
- La actividad que aloja
PdfViewerFragmentdebe extenderAppCompatActivity. - Puedes extender
PdfViewerFragmentpara agregar comportamientos personalizados. - Personaliza la IU de
PdfViewerFragmentanulando los atributos XML.