Skip to content

Commit e16faca

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Propagate layout direction to Android Views and Drawables
Summary: Right now we use layout direction determined by I18NManager to influence the root Yoga layout direction. Individual views may have a different resolved layout direction (e.g. due to `direction` style prop), and even though we don't rely on Android layout props, Android components still need to inherit or know the right layout direction to still do correct drawing. Example of this was scrollbar showing up on the left, instead of right in RTL, as soon as ScrollView knew it was in RTL. This has potential to change a good amount of behavior, so this is under QE. Changelog: [Android][Fixed] - Propagate layout direction to Android Views and Drawables Reviewed By: joevilches Differential Revision: D57248417 fbshipit-source-id: 4bcdf2b23277ff926a796b8377df08d49c7b914c
1 parent 355ca28 commit e16faca

27 files changed

+216
-79
lines changed

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2790,7 +2790,7 @@ public class com/facebook/react/fabric/mounting/SurfaceMountingManager {
27902790
public fun setJSResponder (IIZ)V
27912791
public fun stopSurface ()V
27922792
public fun updateEventEmitter (ILcom/facebook/react/fabric/events/EventEmitterWrapper;)V
2793-
public fun updateLayout (IIIIIII)V
2793+
public fun updateLayout (IIIIIIII)V
27942794
public fun updateOverflowInset (IIIII)V
27952795
public fun updatePadding (IIIII)V
27962796
public fun updateProps (ILcom/facebook/react/bridge/ReadableMap;)V
@@ -5527,12 +5527,11 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro
55275527
public fun getComputedBorderRadius ()Lcom/facebook/react/uimanager/style/ComputedBorderRadius;
55285528
public fun getDirectionAwareBorderInsets ()Landroid/graphics/RectF;
55295529
public fun getFullBorderWidth ()F
5530+
public fun getLayoutDirection ()I
55305531
public fun getOpacity ()I
55315532
public fun getOutline (Landroid/graphics/Outline;)V
5532-
public fun getResolvedLayoutDirection ()I
55335533
public fun hasRoundedBorders ()Z
55345534
protected fun onBoundsChange (Landroid/graphics/Rect;)V
5535-
public fun onResolvedLayoutDirectionChanged (I)Z
55365535
public fun paddingBoxPath ()Landroid/graphics/Path;
55375536
public fun setAlpha (I)V
55385537
public fun setBorderColor (IFF)V
@@ -5542,9 +5541,9 @@ public class com/facebook/react/uimanager/drawable/CSSBackgroundDrawable : andro
55425541
public fun setBorderWidth (IF)V
55435542
public fun setColor (I)V
55445543
public fun setColorFilter (Landroid/graphics/ColorFilter;)V
5544+
public fun setLayoutDirectionOverride (I)V
55455545
public fun setRadius (F)V
55465546
public fun setRadius (FI)V
5547-
public fun setResolvedLayoutDirection (I)Z
55485547
}
55495548

