Overlay View
This example shows you how to overlay views on the document.
/* * Copyright © 2019-2026 PSPDFKit GmbH. All rights reserved. * * The PSPDFKit Sample applications are licensed with a modified BSD license. * Please see License for details. This notice may not be removed from this file. */
package com.pspdfkit.catalog.examples.kotlin
import android.annotation.SuppressLintimport android.content.Contextimport android.graphics.Colorimport android.graphics.Matriximport android.graphics.PointFimport android.graphics.RectFimport android.net.Uriimport android.os.Bundleimport android.util.TypedValueimport android.view.MotionEventimport android.view.Viewimport android.webkit.WebViewimport android.widget.TextViewimport androidx.activity.viewModelsimport com.pspdfkit.annotations.Annotationimport com.pspdfkit.annotations.AnnotationTypeimport com.pspdfkit.annotations.FreeTextAnnotationimport com.pspdfkit.annotations.StampAnnotationimport com.pspdfkit.annotations.appearance.AssetAppearanceStreamGeneratorimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.catalog.tasks.ExtractAssetTaskimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.document.PdfDocumentimport com.pspdfkit.ui.PdfActivityimport com.pspdfkit.ui.PdfActivityIntentBuilderimport com.pspdfkit.ui.PdfFragmentimport com.pspdfkit.ui.overlay.OverlayLayoutParamsimport com.pspdfkit.ui.overlay.OverlayViewProviderimport kotlinx.coroutines.runBlockingimport kotlin.getValue
class OverlayViewsExample(context: Context) : SdkExample(context, R.string.overlayViewsExample, R.string.overlayViewsExampleDescription) {
override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) { // We use a custom utility class to extract the example document from the assets. ExtractAssetTask.extract(WELCOME_DOC, title, context, true) { documentFile -> val intent = PdfActivityIntentBuilder.fromUri(context, Uri.fromFile(documentFile)) .configuration( configuration // We disable annotation editing to keep the example focused. .editableAnnotationTypes(listOf(AnnotationType.NONE)) .build() ) .activityClass(OverlayViewsActivity::class) .build()
// Start the OverlayViewsActivity for the extracted document. context.startActivity(intent) } }}
// Suppress warnings about experimental classes.class OverlayViewsActivity : PdfActivity() {
private val viewModel: AnnotationCreationViewModel by viewModels()
private lateinit var viewProvider: MyViewProvider
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
viewProvider = MyViewProvider(this, requirePdfFragment(), savedInstanceState) // Add our view provider to the fragment. requirePdfFragment().addOverlayViewProvider(viewProvider) }
override fun onDocumentLoaded(document: PdfDocument) { super.onDocumentLoaded(document)
viewModel.createObjects { // We add a simple annotation to explain to users what to do. val clickHereAnnotation = FreeTextAnnotation( 0, RectF(50f, 600f, 350f, 400f), "Tap Anywhere on The Page" ) clickHereAnnotation.textSize = 48f clickHereAnnotation.color = Color.RED
runBlocking { document.annotationProvider.addAnnotationToPage(clickHereAnnotation) }
// Add a stamp with a custom AP stream that overlaps the overlay view for z-order testing. val stampAnnotation = StampAnnotation( 0, RectF(320f, 460f, 520f, 300f), "Overlay Order Stamp" ).apply { appearanceStreamGenerator = AssetAppearanceStreamGenerator("images/Nutrient_Logo.pdf") } runBlocking { document.annotationProvider.addAnnotationToPage(stampAnnotation) } } }
override fun onPageClick(document: PdfDocument, pageIndex: Int, event: MotionEvent?, pagePosition: PointF?, clickedAnnotation: Annotation?): Boolean { if (pageIndex == 0) { // User tapped on first page, toggle overlay ordering. viewProvider.toggleAnnotationOverlayOrder() return true } if (pageIndex == 1) { // User tapped on second page, add our overlay views. viewProvider.areSecondPageViewsVisible = !viewProvider.areSecondPageViewsVisible return true }
return super.onPageClick(document, pageIndex, event, pagePosition, clickedAnnotation) }
override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) // In order to remember what the user entered we have to save it here. viewProvider.saveState(outState) }}
/** A custom OverlayViewProvider that will display views on the first and second. */@SuppressLint("SetTextI18n", "SetJavaScriptEnabled")class MyViewProvider( context: Context, private val pdfFragment: PdfFragment, savedInstanceState: Bundle?) : OverlayViewProvider() {
companion object { private const val STATE_FIRST_PAGE_CLICKED = "MyOverlayProvider.FirstPageClicked" private const val STATE_SECOND_PAGE_CLICKED = "MyOverlayProvider.SecondPageClicked" private const val STATE_ANNOTATION_OVERLAY_ON_TOP = "MyOverlayProvider.AnnotationOverlayOnTop"
private const val TEXT_SIZE = 24f }
private val firstPageView: TextView private val secondPageView: WebView
private val pageToViewTransformation = Matrix()
/** This indicates whether our other form views are visible or not. */ var areFirstPageViewsVisible = true set(value) { field = value notifyOverlayViewsChanged() }
/** This indicates whether our other form views are visible or not. */ var areSecondPageViewsVisible = false set(value) { field = value notifyOverlayViewsChanged() }
private var isAnnotationOverlayAboveOverlayViews = false set(value) { field = value pdfFragment.isAnnotationOverlayAboveOverlayViews = value updateFirstPageText() notifyOverlayViewsChanged() }
init { // If you need a static set of views pre creating them is the most efficient way to go. firstPageView = TextView(context) updateFirstPageText() firstPageView.setBackgroundColor(Color.WHITE) firstPageView.setOnClickListener { isAnnotationOverlayAboveOverlayViews = !isAnnotationOverlayAboveOverlayViews } // We put this view next to our freetext annotation.
// The rect are the PDF coordinates on the page where our TextView should be. firstPageView.layoutParams = OverlayLayoutParams( RectF(250f, 500f, 668f, 300f), // We use SizingMode.SCALING here, this has the effect that the view will only be measured once and then a scale will be applied to it. // This means that the text size will automatically scale up as the page is zoomed. OverlayLayoutParams.SizingMode.SCALING )
// You can embed any kind of view, even a WebView. secondPageView = WebView(context) secondPageView.settings.javaScriptEnabled = true // Load the Nutrient homepage. secondPageView.loadUrl("https://nutrient.io/") // We fill the entire page, the Webview will consume all scroll events so scrolling the page will only work in the margins. secondPageView.layoutParams = OverlayLayoutParams( RectF(0f, 1024f, 768f, 0f), // We use the SizingMode.LAYOUT here since we want the WebView to actually increase the available size if the page is zoomed in. OverlayLayoutParams.SizingMode.LAYOUT )
if (savedInstanceState != null) { // If we have a saved state we restore it here. areFirstPageViewsVisible = savedInstanceState.getBoolean(STATE_FIRST_PAGE_CLICKED) areSecondPageViewsVisible = savedInstanceState.getBoolean(STATE_SECOND_PAGE_CLICKED) isAnnotationOverlayAboveOverlayViews = savedInstanceState.getBoolean(STATE_ANNOTATION_OVERLAY_ON_TOP) } }
// We're only putting views on Page 13 containing the form elements. override fun getPagesWithViews(): Set<Int> = setOf(0, 1)
// This method is called for all pages returned by getFilteredPages(). // You simply return a list of all views for the page and they will be displayed. override fun getViewsForPage(context: Context, document: PdfDocument, pageIndex: Int): List<View> { val views = mutableListOf<View>()
if (areFirstPageViewsVisible && pageIndex == 0) { // If the user clicked on the first page we add our text view to the list of views. // This way it will be overlaid on the document.
// We update the text size so it stays consistent no matter the device size. val matrix = pdfFragment.viewProjection.getPageToViewTransformation(pageIndex, pageToViewTransformation) firstPageView.setTextSize(TypedValue.COMPLEX_UNIT_PX, convertPdfPointsToPixel(TEXT_SIZE, pageIndex, matrix)) views.add(firstPageView) }
if (areSecondPageViewsVisible && pageIndex == 1) { views.add(secondPageView) }
return views }
override fun onViewsRecycled(pageIndex: Int, views: List<View>) { // This is called for every page with the views for the page that were recycled. // If you are pooling your views for efficient reuse this is good place to return them to the pool. }
override fun onViewsShown(pageIndex: Int, views: List<View>) { // This is called for every page with the views for the page that are now visible because the hosting page is now visible. }
override fun onViewsHidden(pageIndex: Int, views: List<View>) { // This is called for every page with the views for the page that are no longer visible because the hosting page is no longer visible. }
fun saveState(outState: Bundle) { // In our example we simply store whatever the user entered. outState.putBoolean(STATE_FIRST_PAGE_CLICKED, areFirstPageViewsVisible) outState.putBoolean(STATE_SECOND_PAGE_CLICKED, areSecondPageViewsVisible) outState.putBoolean(STATE_ANNOTATION_OVERLAY_ON_TOP, isAnnotationOverlayAboveOverlayViews) }
private fun updateFirstPageText() { val orderLabel = if (isAnnotationOverlayAboveOverlayViews) { "Annotation overlay above overlay views" } else { "Overlay views above annotation overlay" } firstPageView.text = "\uD83D\uDC4B I'm a custom overlay view!\nTap me or the page to toggle order.\nNow: $orderLabel" }
fun toggleAnnotationOverlayOrder() { isAnnotationOverlayAboveOverlayViews = !isAnnotationOverlayAboveOverlayViews }
/** This method converts the given pdf points to pixels for use in our text size. */ private fun convertPdfPointsToPixel(pdfPoints: Float, pageIndex: Int, pageToViewTransformation: Matrix): Float { val f = FloatArray(9) pageToViewTransformation.getValues(f)
// We multiply by the scale value stored in the matrix, but divide by the zoomscale so the result doesn't change as we zoom in. return (pdfPoints * f[Matrix.MSCALE_X]) / pdfFragment.getZoomScale(pageIndex) }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.