Custom Note Annotation Hinter Provider
Example of how to create a custom annotation hinter drawable provider.
/* * Copyright © 2020-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.content.Contextimport android.graphics.Canvasimport android.graphics.ColorFilterimport android.graphics.Matriximport android.graphics.PixelFormatimport android.graphics.PointFimport android.graphics.Rectimport android.graphics.RectFimport android.graphics.drawable.Drawableimport android.net.Uriimport android.os.Bundleimport android.text.TextUtilsimport androidx.annotation.IntRangeimport androidx.core.content.ContextCompatimport androidx.core.graphics.drawable.DrawableCompatimport com.pspdfkit.annotations.Annotationimport com.pspdfkit.annotations.AnnotationProvider.OnAnnotationUpdatedListenerimport com.pspdfkit.annotations.AnnotationTypeimport com.pspdfkit.catalog.Rimport com.pspdfkit.catalog.SdkExampleimport com.pspdfkit.catalog.tasks.ExtractAssetTaskimport com.pspdfkit.catalog.utils.Utilsimport com.pspdfkit.configuration.activity.PdfActivityConfigurationimport com.pspdfkit.document.PdfDocumentimport com.pspdfkit.ui.PdfActivityimport com.pspdfkit.ui.PdfActivityIntentBuilderimport com.pspdfkit.ui.drawable.PdfDrawableimport com.pspdfkit.ui.drawable.PdfDrawableProvider
/** * Shows how to create a custom annotation note hinter extending [PdfDrawableProvider]. */class CustomAnnotationNoteHinterProviderExample(context: Context) : SdkExample( context, R.string.customAnnotationNoteHinterProviderExampleTitle, R.string.customAnnotationNoteHinterProviderExampleDescription) { override fun launchExample(context: Context, configuration: PdfActivityConfiguration.Builder) { // Disable default annotation hinter provider. configuration.setAnnotationNoteHintingEnabled(false)
// We use a custom utility class to extract the example document from the assets. ExtractAssetTask.extract(WELCOME_DOC, title, context) { documentFile -> // To start the CustomAnnotationNoteHinterProviderActivity create a launch intent using the builder. val intent = PdfActivityIntentBuilder.fromUri(context, Uri.fromFile(documentFile)) .configuration(configuration.build()) .activityClass(CustomAnnotationNoteHinterProviderActivity::class) .build()
// Start the activity for the extracted document. context.startActivity(intent) } }}
class CustomAnnotationNoteHinterProviderActivity : PdfActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState)
val customAnnotationNoteHinter = CustomAnnotationNoteHinter(this)
// Inject our custom hinter to the framework. requirePdfFragment().apply { // We need to register custom drawable provider so we can provide our custom hint drawables. addDrawableProvider(customAnnotationNoteHinter)
// We also want to react to annotation updated events in our hinter. requirePdfFragment().addOnAnnotationUpdatedListener(customAnnotationNoteHinter) } }}
/** * A custom annotation note hinter provider that works only for ink annotations. */private class CustomAnnotationNoteHinter(private val pdfActivity: PdfActivity) : PdfDrawableProvider(), OnAnnotationUpdatedListener { private val noteIcon: Drawable = ContextCompat.getDrawable(pdfActivity, R.drawable.ic_pin_drop) ?: throw IllegalStateException("Can't retrieve note drawable from resources.")
override suspend fun getDrawablesForPage( context: Context, document: PdfDocument, @IntRange(from = 0) pageIndex: Int ): List<PdfDrawable> { return document.annotationProvider.getAnnotations(pageIndex) .asSequence() .filter { it.type == AnnotationType.INK } .map { NoteInkHinterDrawable(pdfActivity, noteIcon, it) } .toList() }
// We notify change to provided drawables whenever any ink annotation changes (is created, updated or removed). override fun onAnnotationCreated(annotation: Annotation) = notifyDrawablesChangedIfSupported(annotation) override fun onAnnotationUpdated(annotation: Annotation) = notifyDrawablesChangedIfSupported(annotation) override fun onAnnotationRemoved(annotation: Annotation) = notifyDrawablesChangedIfSupported(annotation)
private fun notifyDrawablesChangedIfSupported(annotation: Annotation) { if (annotation.type == AnnotationType.INK) { notifyDrawablesChanged() } }
// There's no need to update note hinter drawables on Annotation Z-order changes. This method is a no-op. override fun onAnnotationZOrderChanged(pageIndex: Int, oldOrder: List<Annotation>, newOrder: List<Annotation>) = Unit}
private class NoteInkHinterDrawable internal constructor( private val activity: PdfActivity, private val noteIcon: Drawable, private val annotation: Annotation) : PdfDrawable() { private val viewBoundingBoxRounded: Rect = Rect() private val pdfBoundingBox: RectF = RectF() private val viewPoint: PointF = PointF() private val viewBoundingBox: RectF = RectF() private val widthPx: Int private val heightPx: Int private val halfWidthPx: Int private val halfHeightPx: Int
init { annotation.getBoundingBox(pdfBoundingBox) widthPx = Utils.dpToPx(activity, 24) heightPx = Utils.dpToPx(activity, 24) halfWidthPx = widthPx / 2 halfHeightPx = heightPx / 2 }
override fun draw(canvas: Canvas) { invalidateSelf() if (TextUtils.isEmpty(annotation.contents)) { return } DrawableCompat.setTint(noteIcon, annotation.color) noteIcon.bounds = viewBoundingBoxRounded noteIcon.draw(canvas) }
override fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) { noteIcon.alpha = ALPHA }
override fun setColorFilter(colorFilter: ColorFilter?) { noteIcon.colorFilter = colorFilter }
@Deprecated("Deprecated in Java") override fun getOpacity(): Int { return PixelFormat.TRANSLUCENT }
override fun updatePdfToViewTransformation(matrix: Matrix) { super.updatePdfToViewTransformation(matrix)
annotation.getBoundingBox(pdfBoundingBox) viewPoint.set(pdfBoundingBox.centerX(), pdfBoundingBox.centerY())
activity.requirePdfFragment().viewProjection.toViewPoint(viewPoint, annotation.pageIndex)
viewBoundingBox.set( viewPoint.x - halfWidthPx, viewPoint.y - halfHeightPx, viewPoint.x + halfWidthPx, viewPoint.y + halfHeightPx ) viewBoundingBox.round(viewBoundingBoxRounded) }
companion object { private const val ALPHA = 255 }}This code sample is an example that illustrates how to use our SDK. Please adapt it to your specific use case.