Mobile Development 17 min read

Enabling MIUI Long Screenshot in Flutter: Xianyu’s Non‑Intrusive Integration

This article details how Xianyu’s engineering team analyzed and adapted Xiaomi’s MIUI long‑screenshot capability for Flutter list pages, covering the problem background, system source investigation, scroll view detection, event proxy design, asynchronous channel and synchronous FFI communication, and performance results.

Xianyu Technology
Xianyu Technology
Xianyu Technology
Enabling MIUI Long Screenshot in Flutter: Xianyu’s Non‑Intrusive Integration

The majority of Android apps display content in long lists, and manufacturers provide system‑level long‑screenshot features. However, Flutter list pages on many devices, especially Xiaomi’s MIUI, cannot be captured because the framework lacks native support. Xianyu, a large Flutter‑based product, needed a solution so its users could share long screenshots of items and community posts.

MIUI Long‑Screenshot Analysis

Statistical analysis of user feedback showed Xiaomi devices contributed the most complaints. The team examined MIUI’s source code (e.g., miui.util.LongScreenshotUtils&ContentPort) and identified the key steps: locate a scrollable view, drive scrolling, capture segmented screenshots, and merge them.

Finding the Scrollable View

The system checks view visibility, canScrollVertically(1), and size thresholds (width > screen/3, height > screen/2). By logging RecyclerView method calls, they confirmed the relevant class is miui.util.LongScreenshotUtils. For older MIUI versions, the code resides in /system/framework/framework.jar or open‑source repositories.

Triggering Scrolling and Capturing

Determine if the current page supports long‑screenshot (button enabled).

Before each scroll, call canScrollVertically(1) to verify further scrolling.

Scroll the view using dispatchFakeTouchEvent or scrollListBy for AbsListView, or view.scrollBy for generic views.

Detect scroll end by comparing scrollY with the previous value.

After each scroll, capture a bitmap segment and finally stitch all segments into a single image file.

Xianyu Adaptation Strategy

Flutter containers ( SurfaceView / TextureView) do not override canScrollVertically, so MIUI cannot recognize them as scrollable. Overriding getScrollY is impossible because it is final. The solution avoids modifying the Flutter view directly.

Non‑Intrusive Event Proxy

A hidden ControlView (a ScrollView) is inserted into the view hierarchy. MIUI treats this view as scrollable, receives scroll commands, and forwards them to the real Flutter render object via a custom communication channel.

public static FrameLayout setupLongScreenshotSupport(FrameLayout parent, View targetChild, IMiuiLongScreenshotViewDelegate delegate) {
    Context context = targetChild.getContext();
    MiuiLongScreenshotView screenshotView = new MiuiLongScreenshotView(context);
    screenshotView.setDelegate(delegate);
    screenshotView.addView(targetChild, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
    MiuiLongScreenshotControlView controlView = new MiuiLongScreenshotControlView(context);
    controlView.bindRealScrollView(screenshotView);
    if (parent == null) { parent = new FrameLayout(context); }
    parent.addView(screenshotView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
    parent.addView(controlView);
    return parent;
}

The ControlView registers for MIUI screenshot broadcasts, forwards scrollBy, canScrollVertically, and getScrollY calls to the real Flutter view, and overrides drawing cache methods to ensure bitmap capture works without interfering with user interaction.

Detecting Visibility and Obstruction

To ensure the target RenderViewportBase is not covered by overlay widgets (e.g., comment panels), the team performs hit‑testing at the center of the screen and walks up the render tree to locate a RenderPointerListener. If the listener is intercepted, the view is considered obstructed and excluded from scrolling.

bool _hitTest(RenderView? root, RenderViewportBase? result) {
    if (root == null || result == null) return false;
    Size rootSize = size(root, Size.zero);
    HitTestResult hitResult = HitTestResult();
    root.hitTest(hitResult, position: Offset(rootSize.width/2, rootSize.height/2));
    for (HitTestEntry entry in hitResult.path) {
        if (entry.target == result) return true;
    }
    // Check for RenderPointerListener within 5 ancestors
    RenderPointerListener? pointerListenerParent;
    AbstractNode? parent = result.parent;
    int lookupCount = 0;
    while (parent != null && lookupCount < 5 && parent.runtimeType.toString() != '_RenderTheatre') {
        lookupCount++;
        if (parent is RenderPointerListener) pointerListenerParent = parent;
        parent = parent.parent;
    }
    if (pointerListenerParent != null) {
        for (HitTestEntry entry in hitResult.path) {
            if (entry.target == pointerListenerParent) return true;
        }
    }
    return false;
}

Communication Between MIUI and Flutter

Asynchronous Channel

MIUI uses EventChannel and MethodChannel on the Java side, which run on the main thread, while Dart handling occurs on the UI isolate. This introduces two thread switches per call. Performance tests on a Xiaomi K50 showed the first canScrollVertically call costs 10‑30 ms, subsequent calls under 5 ms.

Synchronous FFI Channel

To guarantee up‑to‑date values on low‑end devices, a synchronous FFI bridge was added. Native C++ code posts results back to Java via JNI, which then invokes the static Java callback.

public static native void nativeCanScrollVertically(int direction, boolean startScreenshot, int callbackId);
public static native void nativeGetScrollY(int screenWidth, int callbackId);
public static native void nativeScrollBy(int screenWidth, int x, int y);

public static boolean canScrollVertically(final int direction, final boolean startScreenshot) {
    AwaitCallback callback = FlutterLongScreenshotCallbacks.newCallback();
    nativeCanScrollVertically(direction, startScreenshot, callback.id());
    int result = callback.waitCallback().getResult();
    return result == 1;
}

public static int getScrollY(final int screenWidth) {
    AwaitCallback callback = FlutterLongScreenshotCallbacks.newCallback();
    nativeGetScrollY(screenWidth, callback.id());
    return callback.waitCallback().getResult();
}

public static void scrollBy(final int screenWidth, int x, int y) {
    nativeScrollBy(screenWidth, x, y);
}

Performance of the synchronous path stays below 5 ms on typical devices.

Result and Lessons Learned

After integrating the ControlView and communication layers, Xianyu’s Flutter pages now fully support MIUI long‑screenshot on major Chinese Android models. User complaints have virtually disappeared. The experience demonstrates that even when a system feature does not expose a Flutter‑compatible API, a non‑intrusive bridge can be built by:

Formulating reasonable hypotheses about system‑app interactions.

Using tooling (ASM hooks, log analysis) to verify assumptions.

Searching and decompiling relevant system source.

Thinking divergently to replace the target view with a proxy.

Implementing a solution that requires a single integration point for all business pages.

These steps provide a general methodology for adapting platform‑specific capabilities to cross‑platform frameworks.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FlutterAndroidFFINative integrationLong ScreenshotMIUI
Xianyu Technology
Written by

Xianyu Technology

Official account of the Xianyu technology team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.