Annotation Overlay
Display annotations in overlay mode as separate Android Views above the page.
/* * Copyright © 2018-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.java;
import android.content.Context;import android.content.Intent;import android.net.Uri;import android.os.Bundle;import android.view.ContextMenu;import android.view.MenuItem;import android.view.View;import androidx.annotation.NonNull;import com.google.android.material.floatingactionbutton.FloatingActionButton;import com.pspdfkit.annotations.Annotation;import com.pspdfkit.annotations.AnnotationType;import com.pspdfkit.catalog.R;import com.pspdfkit.catalog.SdkExample;import com.pspdfkit.catalog.tasks.ExtractAssetTask;import com.pspdfkit.compose.toolbar.state.ToolbarIcon;import com.pspdfkit.compose.toolbar.state.ToolbarItem;import com.pspdfkit.compose.toolbar.state.ToolbarItemState;import com.pspdfkit.configuration.activity.PdfActivityConfiguration;import com.pspdfkit.ui.PdfActivity;import com.pspdfkit.ui.PdfActivityIntentBuilder;import com.pspdfkit.ui.annotations.OnAnnotationSelectedListener;import com.pspdfkit.ui.rendering.AnnotationOverlayRenderStrategy;import java.util.ArrayList;import java.util.EnumSet;import java.util.List;
/** Showcases how to display annotations in overlay mode. */public class AnnotationOverlayExample extends SdkExample {
public AnnotationOverlayExample(@NonNull final Context context) { super(context, R.string.annotationOverlayExampleTitle, R.string.annotationOverlayExampleDescription); }
@Override public void launchExample( @NonNull final Context context, @NonNull final PdfActivityConfiguration.Builder configuration) { // Use custom layout with FAB for per-annotation overlay toggle. configuration.layout(R.layout.activity_annotation_overlay);
// Extract the document from the assets. ExtractAssetTask.extract(ANNOTATIONS_EXAMPLE, getTitle(), context, documentFile -> { // To start the example create a launch intent using the builder. final Intent intent = PdfActivityIntentBuilder.fromUri(context, Uri.fromFile(documentFile)) .configuration(configuration.build()) .activityClass(AnnotationOverlayActivity.class) .build();
context.startActivity(intent); }); }
/** Showcases how to enable overlay mode for annotations. */ public static class AnnotationOverlayActivity extends PdfActivity implements OnAnnotationSelectedListener {
/** * Holds per-type overlay overrides. When overlay is globally off, these types are * included in overlay. When overlay is globally on, these types are excluded from overlay. */ @NonNull private final EnumSet<AnnotationType> perTypeOverrides = EnumSet.noneOf(AnnotationType.class);
/** Current strategy used for rendering annotations in overlay. */ @NonNull private AnnotationOverlayRenderStrategy.Strategy currentOverlayRenderingStrategy = AnnotationOverlayRenderStrategy.Strategy.AP_STREAM_RENDERING;
/** Flag indicating whether annotation overlay is enabled. */ private boolean annotationOverlayEnabled = false;
/** FAB for toggling overlay on selected annotations. */ private FloatingActionButton overlayFab;
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
// Set up the FAB for toggling overlay on selected annotation types. overlayFab = findViewById(R.id.fab_toggle_annotation_overlay); registerForContextMenu(overlayFab); overlayFab.setOnClickListener(View::showContextMenu);
// Listen for annotation selection changes to show/hide the FAB. requirePdfFragment().addOnAnnotationSelectedListener(this);
// We'll set overlay render strategy that just returns currently configured strategy for all // annotations. requirePdfFragment().setAnnotationOverlayRenderStrategy(annotation -> currentOverlayRenderingStrategy);
// Add custom toolbar items, computing their labels/visibility from current state. setToolbarItemsProvider(defaultItems -> { final List<ToolbarItem> items = new ArrayList<>(defaultItems); items.add(new ToolbarItem.Button( "toggle_annotation_overlay", R.id.toggle_annotation_overlay, annotationOverlayEnabled ? "Disable annotation overlay" : "Enable annotation overlay", ToolbarIcon.None.INSTANCE, new ToolbarItemState(true, false, true), ToolbarItem.ItemEdge.End, ToolbarItem.OverflowBehavior.Never)); // Toggling overlay strategy only makes sense when annotation overlay is enabled, // so the item is hidden otherwise. final String renderingStrategyTitle = currentOverlayRenderingStrategy == AnnotationOverlayRenderStrategy.Strategy.PLATFORM_RENDERING ? "Use AP stream rendering" : "Use platform rendering"; items.add(new ToolbarItem.Button( "toggle_overlay_rendering_strategy", R.id.toggle_overlay_rendering_strategy, renderingStrategyTitle, ToolbarIcon.None.INSTANCE, new ToolbarItemState(true, false, annotationOverlayEnabled), ToolbarItem.ItemEdge.End, ToolbarItem.OverflowBehavior.Never)); items.add(new ToolbarItem.Button( "fire_low_memory_notification", R.id.fire_low_memory_notification, "Fire low memory notification", ToolbarIcon.None.INSTANCE, new ToolbarItemState(true, false, true), ToolbarItem.ItemEdge.End, ToolbarItem.OverflowBehavior.Never)); return items; });
setOnToolbarItemClickListener(id -> { if (id == R.id.toggle_annotation_overlay) { if (annotationOverlayEnabled) { disableOverlayForAllAnnotationTypes(); } else { enableOverlayForSupportedAnnotationTypes(); } invalidateToolbarItems(); return true; } else if (id == R.id.fire_low_memory_notification) { // We fire low memory notification manually to showcase how annotation overlay // mode behaves when system is low on memory. requirePdfFragment().onLowMemory(); return true; } else if (id == R.id.toggle_overlay_rendering_strategy) { // Toggle the current overlay rendering strategy. if (currentOverlayRenderingStrategy == AnnotationOverlayRenderStrategy.Strategy.AP_STREAM_RENDERING) { currentOverlayRenderingStrategy = AnnotationOverlayRenderStrategy.Strategy.PLATFORM_RENDERING; } else { currentOverlayRenderingStrategy = AnnotationOverlayRenderStrategy.Strategy.AP_STREAM_RENDERING; } // Invalidate items to change button text to current state. invalidateToolbarItems(); return true; } return false; });
// We'll enable overlay for all supported annotation types immediately after activity // creation. enableOverlayForSupportedAnnotationTypes(); invalidateToolbarItems(); }
@Override public void onDestroy() { super.onDestroy(); requirePdfFragment().removeOnAnnotationSelectedListener(this); }
@Override public void onAnnotationSelected(@NonNull Annotation annotation, boolean annotationCreated) { overlayFab.setVisibility(View.VISIBLE); }
@Override public void onAnnotationDeselected(@NonNull Annotation annotation, boolean reselected) { if (!reselected) { overlayFab.setVisibility(View.GONE); } }
private void disableOverlayForAllAnnotationTypes() { annotationOverlayEnabled = false; perTypeOverrides.clear(); applyOverlayTypes(); }
private void enableOverlayForSupportedAnnotationTypes() { annotationOverlayEnabled = true; perTypeOverrides.clear(); applyOverlayTypes(); }
private void applyOverlayTypes() { final EnumSet<AnnotationType> overlayTypes; if (annotationOverlayEnabled) { // Start with all types, then remove manually excluded ones. overlayTypes = EnumSet.allOf(AnnotationType.class); overlayTypes.removeAll(perTypeOverrides); } else { // Start with none, then add manually included ones. overlayTypes = EnumSet.noneOf(AnnotationType.class); overlayTypes.addAll(perTypeOverrides); } requirePdfFragment().setOverlaidAnnotationTypes(overlayTypes); }
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo);
List<Annotation> selectedAnnotations = requirePdfFragment().getSelectedAnnotations(); if (selectedAnnotations.size() != 1) return;
final AnnotationType annotationType = selectedAnnotations.get(0).getType(); // The effective overlay state for this type is: globally on XOR overridden. final boolean effectivelyOverlaid = annotationOverlayEnabled ^ perTypeOverrides.contains(annotationType); menu.add(0, R.id.toggle_annotation_overlay, 0, effectivelyOverlaid ? "Disable overlay" : "Enable overlay"); }
@Override public boolean onContextItemSelected(@NonNull MenuItem item) { if (requirePdfFragment().getSelectedAnnotations().size() != 1) { return super.onContextItemSelected(item); } final Annotation annotation = requirePdfFragment().getSelectedAnnotations().get(0); final AnnotationType annotationType = annotation.getType();
final int itemId = item.getItemId(); if (itemId == R.id.toggle_annotation_overlay) { if (perTypeOverrides.contains(annotationType)) { perTypeOverrides.remove(annotationType); } else { perTypeOverrides.add(annotationType); } applyOverlayTypes(); return true; } return super.onContextItemSelected(item); } }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.