diff --git a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java index 077eb5fd7b2..2ecfdaf7639 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java +++ b/ReactAndroid/src/main/java/com/facebook/react/config/ReactFeatureFlags.java @@ -95,4 +95,6 @@ public class ReactFeatureFlags { public static boolean enableLockFreeEventDispatcher = false; public static boolean enableAggressiveEventEmitterCleanup = false; + + public static boolean insertZReorderBarriersOnViewGroupChildren = true; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/CanvasUtil.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/CanvasUtil.java new file mode 100644 index 00000000000..f65353bb8dd --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/CanvasUtil.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.view; + +import android.annotation.SuppressLint; +import android.graphics.Canvas; +import android.os.Build; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.annotation.Nullable; + +/** + * Copied from + * Compose canvas utils + */ +public class CanvasUtil { + private CanvasUtil() {} + + private @Nullable static Method mReorderBarrierMethod = null; + private @Nullable static Method mInorderBarrierMethod = null; + private static boolean mOrderMethodsFetched = false; + + /** + * Enables Z support for the Canvas. The method is publicly available starting from API 29 and was + * hidden before, so we have to resort to reflection tricks to ensure we can use this API. + */ + @SuppressLint({"SoonBlockedPrivateApi", "PrivateApi"}) + public static void enableZ(Canvas canvas, boolean enable) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return; + } + + if (Build.VERSION.SDK_INT >= 29) { + if (enable) { + canvas.enableZ(); + } else { + canvas.disableZ(); + } + } else { + fetchOrderMethods(); + try { + if (enable && mReorderBarrierMethod != null) { + mReorderBarrierMethod.invoke(canvas); + } + if (!enable && mInorderBarrierMethod != null) { + mInorderBarrierMethod.invoke(canvas); + } + } catch (IllegalAccessException | InvocationTargetException ignore) { + // Do nothing + } + } + } + + private static void fetchOrderMethods() { + if (!mOrderMethodsFetched) { + try { + if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { + // use double reflection to avoid grey list on P + Method getDeclaredMethod = + Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class); + mReorderBarrierMethod = + (Method) getDeclaredMethod.invoke(Canvas.class, "insertReorderBarrier", new Class[0]); + mInorderBarrierMethod = + (Method) getDeclaredMethod.invoke(Canvas.class, "insertInorderBarrier", new Class[0]); + } else { + mReorderBarrierMethod = Canvas.class.getDeclaredMethod("insertReorderBarrier"); + mInorderBarrierMethod = Canvas.class.getDeclaredMethod("insertInorderBarrier"); + } + + if (mReorderBarrierMethod == null || mInorderBarrierMethod == null) { + return; + } + + mReorderBarrierMethod.setAccessible(true); + mInorderBarrierMethod.setAccessible(true); + } catch (IllegalAccessException ignore) { + // Do nothing + } catch (InvocationTargetException ignore) { + // Do nothing + } catch (NoSuchMethodException ignore) { + // Do nothing + } + mOrderMethodsFetched = true; + } + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java index 03f374a7c37..c0bf085c8f2 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java @@ -33,6 +33,7 @@ import com.facebook.react.bridge.ReactNoCrashSoftException; import com.facebook.react.bridge.ReactSoftExceptionLogger; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.annotations.VisibleForTesting; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.touch.OnInterceptTouchEventListener; import com.facebook.react.touch.ReactHitSlopView; @@ -759,6 +760,23 @@ public class ReactViewGroup extends ViewGroup } } + @Override + protected boolean drawChild(Canvas canvas, View child, long drawingTime) { + boolean drawWithZ = + child.getElevation() > 0 && ReactFeatureFlags.insertZReorderBarriersOnViewGroupChildren; + + if (drawWithZ) { + CanvasUtil.enableZ(canvas, false); + } + + boolean result = super.drawChild(canvas, child, drawingTime); + + if (drawWithZ) { + CanvasUtil.enableZ(canvas, true); + } + return result; + } + private void dispatchOverflowDraw(Canvas canvas) { if (mOverflow != null) { switch (mOverflow) {