---
title: "Draw content above displayed document on Android | Nutrient"
canonical_url: "https://www.nutrient.io/guides/android/features/drawable-api/"
md_url: "https://www.nutrient.io/guides/android/features/drawable-api.md"
last_updated: "2026-10-01T15:29:10.333Z"
description: "An app using Nutrient Android SDK can draw content above a displayed document using PdfDrawable, which is a subtype of Android’s Drawable class."
---

# Drawing content above displayed documents

An app using Nutrient Android SDK can draw content above a displayed document using [`PdfDrawable`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable/index.html). `PdfDrawable` is a subtype of Android’s [`Drawable`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html) class, so it works similarly. This article guides you through the process of creating a drawable that paints a red rectangle on a document, positioning it using [PDF page coordinates](https://www.nutrient.io/guides/android/faq/coordinate-spaces.md).

For more information on how to use the Drawable API to draw watermarks, check out `WatermarkExample` inside the Catalog app. Another great resource is `ScreenReaderExample`. It’s a complete example of all the features and elements discussed in this article.

## Using drawables

To draw a [`PdfDrawable`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable/index.html) onto a page, you need to provide it to the [`PdfFragment`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-fragment/index.html) using a [`PdfDrawableProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable-provider/index.html). You can also provide it to [`PdfThumbnailBar`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-thumbnail-bar/index.html) and [`PdfThumbnailGrid`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-thumbnail-grid/index.html) to draw on thumbnails.

You can think of the [`PdfDrawableProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable-provider/index.html) like the adapter of a list view, but instead of creating and serving list view items, it serves drawables. A very simple implementation of a drawable provider might serve a single `RectDrawable` painted on every page, like this:

### KOTLIN

```kotlin

// The drawable provider needs to return a list of drawables for a page.
// Since we're only serving a single drawable, we create a one-element list.
val drawables = Collections.singletonList(SquareDrawable(RectF(0f, 100f, 100f, 0f)))

// This is the drawable provider. You only need to implement a single method.
val provider = object : PdfDrawableProvider() {
    override fun getDrawablesForPage(
            context: Context,
            document: PdfDocument,
            pageIndex: Int): List<PdfDrawable>? {
        // Return the same drawable for every requested page.
        return drawables
    }
}

```

### JAVA

```java

// The drawable provider needs to return a list of drawables for a page.
// Since we're only serving a single drawable, we create a one-element list.
private final List<? extends PdfDrawable> drawables = Collections.singletonList(
		new SquareDrawable(new RectF(0f, 100f, 100f, 0f)));

// This is the drawable provider. You only need to implement a single method.
private final PdfDrawableProvider provider = new PdfDrawableProvider() {
    @Override
	public List<? extends PdfDrawable> getDrawablesForPage(
            @NonNull Context context,
            @NonNull PdfDocument document,
            @IntRange(from = 0) int pageIndex) {

    	// Return the same drawable for every requested page.
        return drawables;
    }
};

```

To start drawing the [`PdfDrawable`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable/index.html) on the page, you need to add the provider by calling [`addDrawableProvider(provider)`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-fragment/add-drawable-provider.html) on the fragment:

### KOTLIN

```kotlin

pdfFragment.addDrawableProvider(provider)

```

### JAVA

```java

getPdfFragment().addDrawableProvider(provider);

```

To draw on thumbnails for [`PdfThumbnailBar`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-thumbnail-bar/index.html) and [`PdfThumbnailGrid`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-thumbnail-grid/index.html), use the following:

### MYACTIVITY.KT

```kt

// Thumbnail Grid
pspdfKitViews.thumbnailGridView?.addDrawableProvider(provider)
// Thumbnail Bar
pspdfKitViews.thumbnailBarView?.addDrawableProvider(provider)

```

### MYACTIVITY.JAVA

```java

// Thumbnail Grid
getPSPDFKitViews().getThumbnailGridView().addDrawableProvider(provider);
// Thumbnail Bar
getPSPDFKitViews().getThumbnailBarView().addDrawableProvider(provider);

```

If the set of drawables changes after you register the provider — for example, after loading data asynchronously — call `notifyDrawablesChanged()` on your [`PdfDrawableProvider`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable-provider/index.html). This tells Nutrient Android SDK to request the latest drawables and redraw affected pages.

## Creating a custom drawable

Your custom drawable needs to extend the [`PdfDrawable`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable/index.html), which itself extends Android’s abstract [`Drawable`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html) class. You must override and implement a couple of its methods. We’ll look at them one after another:

```java

public void draw(Canvas canvas);
public void setAlpha(int alpha);
public void setColorFilter(ColorFilter colorFilter);
public int getOpacity();

```

The [`#draw(Canvas)`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#draw(android.graphics.Canvas)) method is where the drawable performs all of its onscreen drawing operations by issuing drawing commands on the provided [`Canvas`](https://developer.android.com/reference/android/graphics/Canvas.html). Use [`#setAlpha(int)`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#setAlpha(int)) and [`#setColorFilter(ColorFilter)`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#setColorFilter(android.graphics.ColorFilter)) to adapt the drawable to presentation specifics, e.g. to enable translucency or blending modes.

### Preparing to draw

Drawing on a canvas requires a [`Paint`](https://developer.android.com/reference/android/graphics/Paint.html) object specifying drawing settings like color, thickness, opacity, and more. Since subsequent drawing passes [should always happen in less than 16 ms](https://www.youtube.com/watch?v=CaMTIgxCSqU), prepare the [`Paint`](https://developer.android.com/reference/android/graphics/Paint.html) object upfront. Do this, for example, in the constructor of your drawable:

### RECTDRAWABLE.KT

```kt

class RectDrawable : PdfDrawable() {
    private val paint: Paint = Paint()

    init {
        paint.color = Color.RED
        paint.style = Paint.Style.FILL
    }
}

```

### RECTDRAWABLE.JAVA

```java

public final class RectDrawable extends PdfDrawable {
    @NonNull private final Paint paint = new Paint();

    public RectDrawable() {
        paint.setColor(Color.RED);
        this.paint.setStyle(Paint.Style.FILL);
    }
}

```

### Perform drawing

Inside the `draw()` method, you can use the provided [`Canvas`](https://developer.android.com/reference/android/graphics/Canvas.html), passing it the `paint`. The `Canvas` class offers many different call methods — this example uses the [`Canvas#drawRect(Rect, Paint)`](https://developer.android.com/reference/android/graphics/Canvas.html#drawRect(android.graphics.RectF,%20android.graphics.Paint)) method:

### RECTDRAWABLE.KT

```kt

override fun draw(canvas: Canvas) {
    canvas.drawRect(bounds, paint)
}

```

### RECTDRAWABLE.JAVA

```java

@Override public draw(Canvas canvas) {
    canvas.drawRect(getBounds(), paint);
}

```

The [`Drawable#getBounds`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#getBounds()) method used by the above snippet returns the screen rectangle the drawable will cover. Thus, our `RectDrawable` will take all of the space it is assigned and will draw a single rectangle inside that space.

Next, a drawable must implement the [`#getOpacity`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#getOpacity()) method. It returns a flag specifying whether the background shines through the drawable, or whether the drawable is completely opaque. This is mainly for improving drawing performance — something you should always aim for:

### RECTDRAWABLE.KT

```kt

override fun int getOpacity() {
    // The drawable paints a solid rectangle. Since nothing from behind the drawable
    // is visible inside the drawable bounds, we specify the drawable as `OPAQUE`.
    return PixelFormat.OPAQUE
}

```

### RECTDRAWABLE.JAVA

```java

@Override public int getOpacity() {
    // The drawable paints a solid rectangle. Since nothing from behind the drawable
    // is visible inside the drawable bounds, we specify the drawable as `OPAQUE`.
    return PixelFormat.OPAQUE;
}

```

The [`#getOpacity`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#getOpacity()) method must return one [`PixelFormat`](https://developer.android.com/reference/android/graphics/PixelFormat.html) — [`UNKNOWN`](https://developer.android.com/reference/android/graphics/PixelFormat.html#UNKNOWN), [`OPAQUE`](https://developer.android.com/reference/android/graphics/PixelFormat.html#OPAQUE), [`TRANSPARENT`](https://developer.android.com/reference/android/graphics/PixelFormat.html#TRANSPARENT), or [`TRANSLUCENT`](https://developer.android.com/reference/android/graphics/PixelFormat.html#TRANSLUCENT) — depending on the content that is drawn.

### Defining drawable bounds

Setting new bounds of your drawable is as simple as calling [`this.setBounds(newDrawableBounds)`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#setBounds(android.graphics.Rect)) inside your drawable:

### RECTDRAWABLE.KT

```kt

// This will cause the drawable to paint a 100x100 px square
// at the very top-left corner of the page.
bounds = Rect(0, 0, 100, 100)

```

### RECTDRAWABLE.JAVA

```java

// This will cause the drawable to paint a 100x100 px square
// at the very top-left corner of the page.
setBounds(new Rect(0, 0, 100, 100));

```

This works fine if you want to draw something in pixels (i.e. in view coordinates). When doing so, the drawable will always be at the top left of the page, keeping the same size without being influenced by the zoom.

#### Drawing in PDF coordinates

To align your drawables with page text, annotations, or other content, you need to understand [how the PDF coordinate space works](https://www.nutrient.io/guides/android/faq/coordinate-spaces.md). The [`Canvas`](https://developer.android.com/reference/android/graphics/Canvas.html) object expects coordinates in pixel space instead of PDF coordinates. [`PdfDrawable#getPdfToPageTransformation`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable/update-pdf-to-view-transformation.html) provides a transformation [`Matrix`](https://developer.android.com/reference/android/graphics/Matrix.html) you can use to convert your PDF coordinates to view coordinates:

### RECTDRAWABLE.KT

```kt

// This is a 100x100pt square on the bottom left of the page.
// Don't confuse this with pixels. Usually 1pt is 1/72 inch.
val pageCoordinates = RectF(0f, 100f, 100f, 0f)

// This will contain the screen coordinates (in pixels).
val screenCoordinates = RectF()

private fun updateBoundingBox() {
    // Transform PDF coordinates into screen coordinates.
    pdfToPageTransformation.mapRect(screenCoordinates, pageCoordinates)

    // Since the drawable bounds are `Rect` (`int`) and our transformed
    // screen coordinates are `RectF` (`float`), we need to round the coordinates before applying.
    val newBounds = this.bounds
    screenCoordinates.roundOut(newBounds)
    this.bounds = newBounds
}

```

### RECTDRAWABLE.JAVA

```java

// This is a 100x100pt square on the bottom left of the page.
// Don't confuse this with pixels. Usually 1pt is 1/72 inch.
private final RectF pageCoordinates = new RectF(0f, 100f, 100f, 0f);

// This will contain the screen coordinates (in pixels).
private final RectF screenCoordinates = new RectF();

private void updateBoundingBox() {
    // Transform PDF coordinates into screen coordinates.
    getPdfToPageTransformation().mapRect(screenCoordinates, pageCoordinates);

    // Since the drawable bounds are Rect (int) and our transformed
    // screen coordinates are RectF (float), we need to round the coordinates before applying.
    final Rect bounds = getBounds();
    screenCoordinates.roundOut(bounds);
    setBounds(bounds);
}

```

Every time the page you’re drawing to moves — for example, because of zooming or scrolling — Nutrient Android SDK recalculates the transformation [`Matrix`](https://developer.android.com/reference/android/graphics/Matrix.html). It then notifies the drawable via its [`PdfDrawable#updatePdfToViewTransformation`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable/update-pdf-to-view-transformation.html) method. You can override this method and use it as a hook for recalculating the screen coordinates:

### RECTDRAWABLE.KT

```kt

override fun updatePdfToViewTransformation(matrix: Matrix) {
    super.updatePdfToViewTransformation(matrix)

    // We simply call the method we implemented earlier. It will take the new matrix
    // and use it to calculate screen coordinates.
    updateBoundingBox()
}

```

### RECTDRAWABLE.JAVA

```java

@Override public void updatePdfToViewTransformation(@NonNull Matrix matrix) {
    super.updatePdfToViewTransformation(matrix);

    // We simply call the method we implemented earlier. It will take the new matrix
    // and use it to calculate screen coordinates.
    updateBoundingBox();
}

```

> **Important:** Don’t forget to call `super.updatePdfToViewTransformation(matrix)` first inside the method, or `PdfDrawable#getPdfToPageTransformation` will stop working.

### Invalidating the drawable

Whenever the visual representation of your drawable changes, you need to tell the rendering system about that change. For example, if you update the alpha value with a call to `setAlpha()`, call [`Drawable#invalidateSelf`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#invalidateSelf()) to trigger an invalidation. This rerenders the updated drawable on the screen:

### RECTDRAWABLE.KT

```kt

@UiThread override fun setAlpha(alpha: Int) {
    paint.alpha = alpha
    // Drawable invalidation is only allowed from a UI thread.
    invalidateSelf()
}

```

### RECTDRAWABLE.JAVA

```java

@UiThread
@Override public void setAlpha(int alpha) {
    paint.setAlpha(alpha);
    // Drawable invalidation is only allowed from a UI thread.
    invalidateSelf();
}

```

> **Important:** `invalidateSelf()` may only be called from the UI thread. Any call from a background thread will raise an exception.

## Refreshing drawables after data changes

Call [`PdfDrawableProvider#notifyDrawablesChanged()`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable-provider/notify-drawables-changed.html) when the set of drawables changes. If only one page changed, call [`PdfDrawableProvider#notifyDrawablesChanged(pageIndex)`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui.drawable/-pdf-drawable-provider/notify-drawables-changed.html) to reload and redraw drawables for that page only.

Use provider invalidation for data changes, such as loading overlay metadata after a page jump. Use [`Drawable#invalidateSelf`](https://developer.android.com/reference/android/graphics/drawable/Drawable.html#invalidateSelf()) for visual changes inside an existing drawable, such as alpha, color, or cached bitmap updates.

`PdfDrawable.draw()` is called only when the drawable is part of a page that needs to be rendered. A provider can be queried before the corresponding visible page is drawn, especially after large programmatic jumps or layout changes. Don’t use `draw()` as a lifecycle callback for page visibility.

If the surrounding view layout changes, call [`PdfFragment#notifyLayoutChanged()`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-fragment/notify-layout-changed.html) so the fragment updates its internal layout state. Reserve [`PdfFragment#refreshPages()`](https://www.nutrient.io/api/android/nutrient/com.pspdfkit.ui/-pdf-fragment/refresh-pages.html) for cases where the document view has no visible pages and needs to be reinitialized.

## The final drawable

Here is the final drawable. This version also supports setting new PDF coordinates by calling the `RectDrawable#setPageCoordinates` method:

### RECTDRAWABLE.KT

```kt

class RectDrawable(pageCoordinates: RectF) : PdfDrawable() {

    private val paint = Paint()
    private val screenCoordinates = RectF()

    var pageCoordinates: RectF = pageCoordinates
        set(value) {
            field.set(value)
            updateScreenCoordinates()
        }

    init {
        paint.color = Color.RED
        paint.style = Paint.Style.FILL
    }

    /**
     * All the drawing is performed here. Keep this method fast to maintain 60 fps.
     */
    override fun draw(canvas: Canvas) {
        canvas.drawRect(bounds, paint)
    }

    /**
     * PSPDFKit calls this method every time the page is moved or resized on the screen.
     * It will provide a fresh transformation for calculating screen coordinates from
     * PDF coordinates.
     */
    override fun updatePdfToViewTransformation(matrix: Matrix) {
        super.updatePdfToViewTransformation(matrix)
        updateScreenCoordinates()
    }

    @UiThread override fun setAlpha(alpha: Int) {
        paint.alpha = alpha
        // Drawable invalidation is only allowed from a UI thread.
        invalidateSelf()
    }

    @UiThread override fun setColorFilter(colorFilter: ColorFilter?) {
        paint.colorFilter = colorFilter
        // Drawable invalidation is only allowed from a UI thread.
        invalidateSelf()
    }

    override fun getOpacity(): Int {
        return PixelFormat.OPAQUE
    }

    private fun updateScreenCoordinates() {
        // Calculate the screen coordinates by applying the PDF-to-view transformation.
        pdfToPageTransformation.mapRect(screenCoordinates, pageCoordinates)

        // Rounding out ensures no clipping of content.
        val newBounds = this.bounds
        screenCoordinates.roundOut(newBounds)
        this.bounds = newBounds
    }
}

```

### RECTDRAWABLE.JAVA

```java

public final class RectDrawable extends PdfDrawable {

    @NonNull private final Paint paint = new Paint();
    @NonNull private final RectF pageCoordinates;
    @NonNull private final RectF screenCoordinates = new RectF();

    public RectDrawable(@NonNull final RectF pageCoordinates) {
        this.pageCoordinates = pageCoordinates;
        this.paint.setColor(Color.RED);
        this.paint.setStyle(Paint.Style.FILL);
    }

    public void setPageCoordinates(@NonNull final RectF pageCoordinates) {
        // Copy over the value to prevent undesired mutation.
        this.pageCoordinates.set(pageCoordinates);
        updateScreenCoordinates();
    }

    /**
     * All the drawing is performed here. Keep this method fast to maintain 60 fps.
     */
    @Override public void draw(Canvas canvas) {
        canvas.drawRect(getBounds(), paint);
    }

    /**
     * PSPDFKit calls this method every time the page is moved or resized on the screen.
     * It will provide a fresh transformation for calculating screen coordinates from
     * PDF coordinates.
     */
    @Override public void updatePdfToViewTransformation(@NonNull Matrix matrix) {
        super.updatePdfToViewTransformation(matrix);
        updateScreenCoordinates();
    }

    @UiThread
    @Override public void setAlpha(int alpha) {
        paint.setAlpha(alpha);
        // Drawable invalidation is only allowed from a UI thread.
        invalidateSelf();
    }

    @UiThread
    @Override public void setColorFilter(ColorFilter colorFilter) {
        paint.setColorFilter(colorFilter);
        // Drawable invalidation is only allowed from a UI thread.
        invalidateSelf();
    }

    @Override public int getOpacity() {
        return PixelFormat.OPAQUE;
    }

    private void updateScreenCoordinates() {
        // Calculate the screen coordinates by applying the PDF-to-view transformation.
        getPdfToPageTransformation().mapRect(screenCoordinates, pageCoordinates);

        // Rounding out ensures no clipping of content.
        final Rect bounds = getBounds();
        screenCoordinates.roundOut(bounds);
        setBounds(bounds);
    }
}

```
---

## Related pages

- [Rendering annotations on Android](/guides/android/annotations/rendering-annotations.md)
- [Coordinate space conversion](/guides/android/faq/coordinate-spaces.md)
- [Customize PDF fonts for Android applications](/guides/android/features/custom-fonts.md)
- [Customize the rendering resolution in our Android viewer](/guides/android/features/low-res-render-api.md)
- [PDF renderer library for Android](/guides/android/features/rendering-pdf-pages.md)
- [Render PDF forms on Android](/guides/android/viewer/rendering/pdf-forms.md)

