ScreenshotNeo

BlogHow-to

How to Take Full-Page Screenshots in Kotlin

Learn the right Kotlin approach for Android scrolling screenshots, custom scroll capture, WebView rendering, and app-owned tall images.

By the ScreenshotNeo team1 October 20268 min read

“Full-page screenshot” means three different things on Android: a user-triggered scrolling screenshot, an app that participates in Android’s system scroll capture, or Kotlin code that renders and saves its own tall bitmap. Choose the approach based on who starts the capture and whether your app needs an image file.

Choose the right approach

Approach Use it when Availability Limit
System Capture more A person wants a long screenshot from the Android UI Android 12 and later; Google documents it for most scrollable screens It is user-facing system UI, not a Kotlin API that returns a bitmap
ScrollCaptureCallback Your custom scrollable view should provide content to system capture API 31+ You must implement capture bounds, image requests, cancellation, and state restoration
App-rendered bitmap Your app must save or upload an image under its own control Depends on the view and rendering path Off-screen content must be laid out or rendered deliberately
PixelCopy You need current pixels from a Surface, SurfaceView, or Window API 24+ overloads It does not scroll a container or include off-screen document content

Android’s official help describes the built-in workflow as taking a screenshot, tapping Capture more, and adjusting crop guides on Android 12+ supported screens. See Google’s scrolling screenshot instructions.

Take a scrolling screenshot with Android’s built-in UI

  1. Open the screen you want to capture.
  2. Use the device’s normal screenshot gesture or hardware-key combination.
  3. Tap Capture more when the control appears.
  4. Drag the crop handles to include the required content.
  5. Save or share the result.

This is the correct answer when the requirement is “let a user capture a long screen.” It is not a method your Kotlin code can call to obtain a tall bitmap. Google says the feature works on Android 12 and later on most screens that allow scrolling; custom renderers and unsupported screens may not expose the control.

Support system capture with ScrollCaptureCallback

Use ScrollCaptureCallback when your app owns a custom scrolling surface and needs to participate in Android’s system scrolling screenshot feature. The API was added in API level 31. Read the current API reference against the compile and target SDK used by your project.

What your callback must handle

  • Report the region that can be captured.
  • Move the content to the requested position and render the requested image area.
  • Respond to cancellation promptly.
  • Finish capture and restore the original visual state.
  • At minimum, restore the original scroll position after capture.

The exact method signatures and registration details are SDK-specific. Implement against the API reference for your target SDK rather than copying an outdated snippet. Test custom nested scrolling, overscroll, animations, fixed headers, and content that changes while capture is running.

State-restoration checklist

  • Record the initial scroll offset before capture starts.
  • Pause or make deterministic animations and transitions.
  • Keep asynchronous image loading from changing already-captured regions.
  • Restore scroll offset, translation, clipping, and any temporary overlays in both success and cancellation paths.
  • Release temporary bitmaps and drawing resources when the session ends.

Render an app-owned tall bitmap

If your requirement is a file produced by the app, render the content intentionally. There is no universal Kotlin helper that captures every View, Compose layout, WebView, and hardware surface as one full-page image. The implementation depends on whether all content can be laid out off screen and whether the view draws through ordinary Canvas rendering.

Basic View rendering pattern

For a regular View hierarchy, measure and lay out the content at the width and height you intend to export, allocate a bitmap, and draw the hierarchy into a Canvas. Keep the height within practical memory limits; a bitmap uses roughly width × height × 4 bytes for ARGB_8888 before additional temporary allocations.

