PdfViewerFragment to specjalny Fragment, którego możesz używać do wyświetlania dokumentów PDF w aplikacji na Androida.
PdfViewerFragment upraszcza renderowanie plików PDF, dzięki czemu możesz skupić się na innych aspektach funkcjonalności aplikacji.
Wyniki
Zgodność wersji
Aby korzystać z PdfViewerFragment, aplikacja musi być kierowana co najmniej na Androida S (API na poziomie 31) i poziom rozszerzenia SDK 13. Jeśli te wymagania dotyczące zgodności nie są spełnione, biblioteka zgłasza wyjątek UnsupportedOperationException.
Wersję rozszerzenia pakietu SDK możesz sprawdzić w czasie działania programu za pomocą modułu SdkExtensions. Dzięki temu możesz warunkowo wczytywać fragment i dokument PDF tylko wtedy, gdy urządzenie spełnia niezbędne wymagania.
if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 13) {
// Load the fragment and document.
}
Zależności
Aby dodać przeglądarkę plików PDF do aplikacji, zadeklaruj zależność androidx.pdf w pliku modułu build.gradle aplikacji. Biblioteka PDF jest dostępna w repozytorium Google Maven.
dependencies {
val pdfVersion = "1.0.0-alpha0X"
implementation("androidx.pdf:pdf:pdf-viewer-fragment:$pdfVersion")
}
Funkcje (PdfViewerFragment)
PdfViewerFragment wyświetla dokumenty PDF w formacie z podziałem na strony, co ułatwia nawigację. Aby zapewnić wydajne wczytywanie, fragment wykorzystuje dwuetapową strategię renderowania, która stopniowo wczytuje wymiary strony.
Aby zoptymalizować wykorzystanie pamięci, PdfViewerFragment renderuje tylko aktualnie widoczne strony i zwalnia mapy bitowe stron, które są poza ekranem.
Dodatkowo PdfViewerFragment zawiera pływający przycisk czynności (FAB), który obsługuje adnotacje, wywołując niejawną intencję android.intent.action.ANNOTATE zawierającą identyfikator URI dokumentu.
Implementacja
Dodawanie przeglądarki plików PDF do aplikacji na Androida to proces wieloetapowy.
Tworzenie układu aktywności
Zacznij od zdefiniowania układu XML aktywności, która będzie hostować przeglądarkę plików PDF. Układ powinien zawierać element FrameLayout, w którym będą się znajdować elementy PdfViewerFragment i przyciski umożliwiające interakcje użytkownika, np. wyszukiwanie w dokumencie.
<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>
Konfigurowanie aktywności
Aktywność, która zawiera PdfViewerFragment, musi być rozszerzeniem elementu AppCompatActivity. W metodzie onCreate() działania ustaw widok treści na utworzony układ i zainicjuj wszystkie niezbędne elementy interfejsu.
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)
}
}
Zainicjuj PdfViewerFragment
Utwórz instancję PdfViewerFragment za pomocą menedżera fragmentów uzyskanego z getSupportFragmentManager(). Zanim utworzysz nowy fragment, sprawdź, czy jego instancja już istnieje, zwłaszcza podczas zmian konfiguracji.
W przykładzie poniżej funkcja initializePdfViewerFragment() obsługuje tworzenie i zatwierdzanie transakcji fragmentu. Funkcja zastępuje istniejący fragment w kontenerze instancją 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"
}
}
Rozszerzanie funkcji PdfViewerFragment
PdfViewerFragment udostępnia funkcje publiczne, które możesz zastąpić, aby rozszerzyć jego możliwości. Utwórz nową klasę, która dziedziczy po PdfViewerFragment. W klasie podrzędnej zastąp metody takie jak onLoadDocumentSuccess() i onLoadDocumentError(), aby dodać niestandardową logikę, np. rejestrowanie danych.
@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)
}
}
Włącz wyszukiwanie dokumentów
PdfViewerFragment nie zawiera wbudowanego menu wyszukiwania, ale ma pasek wyszukiwania. Widoczność paska wyszukiwania możesz kontrolować za pomocą interfejsu isTextSearchActive API. Aby włączyć wyszukiwanie dokumentów, ustaw właściwość
isTextSearchActive instancji PdfViewerFragment.
Użyj
WindowCompat.setDecorFitsSystemWindows()
aby mieć pewność, że WindowInsetsCompat jest prawidłowo przekazywany do widoków treści, co jest
niezbędne do prawidłowego pozycjonowania widoku wyszukiwania.
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)
}
}
Integracja z oknem wyboru plików
Aby umożliwić użytkownikom wybieranie plików PDF z urządzenia, zintegruj aplikację z selektorem plików na Androidzie.PdfViewerFragment Najpierw zaktualizuj plik XML układu aktywności, aby zawierał przycisk uruchamiający selektor plików.
<...>
<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>
Następnie w aktywności uruchom selektor plików za pomocą funkcji
registerForActivityResult(GetContent()). Gdy użytkownik wybierze plik, wywołanie zwrotne zwróci identyfikator URI. Następnie ustawiasz właściwość documentUri instancji PdfViewerFragment za pomocą tego URI, aby wczytać i wyświetlić wybrany plik 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"
// ...
}
}
Dostosowywanie interfejsu
Możesz dostosować interfejs PdfViewerFragment, zastępując atrybuty XML udostępniane przez bibliotekę. Dzięki temu możesz dostosować wygląd elementów takich jak pasek przewijania i wskaźnik strony do projektu aplikacji.
Atrybuty, które można dostosować, to:
fastScrollVerticalThumbDrawable– ustawia rysunek dla suwaka paska przewijania.fastScrollPageIndicatorBackgroundDrawable– ustawia rysunek tła wskaźnika strony.fastScrollPageIndicatorMarginEnd– ustawia prawy margines wskaźnika strony. Sprawdź, czy wartości marginesu są dodatnie.fastScrollVerticalThumbMarginEnd– ustawia prawy margines suwaka paska przewijania w pionie. Sprawdź, czy wartości marginesu są dodatnie.
Aby zastosować te dostosowania, zdefiniuj styl niestandardowy w zasobach 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>
Następnie przekaż zasób stylu niestandardowego do PdfViewerFragment za pomocą
PdfStylingOptions podczas tworzenia instancji fragmentu za pomocą
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.
}
}
Jeśli masz podklasę PdfViewerFragment, użyj chronionego konstruktora, aby podać opcje stylu. Dzięki temu niestandardowe style zostaną prawidłowo zastosowane do rozszerzonego fragmentu.
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)
}
}
}
Pełna implementacja
Poniższy kod zawiera pełny przykład implementacji PdfViewerFragment w aktywności, w tym inicjowanie, integrację selektora plików, funkcję wyszukiwania i dostosowywanie interfejsu.
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"
}
}
Najważniejsze informacje o kodzie
- Upewnij się, że Twój projekt spełnia minimalne wymagania dotyczące poziomu API i rozszerzenia pakietu SDK.
- Aktywność hostująca
PdfViewerFragmentmusi być rozszerzeniem elementuAppCompatActivity. - Możesz rozszerzyć
PdfViewerFragment, aby dodać niestandardowe zachowania. - Dostosuj interfejs
PdfViewerFragment, zastępując atrybuty XML.