From f939f1f02db5e4d746b321138369b9eca3fdfeca Mon Sep 17 00:00:00 2001
From: David Vacca <515103+mdvacca@users.noreply.github.com>
Date: Thu, 19 Sep 2024 20:25:37 -0700
Subject: [PATCH 1/2] Back out "Convert ReactViewGroup to Kotlin"
Summary:
Original commit changeset: 780350e7e449
Original Phabricator Diff: D62642663
Differential Revision: D63076763
---
.../ReactAndroid/api/ReactAndroid.api | 1 +
.../react/views/modal/ReactModalHostView.kt | 16 +-
.../react/views/view/ReactViewGroup.java | 966 ++++++++++++++++++
.../react/views/view/ReactViewGroup.kt | 826 ---------------
.../react/views/view/ReactViewManager.kt | 10 +-
5 files changed, 980 insertions(+), 839 deletions(-)
create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api
index 24c8a318da12ba..7fb74ff9b41bdd 100644
--- a/packages/react-native/ReactAndroid/api/ReactAndroid.api
+++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api
@@ -8326,6 +8326,7 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
public fun draw (Landroid/graphics/Canvas;)V
protected fun drawChild (Landroid/graphics/Canvas;Landroid/view/View;J)Z
protected fun getChildDrawingOrder (II)I
+ public fun getChildVisibleRect (Landroid/view/View;Landroid/graphics/Rect;Landroid/graphics/Point;)Z
public fun getClippingRect (Landroid/graphics/Rect;)V
public fun getHitSlopRect ()Landroid/graphics/Rect;
public fun getOverflow ()Ljava/lang/String;
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt
index e6403c31fb16e2..316efcd44b177f 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt
@@ -432,21 +432,21 @@ public class ReactModalHostView(context: ThemedReactContext) :
reactContext.reactApplicationContext.handleException(RuntimeException(t))
}
- override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
eventDispatcher?.let { eventDispatcher ->
- jSTouchDispatcher.handleTouchEvent(ev, eventDispatcher, reactContext)
- jSPointerDispatcher?.handleMotionEvent(ev, eventDispatcher, true)
+ jSTouchDispatcher.handleTouchEvent(event, eventDispatcher, reactContext)
+ jSPointerDispatcher?.handleMotionEvent(event, eventDispatcher, true)
}
- return super.onInterceptTouchEvent(ev)
+ return super.onInterceptTouchEvent(event)
}
@SuppressLint("ClickableViewAccessibility")
- override fun onTouchEvent(ev: MotionEvent): Boolean {
+ override fun onTouchEvent(event: MotionEvent): Boolean {
eventDispatcher?.let { eventDispatcher ->
- jSTouchDispatcher.handleTouchEvent(ev, eventDispatcher, reactContext)
- jSPointerDispatcher?.handleMotionEvent(ev, eventDispatcher, false)
+ jSTouchDispatcher.handleTouchEvent(event, eventDispatcher, reactContext)
+ jSPointerDispatcher?.handleMotionEvent(event, eventDispatcher, false)
}
- super.onTouchEvent(ev)
+ super.onTouchEvent(event)
// In case when there is no children interested in handling touch event, we return true from
// the root view in order to receive subsequent events related to that gesture
return true
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
new file mode 100644
index 00000000000000..7f7a799c02e9b1
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java
@@ -0,0 +1,966 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and 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 static com.facebook.infer.annotation.Assertions.nullsafeFIXME;
+import static com.facebook.react.common.ReactConstants.TAG;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.BlendMode;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStructure;
+import android.view.animation.Animation;
+import androidx.annotation.Nullable;
+import com.facebook.common.logging.FLog;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.infer.annotation.Nullsafe;
+import com.facebook.react.R;
+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.internal.featureflags.ReactNativeFeatureFlags;
+import com.facebook.react.touch.OnInterceptTouchEventListener;
+import com.facebook.react.touch.ReactHitSlopView;
+import com.facebook.react.touch.ReactInterceptingViewGroup;
+import com.facebook.react.uimanager.BackgroundStyleApplicator;
+import com.facebook.react.uimanager.LengthPercentage;
+import com.facebook.react.uimanager.LengthPercentageType;
+import com.facebook.react.uimanager.MeasureSpecAssertions;
+import com.facebook.react.uimanager.PixelUtil;
+import com.facebook.react.uimanager.PointerEvents;
+import com.facebook.react.uimanager.ReactClippingProhibitedView;
+import com.facebook.react.uimanager.ReactClippingViewGroup;
+import com.facebook.react.uimanager.ReactClippingViewGroupHelper;
+import com.facebook.react.uimanager.ReactOverflowViewWithInset;
+import com.facebook.react.uimanager.ReactPointerEventsView;
+import com.facebook.react.uimanager.ReactZIndexedViewGroup;
+import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
+import com.facebook.react.uimanager.common.UIManagerType;
+import com.facebook.react.uimanager.common.ViewUtil;
+import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable;
+import com.facebook.react.uimanager.style.BorderRadiusProp;
+import com.facebook.react.uimanager.style.BorderStyle;
+import com.facebook.react.uimanager.style.LogicalEdge;
+import com.facebook.react.uimanager.style.Overflow;
+
+/**
+ * Backing for a React View. Has support for borders, but since borders aren't common, lazy
+ * initializes most of the storage needed for them.
+ */
+@Nullsafe(Nullsafe.Mode.LOCAL)
+public class ReactViewGroup extends ViewGroup
+ implements ReactInterceptingViewGroup,
+ ReactClippingViewGroup,
+ ReactPointerEventsView,
+ ReactHitSlopView,
+ ReactZIndexedViewGroup,
+ ReactOverflowViewWithInset {
+
+ private static final int ARRAY_CAPACITY_INCREMENT = 12;
+ private static final int DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT;
+ private static final LayoutParams sDefaultLayoutParam = new ViewGroup.LayoutParams(0, 0);
+ private final Rect mOverflowInset = new Rect();
+ /* should only be used in {@link #updateClippingToRect} */
+ private static final Rect sHelperRect = new Rect();
+
+ /**
+ * This listener will be set for child views when removeClippedSubview property is enabled. When
+ * children layout is updated, it will call {@link #updateSubviewClipStatus} to notify parent view
+ * about that fact so that view can be attached/detached if necessary.
+ *
+ *
TODO(7728005): Attach/detach views in batch - once per frame in case when multiple children
+ * update their layout.
+ */
+ private static final class ChildrenLayoutChangeListener implements View.OnLayoutChangeListener {
+
+ private final ReactViewGroup mParent;
+
+ private ChildrenLayoutChangeListener(ReactViewGroup parent) {
+ mParent = parent;
+ }
+
+ @Override
+ public void onLayoutChange(
+ View v,
+ int left,
+ int top,
+ int right,
+ int bottom,
+ int oldLeft,
+ int oldTop,
+ int oldRight,
+ int oldBottom) {
+ if (mParent.getRemoveClippedSubviews()) {
+ mParent.updateSubviewClipStatus(v);
+ }
+ }
+ }
+
+ // Following properties are here to support the option {@code removeClippedSubviews}. This is a
+ // temporary optimization/hack that is mainly applicable to the large list of images. The way
+ // it's implemented is that we store an additional array of children in view node. We selectively
+ // remove some of the views (detach) from it while still storing them in that additional array.
+ // We override all possible add methods for {@link ViewGroup} so that we can control this process
+ // whenever the option is set. We also override {@link ViewGroup#getChildAt} and
+ // {@link ViewGroup#getChildCount} so those methods may return views that are not attached.
+ // This is risky but allows us to perform a correct cleanup in {@link NativeViewHierarchyManager}.
+ private boolean mRemoveClippedSubviews;
+ private @Nullable View[] mAllChildren;
+ private int mAllChildrenCount;
+ private @Nullable Rect mClippingRect;
+ private @Nullable Rect mHitSlopRect;
+ private Overflow mOverflow;
+ private PointerEvents mPointerEvents;
+ private @Nullable ChildrenLayoutChangeListener mChildrenLayoutChangeListener;
+ private @Nullable CSSBackgroundDrawable mCSSBackgroundDrawable;
+ private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener;
+ private boolean mNeedsOffscreenAlphaCompositing;
+ private @Nullable ViewGroupDrawingOrderHelper mDrawingOrderHelper;
+ private @Nullable Path mPath;
+ private float mBackfaceOpacity;
+ private String mBackfaceVisibility;
+
+ public ReactViewGroup(Context context) {
+ super(context);
+ initView();
+ }
+
+ /**
+ * Set all default values here as opposed to in the constructor or field defaults. It is important
+ * that these properties are set during the constructor, but also on-demand whenever an existing
+ * ReactTextView is recycled.
+ */
+ private void initView() {
+ setClipChildren(false);
+
+ mRemoveClippedSubviews = false;
+ mAllChildren = null;
+ mAllChildrenCount = 0;
+ mClippingRect = null;
+ mHitSlopRect = null;
+ mOverflow = Overflow.VISIBLE;
+ mPointerEvents = PointerEvents.AUTO;
+ mChildrenLayoutChangeListener = null;
+ mCSSBackgroundDrawable = null;
+ mOnInterceptTouchEventListener = null;
+ mNeedsOffscreenAlphaCompositing = false;
+ mDrawingOrderHelper = null;
+ mPath = null;
+ mBackfaceOpacity = 1.f;
+ mBackfaceVisibility = "visible";
+ }
+
+ /* package */ void recycleView() {
+ // Remove dangling listeners
+ if (mAllChildren != null && mChildrenLayoutChangeListener != null) {
+ for (int i = 0; i < mAllChildrenCount; i++) {
+ mAllChildren[i].removeOnLayoutChangeListener(mChildrenLayoutChangeListener);
+ }
+ }
+
+ // Set default field values
+ initView();
+ mOverflowInset.setEmpty();
+ sHelperRect.setEmpty();
+
+ // Remove any children
+ removeAllViews();
+
+ // Reset background, borders
+ updateBackgroundDrawable(null);
+
+ resetPointerEvents();
+ }
+
+ private ViewGroupDrawingOrderHelper getDrawingOrderHelper() {
+ if (mDrawingOrderHelper == null) {
+ mDrawingOrderHelper = new ViewGroupDrawingOrderHelper(this);
+ }
+ return mDrawingOrderHelper;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec);
+
+ setMeasuredDimension(
+ MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ // No-op since UIManagerModule handles actually laying out children.
+ }
+
+ @Override
+ @SuppressLint("MissingSuperCall")
+ public void requestLayout() {
+ // No-op, terminate `requestLayout` here, UIManagerModule handles laying out children and
+ // `layout` is called on all RN-managed views by `NativeViewHierarchyManager`
+ }
+
+ @TargetApi(23)
+ @Override
+ public void dispatchProvideStructure(ViewStructure structure) {
+ try {
+ super.dispatchProvideStructure(structure);
+ } catch (NullPointerException e) {
+ FLog.e(TAG, "NullPointerException when executing dispatchProvideStructure", e);
+ }
+ }
+
+ @Override
+ public void setBackgroundColor(int color) {
+ BackgroundStyleApplicator.setBackgroundColor(this, color);
+ }
+
+ @Deprecated(since = "0.76.0", forRemoval = true)
+ public void setTranslucentBackgroundDrawable(@Nullable Drawable background) {
+ BackgroundStyleApplicator.setFeedbackUnderlay(this, background);
+ }
+
+ @Override
+ public void setOnInterceptTouchEventListener(OnInterceptTouchEventListener listener) {
+ mOnInterceptTouchEventListener = listener;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (mOnInterceptTouchEventListener != null
+ && mOnInterceptTouchEventListener.onInterceptTouchEvent(this, ev)) {
+ return true;
+ }
+ // We intercept the touch event if the children are not supposed to receive it.
+ if (!PointerEvents.canChildrenBeTouchTarget(mPointerEvents)) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ // We do not accept the touch event if this view is not supposed to receive it.
+ if (!PointerEvents.canBeTouchTarget(mPointerEvents)) {
+ return false;
+ }
+ // The root view always assumes any view that was tapped wants the touch
+ // and sends the event to JS as such.
+ // We don't need to do bubbling in native (it's already happening in JS).
+ // For an explanation of bubbling and capturing, see
+ // http://javascript.info/tutorial/bubbling-and-capturing#capturing
+ return true;
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (ReactFeatureFlags.dispatchPointerEvents) {
+ // Match the logic from onTouchEvent if pointer events are enabled
+ return PointerEvents.canBeTouchTarget(mPointerEvents);
+ }
+ return super.onHoverEvent(event);
+ }
+
+ @Override
+ public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+ // We do not dispatch the motion event if its children are not supposed to receive it
+ if (!PointerEvents.canChildrenBeTouchTarget(mPointerEvents)) {
+ return false;
+ }
+
+ return super.dispatchGenericMotionEvent(ev);
+ }
+
+ /**
+ * We override this to allow developers to determine whether they need offscreen alpha compositing
+ * or not. See the documentation of needsOffscreenAlphaCompositing in View.js.
+ */
+ @Override
+ public boolean hasOverlappingRendering() {
+ return mNeedsOffscreenAlphaCompositing;
+ }
+
+ /** See the documentation of needsOffscreenAlphaCompositing in View.js. */
+ public void setNeedsOffscreenAlphaCompositing(boolean needsOffscreenAlphaCompositing) {
+ mNeedsOffscreenAlphaCompositing = needsOffscreenAlphaCompositing;
+ }
+
+ public void setBorderWidth(int position, float width) {
+ BackgroundStyleApplicator.setBorderWidth(
+ this, LogicalEdge.values()[position], PixelUtil.toDIPFromPixel(width));
+ }
+
+ public void setBorderColor(int position, @Nullable Integer color) {
+ BackgroundStyleApplicator.setBorderColor(this, LogicalEdge.values()[position], color);
+ }
+
+ /**
+ * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead.
+ */
+ @Deprecated(since = "0.75.0", forRemoval = true)
+ public void setBorderRadius(float borderRadius) {
+ setBorderRadius(borderRadius, BorderRadiusProp.BORDER_RADIUS.ordinal());
+ }
+
+ /**
+ * @deprecated Use {@link #setBorderRadius(BorderRadiusProp, Float)} instead.
+ */
+ @Deprecated(since = "0.75.0", forRemoval = true)
+ public void setBorderRadius(float borderRadius, int position) {
+ @Nullable
+ LengthPercentage radius =
+ Float.isNaN(borderRadius)
+ ? null
+ : new LengthPercentage(borderRadius, LengthPercentageType.POINT);
+ BackgroundStyleApplicator.setBorderRadius(this, BorderRadiusProp.values()[position], radius);
+ }
+
+ public void setBorderRadius(BorderRadiusProp property, @Nullable LengthPercentage borderRadius) {
+ BackgroundStyleApplicator.setBorderRadius(this, property, borderRadius);
+ }
+
+ public void setBorderStyle(@Nullable String style) {
+ BackgroundStyleApplicator.setBorderStyle(
+ this, style == null ? null : BorderStyle.fromString(style));
+ }
+
+ @Override
+ public void setRemoveClippedSubviews(boolean removeClippedSubviews) {
+ if (removeClippedSubviews == mRemoveClippedSubviews) {
+ return;
+ }
+ mRemoveClippedSubviews = removeClippedSubviews;
+ if (removeClippedSubviews) {
+ mClippingRect = new Rect();
+ ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
+ mAllChildrenCount = getChildCount();
+ int initialSize = Math.max(12, mAllChildrenCount);
+ mAllChildren = new View[initialSize];
+ mChildrenLayoutChangeListener = new ChildrenLayoutChangeListener(this);
+ for (int i = 0; i < mAllChildrenCount; i++) {
+ View child = getChildAt(i);
+ mAllChildren[i] = child;
+ child.addOnLayoutChangeListener(mChildrenLayoutChangeListener);
+ }
+ updateClippingRect();
+ } else {
+ // Add all clipped views back, deallocate additional arrays, remove layoutChangeListener
+ Assertions.assertNotNull(mClippingRect);
+ Assertions.assertNotNull(mAllChildren);
+ Assertions.assertNotNull(mChildrenLayoutChangeListener);
+ for (int i = 0; i < mAllChildrenCount; i++) {
+ mAllChildren[i].removeOnLayoutChangeListener(mChildrenLayoutChangeListener);
+ }
+ getDrawingRect(mClippingRect);
+ updateClippingToRect(mClippingRect);
+ mAllChildren = null;
+ mClippingRect = null;
+ mAllChildrenCount = 0;
+ mChildrenLayoutChangeListener = null;
+ }
+ }
+
+ @Override
+ public boolean getRemoveClippedSubviews() {
+ return mRemoveClippedSubviews;
+ }
+
+ @Override
+ public void getClippingRect(Rect outClippingRect) {
+ outClippingRect.set(nullsafeFIXME(mClippingRect, "Fix in Kotlin"));
+ }
+
+ @Override
+ public void updateClippingRect() {
+ if (!mRemoveClippedSubviews) {
+ return;
+ }
+
+ Assertions.assertNotNull(mClippingRect);
+ Assertions.assertNotNull(mAllChildren);
+
+ ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
+ updateClippingToRect(mClippingRect);
+ }
+
+ private void updateClippingToRect(Rect clippingRect) {
+ Assertions.assertNotNull(mAllChildren);
+ int clippedSoFar = 0;
+ for (int i = 0; i < mAllChildrenCount; i++) {
+ updateSubviewClipStatus(clippingRect, i, clippedSoFar);
+ if (mAllChildren[i].getParent() == null) {
+ clippedSoFar++;
+ }
+ }
+ }
+
+ private void updateSubviewClipStatus(Rect clippingRect, int idx, int clippedSoFar) {
+ UiThreadUtil.assertOnUiThread();
+
+ View child = Assertions.assertNotNull(mAllChildren)[idx];
+ sHelperRect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
+ boolean intersects =
+ clippingRect.intersects(
+ sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom);
+ boolean needUpdateClippingRecursive = false;
+ // We never want to clip children that are being animated, as this can easily break layout :
+ // when layout animation changes size and/or position of views contained inside a listview that
+ // clips offscreen children, we need to ensure that, when view exits the viewport, final size
+ // and position is set prior to removing the view from its listview parent.
+ // Otherwise, when view gets re-attached again, i.e when it re-enters the viewport after scroll,
+ // it won't be size and located properly.
+ Animation animation = child.getAnimation();
+ boolean isAnimating = animation != null && !animation.hasEnded();
+ if (!intersects && child.getParent() != null && !isAnimating) {
+ // We can try saving on invalidate call here as the view that we remove is out of visible area
+ // therefore invalidation is not necessary.
+ removeViewInLayout(child);
+ needUpdateClippingRecursive = true;
+ } else if (intersects && child.getParent() == null) {
+ addViewInLayout(child, idx - clippedSoFar, sDefaultLayoutParam, true);
+ invalidate();
+ needUpdateClippingRecursive = true;
+ } else if (intersects) {
+ // If there is any intersection we need to inform the child to update its clipping rect
+ needUpdateClippingRecursive = true;
+ }
+ if (needUpdateClippingRecursive) {
+ if (child instanceof ReactClippingViewGroup) {
+ // we don't use {@link sHelperRect} until the end of this loop, therefore it's safe
+ // to call this method that may write to the same {@link sHelperRect} object.
+ ReactClippingViewGroup clippingChild = (ReactClippingViewGroup) child;
+ if (clippingChild.getRemoveClippedSubviews()) {
+ clippingChild.updateClippingRect();
+ }
+ }
+ }
+ }
+
+ private void updateSubviewClipStatus(View subview) {
+ if (!mRemoveClippedSubviews || getParent() == null) {
+ return;
+ }
+
+ Assertions.assertNotNull(mClippingRect);
+ Assertions.assertNotNull(mAllChildren);
+
+ // do fast check whether intersect state changed
+ sHelperRect.set(subview.getLeft(), subview.getTop(), subview.getRight(), subview.getBottom());
+ boolean intersects =
+ mClippingRect.intersects(
+ sHelperRect.left, sHelperRect.top, sHelperRect.right, sHelperRect.bottom);
+
+ // If it was intersecting before, should be attached to the parent
+ boolean oldIntersects = (subview.getParent() != null);
+
+ if (intersects != oldIntersects) {
+ int clippedSoFar = 0;
+ for (int i = 0; i < mAllChildrenCount; i++) {
+ if (mAllChildren[i] == subview) {
+ updateSubviewClipStatus(mClippingRect, i, clippedSoFar);
+ break;
+ }
+ if (mAllChildren[i].getParent() == null) {
+ clippedSoFar++;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean getChildVisibleRect(View child, Rect r, android.graphics.Point offset) {
+ return super.getChildVisibleRect(child, r, offset);
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mRemoveClippedSubviews) {
+ updateClippingRect();
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mRemoveClippedSubviews) {
+ updateClippingRect();
+ }
+ }
+
+ private boolean customDrawOrderDisabled() {
+ if (getId() == NO_ID) {
+ return false;
+ }
+
+ // Custom draw order is disabled for Fabric.
+ return ViewUtil.getUIManagerType(getId()) == UIManagerType.FABRIC;
+ }
+
+ private void handleAddView(View view) {
+ UiThreadUtil.assertOnUiThread();
+
+ if (!customDrawOrderDisabled()) {
+ getDrawingOrderHelper().handleAddView(view);
+ setChildrenDrawingOrderEnabled(getDrawingOrderHelper().shouldEnableCustomDrawingOrder());
+ } else {
+ setChildrenDrawingOrderEnabled(false);
+ }
+ }
+
+ private void handleRemoveView(@Nullable View view) {
+ UiThreadUtil.assertOnUiThread();
+
+ if (!customDrawOrderDisabled()) {
+ if (indexOfChild(view) == -1) {
+ return;
+ }
+ getDrawingOrderHelper().handleRemoveView(view);
+ setChildrenDrawingOrderEnabled(getDrawingOrderHelper().shouldEnableCustomDrawingOrder());
+ } else {
+ setChildrenDrawingOrderEnabled(false);
+ }
+ }
+
+ private void handleRemoveViews(int start, int count) {
+ int endIndex = start + count;
+ for (int index = start; index < endIndex; index++) {
+ if (index < getChildCount()) {
+ handleRemoveView(getChildAt(index));
+ }
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, @Nullable ViewGroup.LayoutParams params) {
+ // This will get called for every overload of addView so there is not need to override every
+ // method.
+ handleAddView(child);
+ super.addView(child, index, params);
+ }
+
+ @Override
+ protected boolean addViewInLayout(
+ View child, int index, LayoutParams params, boolean preventRequestLayout) {
+ handleAddView(child);
+ return super.addViewInLayout(child, index, params, preventRequestLayout);
+ }
+
+ @Override
+ public void removeView(@Nullable View view) {
+ handleRemoveView(view);
+ super.removeView(view);
+ }
+
+ @Override
+ public void removeViewAt(int index) {
+ handleRemoveView(getChildAt(index));
+ super.removeViewAt(index);
+ }
+
+ @Override
+ public void removeViewInLayout(View view) {
+ handleRemoveView(view);
+ super.removeViewInLayout(view);
+ }
+
+ @Override
+ public void removeViewsInLayout(int start, int count) {
+ handleRemoveViews(start, count);
+ super.removeViewsInLayout(start, count);
+ }
+
+ @Override
+ public void removeViews(int start, int count) {
+ handleRemoveViews(start, count);
+ super.removeViews(start, count);
+ }
+
+ @Override
+ protected int getChildDrawingOrder(int childCount, int index) {
+ UiThreadUtil.assertOnUiThread();
+
+ if (!customDrawOrderDisabled()) {
+ return getDrawingOrderHelper().getChildDrawingOrder(childCount, index);
+ } else {
+ return index;
+ }
+ }
+
+ @Override
+ public int getZIndexMappedChildIndex(int index) {
+ UiThreadUtil.assertOnUiThread();
+
+ if (!customDrawOrderDisabled() && getDrawingOrderHelper().shouldEnableCustomDrawingOrder()) {
+ return getDrawingOrderHelper().getChildDrawingOrder(getChildCount(), index);
+ }
+
+ // Fabric behavior
+ return index;
+ }
+
+ @Override
+ public void updateDrawingOrder() {
+ if (customDrawOrderDisabled()) {
+ return;
+ }
+
+ getDrawingOrderHelper().update();
+ setChildrenDrawingOrderEnabled(getDrawingOrderHelper().shouldEnableCustomDrawingOrder());
+ invalidate();
+ }
+
+ @Override
+ public PointerEvents getPointerEvents() {
+ return mPointerEvents;
+ }
+
+ @Override
+ protected void dispatchSetPressed(boolean pressed) {
+ // Prevents the ViewGroup from dispatching the pressed state
+ // to it's children.
+ }
+
+ public void setPointerEvents(PointerEvents pointerEvents) {
+ mPointerEvents = pointerEvents;
+ }
+
+ /*package*/ void resetPointerEvents() {
+ mPointerEvents = PointerEvents.AUTO;
+ }
+
+ /*package*/ int getAllChildrenCount() {
+ return mAllChildrenCount;
+ }
+
+ /*package*/ @Nullable
+ View getChildAtWithSubviewClippingEnabled(int index) {
+ return index >= 0 && index < mAllChildrenCount
+ ? Assertions.assertNotNull(mAllChildren)[index]
+ : null;
+ }
+
+ /*package*/ void addViewWithSubviewClippingEnabled(View child, int index) {
+ addViewWithSubviewClippingEnabled(child, index, sDefaultLayoutParam);
+ }
+
+ /*package*/ void addViewWithSubviewClippingEnabled(
+ final View child, int index, ViewGroup.LayoutParams params) {
+ Assertions.assertCondition(mRemoveClippedSubviews);
+ Assertions.assertNotNull(mClippingRect);
+ Assertions.assertNotNull(mAllChildren);
+ addInArray(child, index);
+ // we add view as "clipped" and then run {@link #updateSubviewClipStatus} to conditionally
+ // attach it
+ int clippedSoFar = 0;
+ for (int i = 0; i < index; i++) {
+ if (mAllChildren[i].getParent() == null) {
+ clippedSoFar++;
+ }
+ }
+ updateSubviewClipStatus(mClippingRect, index, clippedSoFar);
+ child.addOnLayoutChangeListener(mChildrenLayoutChangeListener);
+
+ if (child instanceof ReactClippingProhibitedView) {
+ UiThreadUtil.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (!child.isShown()) {
+ ReactSoftExceptionLogger.logSoftException(
+ TAG,
+ new ReactNoCrashSoftException(
+ "Child view has been added to Parent view in which it is clipped and not"
+ + " visible. This is not legal for this particular child view. Child: ["
+ + child.getId()
+ + "] "
+ + child.toString()
+ + " Parent: ["
+ + getId()
+ + "] "
+ + toString()));
+ }
+ }
+ });
+ }
+ }
+
+ /*package*/ void removeViewWithSubviewClippingEnabled(View view) {
+ UiThreadUtil.assertOnUiThread();
+
+ Assertions.assertCondition(mRemoveClippedSubviews);
+ Assertions.assertNotNull(mClippingRect);
+ Assertions.assertNotNull(mAllChildren);
+ view.removeOnLayoutChangeListener(mChildrenLayoutChangeListener);
+ int index = indexOfChildInAllChildren(view);
+ if (mAllChildren[index].getParent() != null) {
+ int clippedSoFar = 0;
+ for (int i = 0; i < index; i++) {
+ if (mAllChildren[i].getParent() == null) {
+ clippedSoFar++;
+ }
+ }
+ removeViewsInLayout(index - clippedSoFar, 1);
+ }
+ removeFromArray(index);
+ }
+
+ /*package*/ void removeAllViewsWithSubviewClippingEnabled() {
+ Assertions.assertCondition(mRemoveClippedSubviews);
+ Assertions.assertNotNull(mAllChildren);
+ for (int i = 0; i < mAllChildrenCount; i++) {
+ mAllChildren[i].removeOnLayoutChangeListener(mChildrenLayoutChangeListener);
+ }
+ removeAllViewsInLayout();
+ mAllChildrenCount = 0;
+ }
+
+ private int indexOfChildInAllChildren(View child) {
+ final int count = mAllChildrenCount;
+ final View[] children = Assertions.assertNotNull(mAllChildren);
+ for (int i = 0; i < count; i++) {
+ if (children[i] == child) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private void addInArray(View child, int index) {
+ View[] children = Assertions.assertNotNull(mAllChildren);
+ final int count = mAllChildrenCount;
+ final int size = children.length;
+ if (index == count) {
+ if (size == count) {
+ mAllChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+ System.arraycopy(children, 0, mAllChildren, 0, size);
+ children = mAllChildren;
+ }
+ children[mAllChildrenCount++] = child;
+ } else if (index < count) {
+ if (size == count) {
+ mAllChildren = new View[size + ARRAY_CAPACITY_INCREMENT];
+ System.arraycopy(children, 0, mAllChildren, 0, index);
+ System.arraycopy(children, index, mAllChildren, index + 1, count - index);
+ children = mAllChildren;
+ } else {
+ System.arraycopy(children, index, children, index + 1, count - index);
+ }
+ children[index] = child;
+ mAllChildrenCount++;
+ } else {
+ throw new IndexOutOfBoundsException("index=" + index + " count=" + count);
+ }
+ }
+
+ private void removeFromArray(int index) {
+ final View[] children = Assertions.assertNotNull(mAllChildren);
+ final int count = mAllChildrenCount;
+ if (index == count - 1) {
+ children[--mAllChildrenCount] = null;
+ } else if (index >= 0 && index < count) {
+ System.arraycopy(children, index + 1, children, index, count - index - 1);
+ children[--mAllChildrenCount] = null;
+ } else {
+ throw new IndexOutOfBoundsException();
+ }
+ }
+
+ private boolean needsIsolatedLayer() {
+ if (!ReactNativeFeatureFlags.enableAndroidMixBlendModeProp()) {
+ return false;
+ }
+
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i).getTag(R.id.mix_blend_mode) != null) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @VisibleForTesting
+ public int getBackgroundColor() {
+ @Nullable Integer color = BackgroundStyleApplicator.getBackgroundColor(this);
+ return color == null ? DEFAULT_BACKGROUND_COLOR : color;
+ }
+
+ @Override
+ public @Nullable Rect getHitSlopRect() {
+ return mHitSlopRect;
+ }
+
+ public void setHitSlopRect(@Nullable Rect rect) {
+ mHitSlopRect = rect;
+ }
+
+ public void setOverflow(@Nullable String overflow) {
+ if (overflow == null) {
+ mOverflow = Overflow.VISIBLE;
+ } else {
+ @Nullable Overflow parsedOverflow = Overflow.fromString(overflow);
+ mOverflow = parsedOverflow == null ? Overflow.VISIBLE : parsedOverflow;
+ }
+
+ invalidate();
+ }
+
+ @Override
+ public @Nullable String getOverflow() {
+ switch (mOverflow) {
+ case HIDDEN:
+ return "hidden";
+ case SCROLL:
+ return "scroll";
+ case VISIBLE:
+ return "visible";
+ }
+
+ return null;
+ }
+
+ @Override
+ public void setOverflowInset(int left, int top, int right, int bottom) {
+ if (needsIsolatedLayer()
+ && (mOverflowInset.left != left
+ || mOverflowInset.top != top
+ || mOverflowInset.right != right
+ || mOverflowInset.bottom != bottom)) {
+ invalidate();
+ }
+ mOverflowInset.set(left, top, right, bottom);
+ }
+
+ @Override
+ public Rect getOverflowInset() {
+ return mOverflowInset;
+ }
+
+ /**
+ * Set the background for the view or remove the background. It calls {@link
+ * #setBackground(Drawable)} or {@link #setBackgroundDrawable(Drawable)} based on the sdk version.
+ *
+ * @param drawable {@link Drawable} The Drawable to use as the background, or null to remove the
+ * background
+ */
+ /* package */ void updateBackgroundDrawable(@Nullable Drawable drawable) {
+ super.setBackground(drawable);
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+ && ViewUtil.getUIManagerType(this) == UIManagerType.FABRIC
+ && needsIsolatedLayer()) {
+
+ // Check if the view is a stacking context and has children, if it does, do the rendering
+ // offscreen and then composite back. This follows the idea of group isolation on blending
+ // https://www.w3.org/TR/compositing-1/#isolationblending
+ Rect overflowInset = getOverflowInset();
+ canvas.saveLayer(
+ overflowInset.left,
+ overflowInset.top,
+ getWidth() + -overflowInset.right,
+ getHeight() + -overflowInset.bottom,
+ null);
+ super.draw(canvas);
+ canvas.restore();
+ } else {
+ super.draw(canvas);
+ }
+ }
+
+ @Override
+ protected void dispatchDraw(Canvas canvas) {
+ if (mOverflow != Overflow.VISIBLE || getTag(R.id.filter) != null) {
+ BackgroundStyleApplicator.clipToPaddingBox(this, canvas);
+ }
+ super.dispatchDraw(canvas);
+ }
+
+ @Override
+ protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+ boolean drawWithZ = child.getElevation() > 0;
+
+ if (drawWithZ) {
+ CanvasUtil.enableZ(canvas, true);
+ }
+
+ BlendMode mixBlendMode = null;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && needsIsolatedLayer()) {
+ mixBlendMode = (BlendMode) child.getTag(R.id.mix_blend_mode);
+ if (mixBlendMode != null) {
+ Paint p = new Paint();
+ p.setBlendMode(mixBlendMode);
+ Rect overflowInset = getOverflowInset();
+ canvas.saveLayer(
+ overflowInset.left,
+ overflowInset.top,
+ getWidth() + -overflowInset.right,
+ getHeight() + -overflowInset.bottom,
+ p);
+ }
+ }
+
+ boolean result = super.drawChild(canvas, child, drawingTime);
+
+ if (mixBlendMode != null) {
+ canvas.restore();
+ }
+
+ if (drawWithZ) {
+ CanvasUtil.enableZ(canvas, false);
+ }
+ return result;
+ }
+
+ public void setOpacityIfPossible(float opacity) {
+ mBackfaceOpacity = opacity;
+ setBackfaceVisibilityDependantOpacity();
+ }
+
+ public void setBackfaceVisibility(String backfaceVisibility) {
+ mBackfaceVisibility = backfaceVisibility;
+ setBackfaceVisibilityDependantOpacity();
+ }
+
+ public void setBackfaceVisibilityDependantOpacity() {
+ boolean isBackfaceVisible = mBackfaceVisibility.equals("visible");
+
+ if (isBackfaceVisible) {
+ setAlpha(mBackfaceOpacity);
+ return;
+ }
+
+ float rotationX = getRotationX();
+ float rotationY = getRotationY();
+
+ boolean isFrontfaceVisible =
+ (rotationX >= -90.f && rotationX < 90.f) && (rotationY >= -90.f && rotationY < 90.f);
+
+ if (isFrontfaceVisible) {
+ setAlpha(mBackfaceOpacity);
+ return;
+ }
+
+ setAlpha(0);
+ }
+}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
deleted file mode 100644
index c080ec54afebd4..00000000000000
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.kt
+++ /dev/null
@@ -1,826 +0,0 @@
-/*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-@file:Suppress("DEPRECATION") // ReactFeatureFlags
-
-package com.facebook.react.views.view
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.graphics.BlendMode
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.graphics.Rect
-import android.graphics.drawable.Drawable
-import android.os.Build
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewStructure
-import com.facebook.common.logging.FLog
-import com.facebook.infer.annotation.Assertions
-import com.facebook.react.R
-import com.facebook.react.bridge.ReactNoCrashSoftException
-import com.facebook.react.bridge.ReactSoftExceptionLogger
-import com.facebook.react.bridge.UiThreadUtil
-import com.facebook.react.common.ReactConstants
-import com.facebook.react.common.annotations.UnstableReactNativeAPI
-import com.facebook.react.common.annotations.VisibleForTesting
-import com.facebook.react.config.ReactFeatureFlags
-import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags
-import com.facebook.react.touch.OnInterceptTouchEventListener
-import com.facebook.react.touch.ReactHitSlopView
-import com.facebook.react.touch.ReactInterceptingViewGroup
-import com.facebook.react.uimanager.BackgroundStyleApplicator
-import com.facebook.react.uimanager.LengthPercentage
-import com.facebook.react.uimanager.LengthPercentageType
-import com.facebook.react.uimanager.MeasureSpecAssertions
-import com.facebook.react.uimanager.PixelUtil.pxToDp
-import com.facebook.react.uimanager.PointerEvents
-import com.facebook.react.uimanager.ReactClippingProhibitedView
-import com.facebook.react.uimanager.ReactClippingViewGroup
-import com.facebook.react.uimanager.ReactClippingViewGroupHelper
-import com.facebook.react.uimanager.ReactOverflowViewWithInset
-import com.facebook.react.uimanager.ReactPointerEventsView
-import com.facebook.react.uimanager.ReactZIndexedViewGroup
-import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper
-import com.facebook.react.uimanager.common.UIManagerType
-import com.facebook.react.uimanager.common.ViewUtil
-import com.facebook.react.uimanager.style.BorderRadiusProp
-import com.facebook.react.uimanager.style.BorderStyle
-import com.facebook.react.uimanager.style.LogicalEdge
-import com.facebook.react.uimanager.style.Overflow
-import kotlin.math.max
-
-/**
- * Backing for a React View. Has support for borders, but since borders aren't common, lazy
- * initializes most of the storage needed for them.
- */
-@OptIn(UnstableReactNativeAPI::class)
-public open class ReactViewGroup(context: Context) :
- ViewGroup(context),
- ReactInterceptingViewGroup,
- ReactClippingViewGroup,
- ReactPointerEventsView,
- ReactHitSlopView,
- ReactZIndexedViewGroup,
- ReactOverflowViewWithInset {
-
- private companion object {
- private const val ARRAY_CAPACITY_INCREMENT = 12
- private const val DEFAULT_BACKGROUND_COLOR = Color.TRANSPARENT
- private val defaultLayoutParam = LayoutParams(0, 0)
- }
-
- private val _overflowInset = Rect()
-
- /**
- * This listener will be set for child views when removeClippedSubview property is enabled. When
- * children layout is updated, it will call [updateSubviewClipStatus] to notify parent view about
- * that fact so that view can be attached/detached if necessary.
- *
- * TODO(7728005): Attach/detach views in batch - once per frame in case when multiple children
- * update their layout.
- */
- private class ChildrenLayoutChangeListener(private val parent: ReactViewGroup) :
- OnLayoutChangeListener {
- override fun onLayoutChange(
- v: View,
- left: Int,
- top: Int,
- right: Int,
- bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
- ) {
- if (parent.removeClippedSubviews) {
- parent.updateSubviewClipStatus(v)
- }
- }
- }
-
- // Following properties are here to support the option {@code removeClippedSubviews}. This is a
- // temporary optimization/hack that is mainly applicable to the large list of images. The way
- // it's implemented is that we store an additional array of children in view node. We selectively
- // remove some of the views (detach) from it while still storing them in that additional array.
- // We override all possible add methods for [ViewGroup] so that we can control this process
- // whenever the option is set. We also override [ViewGroup#getChildAt] and
- // [ViewGroup#getChildCount] so those methods may return views that are not attached.
- // This is risky but allows us to perform a correct cleanup in [NativeViewHierarchyManager].
- private var _removeClippedSubviews = false
-
- private var allChildren: Array? = null
- internal var allChildrenCount: Int = 0
- private set
-
- private var _clippingRect: Rect? = null
- public override var hitSlopRect: Rect? = null
- private var _overflow: Overflow = Overflow.VISIBLE
- private var _pointerEvents: PointerEvents = PointerEvents.AUTO
- private var childrenLayoutChangeListener: ChildrenLayoutChangeListener? = null
- private var onInterceptTouchEventListener: OnInterceptTouchEventListener? = null
- private var needsOffscreenAlphaCompositing = false
- private var _drawingOrderHelper: ViewGroupDrawingOrderHelper? = null
- private var backfaceOpacity = 1f
- private var backfaceVisibility: String? = "visible"
-
- /**
- * Set all default values here as opposed to in the constructor or field defaults. It is important
- * that these properties are set during the constructor, but also on-demand whenever an existing
- * ReactTextView is recycled.
- */
- private fun initView() {
- clipChildren = false
- _removeClippedSubviews = false
- allChildren = null
- allChildrenCount = 0
- _clippingRect = null
- hitSlopRect = null
- _overflow = Overflow.VISIBLE
- resetPointerEvents()
- childrenLayoutChangeListener = null
- onInterceptTouchEventListener = null
- needsOffscreenAlphaCompositing = false
- _drawingOrderHelper = null
- backfaceOpacity = 1f
- backfaceVisibility = "visible"
- }
-
- internal open fun recycleView(): Unit {
- // Remove dangling listeners
- val children = allChildren
- val listener = childrenLayoutChangeListener
- if (children != null && listener != null) {
- for (i in 0 until allChildrenCount) {
- children[i]?.removeOnLayoutChangeListener(listener)
- }
- }
-
- // Set default field values
- initView()
- _overflowInset.setEmpty()
-
- // Remove any children
- removeAllViews()
-
- // Reset background, borders
- updateBackgroundDrawable(null)
- resetPointerEvents()
- }
-
- private val drawingOrderHelper: ViewGroupDrawingOrderHelper
- get() {
- return _drawingOrderHelper
- ?: ViewGroupDrawingOrderHelper(this).also { _drawingOrderHelper = it }
- }
-
- init {
- initView()
- }
-
- override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
- MeasureSpecAssertions.assertExplicitMeasureSpec(widthMeasureSpec, heightMeasureSpec)
- setMeasuredDimension(
- MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec))
- }
-
- // No-op since UIManagerModule handles actually laying out children.
- override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int): Unit = Unit
-
- @SuppressLint("MissingSuperCall")
- // No-op, terminate `requestLayout` here, UIManagerModule handles laying out children and `layout`
- // is called on all RN-managed views by `NativeViewHierarchyManager`
- override fun requestLayout(): Unit = Unit
-
- override fun dispatchProvideStructure(structure: ViewStructure) {
- try {
- super.dispatchProvideStructure(structure)
- } catch (e: NullPointerException) {
- FLog.e(ReactConstants.TAG, "NullPointerException when executing dispatchProvideStructure", e)
- }
- }
-
- override fun setBackgroundColor(color: Int) {
- BackgroundStyleApplicator.setBackgroundColor(this, color)
- }
-
- @Deprecated(
- "Don't use setTranslucentBackgroundDrawable as it was deprecated in React Native 0.76.0.",
- ReplaceWith(
- "BackgroundStyleApplicator.setFeedbackUnderlay(this, background)",
- "com.facebook.react.uimanager.BackgroundStyleApplicator"))
- public open fun setTranslucentBackgroundDrawable(background: Drawable?): Unit {
- BackgroundStyleApplicator.setFeedbackUnderlay(this, background)
- }
-
- override fun setOnInterceptTouchEventListener(listener: OnInterceptTouchEventListener) {
- onInterceptTouchEventListener = listener
- }
-
- override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
- if (onInterceptTouchEventListener?.onInterceptTouchEvent(this, ev) == true) {
- return true
- }
- // We intercept the touch event if the children are not supposed to receive it.
- return !PointerEvents.canChildrenBeTouchTarget(_pointerEvents) ||
- super.onInterceptTouchEvent(ev)
- }
-
- override fun onTouchEvent(ev: MotionEvent): Boolean {
- // We do not accept the touch event if this view is not supposed to receive it.
- // The root view always assumes any view that was tapped wants the touch
- // and sends the event to JS as such.
- // We don't need to do bubbling in native (it's already happening in JS).
- // For an explanation of bubbling and capturing, see
- // http://javascript.info/tutorial/bubbling-and-capturing#capturing
- return PointerEvents.canBeTouchTarget(_pointerEvents)
- }
-
- override fun onHoverEvent(event: MotionEvent): Boolean =
- if (ReactFeatureFlags.dispatchPointerEvents) {
- // Match the logic from onTouchEvent if pointer events are enabled
- PointerEvents.canBeTouchTarget(_pointerEvents)
- } else {
- super.onHoverEvent(event)
- }
-
- override fun dispatchGenericMotionEvent(ev: MotionEvent): Boolean =
- // We do not dispatch the motion event if its children are not supposed to receive it
- PointerEvents.canChildrenBeTouchTarget(_pointerEvents) || super.dispatchGenericMotionEvent(ev)
-
- /**
- * We override this to allow developers to determine whether they need offscreen alpha compositing
- * or not. See the documentation of needsOffscreenAlphaCompositing in View.js.
- */
- override fun hasOverlappingRendering(): Boolean = needsOffscreenAlphaCompositing
-
- /** See the documentation of needsOffscreenAlphaCompositing in View.js. */
- public open fun setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing: Boolean): Unit {
- this.needsOffscreenAlphaCompositing = needsOffscreenAlphaCompositing
- }
-
- public open fun setBorderWidth(position: Int, width: Float): Unit {
- BackgroundStyleApplicator.setBorderWidth(this, LogicalEdge.entries[position], width.pxToDp())
- }
-
- public open fun setBorderColor(position: Int, color: Int?): Unit {
- BackgroundStyleApplicator.setBorderColor(this, LogicalEdge.entries[position], color)
- }
-
- @Deprecated(
- "Deprecated in React Native 0.75.0, in favor of setBorderRadius(BorderRadiusProp, Float)",
- ReplaceWith(
- "setBorderRadius(BorderRadiusProp.BORDER_RADIUS, borderRadius)",
- "com.facebook.react.uimanager.style.BorderRadiusProp",
- ))
- @Suppress("DEPRECATION")
- public open fun setBorderRadius(borderRadius: Float): Unit {
- this.setBorderRadius(borderRadius, BorderRadiusProp.BORDER_RADIUS.ordinal)
- }
-
- @Deprecated(
- "Deprecated in React Native 0.75.0, in favor of setBorderRadius(BorderRadiusProp, Float)",
- ReplaceWith(
- "setBorderRadius(BorderRadiusProp.entries[position], borderRadius)",
- "com.facebook.react.uimanager.style.BorderRadiusProp",
- ))
- public open fun setBorderRadius(borderRadius: Float, position: Int): Unit {
- val radius =
- when {
- borderRadius.isNaN() -> null
- else -> LengthPercentage(borderRadius, LengthPercentageType.POINT)
- }
- BackgroundStyleApplicator.setBorderRadius(this, BorderRadiusProp.entries[position], radius)
- }
-
- public open fun setBorderRadius(
- property: BorderRadiusProp,
- borderRadius: LengthPercentage?
- ): Unit {
- BackgroundStyleApplicator.setBorderRadius(this, property, borderRadius)
- }
-
- public open fun setBorderStyle(style: String?): Unit {
- BackgroundStyleApplicator.setBorderStyle(this, style?.let { BorderStyle.fromString(style) })
- }
-
- override fun setRemoveClippedSubviews(removeClippedSubviews: Boolean) {
- if (removeClippedSubviews == _removeClippedSubviews) {
- return
- }
- _removeClippedSubviews = removeClippedSubviews
- if (removeClippedSubviews) {
- val clippingRect = Rect()
- ReactClippingViewGroupHelper.calculateClippingRect(this, clippingRect)
- allChildrenCount = childCount
- val initialSize = max(12, allChildrenCount)
- val children = arrayOfNulls(initialSize)
- childrenLayoutChangeListener = ChildrenLayoutChangeListener(this)
- for (i in 0 until allChildrenCount) {
- children[i] =
- getChildAt(i).apply { addOnLayoutChangeListener(childrenLayoutChangeListener) }
- }
- _clippingRect = clippingRect
- allChildren = children
- updateClippingRect()
- } else {
- // Add all clipped views back, deallocate additional arrays, remove layoutChangeListener
- val clippingRect = checkNotNull(_clippingRect)
- val children = checkNotNull(allChildren)
- val listener = checkNotNull(childrenLayoutChangeListener)
- for (i in 0 until allChildrenCount) {
- children[i]?.removeOnLayoutChangeListener(listener)
- }
- getDrawingRect(clippingRect)
- updateClippingToRect(clippingRect)
- allChildren = null
- _clippingRect = null
- allChildrenCount = 0
- childrenLayoutChangeListener = null
- }
- }
-
- override fun getRemoveClippedSubviews(): Boolean = _removeClippedSubviews
-
- override fun getClippingRect(outClippingRect: Rect) {
- outClippingRect.set(
- checkNotNull(_clippingRect) { "getClippingRect called when removeClippedSubviews not set" })
- }
-
- override fun updateClippingRect() {
- if (!_removeClippedSubviews) {
- return
- }
- val clippingRect = checkNotNull(_clippingRect)
- checkNotNull(allChildren)
- ReactClippingViewGroupHelper.calculateClippingRect(this, clippingRect)
- updateClippingToRect(clippingRect)
- }
-
- private fun updateClippingToRect(clippingRect: Rect) {
- val children = checkNotNull(allChildren)
- var clippedSoFar = 0
- for (i in 0 until allChildrenCount) {
- updateSubviewClipStatus(clippingRect, i, clippedSoFar)
- if (children[i]?.parent == null) {
- clippedSoFar++
- }
- }
- }
-
- private fun updateSubviewClipStatus(clippingRect: Rect, idx: Int, clippedSoFar: Int) {
- UiThreadUtil.assertOnUiThread()
- val child = checkNotNull(allChildren?.get(idx))
- val intersects = clippingRect.intersects(child.left, child.top, child.right, child.bottom)
- var needUpdateClippingRecursive = false
- // We never want to clip children that are being animated, as this can easily break layout :
- // when layout animation changes size and/or position of views contained inside a listview that
- // clips offscreen children, we need to ensure that, when view exits the viewport, final size
- // and position is set prior to removing the view from its listview parent.
- // Otherwise, when view gets re-attached again, i.e when it re-enters the viewport after scroll,
- // it won't be size and located properly.
- val animation = child.animation
- val isAnimating = animation?.hasEnded() == false
- if (!intersects && child.parent != null && !isAnimating) {
- // We can try saving on invalidate call here as the view that we remove is out of visible
- // area
- // therefore invalidation is not necessary.
- removeViewInLayout(child)
- needUpdateClippingRecursive = true
- } else if (intersects && child.parent == null) {
- addViewInLayout(child, idx - clippedSoFar, defaultLayoutParam, true)
- invalidate()
- needUpdateClippingRecursive = true
- } else if (intersects) {
- // If there is any intersection we need to inform the child to update its clipping rect
- needUpdateClippingRecursive = true
- }
- if (needUpdateClippingRecursive &&
- child is ReactClippingViewGroup &&
- child.removeClippedSubviews) {
- child.updateClippingRect()
- }
- }
-
- private fun updateSubviewClipStatus(subview: View) {
- if (!_removeClippedSubviews || parent == null) {
- return
- }
- val clippingRect = checkNotNull(_clippingRect)
- val children = checkNotNull(allChildren)
-
- // do fast check whether intersect state changed
- val intersects =
- clippingRect.intersects(subview.left, subview.top, subview.right, subview.bottom)
-
- // If it was intersecting before, should be attached to the parent
- val oldIntersects = subview.parent != null
- if (intersects != oldIntersects) {
- var clippedSoFar = 0
- for (i in 0 until allChildrenCount) {
- if (children[i] === subview) {
- updateSubviewClipStatus(clippingRect, i, clippedSoFar)
- break
- }
- if (children[i]?.parent == null) {
- clippedSoFar++
- }
- }
- }
- }
-
- override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
- super.onSizeChanged(w, h, oldw, oldh)
- updateClippingRect()
- }
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- updateClippingRect()
- }
-
- private fun customDrawOrderDisabled(): Boolean =
- // Custom draw order is disabled for Fabric.
- id != NO_ID && ViewUtil.getUIManagerType(id) == UIManagerType.FABRIC
-
- private fun handleAddView(view: View) {
- UiThreadUtil.assertOnUiThread()
- if (!customDrawOrderDisabled()) {
- drawingOrderHelper.handleAddView(view)
- isChildrenDrawingOrderEnabled = drawingOrderHelper.shouldEnableCustomDrawingOrder()
- } else {
- isChildrenDrawingOrderEnabled = false
- }
- }
-
- private fun handleRemoveView(view: View?) {
- UiThreadUtil.assertOnUiThread()
- if (!customDrawOrderDisabled()) {
- if (indexOfChild(view) == -1) {
- return
- }
- drawingOrderHelper.handleRemoveView(view)
- isChildrenDrawingOrderEnabled = drawingOrderHelper.shouldEnableCustomDrawingOrder()
- } else {
- isChildrenDrawingOrderEnabled = false
- }
- }
-
- private fun handleRemoveViews(start: Int, count: Int) {
- val endIndex = start + count
- for (index in start until endIndex) {
- if (index < childCount) {
- handleRemoveView(getChildAt(index))
- }
- }
- }
-
- override fun addView(child: View, index: Int, params: LayoutParams?) {
- // This will get called for every overload of addView so there is not need to override every
- // method.
- handleAddView(child)
- super.addView(child, index, params)
- }
-
- override fun addViewInLayout(
- child: View,
- index: Int,
- params: LayoutParams,
- preventRequestLayout: Boolean
- ): Boolean {
- handleAddView(child)
- return super.addViewInLayout(child, index, params, preventRequestLayout)
- }
-
- override fun removeView(view: View?) {
- handleRemoveView(view)
- super.removeView(view)
- }
-
- override fun removeViewAt(index: Int) {
- handleRemoveView(getChildAt(index))
- super.removeViewAt(index)
- }
-
- override fun removeViewInLayout(view: View) {
- handleRemoveView(view)
- super.removeViewInLayout(view)
- }
-
- override fun removeViewsInLayout(start: Int, count: Int) {
- handleRemoveViews(start, count)
- super.removeViewsInLayout(start, count)
- }
-
- override fun removeViews(start: Int, count: Int) {
- handleRemoveViews(start, count)
- super.removeViews(start, count)
- }
-
- override fun getChildDrawingOrder(childCount: Int, index: Int): Int {
- UiThreadUtil.assertOnUiThread()
- return if (!customDrawOrderDisabled()) {
- drawingOrderHelper.getChildDrawingOrder(childCount, index)
- } else {
- index
- }
- }
-
- override fun getZIndexMappedChildIndex(index: Int): Int {
- UiThreadUtil.assertOnUiThread()
- return if (!customDrawOrderDisabled() && drawingOrderHelper.shouldEnableCustomDrawingOrder()) {
- drawingOrderHelper.getChildDrawingOrder(childCount, index)
- } else {
- // Fabric behavior
- index
- }
- }
-
- override fun updateDrawingOrder() {
- if (customDrawOrderDisabled()) {
- return
- }
- drawingOrderHelper.update()
- isChildrenDrawingOrderEnabled = drawingOrderHelper.shouldEnableCustomDrawingOrder()
- invalidate()
- }
-
- override fun getPointerEvents(): PointerEvents = _pointerEvents
-
- override fun dispatchSetPressed(pressed: Boolean) {
- // Prevents the ViewGroup from dispatching the pressed state
- // to it's children.
- }
-
- public open fun setPointerEvents(pointerEvents: PointerEvents?): Unit {
- if (pointerEvents != null) {
- _pointerEvents = pointerEvents
- } else {
- resetPointerEvents()
- }
- }
-
- internal fun resetPointerEvents(): Unit {
- _pointerEvents = PointerEvents.AUTO
- }
-
- internal open fun getChildAtWithSubviewClippingEnabled(index: Int): View? =
- if (index in 0 until allChildrenCount) {
- checkNotNull(allChildren)[index]
- } else {
- null
- }
-
- internal open fun addViewWithSubviewClippingEnabled(child: View, index: Int): Unit {
- Assertions.assertCondition(_removeClippedSubviews)
- val clippingRect = checkNotNull(_clippingRect)
- val children = checkNotNull(allChildren)
- addInArray(child, index)
- // we add view as "clipped" and then run [updateSubviewClipStatus] to conditionally
- // attach it
- var clippedSoFar = 0
- for (i in 0 until index) {
- if (children[i]?.parent == null) {
- clippedSoFar++
- }
- }
- updateSubviewClipStatus(clippingRect, index, clippedSoFar)
- child.addOnLayoutChangeListener(childrenLayoutChangeListener)
- if (child is ReactClippingProhibitedView) {
- UiThreadUtil.runOnUiThread {
- if (!child.isShown) {
- ReactSoftExceptionLogger.logSoftException(
- ReactConstants.TAG,
- ReactNoCrashSoftException(
- """
- |Child view has been added to Parent view in which it is clipped and not
- |visible. This is not legal for this particular child view. Child: [${child.id}]
- | $child Parent: [$id] $parent"""
- .trimMargin()))
- }
- }
- }
- }
-
- internal open fun removeViewWithSubviewClippingEnabled(view: View): Unit {
- UiThreadUtil.assertOnUiThread()
- Assertions.assertCondition(_removeClippedSubviews)
- checkNotNull(_clippingRect)
- val children = checkNotNull(allChildren)
- view.removeOnLayoutChangeListener(childrenLayoutChangeListener)
- val index = indexOfChildInAllChildren(view)
- if (children[index]?.parent != null) {
- var clippedSoFar = 0
- for (i in 0 until index) {
- if (children[i]?.parent == null) {
- clippedSoFar++
- }
- }
- removeViewsInLayout(index - clippedSoFar, 1)
- }
- removeFromArray(index)
- }
-
- internal open fun removeAllViewsWithSubviewClippingEnabled(): Unit {
- Assertions.assertCondition(_removeClippedSubviews)
- val children = checkNotNull(allChildren)
- for (i in 0 until allChildrenCount) {
- children[i]?.removeOnLayoutChangeListener(childrenLayoutChangeListener)
- }
- removeAllViewsInLayout()
- allChildrenCount = 0
- }
-
- private fun indexOfChildInAllChildren(child: View): Int {
- val count = allChildrenCount
- val children = checkNotNull(allChildren)
- return (0 until count).firstOrNull { i -> children[i] === child } ?: -1
- }
-
- private fun addInArray(child: View, index: Int) {
- var children = checkNotNull(allChildren)
- val count = allChildrenCount
- val size = children.size
- if (index == count) {
- if (size == count) {
- children = arrayOfNulls(size + ARRAY_CAPACITY_INCREMENT)
- System.arraycopy(children, 0, children, 0, size)
- allChildren = children
- }
- children[allChildrenCount++] = child
- } else if (index < count) {
- if (size == count) {
- children = arrayOfNulls(size + ARRAY_CAPACITY_INCREMENT)
- System.arraycopy(children, 0, children, 0, index)
- System.arraycopy(children, index, children, index + 1, count - index)
- allChildren = children
- } else {
- System.arraycopy(children, index, children, index + 1, count - index)
- }
- children[index] = child
- allChildrenCount++
- } else {
- throw IndexOutOfBoundsException("index=$index count=$count")
- }
- }
-
- private fun removeFromArray(index: Int) {
- val children = checkNotNull(allChildren)
- val count = allChildrenCount
- if (index == count - 1) {
- children[--allChildrenCount] = null
- } else if (index in 0.. getChildAt(i).getTag(R.id.mix_blend_mode) != null }
- }
-
- @VisibleForTesting
- protected open fun getBackgroundColor(): Int =
- BackgroundStyleApplicator.getBackgroundColor(this) ?: DEFAULT_BACKGROUND_COLOR
-
- // TODO: convert to val
- public open fun setOverflow(overflow: String?): Unit {
- _overflow =
- if (overflow == null) {
- Overflow.VISIBLE
- } else {
- Overflow.fromString(overflow) ?: Overflow.VISIBLE
- }
- invalidate()
- }
-
- override fun getOverflow(): String? =
- when (_overflow) {
- Overflow.HIDDEN -> "hidden"
- Overflow.SCROLL -> "scroll"
- Overflow.VISIBLE -> "visible"
- }
-
- override fun setOverflowInset(left: Int, top: Int, right: Int, bottom: Int) {
- if (needsIsolatedLayer() &&
- (_overflowInset.left != left ||
- _overflowInset.top != top ||
- _overflowInset.right != right ||
- _overflowInset.bottom != bottom)) {
- invalidate()
- }
- _overflowInset.set(left, top, right, bottom)
- }
-
- // TODO: this is mutable!
- override fun getOverflowInset(): Rect = _overflowInset
-
- /**
- * Set the background for the view or remove the background. It calls [setBackground(Drawable)] or
- * [setBackgroundDrawable(Drawable)] based on the sdk version.
- *
- * @param drawable [Drawable] The Drawable to use as the background, or null to remove the
- * background
- */
- internal fun updateBackgroundDrawable(drawable: Drawable?): Unit {
- super.setBackground(drawable)
- }
-
- override fun draw(canvas: Canvas) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
- ViewUtil.getUIManagerType(this) == UIManagerType.FABRIC &&
- needsIsolatedLayer()) {
-
- // Check if the view is a stacking context and has children, if it does, do the rendering
- // offscreen and then composite back. This follows the idea of group isolation on blending
- // https://www.w3.org/TR/compositing-1/#isolationblending
- val overflowInset = this.overflowInset
- canvas.saveLayer(
- overflowInset.left.toFloat(),
- overflowInset.top.toFloat(),
- (width + -overflowInset.right).toFloat(),
- (height + -overflowInset.bottom).toFloat(),
- null)
- super.draw(canvas)
- canvas.restore()
- } else {
- super.draw(canvas)
- }
- }
-
- override fun dispatchDraw(canvas: Canvas) {
- if (_overflow != Overflow.VISIBLE || getTag(R.id.filter) != null) {
- BackgroundStyleApplicator.clipToPaddingBox(this, canvas)
- }
- super.dispatchDraw(canvas)
- }
-
- override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
- val drawWithZ = child.elevation > 0
- if (drawWithZ) {
- CanvasUtil.enableZ(canvas, true)
- }
- var mixBlendMode: BlendMode? = null
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && needsIsolatedLayer()) {
- mixBlendMode = child.getTag(R.id.mix_blend_mode) as? BlendMode
- if (mixBlendMode != null) {
- val p = Paint()
- p.blendMode = mixBlendMode
- val overflowInset = this.overflowInset
- canvas.saveLayer(
- overflowInset.left.toFloat(),
- overflowInset.top.toFloat(),
- (width + -overflowInset.right).toFloat(),
- (height + -overflowInset.bottom).toFloat(),
- p)
- }
- }
- val result = super.drawChild(canvas, child, drawingTime)
- if (mixBlendMode != null) {
- canvas.restore()
- }
- if (drawWithZ) {
- CanvasUtil.enableZ(canvas, false)
- }
- return result
- }
-
- public open fun setOpacityIfPossible(opacity: Float): Unit {
- backfaceOpacity = opacity
- setBackfaceVisibilityDependantOpacity()
- }
-
- public open fun setBackfaceVisibility(backfaceVisibility: String?): Unit {
- this.backfaceVisibility = backfaceVisibility
- setBackfaceVisibilityDependantOpacity()
- }
-
- public open fun setBackfaceVisibilityDependantOpacity(): Unit {
- val isBackfaceVisible = backfaceVisibility == "visible"
- if (isBackfaceVisible) {
- alpha = backfaceOpacity
- return
- }
- val rotationX = rotationX
- val rotationY = rotationY
- val isFrontfaceVisible =
- rotationX >= -90f && rotationX < 90f && rotationY >= -90f && rotationY < 90f
- if (isFrontfaceVisible) {
- alpha = backfaceOpacity
- return
- }
- alpha = 0f
- }
-}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt
index aac6e9347b7389..4b6dc1957e26f1 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt
@@ -166,23 +166,23 @@ public open class ReactViewManager : ReactClippingViewManager()
when (hitSlop.type) {
ReadableType.Map -> {
val hitSlopMap = hitSlop.asMap()
- view.hitSlopRect =
+ view.setHitSlopRect(
Rect(
getPixels(hitSlopMap, "left"),
getPixels(hitSlopMap, "top"),
getPixels(hitSlopMap, "right"),
- getPixels(hitSlopMap, "bottom"))
+ getPixels(hitSlopMap, "bottom")))
}
ReadableType.Number -> {
val hitSlopValue = hitSlop.asDouble().dpToPx().toInt()
- view.hitSlopRect = Rect(hitSlopValue, hitSlopValue, hitSlopValue, hitSlopValue)
+ view.setHitSlopRect(Rect(hitSlopValue, hitSlopValue, hitSlopValue, hitSlopValue))
}
- ReadableType.Null -> view.hitSlopRect = null
+ ReadableType.Null -> view.setHitSlopRect(null)
else -> {
FLog.w(ReactConstants.TAG, "Invalid type for 'hitSlop' value ${hitSlop.type}")
- view.hitSlopRect = null
+ view.setHitSlopRect(null)
}
}
}
From 0e25a02185d4b8074405cb1b6f4a768dc6807ec8 Mon Sep 17 00:00:00 2001
From: David Vacca <515103+mdvacca@users.noreply.github.com>
Date: Thu, 19 Sep 2024 20:25:37 -0700
Subject: [PATCH 2/2] Back out "Convert ReactViewManager,
ReactClippingViewManager to Kotlin"
Summary:
Original commit changeset: 09a81c91a1c9
Original Phabricator Diff: D62776270
Differential Revision: D63076764
---
.../ReactAndroid/api/ReactAndroid.api | 9 +-
.../views/view/ReactClippingViewManager.java | 95 ++++
.../views/view/ReactClippingViewManager.kt | 73 ---
.../react/views/view/ReactViewManager.java | 425 ++++++++++++++++++
.../react/views/view/ReactViewManager.kt | 390 ----------------
5 files changed, 522 insertions(+), 470 deletions(-)
create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java
delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt
create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt
diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api
index 7fb74ff9b41bdd..79ae4e24dd0f56 100644
--- a/packages/react-native/ReactAndroid/api/ReactAndroid.api
+++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api
@@ -8371,8 +8371,6 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
}
public class com/facebook/react/views/view/ReactViewManager : com/facebook/react/views/view/ReactClippingViewManager {
- public static final field Companion Lcom/facebook/react/views/view/ReactViewManager$Companion;
- public static final field REACT_CLASS Ljava/lang/String;
public fun ()V
public synthetic fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Landroid/view/View;
public fun createViewInstance (Lcom/facebook/react/uimanager/ThemedReactContext;)Lcom/facebook/react/views/view/ReactViewGroup;
@@ -8383,7 +8381,7 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
public fun nextFocusLeft (Lcom/facebook/react/views/view/ReactViewGroup;I)V
public fun nextFocusRight (Lcom/facebook/react/views/view/ReactViewGroup;I)V
public fun nextFocusUp (Lcom/facebook/react/views/view/ReactViewGroup;I)V
- public synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
+ protected synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/view/ReactViewGroup;)Lcom/facebook/react/views/view/ReactViewGroup;
public synthetic fun receiveCommand (Landroid/view/View;ILcom/facebook/react/bridge/ReadableArray;)V
public synthetic fun receiveCommand (Landroid/view/View;Ljava/lang/String;Lcom/facebook/react/bridge/ReadableArray;)V
@@ -8412,7 +8410,7 @@ public class com/facebook/react/views/view/ReactViewManager : com/facebook/react
public fun setOverflow (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;)V
public fun setPointerEvents (Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;)V
public fun setTVPreferredFocus (Lcom/facebook/react/views/view/ReactViewGroup;Z)V
- public synthetic fun setTransformProperty (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
+ protected synthetic fun setTransformProperty (Landroid/view/View;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
protected fun setTransformProperty (Lcom/facebook/react/views/view/ReactViewGroup;Lcom/facebook/react/bridge/ReadableArray;Lcom/facebook/react/bridge/ReadableArray;)V
}
@@ -8423,9 +8421,6 @@ public class com/facebook/react/views/view/ReactViewManager$$PropsSetter : com/f
public fun setProperty (Lcom/facebook/react/views/view/ReactViewManager;Lcom/facebook/react/views/view/ReactViewGroup;Ljava/lang/String;Ljava/lang/Object;)V
}
-public final class com/facebook/react/views/view/ReactViewManager$Companion {
-}
-
public final class com/facebook/react/views/view/ViewGroupClickEvent : com/facebook/react/uimanager/events/Event {
public fun (I)V
public fun (II)V
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java
new file mode 100644
index 00000000000000..525183053a0ef0
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and 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.view.View;
+import androidx.annotation.Nullable;
+import com.facebook.infer.annotation.Nullsafe;
+import com.facebook.react.bridge.UiThreadUtil;
+import com.facebook.react.uimanager.ViewGroupManager;
+import com.facebook.react.uimanager.annotations.ReactProp;
+
+/**
+ * View manager which handles clipped subviews. Useful for custom views which extends from {@link
+ * com.facebook.react.views.view.ReactViewGroup}
+ */
+@Nullsafe(Nullsafe.Mode.LOCAL)
+public abstract class ReactClippingViewManager
+ extends ViewGroupManager {
+
+ @ReactProp(
+ name = com.facebook.react.uimanager.ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
+ public void setRemoveClippedSubviews(T view, boolean removeClippedSubviews) {
+ UiThreadUtil.assertOnUiThread();
+
+ view.setRemoveClippedSubviews(removeClippedSubviews);
+ }
+
+ @Override
+ public void addView(T parent, View child, int index) {
+ UiThreadUtil.assertOnUiThread();
+
+ boolean removeClippedSubviews = parent.getRemoveClippedSubviews();
+ if (removeClippedSubviews) {
+ parent.addViewWithSubviewClippingEnabled(child, index);
+ } else {
+ parent.addView(child, index);
+ }
+ }
+
+ @Override
+ public int getChildCount(T parent) {
+ boolean removeClippedSubviews = parent.getRemoveClippedSubviews();
+ if (removeClippedSubviews) {
+ return parent.getAllChildrenCount();
+ } else {
+ return parent.getChildCount();
+ }
+ }
+
+ @Override
+ @Nullable
+ public View getChildAt(T parent, int index) {
+ boolean removeClippedSubviews = parent.getRemoveClippedSubviews();
+ if (removeClippedSubviews) {
+ return parent.getChildAtWithSubviewClippingEnabled(index);
+ } else {
+ return parent.getChildAt(index);
+ }
+ }
+
+ @Override
+ public void removeViewAt(T parent, int index) {
+ UiThreadUtil.assertOnUiThread();
+
+ boolean removeClippedSubviews = parent.getRemoveClippedSubviews();
+ if (removeClippedSubviews) {
+ View child = getChildAt(parent, index);
+ if (child != null) {
+ if (child.getParent() != null) {
+ parent.removeView(child);
+ }
+ parent.removeViewWithSubviewClippingEnabled(child);
+ }
+ } else {
+ parent.removeViewAt(index);
+ }
+ }
+
+ @Override
+ public void removeAllViews(T parent) {
+ UiThreadUtil.assertOnUiThread();
+
+ boolean removeClippedSubviews = parent.getRemoveClippedSubviews();
+ if (removeClippedSubviews) {
+ parent.removeAllViewsWithSubviewClippingEnabled();
+ } else {
+ parent.removeAllViews();
+ }
+ }
+}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt
deleted file mode 100644
index d77075ed705586..00000000000000
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactClippingViewManager.kt
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) Meta Platforms, Inc. and 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.view.View
-import com.facebook.react.bridge.UiThreadUtil
-import com.facebook.react.uimanager.ReactClippingViewGroupHelper
-import com.facebook.react.uimanager.ViewGroupManager
-import com.facebook.react.uimanager.annotations.ReactProp
-
-/**
- * View manager which handles clipped subviews. Useful for custom views which extends from
- * [com.facebook.react.views.view.ReactViewGroup]
- */
-public abstract class ReactClippingViewManager : ViewGroupManager() {
-
- @ReactProp(name = ReactClippingViewGroupHelper.PROP_REMOVE_CLIPPED_SUBVIEWS)
- public open fun setRemoveClippedSubviews(view: T, removeClippedSubviews: Boolean) {
- UiThreadUtil.assertOnUiThread()
- view.removeClippedSubviews = removeClippedSubviews
- }
-
- override fun addView(parent: T, child: View, index: Int) {
- UiThreadUtil.assertOnUiThread()
- if (parent.removeClippedSubviews) {
- parent.addViewWithSubviewClippingEnabled(child, index)
- } else {
- parent.addView(child, index)
- }
- }
-
- override fun getChildCount(parent: T): Int =
- if (parent.removeClippedSubviews) {
- parent.allChildrenCount
- } else {
- parent.childCount
- }
-
- override fun getChildAt(parent: T, index: Int): View? =
- if (parent.removeClippedSubviews) {
- parent.getChildAtWithSubviewClippingEnabled(index)
- } else {
- parent.getChildAt(index)
- }
-
- override fun removeViewAt(parent: T, index: Int) {
- UiThreadUtil.assertOnUiThread()
- if (parent.removeClippedSubviews) {
- val child = getChildAt(parent, index) ?: return
- if (child.parent != null) {
- parent.removeView(child)
- } else {
- parent.removeViewWithSubviewClippingEnabled(child)
- }
- } else {
- parent.removeViewAt(index)
- }
- }
-
- override fun removeAllViews(parent: T) {
- UiThreadUtil.assertOnUiThread()
- if (parent.removeClippedSubviews) {
- parent.removeAllViewsWithSubviewClippingEnabled()
- } else {
- parent.removeAllViews()
- }
- }
-}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
new file mode 100644
index 00000000000000..548fe524399cf9
--- /dev/null
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (c) Meta Platforms, Inc. and 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.graphics.Rect;
+import android.view.View;
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+import com.facebook.common.logging.FLog;
+import com.facebook.infer.annotation.Nullsafe;
+import com.facebook.react.bridge.Dynamic;
+import com.facebook.react.bridge.DynamicFromObject;
+import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReadableArray;
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.annotations.VisibleForTesting;
+import com.facebook.react.module.annotations.ReactModule;
+import com.facebook.react.uimanager.BackgroundStyleApplicator;
+import com.facebook.react.uimanager.LengthPercentage;
+import com.facebook.react.uimanager.LengthPercentageType;
+import com.facebook.react.uimanager.PixelUtil;
+import com.facebook.react.uimanager.PointerEvents;
+import com.facebook.react.uimanager.Spacing;
+import com.facebook.react.uimanager.ThemedReactContext;
+import com.facebook.react.uimanager.UIManagerHelper;
+import com.facebook.react.uimanager.ViewProps;
+import com.facebook.react.uimanager.annotations.ReactProp;
+import com.facebook.react.uimanager.annotations.ReactPropGroup;
+import com.facebook.react.uimanager.common.UIManagerType;
+import com.facebook.react.uimanager.common.ViewUtil;
+import com.facebook.react.uimanager.events.EventDispatcher;
+import com.facebook.react.uimanager.style.BackgroundImageLayer;
+import com.facebook.react.uimanager.style.BorderRadiusProp;
+import com.facebook.react.uimanager.style.BorderStyle;
+import com.facebook.react.uimanager.style.LogicalEdge;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/** View manager for AndroidViews (plain React Views). */
+@ReactModule(name = ReactViewManager.REACT_CLASS)
+@Nullsafe(Nullsafe.Mode.LOCAL)
+public class ReactViewManager extends ReactClippingViewManager {
+
+ @VisibleForTesting public static final String REACT_CLASS = ViewProps.VIEW_CLASS_NAME;
+
+ private static final int[] SPACING_TYPES = {
+ Spacing.ALL,
+ Spacing.LEFT,
+ Spacing.RIGHT,
+ Spacing.TOP,
+ Spacing.BOTTOM,
+ Spacing.START,
+ Spacing.END,
+ Spacing.BLOCK,
+ Spacing.BLOCK_END,
+ Spacing.BLOCK_START
+ };
+ private static final int CMD_HOTSPOT_UPDATE = 1;
+ private static final int CMD_SET_PRESSED = 2;
+ private static final String HOTSPOT_UPDATE_KEY = "hotspotUpdate";
+
+ public ReactViewManager() {
+ super();
+
+ setupViewRecycling();
+ }
+
+ @Override
+ protected @Nullable ReactViewGroup prepareToRecycleView(
+ ThemedReactContext reactContext, ReactViewGroup view) {
+ // BaseViewManager
+ ReactViewGroup preparedView = super.prepareToRecycleView(reactContext, view);
+ if (preparedView != null) {
+ preparedView.recycleView();
+ }
+ return view;
+ }
+
+ @ReactProp(name = "accessible")
+ public void setAccessible(ReactViewGroup view, boolean accessible) {
+ view.setFocusable(accessible);
+ }
+
+ @ReactProp(name = "hasTVPreferredFocus")
+ public void setTVPreferredFocus(ReactViewGroup view, boolean hasTVPreferredFocus) {
+ if (hasTVPreferredFocus) {
+ view.setFocusable(true);
+ view.setFocusableInTouchMode(true);
+ view.requestFocus();
+ }
+ }
+
+ @ReactProp(name = ViewProps.BACKGROUND_IMAGE, customType = "BackgroundImage")
+ public void setBackgroundImage(ReactViewGroup view, @Nullable ReadableArray backgroundImage) {
+ if (ViewUtil.getUIManagerType(view) == UIManagerType.FABRIC) {
+ if (backgroundImage != null && backgroundImage.size() > 0) {
+ List backgroundImageLayers = new ArrayList<>(backgroundImage.size());
+ for (int i = 0; i < backgroundImage.size(); i++) {
+ ReadableMap backgroundImageMap = backgroundImage.getMap(i);
+ BackgroundImageLayer layer =
+ new BackgroundImageLayer(backgroundImageMap, view.getContext());
+ backgroundImageLayers.add(layer);
+ }
+ BackgroundStyleApplicator.setBackgroundImage(view, backgroundImageLayers);
+ } else {
+ BackgroundStyleApplicator.setBackgroundImage(view, null);
+ }
+ }
+ }
+
+ @ReactProp(name = "nextFocusDown", defaultInt = View.NO_ID)
+ public void nextFocusDown(ReactViewGroup view, int viewId) {
+ view.setNextFocusDownId(viewId);
+ }
+
+ @ReactProp(name = "nextFocusForward", defaultInt = View.NO_ID)
+ public void nextFocusForward(ReactViewGroup view, int viewId) {
+ view.setNextFocusForwardId(viewId);
+ }
+
+ @ReactProp(name = "nextFocusLeft", defaultInt = View.NO_ID)
+ public void nextFocusLeft(ReactViewGroup view, int viewId) {
+ view.setNextFocusLeftId(viewId);
+ }
+
+ @ReactProp(name = "nextFocusRight", defaultInt = View.NO_ID)
+ public void nextFocusRight(ReactViewGroup view, int viewId) {
+ view.setNextFocusRightId(viewId);
+ }
+
+ @ReactProp(name = "nextFocusUp", defaultInt = View.NO_ID)
+ public void nextFocusUp(ReactViewGroup view, int viewId) {
+ view.setNextFocusUpId(viewId);
+ }
+
+ @ReactPropGroup(
+ names = {
+ ViewProps.BORDER_RADIUS,
+ ViewProps.BORDER_TOP_LEFT_RADIUS,
+ ViewProps.BORDER_TOP_RIGHT_RADIUS,
+ ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
+ ViewProps.BORDER_BOTTOM_LEFT_RADIUS,
+ ViewProps.BORDER_TOP_START_RADIUS,
+ ViewProps.BORDER_TOP_END_RADIUS,
+ ViewProps.BORDER_BOTTOM_START_RADIUS,
+ ViewProps.BORDER_BOTTOM_END_RADIUS,
+ ViewProps.BORDER_END_END_RADIUS,
+ ViewProps.BORDER_END_START_RADIUS,
+ ViewProps.BORDER_START_END_RADIUS,
+ ViewProps.BORDER_START_START_RADIUS,
+ })
+ public void setBorderRadius(ReactViewGroup view, int index, Dynamic rawBorderRadius) {
+ @Nullable LengthPercentage borderRadius = LengthPercentage.setFromDynamic(rawBorderRadius);
+
+ // We do not support percentage border radii on Paper in order to be consistent with iOS (to
+ // avoid developer surprise if it works on one platform but not another).
+ if (ViewUtil.getUIManagerType(view) != UIManagerType.FABRIC
+ && borderRadius != null
+ && borderRadius.getType() == LengthPercentageType.PERCENT) {
+ borderRadius = null;
+ }
+
+ BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], borderRadius);
+ }
+
+ /**
+ * @deprecated Use {@link #setBorderRadius(ReactViewGroup, int, Dynamic)} instead.
+ */
+ @Deprecated(since = "0.75.0", forRemoval = true)
+ public void setBorderRadius(ReactViewGroup view, int index, float borderRadius) {
+ setBorderRadius(view, index, new DynamicFromObject(borderRadius));
+ }
+
+ @ReactProp(name = "borderStyle")
+ public void setBorderStyle(ReactViewGroup view, @Nullable String borderStyle) {
+ @Nullable
+ BorderStyle parsedBorderStyle =
+ borderStyle == null ? null : BorderStyle.fromString(borderStyle);
+ BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle);
+ }
+
+ @ReactProp(name = "hitSlop")
+ public void setHitSlop(final ReactViewGroup view, Dynamic hitSlop) {
+ switch (hitSlop.getType()) {
+ case Map:
+ ReadableMap hitSlopMap = hitSlop.asMap();
+ view.setHitSlopRect(
+ new Rect(
+ hitSlopMap.hasKey("left")
+ ? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("left"))
+ : 0,
+ hitSlopMap.hasKey("top")
+ ? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("top"))
+ : 0,
+ hitSlopMap.hasKey("right")
+ ? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("right"))
+ : 0,
+ hitSlopMap.hasKey("bottom")
+ ? (int) PixelUtil.toPixelFromDIP(hitSlopMap.getDouble("bottom"))
+ : 0));
+ break;
+ case Number:
+ int hitSlopValue = (int) PixelUtil.toPixelFromDIP(hitSlop.asDouble());
+ view.setHitSlopRect(new Rect(hitSlopValue, hitSlopValue, hitSlopValue, hitSlopValue));
+ break;
+ default:
+ FLog.w(ReactConstants.TAG, "Invalid type for 'hitSlop' value " + hitSlop.getType());
+ /* falls through */
+ case Null:
+ view.setHitSlopRect(null);
+ break;
+ }
+ }
+
+ @ReactProp(name = ViewProps.POINTER_EVENTS)
+ public void setPointerEvents(ReactViewGroup view, @Nullable String pointerEventsStr) {
+ view.setPointerEvents(PointerEvents.parsePointerEvents(pointerEventsStr));
+ }
+
+ @ReactProp(name = "nativeBackgroundAndroid")
+ public void setNativeBackground(ReactViewGroup view, @Nullable ReadableMap bg) {
+ view.setTranslucentBackgroundDrawable(
+ bg == null
+ ? null
+ : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
+ }
+
+ @ReactProp(name = "nativeForegroundAndroid")
+ public void setNativeForeground(ReactViewGroup view, @Nullable ReadableMap fg) {
+ view.setForeground(
+ fg == null
+ ? null
+ : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), fg));
+ }
+
+ @ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
+ public void setNeedsOffscreenAlphaCompositing(
+ ReactViewGroup view, boolean needsOffscreenAlphaCompositing) {
+ view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing);
+ }
+
+ @ReactPropGroup(
+ names = {
+ ViewProps.BORDER_WIDTH,
+ ViewProps.BORDER_LEFT_WIDTH,
+ ViewProps.BORDER_RIGHT_WIDTH,
+ ViewProps.BORDER_TOP_WIDTH,
+ ViewProps.BORDER_BOTTOM_WIDTH,
+ ViewProps.BORDER_START_WIDTH,
+ ViewProps.BORDER_END_WIDTH,
+ },
+ defaultFloat = Float.NaN)
+ public void setBorderWidth(ReactViewGroup view, int index, float width) {
+ BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width);
+ }
+
+ @ReactPropGroup(
+ names = {
+ ViewProps.BORDER_COLOR,
+ ViewProps.BORDER_LEFT_COLOR,
+ ViewProps.BORDER_RIGHT_COLOR,
+ ViewProps.BORDER_TOP_COLOR,
+ ViewProps.BORDER_BOTTOM_COLOR,
+ ViewProps.BORDER_START_COLOR,
+ ViewProps.BORDER_END_COLOR,
+ ViewProps.BORDER_BLOCK_COLOR,
+ ViewProps.BORDER_BLOCK_END_COLOR,
+ ViewProps.BORDER_BLOCK_START_COLOR
+ },
+ customType = "Color")
+ public void setBorderColor(ReactViewGroup view, int index, @Nullable Integer color) {
+ BackgroundStyleApplicator.setBorderColor(
+ view, LogicalEdge.fromSpacingType(SPACING_TYPES[index]), color);
+ }
+
+ @ReactProp(name = ViewProps.COLLAPSABLE)
+ public void setCollapsable(ReactViewGroup view, boolean collapsable) {
+ // no-op: it's here only so that "collapsable" property is exported to JS. The value is actually
+ // handled in NativeViewHierarchyOptimizer
+ }
+
+ @ReactProp(name = ViewProps.COLLAPSABLE_CHILDREN)
+ public void setCollapsableChildren(ReactViewGroup view, boolean collapsableChildren) {
+ // no-op: it's here only so that "collapsableChildren" property is exported to JS.
+ }
+
+ @ReactProp(name = "focusable")
+ public void setFocusable(final ReactViewGroup view, boolean focusable) {
+ if (focusable) {
+ view.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final EventDispatcher mEventDispatcher =
+ UIManagerHelper.getEventDispatcherForReactTag(
+ (ReactContext) view.getContext(), view.getId());
+ if (mEventDispatcher == null) {
+ return;
+ }
+ mEventDispatcher.dispatchEvent(
+ new ViewGroupClickEvent(
+ UIManagerHelper.getSurfaceId(view.getContext()), view.getId()));
+ }
+ });
+
+ // Clickable elements are focusable. On API 26, this is taken care by setClickable.
+ // Explicitly calling setFocusable here for backward compatibility.
+ view.setFocusable(true /*isFocusable*/);
+ } else {
+ view.setOnClickListener(null);
+ view.setClickable(false);
+ // Don't set view.setFocusable(false) because we might still want it to be focusable for
+ // accessibility reasons
+ }
+ }
+
+ @ReactProp(name = ViewProps.OVERFLOW)
+ public void setOverflow(ReactViewGroup view, String overflow) {
+ view.setOverflow(overflow);
+ }
+
+ @ReactProp(name = "backfaceVisibility")
+ public void setBackfaceVisibility(ReactViewGroup view, String backfaceVisibility) {
+ view.setBackfaceVisibility(backfaceVisibility);
+ }
+
+ @Override
+ public void setOpacity(ReactViewGroup view, float opacity) {
+ view.setOpacityIfPossible(opacity);
+ }
+
+ @Override
+ protected void setTransformProperty(
+ ReactViewGroup view,
+ @Nullable ReadableArray transforms,
+ @Nullable ReadableArray transformOrigin) {
+ super.setTransformProperty(view, transforms, transformOrigin);
+ view.setBackfaceVisibilityDependantOpacity();
+ }
+
+ @ReactProp(name = ViewProps.BOX_SHADOW, customType = "BoxShadow")
+ public void setBoxShadow(ReactViewGroup view, @Nullable ReadableArray shadows) {
+ BackgroundStyleApplicator.setBoxShadow(view, shadows);
+ }
+
+ @Override
+ public void setBackgroundColor(ReactViewGroup view, @ColorInt int backgroundColor) {
+ BackgroundStyleApplicator.setBackgroundColor(view, backgroundColor);
+ }
+
+ @Override
+ public String getName() {
+ return REACT_CLASS;
+ }
+
+ @Override
+ public ReactViewGroup createViewInstance(ThemedReactContext context) {
+ return new ReactViewGroup(context);
+ }
+
+ @Override
+ public Map getCommandsMap() {
+ return MapBuilder.of(HOTSPOT_UPDATE_KEY, CMD_HOTSPOT_UPDATE, "setPressed", CMD_SET_PRESSED);
+ }
+
+ @Override
+ public void receiveCommand(ReactViewGroup root, int commandId, @Nullable ReadableArray args) {
+ switch (commandId) {
+ case CMD_HOTSPOT_UPDATE:
+ {
+ handleHotspotUpdate(root, args);
+ break;
+ }
+ case CMD_SET_PRESSED:
+ {
+ handleSetPressed(root, args);
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void receiveCommand(ReactViewGroup root, String commandId, @Nullable ReadableArray args) {
+ switch (commandId) {
+ case HOTSPOT_UPDATE_KEY:
+ {
+ handleHotspotUpdate(root, args);
+ break;
+ }
+ case "setPressed":
+ {
+ handleSetPressed(root, args);
+ break;
+ }
+ }
+ }
+
+ private void handleSetPressed(ReactViewGroup root, @Nullable ReadableArray args) {
+ if (args == null || args.size() != 1) {
+ throw new JSApplicationIllegalArgumentException(
+ "Illegal number of arguments for 'setPressed' command");
+ }
+ root.setPressed(args.getBoolean(0));
+ }
+
+ private void handleHotspotUpdate(ReactViewGroup root, @Nullable ReadableArray args) {
+ if (args == null || args.size() != 2) {
+ throw new JSApplicationIllegalArgumentException(
+ "Illegal number of arguments for 'updateHotspot' command");
+ }
+
+ float x = PixelUtil.toPixelFromDIP(args.getDouble(0));
+ float y = PixelUtil.toPixelFromDIP(args.getDouble(1));
+ root.drawableHotspotChanged(x, y);
+ }
+}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt
deleted file mode 100644
index 4b6dc1957e26f1..00000000000000
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewManager.kt
+++ /dev/null
@@ -1,390 +0,0 @@
-/*
- * Copyright (c) Meta Platforms, Inc. and 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.graphics.Rect
-import android.view.View
-import androidx.annotation.ColorInt
-import com.facebook.common.logging.FLog
-import com.facebook.react.bridge.Dynamic
-import com.facebook.react.bridge.DynamicFromObject
-import com.facebook.react.bridge.JSApplicationIllegalArgumentException
-import com.facebook.react.bridge.ReactContext
-import com.facebook.react.bridge.ReadableArray
-import com.facebook.react.bridge.ReadableMap
-import com.facebook.react.bridge.ReadableType
-import com.facebook.react.common.ReactConstants
-import com.facebook.react.common.annotations.UnstableReactNativeAPI
-import com.facebook.react.module.annotations.ReactModule
-import com.facebook.react.uimanager.BackgroundStyleApplicator
-import com.facebook.react.uimanager.LengthPercentage.Companion.setFromDynamic
-import com.facebook.react.uimanager.LengthPercentageType
-import com.facebook.react.uimanager.PixelUtil.dpToPx
-import com.facebook.react.uimanager.PointerEvents.Companion.parsePointerEvents
-import com.facebook.react.uimanager.Spacing
-import com.facebook.react.uimanager.ThemedReactContext
-import com.facebook.react.uimanager.UIManagerHelper
-import com.facebook.react.uimanager.ViewProps
-import com.facebook.react.uimanager.annotations.ReactProp
-import com.facebook.react.uimanager.annotations.ReactPropGroup
-import com.facebook.react.uimanager.common.UIManagerType
-import com.facebook.react.uimanager.common.ViewUtil
-import com.facebook.react.uimanager.style.BackgroundImageLayer
-import com.facebook.react.uimanager.style.BorderRadiusProp
-import com.facebook.react.uimanager.style.BorderStyle
-import com.facebook.react.uimanager.style.BorderStyle.Companion.fromString
-import com.facebook.react.uimanager.style.LogicalEdge
-import com.facebook.react.uimanager.style.LogicalEdge.Companion.fromSpacingType
-
-/** View manager for AndroidViews (plain React Views). */
-@ReactModule(name = ReactViewManager.REACT_CLASS)
-public open class ReactViewManager : ReactClippingViewManager() {
-
- init {
- setupViewRecycling()
- }
-
- override fun prepareToRecycleView(
- reactContext: ThemedReactContext,
- view: ReactViewGroup
- ): ReactViewGroup {
- // BaseViewManager
- super.prepareToRecycleView(reactContext, view)?.recycleView()
- return view
- }
-
- @ReactProp(name = "accessible")
- public open fun setAccessible(view: ReactViewGroup, accessible: Boolean): Unit {
- view.isFocusable = accessible
- }
-
- @ReactProp(name = "hasTVPreferredFocus")
- public open fun setTVPreferredFocus(view: ReactViewGroup, hasTVPreferredFocus: Boolean): Unit {
- if (hasTVPreferredFocus) {
- view.isFocusable = true
- view.isFocusableInTouchMode = true
- view.requestFocus()
- }
- }
-
- @OptIn(UnstableReactNativeAPI::class)
- @ReactProp(name = ViewProps.BACKGROUND_IMAGE, customType = "BackgroundImage")
- public open fun setBackgroundImage(view: ReactViewGroup, backgroundImage: ReadableArray?): Unit {
- if (ViewUtil.getUIManagerType(view) == UIManagerType.FABRIC) {
- val size = backgroundImage?.size()
- if (size != null && size > 0) {
- val backgroundImageLayers = ArrayList(size)
- repeat(size) { i ->
- backgroundImageLayers.add(BackgroundImageLayer(backgroundImage.getMap(i), view.context))
- }
- BackgroundStyleApplicator.setBackgroundImage(view, backgroundImageLayers)
- } else {
- BackgroundStyleApplicator.setBackgroundImage(view, null)
- }
- }
- }
-
- @ReactProp(name = "nextFocusDown", defaultInt = View.NO_ID)
- public open fun nextFocusDown(view: ReactViewGroup, viewId: Int): Unit {
- view.nextFocusDownId = viewId
- }
-
- @ReactProp(name = "nextFocusForward", defaultInt = View.NO_ID)
- public open fun nextFocusForward(view: ReactViewGroup, viewId: Int): Unit {
- view.nextFocusForwardId = viewId
- }
-
- @ReactProp(name = "nextFocusLeft", defaultInt = View.NO_ID)
- public open fun nextFocusLeft(view: ReactViewGroup, viewId: Int): Unit {
- view.nextFocusLeftId = viewId
- }
-
- @ReactProp(name = "nextFocusRight", defaultInt = View.NO_ID)
- public open fun nextFocusRight(view: ReactViewGroup, viewId: Int): Unit {
- view.nextFocusRightId = viewId
- }
-
- @ReactProp(name = "nextFocusUp", defaultInt = View.NO_ID)
- public open fun nextFocusUp(view: ReactViewGroup, viewId: Int): Unit {
- view.nextFocusUpId = viewId
- }
-
- @ReactPropGroup(
- names =
- [
- ViewProps.BORDER_RADIUS,
- ViewProps.BORDER_TOP_LEFT_RADIUS,
- ViewProps.BORDER_TOP_RIGHT_RADIUS,
- ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
- ViewProps.BORDER_BOTTOM_LEFT_RADIUS,
- ViewProps.BORDER_TOP_START_RADIUS,
- ViewProps.BORDER_TOP_END_RADIUS,
- ViewProps.BORDER_BOTTOM_START_RADIUS,
- ViewProps.BORDER_BOTTOM_END_RADIUS,
- ViewProps.BORDER_END_END_RADIUS,
- ViewProps.BORDER_END_START_RADIUS,
- ViewProps.BORDER_START_END_RADIUS,
- ViewProps.BORDER_START_START_RADIUS])
- public open fun setBorderRadius(
- view: ReactViewGroup,
- index: Int,
- rawBorderRadius: Dynamic
- ): Unit {
- var borderRadius = setFromDynamic(rawBorderRadius)
-
- // We do not support percentage border radii on Paper in order to be consistent with iOS (to
- // avoid developer surprise if it works on one platform but not another).
- if (ViewUtil.getUIManagerType(view) != UIManagerType.FABRIC &&
- borderRadius?.type == LengthPercentageType.PERCENT) {
- borderRadius = null
- }
- BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], borderRadius)
- }
-
- @Deprecated(
- "Use setBorderRadius(ReactViewGroup, int, Dynamic) instead.",
- ReplaceWith(
- "setBorderRadius(view, index, DynamicFromObject(borderRadius))",
- "com.facebook.react.bridge.DynamicFromObject.DynamicFromObject"))
- public open fun setBorderRadius(view: ReactViewGroup, index: Int, borderRadius: Float): Unit {
- this.setBorderRadius(view, index, DynamicFromObject(borderRadius))
- }
-
- @ReactProp(name = "borderStyle")
- public open fun setBorderStyle(view: ReactViewGroup, borderStyle: String?): Unit {
- val parsedBorderStyle = borderStyle?.let { BorderStyle.fromString(it) }
- BackgroundStyleApplicator.setBorderStyle(view, parsedBorderStyle)
- }
-
- @ReactProp(name = "hitSlop")
- public open fun setHitSlop(view: ReactViewGroup, hitSlop: Dynamic): Unit {
- when (hitSlop.type) {
- ReadableType.Map -> {
- val hitSlopMap = hitSlop.asMap()
- view.setHitSlopRect(
- Rect(
- getPixels(hitSlopMap, "left"),
- getPixels(hitSlopMap, "top"),
- getPixels(hitSlopMap, "right"),
- getPixels(hitSlopMap, "bottom")))
- }
-
- ReadableType.Number -> {
- val hitSlopValue = hitSlop.asDouble().dpToPx().toInt()
- view.setHitSlopRect(Rect(hitSlopValue, hitSlopValue, hitSlopValue, hitSlopValue))
- }
-
- ReadableType.Null -> view.setHitSlopRect(null)
- else -> {
- FLog.w(ReactConstants.TAG, "Invalid type for 'hitSlop' value ${hitSlop.type}")
- view.setHitSlopRect(null)
- }
- }
- }
-
- private fun getPixels(map: ReadableMap, key: String): Int =
- if (map.hasKey(key)) {
- map.getDouble(key).dpToPx().toInt()
- } else {
- 0
- }
-
- @ReactProp(name = ViewProps.POINTER_EVENTS)
- public open fun setPointerEvents(view: ReactViewGroup, pointerEventsStr: String?): Unit {
- view.setPointerEvents(parsePointerEvents(pointerEventsStr))
- }
-
- @ReactProp(name = "nativeBackgroundAndroid")
- public open fun setNativeBackground(view: ReactViewGroup, background: ReadableMap?): Unit {
- val translucentBg =
- background?.let { ReactDrawableHelper.createDrawableFromJSDescription(view.context, it) }
- BackgroundStyleApplicator.setFeedbackUnderlay(view, translucentBg)
- }
-
- @ReactProp(name = "nativeForegroundAndroid")
- public open fun setNativeForeground(view: ReactViewGroup, foreground: ReadableMap?): Unit {
- view.foreground =
- foreground?.let { ReactDrawableHelper.createDrawableFromJSDescription(view.context, it) }
- }
-
- @ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
- public open fun setNeedsOffscreenAlphaCompositing(
- view: ReactViewGroup,
- needsOffscreenAlphaCompositing: Boolean
- ): Unit {
- view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing)
- }
-
- @ReactPropGroup(
- names =
- [
- ViewProps.BORDER_WIDTH,
- ViewProps.BORDER_LEFT_WIDTH,
- ViewProps.BORDER_RIGHT_WIDTH,
- ViewProps.BORDER_TOP_WIDTH,
- ViewProps.BORDER_BOTTOM_WIDTH,
- ViewProps.BORDER_START_WIDTH,
- ViewProps.BORDER_END_WIDTH],
- defaultFloat = Float.NaN)
- public open fun setBorderWidth(view: ReactViewGroup, index: Int, width: Float): Unit {
- BackgroundStyleApplicator.setBorderWidth(view, LogicalEdge.values()[index], width)
- }
-
- @ReactPropGroup(
- names =
- [
- ViewProps.BORDER_COLOR,
- ViewProps.BORDER_LEFT_COLOR,
- ViewProps.BORDER_RIGHT_COLOR,
- ViewProps.BORDER_TOP_COLOR,
- ViewProps.BORDER_BOTTOM_COLOR,
- ViewProps.BORDER_START_COLOR,
- ViewProps.BORDER_END_COLOR,
- ViewProps.BORDER_BLOCK_COLOR,
- ViewProps.BORDER_BLOCK_END_COLOR,
- ViewProps.BORDER_BLOCK_START_COLOR],
- customType = "Color")
- public open fun setBorderColor(view: ReactViewGroup, index: Int, color: Int?): Unit {
- BackgroundStyleApplicator.setBorderColor(
- view, LogicalEdge.fromSpacingType(SPACING_TYPES[index]), color)
- }
-
- @ReactProp(name = ViewProps.COLLAPSABLE)
- @Suppress("UNUSED_PARAMETER")
- public open fun setCollapsable(view: ReactViewGroup?, collapsable: Boolean): Unit {
- // no-op: it's here only so that "collapsable" property is exported to JS. The value is actually
- // handled in NativeViewHierarchyOptimizer
- }
-
- @ReactProp(name = ViewProps.COLLAPSABLE_CHILDREN)
- @Suppress("UNUSED_PARAMETER")
- public open fun setCollapsableChildren(
- view: ReactViewGroup?,
- collapsableChildren: Boolean
- ): Unit {
- // no-op: it's here only so that "collapsableChildren" property is exported to JS.
- }
-
- @ReactProp(name = "focusable")
- public open fun setFocusable(view: ReactViewGroup, focusable: Boolean): Unit {
- if (focusable) {
- view.setOnClickListener {
- val eventDispatcher =
- UIManagerHelper.getEventDispatcherForReactTag((view.context as ReactContext), view.id)
- eventDispatcher?.dispatchEvent(
- ViewGroupClickEvent(UIManagerHelper.getSurfaceId(view.context), view.id))
- }
-
- // Clickable elements are focusable. On API 26, this is taken care by setClickable.
- // Explicitly calling setFocusable here for backward compatibility.
- view.isFocusable = true
- } else {
- view.setOnClickListener(null)
- view.isClickable = false
- // Don't set view.setFocusable(false) because we might still want it to be focusable for
- // accessibility reasons
- }
- }
-
- @ReactProp(name = ViewProps.OVERFLOW)
- public open fun setOverflow(view: ReactViewGroup, overflow: String): Unit {
- view.overflow = overflow
- }
-
- @ReactProp(name = "backfaceVisibility")
- public open fun setBackfaceVisibility(view: ReactViewGroup, backfaceVisibility: String): Unit {
- view.setBackfaceVisibility(backfaceVisibility)
- }
-
- override fun setOpacity(view: ReactViewGroup, opacity: Float) {
- view.setOpacityIfPossible(opacity)
- }
-
- override fun setTransformProperty(
- view: ReactViewGroup,
- transforms: ReadableArray?,
- transformOrigin: ReadableArray?
- ) {
- super.setTransformProperty(view, transforms, transformOrigin)
- view.setBackfaceVisibilityDependantOpacity()
- }
-
- @ReactProp(name = ViewProps.BOX_SHADOW, customType = "BoxShadow")
- public open fun setBoxShadow(view: ReactViewGroup, shadows: ReadableArray?): Unit {
- BackgroundStyleApplicator.setBoxShadow(view, shadows)
- }
-
- override fun setBackgroundColor(view: ReactViewGroup, @ColorInt backgroundColor: Int) {
- BackgroundStyleApplicator.setBackgroundColor(view, backgroundColor)
- }
-
- override fun getName(): String = REACT_CLASS
-
- public override fun createViewInstance(context: ThemedReactContext): ReactViewGroup =
- ReactViewGroup(context)
-
- override fun getCommandsMap(): Map =
- mapOf(
- HOTSPOT_UPDATE_KEY to CMD_HOTSPOT_UPDATE,
- "setPressed" to CMD_SET_PRESSED,
- )
-
- @Deprecated("Deprecated in ViewManager")
- override fun receiveCommand(root: ReactViewGroup, commandId: Int, args: ReadableArray?) {
- when (commandId) {
- CMD_HOTSPOT_UPDATE -> handleHotspotUpdate(root, args)
- CMD_SET_PRESSED -> handleSetPressed(root, args)
- else -> {}
- }
- }
-
- override fun receiveCommand(root: ReactViewGroup, commandId: String, args: ReadableArray?) {
- when (commandId) {
- HOTSPOT_UPDATE_KEY -> handleHotspotUpdate(root, args)
- "setPressed" -> handleSetPressed(root, args)
- else -> {}
- }
- }
-
- private fun handleSetPressed(root: ReactViewGroup, args: ReadableArray?) {
- if (args?.size() != 1) {
- throw JSApplicationIllegalArgumentException(
- "Illegal number of arguments for 'setPressed' command")
- }
- root.isPressed = args.getBoolean(0)
- }
-
- private fun handleHotspotUpdate(root: ReactViewGroup, args: ReadableArray?) {
- if (args?.size() != 2) {
- throw JSApplicationIllegalArgumentException(
- "Illegal number of arguments for 'updateHotspot' command")
- }
- val x = args.getDouble(0).dpToPx()
- val y = args.getDouble(1).dpToPx()
- root.drawableHotspotChanged(x, y)
- }
-
- public companion object {
- public const val REACT_CLASS: String = ViewProps.VIEW_CLASS_NAME
- private val SPACING_TYPES =
- intArrayOf(
- Spacing.ALL,
- Spacing.LEFT,
- Spacing.RIGHT,
- Spacing.TOP,
- Spacing.BOTTOM,
- Spacing.START,
- Spacing.END,
- Spacing.BLOCK,
- Spacing.BLOCK_END,
- Spacing.BLOCK_START)
- private const val CMD_HOTSPOT_UPDATE = 1
- private const val CMD_SET_PRESSED = 2
- private const val HOTSPOT_UPDATE_KEY = "hotspotUpdate"
- }
-}