Shows how to customize annotation inspector.


/*
* Copyright © 2014-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 static com.pspdfkit.catalog.utils.Utils.dpToPx;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.pspdfkit.annotations.Annotation;
import com.pspdfkit.annotations.AnnotationType;
import com.pspdfkit.annotations.defaults.AnnotationPreferencesManager;
import com.pspdfkit.catalog.R;
import com.pspdfkit.catalog.SdkExample;
import com.pspdfkit.catalog.tasks.ExtractAssetTask;
import com.pspdfkit.configuration.activity.PdfActivityConfiguration;
import com.pspdfkit.ui.PdfActivity;
import com.pspdfkit.ui.PdfActivityIntentBuilder;
import com.pspdfkit.ui.PdfFragment;
import com.pspdfkit.ui.inspector.PropertyInspector;
import com.pspdfkit.ui.inspector.PropertyInspectorView;
import com.pspdfkit.ui.inspector.annotation.AnnotationCreationInspectorController;
import com.pspdfkit.ui.inspector.annotation.DefaultAnnotationEditingInspectorController;
import com.pspdfkit.ui.inspector.views.ColorPickerInspectorDetailView;
import com.pspdfkit.ui.inspector.views.PropertyInspectorDividerDecoration;
import com.pspdfkit.ui.inspector.views.SliderPickerInspectorView;
import com.pspdfkit.ui.special_mode.controller.AnnotationCreationController;
import com.pspdfkit.ui.special_mode.controller.AnnotationInspectorController;
import com.pspdfkit.ui.special_mode.controller.AnnotationTool;
import com.pspdfkit.ui.special_mode.controller.AnnotationToolVariant;
import java.util.ArrayList;
import java.util.List;
/** Showcases how to create custom annotation inspector from scratch. */
public class CustomAnnotationInspectorExample extends SdkExample {
public CustomAnnotationInspectorExample(@NonNull final Context context) {
super(
context,
R.string.annotationCustomInspectorExampleTitle,
R.string.annotationCustomInspectorExampleDescription);
}
@Override
public void launchExample(
@NonNull final Context context, @NonNull final PdfActivityConfiguration.Builder configuration) {
// Extract the document from the assets. The launched activity will add annotations to that
// document.
ExtractAssetTask.extract(WELCOME_DOC, getTitle(), context, documentFile -> {
final Intent intent = PdfActivityIntentBuilder.fromUri(context, Uri.fromFile(documentFile))
.configuration(configuration.build())
.activityClass(CustomAnnotationInspectorActivity.class)
.build();
// Start the CustomAnnotationInspectorActivity for the extracted document.
context.startActivity(intent);
});
}
/**
* Shows how to implement custom {@link AnnotationInspectorController} and how to add custom views
* to existing annotation inspector.
*/
public static class CustomAnnotationInspectorActivity extends PdfActivity {
/** Colors that are going to be available in annotation creation inspector's color picker. */
private static final List<Integer> CREATION_PICKER_COLORS = List.of(
Color.rgb(244, 67, 54), // RED
Color.rgb(139, 195, 74), // LIGHT GREEN
Color.rgb(33, 150, 243), // BLUE
Color.rgb(252, 237, 140), // YELLOW
Color.rgb(233, 30, 99), // PINK
// GRAYSCALE below
Color.rgb(255, 255, 255),
Color.rgb(224, 224, 224),
Color.rgb(158, 158, 158),
Color.rgb(66, 66, 66),
Color.rgb(0, 0, 0));
/** Colors that are going to be displayed in annotation editing inspector's root layout. */
private static final List<Integer> EDITING_PICKER_COLORS = List.of(
Color.rgb(244, 67, 54), // RED
Color.rgb(139, 195, 74), // LIGHT GREEN
Color.rgb(33, 150, 243), // BLUE
Color.rgb(252, 237, 140), // YELLOW
Color.rgb(233, 30, 99)); // PINK
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create custom annotation creation inspector displaying custom UI in modal dialog.
CustomAnnotationCreationInspector customAnnotationCreationInspector =
new CustomAnnotationCreationInspector();
// Add custom color picker view to annotation editing inspector.
DefaultAnnotationEditingInspectorController customAnnotationEditingInspector =
new DefaultAnnotationEditingInspectorController(this, getPropertyInspectorCoordinator()) {
@Override
public void onPreparePropertyInspector(@NonNull PropertyInspector inspector) {
// Prepare standard property inspector.
super.onPreparePropertyInspector(inspector);
// Add decoration to show dividers between inspector items.
inspector.addItemDecoration(new PropertyInspectorDividerDecoration(getContext()));
// Add custom color picker for the ink annotation.
if (getAnnotationEditingController() == null) return;
final List<Annotation> annotations =
getAnnotationEditingController().getCurrentlySelectedAnnotations();
if (annotations.isEmpty()) return;
for (Annotation annotation : annotations) {
if (annotation.getType() != AnnotationType.INK) return;
}
final var colorPickerInspectorDetailView = getColorPickerInspectorDetailView(annotations);
inspector.addInspectorView(colorPickerInspectorDetailView);
}
private @NonNull ColorPickerInspectorDetailView getColorPickerInspectorDetailView(
List<Annotation> annotations) {
final var detailView = new ColorPickerInspectorDetailView(
getContext(), EDITING_PICKER_COLORS, EDITING_PICKER_COLORS.get(0), false);
detailView.setShowSelectionIndicator(false);
detailView.setOnColorPickedListener((view, color) -> {
if (getAnnotationEditingController() != null) {
for (Annotation annotation : annotations) {
annotation.setColor(color);
}
}
});
return detailView;
}
};
// Set custom annotation inspector controllers to the activity.
setAnnotationCreationInspectorController(customAnnotationCreationInspector);
setAnnotationEditingInspectorController(customAnnotationEditingInspector);
// Restore inspectors state.
if (savedInstanceState != null) {
customAnnotationCreationInspector.onRestoreInstanceState(savedInstanceState);
customAnnotationEditingInspector.onRestoreInstanceState(savedInstanceState);
}
}
@NonNull
private Context getContext() {
return this;
}
/**
* Custom implementation of the {@link AnnotationCreationInspectorController} showing simplified
* annotation inspector in a dialog.
*/
private class CustomAnnotationCreationInspector implements AnnotationCreationInspectorController {
private static final String STATE_INSPECTOR_DIALOG_VISIBLE = "STATE_INSPECTOR_DIALOG_VISIBLE";
@Nullable
private AnnotationCreationController controller;
@Nullable
private AlertDialog dialog;
@Nullable
private Bundle restoredInstanceState;
public void bindAnnotationCreationController(@NonNull AnnotationCreationController controller) {
this.controller = controller;
// Bind to annotation creation controller.
controller.bindAnnotationInspectorController(this);
// Restore saved state if available.
onRestoreState();
}
public void unbindAnnotationCreationController() {
cancel();
if (controller != null) {
controller.unbindAnnotationInspectorController();
this.controller = null;
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
outState.putBoolean(STATE_INSPECTOR_DIALOG_VISIBLE, isAnnotationInspectorVisible());
}
@Override
public void onRestoreInstanceState(@NonNull Bundle savedState) {
this.restoredInstanceState = savedState;
onRestoreState();
}
@Override
public void showAnnotationInspector(boolean animate) {
// Create custom layout for your inspector.
View inspectorRoot = getLayoutInflater().inflate(R.layout.custom_annotation_inspector, null);
LinearLayout inspectorContainer = inspectorRoot.findViewById(R.id.inspectorContainer);
// We are reusing our inspector views just for the sake of simplicity.
List<PropertyInspectorView> inspectorViews = getInspectorViews();
if (inspectorViews == null) {
cancel();
return;
}
for (PropertyInspectorView inspectorView : inspectorViews) {
inspectorContainer.addView(inspectorView.getView());
}
// Create alert dialog with inspector layout set as content view.
this.dialog = new AlertDialog.Builder(getContext())
.setView(inspectorRoot)
.setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss())
.setCancelable(true)
.show();
}
@Override
public void hideAnnotationInspector(boolean animate) {
if (dialog != null) {
dialog.cancel();
}
}
@Override
public void toggleAnnotationInspector(boolean animate) {
if (isAnnotationInspectorVisible()) {
hideAnnotationInspector(animate);
} else {
showAnnotationInspector(animate);
}
}
@Override
public boolean isAnnotationInspectorVisible() {
return dialog != null && dialog.isShowing();
}
@Override
public boolean hasAnnotationInspector() {
// Enable annotation inspector only for ink and free-text annotations.
return controller != null
&& controller.getActiveAnnotationTool() != null
&& (controller.getActiveAnnotationTool() == AnnotationTool.INK
|| controller.getActiveAnnotationTool() == AnnotationTool.FREETEXT);
}
public void cancel() {
if (dialog != null) {
dialog.cancel();
dialog = null;
}
}
@Nullable
private List<PropertyInspectorView> getInspectorViews() {
PdfFragment fragment = getPdfFragment();
if (controller == null || fragment == null) return null;
final AnnotationPreferencesManager annotationPreferences = fragment.getAnnotationPreferences();
final AnnotationTool annotationTool = controller.getActiveAnnotationTool();
final AnnotationToolVariant annotationToolVariant = controller.getActiveAnnotationToolVariant();
if (annotationTool == null || annotationToolVariant == null) return null;
List<PropertyInspectorView> inspectorViews = new ArrayList<>();
// Create color picker.
ColorPickerInspectorDetailView colorPicker = new ColorPickerInspectorDetailView(
getContext(), CREATION_PICKER_COLORS, controller.getColor(), false);
colorPicker.setOnColorPickedListener((view, color) -> {
annotationPreferences.setColor(annotationTool, annotationToolVariant, color);
controller.setColor(color);
});
int padding = dpToPx(getContext(), 8);
colorPicker.setPadding(padding, padding, padding, padding);
inspectorViews.add(colorPicker);
// Create thickness picker for ink annotations.
if (annotationTool == AnnotationTool.INK) {
SliderPickerInspectorView thicknessPicker = new SliderPickerInspectorView(
getContext(),
"Thickness",
"%d pt",
1,
20,
(int) controller.getThickness(),
(view, value) -> {
annotationPreferences.setThickness(annotationTool, annotationToolVariant, value);
controller.setThickness(value);
});
inspectorViews.add(thicknessPicker);
}
// Create text size picker for free-text annotation.
if (annotationTool == AnnotationTool.FREETEXT) {
SliderPickerInspectorView textSizePicker = new SliderPickerInspectorView(
getContext(),
"Text size",
"%d pt",
10,
32,
(int) controller.getTextSize(),
(view, value) -> {
annotationPreferences.setTextSize(annotationTool, annotationToolVariant, value);
controller.setTextSize(value);
});
inspectorViews.add(textSizePicker);
}
return inspectorViews;
}
private void onRestoreState() {
// Restore state when bound to controller and having restored state.
if (controller == null || restoredInstanceState == null) return;
boolean isDialogVisible = restoredInstanceState.getBoolean(STATE_INSPECTOR_DIALOG_VISIBLE, false);
if (isDialogVisible) {
showAnnotationInspector(false);
}
restoredInstanceState = null;
}
}
}
}

This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.