55505549
public abstract interface class com/facebook/react/uimanager/events/BatchEventDispatchedListener {
@@ -6633,6 +6632,7 @@ public class com/facebook/react/views/scroll/OnScrollDispatchHelper {
66336632

66346633
public class com/facebook/react/views/scroll/ReactHorizontalScrollContainerView : com/facebook/react/views/view/ReactViewGroup {
66356634
public fun <init> (Landroid/content/Context;)V
6635+
public fun getLayoutDirection ()I
66366636
protected fun onLayout (ZIIII)V
66376637
public fun setRemoveClippedSubviews (Z)V
66386638
}
@@ -7927,7 +7927,6 @@ public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGro
79277927
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
79287928
protected fun onLayout (ZIIII)V
79297929
protected fun onMeasure (II)V
7930-
public fun onRtlPropertiesChanged (I)V
79317930
protected fun onSizeChanged (IIII)V
79327931
public fun onTouchEvent (Landroid/view/MotionEvent;)Z
79337932
public fun removeView (Landroid/view/View;)V

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -938,7 +938,14 @@ public void sendAccessibilityEvent(int reactTag, int eventType) {
938938

939939
@UiThread
940940
public void updateLayout(
941-
int reactTag, int parentTag, int x, int y, int width, int height, int displayType) {
941+
int reactTag,
942+
int parentTag,
943+
int x,
944+
int y,
945+
int width,
946+
int height,
947+
int displayType,
948+
int layoutDirection) {
942949
if (isStopped()) {
943950
return;
944951
}
@@ -954,6 +961,13 @@ public void updateLayout(
954961
throw new IllegalStateException("Unable to find View for tag: " + reactTag);
955962
}
956963

964+
if (ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
965+
viewToUpdate.setLayoutDirection(
966+
layoutDirection == 1
967+
? View.LAYOUT_DIRECTION_LTR
968+
: layoutDirection == 2 ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_INHERIT);
969+
}
970+
957971
viewToUpdate.measure(
958972
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
959973
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/mountitems/IntBufferBatchMountItem.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.facebook.react.fabric.events.EventEmitterWrapper;
2121
import com.facebook.react.fabric.mounting.MountingManager;
2222
import com.facebook.react.fabric.mounting.SurfaceMountingManager;
23+
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
2324
import com.facebook.react.uimanager.StateWrapper;
2425
import com.facebook.systrace.Systrace;
2526

@@ -150,9 +151,14 @@ public void execute(MountingManager mountingManager) {
150151
int height = mIntBuffer[i++];
151152
int displayType = mIntBuffer[i++];
152153

153-
surfaceMountingManager.updateLayout(
154-
reactTag, parentTag, x, y, width, height, displayType);
155-
154+
if (ReactNativeFeatureFlags.setAndroidLayoutDirection()) {
155+
int layoutDirection = mIntBuffer[i++];
156+
surfaceMountingManager.updateLayout(
157+
reactTag, parentTag, x, y, width, height, displayType, layoutDirection);
158+
} else {
159+
surfaceMountingManager.updateLayout(
160+
reactTag, parentTag, x, y, width, height, displayType, 0);
161+
}
156162
} else if (type == INSTRUCTION_UPDATE_PADDING) {
157163
surfaceMountingManager.updatePadding(
158164
mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++], mIntBuffer[i++]);

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<fbc717f14e387b92aea7cb71202cce95>>
7+
* @generated SignedSource<<0ae7be647ca12c3efcc34f6098155550>>
88
*/
99

1010
/**
@@ -130,6 +130,12 @@ public object ReactNativeFeatureFlags {
130130
@JvmStatic
131131
public fun preventDoubleTextMeasure(): Boolean = accessor.preventDoubleTextMeasure()
132132

133+
/**
134+
* Propagate layout direction to Android views.
135+
*/
136+
@JvmStatic
137+
public fun setAndroidLayoutDirection(): Boolean = accessor.setAndroidLayoutDirection()
138+
133139
/**
134140
* When enabled, it uses the modern fork of RuntimeScheduler that allows scheduling tasks with priorities from any thread.
135141
*/

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<cfbb7879b105079fe76e37b0683f7083>>
7+
* @generated SignedSource<<5323fb8be9ec7ee6ac43d7f01bca020e>>
88
*/
99

1010
/**
@@ -37,6 +37,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
3737
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
3838
private var lazyAnimationCallbacksCache: Boolean? = null
3939
private var preventDoubleTextMeasureCache: Boolean? = null
40+
private var setAndroidLayoutDirectionCache: Boolean? = null
4041
private var useModernRuntimeSchedulerCache: Boolean? = null
4142
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
4243
private var useStateAlignmentMechanismCache: Boolean? = null
@@ -194,6 +195,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso
194195
return cached
195196
}
196197

198+
override fun setAndroidLayoutDirection(): Boolean {
199+
var cached = setAndroidLayoutDirectionCache
200+
if (cached == null) {
201+
cached = ReactNativeFeatureFlagsCxxInterop.setAndroidLayoutDirection()
202+
setAndroidLayoutDirectionCache = cached
203+
}
204+
return cached
205+
}
206+
197207
override fun useModernRuntimeScheduler(): Boolean {
198208
var cached = useModernRuntimeSchedulerCache
199209
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<77b8e93114ed0cc2b6bdd9ee46e162c7>>
7+
* @generated SignedSource<<e68ae1e7b4f0d7714722fb7b442c66c9>>
88
*/
99

1010
/**
@@ -62,6 +62,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
6262

6363
@DoNotStrip @JvmStatic public external fun preventDoubleTextMeasure(): Boolean
6464

65+
@DoNotStrip @JvmStatic public external fun setAndroidLayoutDirection(): Boolean
66+
6567
@DoNotStrip @JvmStatic public external fun useModernRuntimeScheduler(): Boolean
6668

6769
@DoNotStrip @JvmStatic public external fun useNativeViewConfigsInBridgelessMode(): Boolean

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<13a5d73dea53b8d4f16946cebc49e684>>
7+
* @generated SignedSource<<4f50dab251c41380a14506975355e49d>>
88
*/
99

1010
/**
@@ -57,6 +57,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
5757

5858
override fun preventDoubleTextMeasure(): Boolean = false
5959

60+
override fun setAndroidLayoutDirection(): Boolean = false
61+
6062
override fun useModernRuntimeScheduler(): Boolean = false
6163

6264
override fun useNativeViewConfigsInBridgelessMode(): Boolean = false

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<67f1828f6a4cf9b2cf808be12d3fb06b>>
7+
* @generated SignedSource<<00a695402781a9f3c45ba0594a785b57>>
88
*/
99

1010
/**
@@ -41,6 +41,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
4141
private var inspectorEnableModernCDPRegistryCache: Boolean? = null
4242
private var lazyAnimationCallbacksCache: Boolean? = null
4343
private var preventDoubleTextMeasureCache: Boolean? = null
44+
private var setAndroidLayoutDirectionCache: Boolean? = null
4445
private var useModernRuntimeSchedulerCache: Boolean? = null
4546
private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null
4647
private var useStateAlignmentMechanismCache: Boolean? = null
@@ -215,6 +216,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces
215216
return cached
216217
}
217218

219+
override fun setAndroidLayoutDirection(): Boolean {
220+
var cached = setAndroidLayoutDirectionCache
221+
if (cached == null) {
222+
cached = currentProvider.setAndroidLayoutDirection()
223+
accessedFeatureFlags.add("setAndroidLayoutDirection")
224+
setAndroidLayoutDirectionCache = cached
225+
}
226+
return cached
227+
}
228+
218229
override fun useModernRuntimeScheduler(): Boolean {
219230
var cached = useModernRuntimeSchedulerCache
220231
if (cached == null) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*
7-
* @generated SignedSource<<a6a65fe7d9b04671de407a03e1feb161>>
7+
* @generated SignedSource<<e63ccfcb6e8d1db840cefb3d0a6df6cd>>
88
*/
99

1010
/**
@@ -57,6 +57,8 @@ public interface ReactNativeFeatureFlagsProvider {
5757

5858
@DoNotStrip public fun preventDoubleTextMeasure(): Boolean
5959

60+
@DoNotStrip public fun setAndroidLayoutDirection(): Boolean
61+
6062
@DoNotStrip public fun useModernRuntimeScheduler(): Boolean
6163

6264
@DoNotStrip public fun useNativeViewConfigsInBridgelessMode(): Boolean

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/drawable/CSSBackgroundDrawable.java

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package com.facebook.react.uimanager.drawable;
99

10+
import android.annotation.SuppressLint;
1011
import android.content.Context;
1112
import android.graphics.Canvas;
1213
import android.graphics.Color;
@@ -119,7 +120,9 @@ private enum BorderStyle {
119120
private BorderRadiusStyle mBorderRadius = new BorderRadiusStyle();
120121
private ComputedBorderRadius mComputedBorderRadius = new ComputedBorderRadius();
121122
private final Context mContext;
122-
private int mLayoutDirection;
123+
124+
// Should be removed after migrating to Android layout direction.
125+
private int mLayoutDirectionOverride = -1;
123126

124127
public CSSBackgroundDrawable(Context context) {
125128
mContext = context;
@@ -163,6 +166,19 @@ public void setColorFilter(ColorFilter cf) {
163166
// do nothing
164167
}
165168

169+
@Deprecated
170+
public void setLayoutDirectionOverride(int layoutDirection) {
171+
if (mLayoutDirectionOverride != layoutDirection) {
172+
mLayoutDirectionOverride = layoutDirection;
173+
}
174+
}
175+
176+
@Override
177+
@SuppressLint("WrongConstant")
178+
public int getLayoutDirection() {
179+
return mLayoutDirectionOverride == -1 ? super.getLayoutDirection() : mLayoutDirectionOverride;
180+
}
181+
166182
@Override
167183
public int getOpacity() {
168184
return (Color.alpha(mColor) * mAlpha) >> 8;
@@ -292,25 +308,6 @@ public void setColor(int color) {
292308
invalidateSelf();
293309
}
294310

295-
/** Similar to Drawable.getLayoutDirection, but available in APIs < 23. */
296-
public int getResolvedLayoutDirection() {
297-
return mLayoutDirection;
298-
}
299-
300-
/** Similar to Drawable.setLayoutDirection, but available in APIs < 23. */
301-
public boolean setResolvedLayoutDirection(int layoutDirection) {
302-
if (mLayoutDirection != layoutDirection) {
303-
mLayoutDirection = layoutDirection;
304-
return onResolvedLayoutDirectionChanged(layoutDirection);
305-
}
306-
return false;
307-
}
308-
309-
/** Similar to Drawable.onLayoutDirectionChanged, but available in APIs < 23. */
310-
public boolean onResolvedLayoutDirectionChanged(int layoutDirection) {
311-
return false;
312-
}
313-
314311
@VisibleForTesting
315312
public int getColor() {
316313
return mColor;
@@ -392,7 +389,7 @@ private void drawRoundedBackgroundWithBorders(Canvas canvas) {
392389
// Clip inner border
393390
canvas.clipPath(mInnerClipPathForBorderRadius, Region.Op.DIFFERENCE);
394391

395-
final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
392+
final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
396393
int colorStart = getBorderColor(Spacing.START);
397394
int colorEnd = getBorderColor(Spacing.END);
398395

@@ -591,7 +588,7 @@ private void updatePath() {
591588

592589
mComputedBorderRadius =
593590
mBorderRadius.resolve(
594-
mLayoutDirection,
591+
getLayoutDirection(),
595592
mContext,
596593
mOuterClipTempRectForBorderRadius.width(),
597594
mOuterClipTempRectForBorderRadius.height());
@@ -1080,7 +1077,7 @@ private void drawRectangularBackgroundWithBorders(Canvas canvas) {
10801077
colorTop = colorBlockStart;
10811078
}
10821079

1083-
final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
1080+
final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
10841081
int colorStart = getBorderColor(Spacing.START);
10851082
int colorEnd = getBorderColor(Spacing.END);
10861083

@@ -1305,7 +1302,7 @@ public RectF getDirectionAwareBorderInsets() {
13051302
float borderRightWidth = getBorderWidthOrDefaultTo(borderWidth, Spacing.RIGHT);
13061303

13071304
if (mBorderWidth != null) {
1308-
final boolean isRTL = getResolvedLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
1305+
final boolean isRTL = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
13091306
float borderStartWidth = mBorderWidth.getRaw(Spacing.START);
13101307
float borderEndWidth = mBorderWidth.getRaw(Spacing.END);
13111308

0 commit comments

Comments
 (0)