Fix content origin offset for RTL scripts

Summary:
changelog: [internal]

There were three separate problems preventing measure infra to work correctly with views inside horizontal scroll view in RTL environment.

1. Initial offset is wasn't communicated to Fabric. This is resolved separately as it doesn't affect only RTL: D26778991 (https://github.com/facebook/react-native/commit/630ac87591eb9d535bc32c6c42c624bfc3f1953f).
3. On Android when layout direction is RTL, offset of scrollview is calculated from right.

Reviewed By: mdvacca

Differential Revision: D26779860

fbshipit-source-id: 61572c78091a1f5417102eb38d88ba7d172e6102
This commit is contained in:
Joshua Gross
2021-03-05 22:46:44 -08:00
committed by Facebook GitHub Bot
parent fc032cd8d8
commit 365e12430a
2 changed files with 31 additions and 14 deletions
@@ -64,7 +64,7 @@ public class ReactHorizontalScrollContainerView extends ReactViewGroup {
// Call with the present values in order to re-layout if necessary
ReactHorizontalScrollView parent = (ReactHorizontalScrollView) getParent();
// Fix the ScrollX position when using RTL language
int offsetX = parent.getScrollX() + getWidth() - mCurrentWidth;
int offsetX = parent.getScrollX() + getWidth() - mCurrentWidth - parent.getWidth();
parent.reactScrollTo(offsetX, parent.getScrollY());
}
}
@@ -25,7 +25,6 @@ import android.view.accessibility.AccessibilityEvent;
import android.widget.HorizontalScrollView;
import android.widget.OverScroller;
import androidx.annotation.Nullable;
import androidx.core.text.TextUtilsCompat;
import androidx.core.view.AccessibilityDelegateCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
@@ -34,6 +33,7 @@ import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.facebook.react.uimanager.FabricViewStateManager;
import com.facebook.react.uimanager.MeasureSpecAssertions;
import com.facebook.react.uimanager.PixelUtil;
@@ -45,7 +45,6 @@ import com.facebook.react.views.view.ReactViewBackgroundManager;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/** Similar to {@link ReactScrollView} but only supports horizontal scrolling. */
public class ReactHorizontalScrollView extends HorizontalScrollView
@@ -55,6 +54,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
private static boolean sTriedToGetScrollerField = false;
private static final String CONTENT_OFFSET_LEFT = "contentOffsetLeft";
private static final String CONTENT_OFFSET_TOP = "contentOffsetTop";
private int mLayoutDirection;
private static final int UNSET_CONTENT_OFFSET = -1;
@@ -124,6 +124,10 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
});
mScroller = getOverScrollerFromParent();
mLayoutDirection =
I18nUtil.getInstance().isRTL(context)
? ViewCompat.LAYOUT_DIRECTION_RTL
: ViewCompat.LAYOUT_DIRECTION_LTR;
}
@Nullable
@@ -401,7 +405,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
if (getChildCount() > 0) {
View currentFocused = findFocus();
View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
View rootChild = getChildAt(0);
View rootChild = getContentView();
if (rootChild != null && nextFocused != null && nextFocused.getParent() == rootChild) {
if (!isScrolledInView(nextFocused) && !isMostlyScrolledInView(nextFocused)) {
smoothScrollToNextPage(direction);
@@ -532,7 +536,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
Assertions.assertNotNull(mClippingRect);
ReactClippingViewGroupHelper.calculateClippingRect(this, mClippingRect);
View contentView = getChildAt(0);
View contentView = getContentView();
if (contentView instanceof ReactClippingViewGroup) {
((ReactClippingViewGroup) contentView).updateClippingRect();
}
@@ -555,6 +559,11 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
return getWidth();
}
private View getContentView() {
View contentView = getChildAt(0);
return contentView;
}
public void setEndFillColor(int color) {
if (color != mEndFillColor) {
mEndFillColor = color;
@@ -608,7 +617,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
@Override
public void draw(Canvas canvas) {
if (mEndFillColor != Color.TRANSPARENT) {
final View content = getChildAt(0);
final View content = getContentView();
if (mEndBackground != null && content != null && content.getRight() < getWidth()) {
mEndBackground.setBounds(content.getRight(), 0, getWidth(), getHeight());
mEndBackground.draw(canvas);
@@ -805,10 +814,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
int width = getWidth() - ViewCompat.getPaddingStart(this) - ViewCompat.getPaddingEnd(this);
// offsets are from the right edge in RTL layouts
boolean isRTL =
TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
== ViewCompat.LAYOUT_DIRECTION_RTL;
if (isRTL) {
if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
targetOffset = maximumOffset - targetOffset;
velocityX = -velocityX;
}
@@ -847,7 +853,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
// if scrolling after the last snap offset and snapping to the
// end of the list is disabled, then we allow free scrolling
int currentOffset = getScrollX();
if (isRTL) {
if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
currentOffset = maximumOffset - currentOffset;
}
if (!mSnapToEnd && targetOffset >= lastOffset) {
@@ -881,7 +887,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
// Make sure the new offset isn't out of bounds
targetOffset = Math.min(Math.max(0, targetOffset), maximumOffset);
if (isRTL) {
if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
targetOffset = maximumOffset - targetOffset;
velocityX = -velocityX;
}
@@ -1041,7 +1047,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
* @param y
*/
private void setPendingContentOffsets(int x, int y) {
View child = getChildAt(0);
View child = getContentView();
if (child != null && child.getWidth() != 0 && child.getHeight() != 0) {
pendingContentOffsetX = UNSET_CONTENT_OFFSET;
pendingContentOffsetY = UNSET_CONTENT_OFFSET;
@@ -1063,12 +1069,23 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
mLastStateUpdateScrollX = scrollX;
mLastStateUpdateScrollY = scrollY;
final int fabricScrollX;
if (mLayoutDirection == LAYOUT_DIRECTION_RTL) {
// getScrollX returns offset from left even when layout direction is RTL.
// The following line calculates offset from right.
View child = getContentView();
int contentWidth = child != null ? child.getWidth() : 0;
fabricScrollX = -(contentWidth - scrollX - getWidth());
} else {
fabricScrollX = scrollX;
}
mFabricViewStateManager.setState(
new FabricViewStateManager.StateUpdateCallback() {
@Override
public WritableMap getStateUpdate() {
WritableMap map = new WritableNativeMap();
map.putDouble(CONTENT_OFFSET_LEFT, PixelUtil.toDIPFromPixel(scrollX));
map.putDouble(CONTENT_OFFSET_LEFT, PixelUtil.toDIPFromPixel(fabricScrollX));
map.putDouble(CONTENT_OFFSET_TOP, PixelUtil.toDIPFromPixel(scrollY));
return map;
}