Customizing PDF annotation note hints in Kotlin for Android
Change the way annotation notes are hinted at for ink annotations. Get additional resources by visiting our guide on customizing the note icon in Android.
/* * Copyright © 2020-2025 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 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.