fun renderViewToBitmap(view: View, widthPx: Int, heightPx: Int): Bitmap {
    require(widthPx > 0 && heightPx > 0)

    view.measure(
        View.MeasureSpec.makeMeasureSpec(widthPx, View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(heightPx, View.MeasureSpec.EXACTLY)
    )
    view.layout(0, 0, widthPx, heightPx)

    val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    view.draw(canvas)
    return bitmap
}

This pattern draws what the View can render at the chosen size. It does not automatically load lazy content, traverse a nested scroll container, or reproduce pixels that are rendered only by a separate surface.

Saving the bitmap

fun savePng(context: Context, bitmap: Bitmap, name: String): Uri {
    val file = File(context.cacheDir, name)
    FileOutputStream(file).use { output ->
        check(bitmap.compress(Bitmap.CompressFormat.PNG, 100, output)) {
            "Bitmap compression failed"
        }
    }
    return FileProvider.getUriForFile(
        context,
        "${context.packageName}.fileprovider",
        file
    )
}

Configure a FileProvider for sharing outside your app. For photographs or large screenshots, JPEG or WebP can reduce storage; PNG is appropriate when text sharpness or transparency matters.

WebView-specific guidance

The WebView reference points to onDraw(Canvas) for a bitmap snapshot. WebView.capturePicture() is deprecated since API 19 and should not be a new default. A web archive created with saveWebArchive(String) is a different output from an image.

fun drawWebView(webView: WebView, widthPx: Int, heightPx: Int): Bitmap {
    val bitmap = Bitmap.createBitmap(widthPx, heightPx, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmap)
    webView.draw(canvas)
    return bitmap
}

Before drawing, wait for the page to finish loading and decide how you will handle fixed-position elements, nested scroll regions, lazy images, fonts, and JavaScript-driven content. A viewport snapshot is not automatically a complete document image.

Use PixelCopy for current surface or window pixels

PixelCopy asynchronously copies currently displayed pixels from a Surface, SurfaceView, or Window into a destination bitmap. It is useful when ordinary View drawing omits a hardware-rendered surface. It does not scroll content or expand a viewport to include off-screen material. See the PixelCopy API reference.

@RequiresApi(Build.VERSION_CODES.N)
fun copyWindow(window: Window, destination: Bitmap, onResult: (Int) -> Unit) {
    PixelCopy.request(
        window,
        destination,
        { result -> onResult(result) },
        Handler(Looper.getMainLooper())
    )
}

Handle documented failure results such as missing source data, an invalid or destroyed source, timeout, or an invalid destination. Allocate the destination bitmap with dimensions that match the region you actually want.

Capturing scrollable content yourself

For a long app-owned image, capture in pieces when the full content cannot be rendered at once:

  1. Determine the content width and total scrollable height.
  2. Record the starting scroll position.
  3. Scroll to a stable offset.
  4. Wait for layout and image loading to settle.
  5. Render the visible region into a temporary bitmap.
  6. Composite each region into a destination bitmap or stream tiles to storage.
  7. Restore the original position even if capture is cancelled or fails.

Overlapping a small number of pixels between tiles can hide seams. Avoid capturing while a fling, animation, or asynchronous layout change is active. Nested scrolling parents, sticky headers, and transparent system bars require explicit decisions about whether they belong in the output.

Common errors and fixes

Symptom Cause Fix
No Capture more button Unsupported screen, device version, or renderer Use Android 12+ on a supported scrollable screen, or implement an app-owned capture path
Only the visible viewport is saved The code draws the current View bounds Lay out the full content or capture and composite scrollable tiles
Blank or stale WebView image Capture occurs before page resources and layout settle Wait for page loading and verify the actual content dimensions before drawing
Missing SurfaceView/video/WebGL pixels Ordinary View drawing does not include the separate surface Use PixelCopy for current surface pixels; it still will not capture off-screen content
Out-of-memory crash Very large bitmap plus temporary buffers Reduce dimensions, use tiling or streaming, recycle intermediates, and choose a compressed format
Capture leaves the UI moved Scroll state was not restored on every exit path Restore in success, cancellation, and exception handling
Content shifts between tiles Animations, lazy loading, or network updates continue during capture Wait for stable content, pause animations where possible, and capture deterministically

Performance, reliability, and cost considerations

  • Memory: ARGB_8888 needs about four bytes per pixel, plus drawing and compositing overhead. Prefer tiles for long pages.
  • Threading: View measurement and drawing generally belong on the UI thread. Move encoding and file I/O off the UI thread after rendering.
  • Determinism: Freeze animations and wait for fonts, images, and network-driven layout before capture.
  • Accessibility and privacy: A screenshot can include sensitive content. Apply the same access controls and redaction rules as the screen itself.
  • Testing: Verify API 31+ scroll capture separately from app-rendered output, and test WebView, Compose, nested scrolling, and Surface-based screens on representative devices.

Or skip the browser setup

If the source is a web page rather than an Android app surface, ScreenshotNeo returns a screenshot or PDF through one request. Its capture pipeline accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status.

See the ScreenshotNeo API documentation for all options, including full-page capture, CSS selectors, device presets, dark mode, custom JavaScript and CSS, waits, request blocking, headers, cookies, geolocation, caching, PDFs, async jobs, bulk capture, and signed links.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

FAQ

Can Kotlin trigger Android’s Capture more UI?

The documented workflow is initiated by the user through the system screenshot interface. Kotlin can integrate custom content through ScrollCaptureCallback, but that callback is not a general “return one tall bitmap” method.

Does PixelCopy create a full-page screenshot?

No. It copies currently displayed pixels from a surface or window. It does not scroll or render off-screen content.

Should new code use WebView.capturePicture()?

No. It is deprecated since API 19. Use deliberate Canvas rendering with onDraw, or choose an archive when an image is not the required output.

Which method works for every Android UI toolkit?

None is universal. Standard Views, Compose content, WebViews, and hardware surfaces have different rendering paths. Identify the UI type and whether off-screen content can be laid out before selecting an implementation.

How do I capture a website instead of an app screen?

Use a browser-based screenshot service such as ScreenshotNeo, or operate a browser automation stack yourself. A service avoids maintaining browser setup and can return a full-page image or PDF directly.