-
Notifications
You must be signed in to change notification settings - Fork 368
Align Android rendering with React Native Skia's ViewScreenshotService #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,7 +5,9 @@ | |||||||||||||||||||||||||||||||||||||
| import android.graphics.Canvas; | ||||||||||||||||||||||||||||||||||||||
| import android.graphics.Color; | ||||||||||||||||||||||||||||||||||||||
| import android.graphics.Matrix; | ||||||||||||||||||||||||||||||||||||||
| import android.graphics.drawable.Drawable; | ||||||||||||||||||||||||||||||||||||||
| import android.graphics.Paint; | ||||||||||||||||||||||||||||||||||||||
| import android.graphics.RectF; | ||||||||||||||||||||||||||||||||||||||
| import android.graphics.Point; | ||||||||||||||||||||||||||||||||||||||
| import android.net.Uri; | ||||||||||||||||||||||||||||||||||||||
| import android.os.Build; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -22,6 +24,7 @@ | |||||||||||||||||||||||||||||||||||||
| import android.view.TextureView; | ||||||||||||||||||||||||||||||||||||||
| import android.view.View; | ||||||||||||||||||||||||||||||||||||||
| import android.view.ViewGroup; | ||||||||||||||||||||||||||||||||||||||
| import android.widget.HorizontalScrollView; | ||||||||||||||||||||||||||||||||||||||
| import android.widget.ScrollView; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import com.facebook.react.bridge.Promise; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -30,19 +33,18 @@ | |||||||||||||||||||||||||||||||||||||
| import com.facebook.react.fabric.interop.UIBlockViewResolver; | ||||||||||||||||||||||||||||||||||||||
| import com.facebook.react.uimanager.NativeViewHierarchyManager; | ||||||||||||||||||||||||||||||||||||||
| import com.facebook.react.uimanager.UIBlock; | ||||||||||||||||||||||||||||||||||||||
| import com.facebook.react.views.view.ReactViewGroup; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import java.lang.reflect.Method; | ||||||||||||||||||||||||||||||||||||||
| import java.io.ByteArrayOutputStream; | ||||||||||||||||||||||||||||||||||||||
| import java.io.File; | ||||||||||||||||||||||||||||||||||||||
| import java.io.FileOutputStream; | ||||||||||||||||||||||||||||||||||||||
| import java.io.IOException; | ||||||||||||||||||||||||||||||||||||||
| import java.io.OutputStream; | ||||||||||||||||||||||||||||||||||||||
| import java.nio.ByteBuffer; | ||||||||||||||||||||||||||||||||||||||
| import java.nio.charset.Charset; | ||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||
| import java.util.Arrays; | ||||||||||||||||||||||||||||||||||||||
| import java.util.Collections; | ||||||||||||||||||||||||||||||||||||||
| import java.util.LinkedList; | ||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||
| import java.util.Locale; | ||||||||||||||||||||||||||||||||||||||
| import java.util.Set; | ||||||||||||||||||||||||||||||||||||||
| import java.util.WeakHashMap; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -83,6 +85,21 @@ public class ViewShot implements UIBlock, com.facebook.react.fabric.interop.UIBl | |||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| private static final int SURFACE_VIEW_READ_PIXELS_TIMEOUT = 5; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** Cached reflection handle for ReactViewGroup.dispatchOverflowDraw (z-index support). */ | ||||||||||||||||||||||||||||||||||||||
| @Nullable | ||||||||||||||||||||||||||||||||||||||
| private static final Method sDispatchOverflowDraw; | ||||||||||||||||||||||||||||||||||||||
| static { | ||||||||||||||||||||||||||||||||||||||
| Method m = null; | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| m = ReactViewGroup.class.getDeclaredMethod("dispatchOverflowDraw", Canvas.class); | ||||||||||||||||||||||||||||||||||||||
| m.setAccessible(true); | ||||||||||||||||||||||||||||||||||||||
| } catch (Exception ignored) {} | ||||||||||||||||||||||||||||||||||||||
| sDispatchOverflowDraw = m; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** Reusable matrix to avoid allocation per view during rendering. */ | ||||||||||||||||||||||||||||||||||||||
| private final Matrix tempMatrix = new Matrix(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @SuppressWarnings("WeakerAccess") | ||||||||||||||||||||||||||||||||||||||
| @IntDef({Formats.JPEG, Formats.PNG, Formats.WEBP, Formats.RAW}) | ||||||||||||||||||||||||||||||||||||||
| public @interface Formats { | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -313,28 +330,6 @@ private void saveToBase64String(@NonNull final View view) throws IOException { | |||||||||||||||||||||||||||||||||||||
| promise.resolve(data); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @NonNull | ||||||||||||||||||||||||||||||||||||||
| private List<View> getAllChildren(@NonNull final View v) { | ||||||||||||||||||||||||||||||||||||||
| if (!(v instanceof ViewGroup)) { | ||||||||||||||||||||||||||||||||||||||
| final ArrayList<View> viewArrayList = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||
| viewArrayList.add(v); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return viewArrayList; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| final ArrayList<View> result = new ArrayList<>(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ViewGroup viewGroup = (ViewGroup) v; | ||||||||||||||||||||||||||||||||||||||
| for (int i = 0; i < viewGroup.getChildCount(); i++) { | ||||||||||||||||||||||||||||||||||||||
| View child = viewGroup.getChildAt(i); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| //Do not add any parents, just add child elements | ||||||||||||||||||||||||||||||||||||||
| result.addAll(getAllChildren(child)); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Wrap {@link #captureViewImpl(View, OutputStream)} call and on end close output stream. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -381,62 +376,10 @@ private Point captureViewImpl(@NonNull final View view, @NonNull final OutputStr | |||||||||||||||||||||||||||||||||||||
| // Debug.waitForDebugger(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| final Canvas c = new Canvas(bitmap); | ||||||||||||||||||||||||||||||||||||||
| view.draw(c); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| //after view is drawn, go through children | ||||||||||||||||||||||||||||||||||||||
| final List<View> childrenList = getAllChildren(view); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| for (final View child : childrenList) { | ||||||||||||||||||||||||||||||||||||||
| // skip any child that we don't know how to process | ||||||||||||||||||||||||||||||||||||||
| if (child instanceof TextureView) { | ||||||||||||||||||||||||||||||||||||||
| // skip all invisible to user child views | ||||||||||||||||||||||||||||||||||||||
| if (child.getVisibility() != VISIBLE) continue; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| final TextureView tvChild = (TextureView) child; | ||||||||||||||||||||||||||||||||||||||
| tvChild.setOpaque(false); // <-- switch off background fill | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // NOTE (olku): get re-usable bitmap. TextureView should use bitmaps with matching size, | ||||||||||||||||||||||||||||||||||||||
| // otherwise content of the TextureView will be scaled to provided bitmap dimensions | ||||||||||||||||||||||||||||||||||||||
| final Bitmap childBitmapBuffer = tvChild.getBitmap(getExactBitmapForScreenshot(child.getWidth(), child.getHeight())); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| final int countCanvasSave = c.save(); | ||||||||||||||||||||||||||||||||||||||
| applyTransformations(c, view, child); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // due to re-use of bitmaps for screenshot, we can get bitmap that is bigger in size than requested | ||||||||||||||||||||||||||||||||||||||
| c.drawBitmap(childBitmapBuffer, 0, 0, paint); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| c.restoreToCount(countCanvasSave); | ||||||||||||||||||||||||||||||||||||||
| recycleBitmap(childBitmapBuffer); | ||||||||||||||||||||||||||||||||||||||
| } else if (child instanceof SurfaceView && handleGLSurfaceView) { | ||||||||||||||||||||||||||||||||||||||
| final SurfaceView svChild = (SurfaceView)child; | ||||||||||||||||||||||||||||||||||||||
| final CountDownLatch latch = new CountDownLatch(1); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | ||||||||||||||||||||||||||||||||||||||
| final Bitmap childBitmapBuffer = getExactBitmapForScreenshot(child.getWidth(), child.getHeight()); | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| PixelCopy.request(svChild, childBitmapBuffer, new PixelCopy.OnPixelCopyFinishedListener() { | ||||||||||||||||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||||||||||||||||
| public void onPixelCopyFinished(int copyResult) { | ||||||||||||||||||||||||||||||||||||||
| final int countCanvasSave = c.save(); | ||||||||||||||||||||||||||||||||||||||
| applyTransformations(c, view, child); | ||||||||||||||||||||||||||||||||||||||
| c.drawBitmap(childBitmapBuffer, 0, 0, paint); | ||||||||||||||||||||||||||||||||||||||
| c.restoreToCount(countCanvasSave); | ||||||||||||||||||||||||||||||||||||||
| recycleBitmap(childBitmapBuffer); | ||||||||||||||||||||||||||||||||||||||
| latch.countDown(); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| }, new Handler(Looper.getMainLooper())); | ||||||||||||||||||||||||||||||||||||||
| latch.await(SURFACE_VIEW_READ_PIXELS_TIMEOUT, TimeUnit.SECONDS); | ||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||
| Log.e(TAG, "Cannot PixelCopy for " + svChild, e); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| Bitmap cache = svChild.getDrawingCache(); | ||||||||||||||||||||||||||||||||||||||
| if (cache != null) { | ||||||||||||||||||||||||||||||||||||||
| c.drawBitmap(svChild.getDrawingCache(), 0, 0, paint); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| c.save(); | ||||||||||||||||||||||||||||||||||||||
| c.translate(-view.getLeft(), -view.getTop()); | ||||||||||||||||||||||||||||||||||||||
| renderViewToCanvas(c, view, paint, 1.0f); | ||||||||||||||||||||||||||||||||||||||
| c.restore(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (width != null && height != null && (width != w || height != h)) { | ||||||||||||||||||||||||||||||||||||||
| final Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -462,45 +405,142 @@ public void onPixelCopyFinished(int copyResult) { | |||||||||||||||||||||||||||||||||||||
| return resolution; // return image width and height | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||
| * Concat all the transformation matrix's from parent to child. | ||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||
| @NonNull | ||||||||||||||||||||||||||||||||||||||
| @SuppressWarnings("UnusedReturnValue") | ||||||||||||||||||||||||||||||||||||||
| private Matrix applyTransformations(final Canvas c, @NonNull final View root, @NonNull final View child) { | ||||||||||||||||||||||||||||||||||||||
| final Matrix transform = new Matrix(); | ||||||||||||||||||||||||||||||||||||||
| final LinkedList<View> ms = new LinkedList<>(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // find all parents of the child view | ||||||||||||||||||||||||||||||||||||||
| View iterator = child; | ||||||||||||||||||||||||||||||||||||||
| do { | ||||||||||||||||||||||||||||||||||||||
| ms.add(iterator); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| iterator = (View) iterator.getParent(); | ||||||||||||||||||||||||||||||||||||||
| } while (iterator != root); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // apply transformations from parent --> child order | ||||||||||||||||||||||||||||||||||||||
| Collections.reverse(ms); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| for (final View v : ms) { | ||||||||||||||||||||||||||||||||||||||
| c.save(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // apply each view transformations, so each child will be affected by them | ||||||||||||||||||||||||||||||||||||||
| final float dx = v.getLeft() + ((v != child) ? v.getPaddingLeft() : 0) + v.getTranslationX(); | ||||||||||||||||||||||||||||||||||||||
| final float dy = v.getTop() + ((v != child) ? v.getPaddingTop() : 0) + v.getTranslationY(); | ||||||||||||||||||||||||||||||||||||||
| c.translate(dx, dy); | ||||||||||||||||||||||||||||||||||||||
| c.rotate(v.getRotation(), v.getPivotX(), v.getPivotY()); | ||||||||||||||||||||||||||||||||||||||
| c.scale(v.getScaleX(), v.getScaleY()); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // compute the matrix just for any future use | ||||||||||||||||||||||||||||||||||||||
| transform.postTranslate(dx, dy); | ||||||||||||||||||||||||||||||||||||||
| transform.postRotate(v.getRotation(), v.getPivotX(), v.getPivotY()); | ||||||||||||||||||||||||||||||||||||||
| transform.postScale(v.getScaleX(), v.getScaleY()); | ||||||||||||||||||||||||||||||||||||||
| //region Recursive rendering (adapted from React Native Skia's ViewScreenshotService) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private void renderViewToCanvas(Canvas canvas, View view, Paint paint, float parentOpacity) { | ||||||||||||||||||||||||||||||||||||||
| float combinedOpacity = parentOpacity * view.getAlpha(); | ||||||||||||||||||||||||||||||||||||||
| canvas.save(); | ||||||||||||||||||||||||||||||||||||||
| applyTransformations(canvas, view); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (view instanceof ScrollView || view instanceof HorizontalScrollView) { | ||||||||||||||||||||||||||||||||||||||
| canvas.clipRect( | ||||||||||||||||||||||||||||||||||||||
| view.getScrollX(), | ||||||||||||||||||||||||||||||||||||||
| view.getScrollY(), | ||||||||||||||||||||||||||||||||||||||
| view.getScrollX() + view.getWidth(), | ||||||||||||||||||||||||||||||||||||||
| view.getScrollY() + view.getHeight()); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return transform; | ||||||||||||||||||||||||||||||||||||||
| if (view instanceof ViewGroup && !isSvgView(view)) { | ||||||||||||||||||||||||||||||||||||||
| drawBackgroundIfPresent(canvas, view, combinedOpacity); | ||||||||||||||||||||||||||||||||||||||
| drawChildren(canvas, (ViewGroup) view, paint, combinedOpacity); | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| drawView(canvas, view, combinedOpacity); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| canvas.restore(); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private static void drawBackgroundIfPresent(Canvas canvas, View view, float opacity) { | ||||||||||||||||||||||||||||||||||||||
| Drawable bg = view.getBackground(); | ||||||||||||||||||||||||||||||||||||||
| if (bg != null) { | ||||||||||||||||||||||||||||||||||||||
| int alpha = Math.round(opacity * 255); | ||||||||||||||||||||||||||||||||||||||
| if (alpha < 255) { | ||||||||||||||||||||||||||||||||||||||
| canvas.saveLayerAlpha(new RectF(0, 0, view.getWidth(), view.getHeight()), alpha); | ||||||||||||||||||||||||||||||||||||||
| bg.draw(canvas); | ||||||||||||||||||||||||||||||||||||||
| canvas.restore(); | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| bg.draw(canvas); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+433
to
+443
|
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private void drawChildren(Canvas canvas, ViewGroup group, Paint paint, float parentOpacity) { | ||||||||||||||||||||||||||||||||||||||
| if (sDispatchOverflowDraw != null && group instanceof ReactViewGroup) { | ||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| sDispatchOverflowDraw.invoke(group, canvas); | ||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||
| Log.e(TAG, "couldn't invoke dispatchOverflowDraw() on ReactViewGroup", e); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| for (int i = 0; i < group.getChildCount(); i++) { | ||||||||||||||||||||||||||||||||||||||
| View child = group.getChildAt(i); | ||||||||||||||||||||||||||||||||||||||
| if (child.getVisibility() != VISIBLE) continue; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (child instanceof TextureView) { | ||||||||||||||||||||||||||||||||||||||
| drawTextureView(canvas, (TextureView) child, paint, parentOpacity); | ||||||||||||||||||||||||||||||||||||||
| } else if (child instanceof SurfaceView) { | ||||||||||||||||||||||||||||||||||||||
| if (handleGLSurfaceView) { | ||||||||||||||||||||||||||||||||||||||
| drawSurfaceView(canvas, (SurfaceView) child, paint, parentOpacity); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||
| renderViewToCanvas(canvas, child, paint, parentOpacity); | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+459
to
+466
|
||||||||||||||||||||||||||||||||||||||
| if (child instanceof TextureView) { | |
| drawTextureView(canvas, (TextureView) child, paint, parentOpacity); | |
| } else if (child instanceof SurfaceView) { | |
| if (handleGLSurfaceView) { | |
| drawSurfaceView(canvas, (SurfaceView) child, paint, parentOpacity); | |
| } | |
| } else { | |
| renderViewToCanvas(canvas, child, paint, parentOpacity); | |
| float childOpacity = parentOpacity * child.getAlpha(); | |
| if (child instanceof TextureView) { | |
| drawTextureView(canvas, (TextureView) child, paint, childOpacity); | |
| } else if (child instanceof SurfaceView) { | |
| if (handleGLSurfaceView) { | |
| drawSurfaceView(canvas, (SurfaceView) child, paint, childOpacity); | |
| } | |
| } else { | |
| renderViewToCanvas(canvas, child, paint, childOpacity); |
Copilot
AI
Mar 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Child rendering order here always uses getChildAt(i). For React Native z-index, ViewGroups typically rely on custom drawing order (childrenDrawingOrderEnabled + getChildDrawingOrder). dispatchOverflowDraw() is for overflow clipping (not z-index), so this loop still won’t respect z-index ordering. Consider iterating using getChildDrawingOrder() when enabled, or otherwise mirroring ViewGroup’s draw order logic.
Copilot
AI
Mar 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
drawSurfaceView() waits with a timeout but doesn’t handle the timeout result. If await() returns false (or PixelCopy finishes after the timeout), the bitmap buffer may never be recycled, and the callback could still draw onto the canvas after capture has moved on. Handle the await result (fallback + recycle) and guard the callback to avoid drawing after timeout/finalization.
Copilot
AI
Mar 29, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
applyTransformations() is translating by view padding and subtracting view scroll for every view before drawing. That shifts the view’s own rendering (e.g., backgrounds/text) by its padding and can double-apply scroll (especially for ScrollView where you also clip using scrollX/Y). Consider translating by layout position only (left/top) + concat(view.getMatrix()), and apply padding/scroll only when descending into a ViewGroup’s children (similar to ViewGroup.dispatchDraw).
| c.translate( | |
| view.getLeft() + view.getPaddingLeft() - view.getScrollX(), | |
| view.getTop() + view.getPaddingTop() - view.getScrollY()); | |
| // Translate canvas into the view's layout position; padding and scroll should be | |
| // handled when drawing child views, not when drawing the view itself. | |
| c.translate(view.getLeft(), view.getTop()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opacity is currently propagated by multiplying into combinedOpacity and passing it down to descendants, but ViewGroup rendering is not wrapped in a layer. This changes blending for overlapping children under a partially transparent parent (parent alpha gets applied per-child instead of once to the composited subtree). To match Android rendering, when a view’s combined alpha < 1 you likely need to saveLayerAlpha for the whole view (background + children) and then render descendants without additionally multiplying by the parent’s alpha.