Skip to content

Commit bfb3b70

Browse files
NickGerlemanfacebook-github-bot
authored andcommitted
Support clipping to children everywhere using ReactViewBackgroundManager (facebook#44734)
Summary: Pull Request resolved: facebook#44734 Fixes facebook#44671 This integrates functionality for clipping content to padding box into `ReactViewBackgroundManager`, to be shared between several ViewManagers. In practice, this means: 1. `overflow: hidden` now works on `Text` and `TextInput` 2. ScrollView children are now clipped to the interior of borders, included curved ones via borderRadius This will be made more generic, then start being used in ReactViewGroup, and eventually ReactImage. That abstraction will then hide away extra background management we will use for shadows. Different places in code currently do clipping in any of `draw()`, `onDraw()`, or `dispatchDraw()`. The distinction between these, is that `draw()` allows code to run before drawing background even, `onDraw()` is invoked before drawing foreground, and `dispatchDraw()` is before drawing children. We don't want to clip out borders/shadows, but do want to clip foreground content like text, so I used `onDraw()` here. Changelog: [Android][Fixed] - Better overflow support for ScrollView, Text, TextInput Reviewed By: rozele Differential Revision: D57953429 fbshipit-source-id: ca3b788deb4b32706df7db958877d18f525c039c
1 parent c9d7774 commit bfb3b70

File tree

8 files changed

+115
-27
lines changed

8 files changed

+115
-27
lines changed

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6703,7 +6703,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android
67036703
public fun onChildViewAdded (Landroid/view/View;Landroid/view/View;)V
67046704
public fun onChildViewRemoved (Landroid/view/View;Landroid/view/View;)V
67056705
protected fun onDetachedFromWindow ()V
6706-
protected fun onDraw (Landroid/graphics/Canvas;)V
6706+
public fun onDraw (Landroid/graphics/Canvas;)V
67076707
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
67086708
protected fun onLayout (ZIIII)V
67096709
public fun onLayoutChange (Landroid/view/View;IIIIIIII)V
@@ -6832,6 +6832,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc
68326832
public fun onChildViewAdded (Landroid/view/View;Landroid/view/View;)V
68336833
public fun onChildViewRemoved (Landroid/view/View;Landroid/view/View;)V
68346834
protected fun onDetachedFromWindow ()V
6835+
public fun onDraw (Landroid/graphics/Canvas;)V
68356836
public fun onInitializeAccessibilityNodeInfo (Landroid/view/accessibility/AccessibilityNodeInfo;)V
68366837
public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z
68376838
protected fun onLayout (ZIIII)V
@@ -7383,6 +7384,7 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi
73837384
public fun setMinimumFontSize (F)V
73847385
public fun setNotifyOnInlineViewLayout (Z)V
73857386
public fun setNumberOfLines (I)V
7387+
public fun setOverflow (Ljava/lang/String;)V
73867388
public fun setSpanned (Landroid/text/Spannable;)V
73877389
public fun setText (Lcom/facebook/react/views/text/ReactTextUpdate;)V
73887390
public fun setTextIsSelectable (Z)V
@@ -7408,6 +7410,7 @@ public class com/facebook/react/views/text/ReactTextViewManager : com/facebook/r
74087410
protected fun onAfterUpdateTransaction (Lcom/facebook/react/views/text/ReactTextView;)V
74097411
protected synthetic fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Landroid/view/View;)Landroid/view/View;
74107412
protected fun prepareToRecycleView (Lcom/facebook/react/uimanager/ThemedReactContext;Lcom/facebook/react/views/text/ReactTextView;)Lcom/facebook/react/views/text/ReactTextView;
7413+
public fun setOverflow (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V
74117414
public synthetic fun setPadding (Landroid/view/View;IIII)V
74127415
public fun setPadding (Lcom/facebook/react/views/text/ReactTextView;IIII)V
74137416
public synthetic fun updateExtraData (Landroid/view/View;Ljava/lang/Object;)V
@@ -7687,6 +7690,7 @@ public class com/facebook/react/views/textinput/ReactEditText : androidx/appcomp
76877690
public fun onAttachedToWindow ()V
76887691
public fun onCreateInputConnection (Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection;
76897692
public fun onDetachedFromWindow ()V
7693+
public fun onDraw (Landroid/graphics/Canvas;)V
76907694
public fun onFinishTemporaryDetach ()V
76917695
protected fun onFocusChanged (ZILandroid/graphics/Rect;)V
76927696
public fun onKeyUp (ILandroid/view/KeyEvent;)Z
@@ -7719,6 +7723,7 @@ public class com/facebook/react/views/textinput/ReactEditText : androidx/appcomp
77197723
public fun setLineHeight (I)V
77207724
public fun setMaxFontSizeMultiplier (F)V
77217725
public fun setOnKeyPress (Z)V
7726+
public fun setOverflow (Ljava/lang/String;)V
77227727
public fun setPlaceholder (Ljava/lang/String;)V
77237728
public fun setReturnKeyType (Ljava/lang/String;)V
77247729
public fun setScrollWatcher (Lcom/facebook/react/views/textinput/ScrollWatcher;)V
@@ -7804,6 +7809,7 @@ public class com/facebook/react/views/textinput/ReactTextInputManager : com/face
78047809
public fun setOnKeyPress (Lcom/facebook/react/views/textinput/ReactEditText;Z)V
78057810
public fun setOnScroll (Lcom/facebook/react/views/textinput/ReactEditText;Z)V
78067811
public fun setOnSelectionChange (Lcom/facebook/react/views/textinput/ReactEditText;Z)V
7812+
public fun setOverflow (Lcom/facebook/react/views/textinput/ReactEditText;Ljava/lang/String;)V
78077813
public synthetic fun setPadding (Landroid/view/View;IIII)V
78087814
public fun setPadding (Lcom/facebook/react/views/textinput/ReactEditText;IIII)V
78097815
public fun setPlaceholder (Lcom/facebook/react/views/textinput/ReactEditText;Ljava/lang/String;)V
@@ -7912,12 +7918,14 @@ public class com/facebook/react/views/view/ReactViewBackgroundManager {
79127918
public fun cleanup ()V
79137919
public fun getBackgroundColor ()I
79147920
public fun getBorderColor (I)I
7921+
public fun maybeClipToPaddingBox (Landroid/graphics/Canvas;)V
79157922
public fun setBackgroundColor (I)V
79167923
public fun setBorderColor (IFF)V
79177924
public fun setBorderRadius (F)V
79187925
public fun setBorderRadius (FI)V
79197926
public fun setBorderStyle (Ljava/lang/String;)V
79207927
public fun setBorderWidth (IF)V
7928+
public fun setOverflow (Ljava/lang/String;)V
79217929
}
79227930

79237931
public class com/facebook/react/views/view/ReactViewGroup : android/view/ViewGroup, com/facebook/react/touch/ReactHitSlopView, com/facebook/react/touch/ReactInterceptingViewGroup, com/facebook/react/uimanager/ReactClippingViewGroup, com/facebook/react/uimanager/ReactOverflowViewWithInset, com/facebook/react/uimanager/ReactPointerEventsView, com/facebook/react/uimanager/ReactZIndexedViewGroup {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
8484
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
8585
private final @Nullable OverScroller mScroller;
8686
private final VelocityHelper mVelocityHelper = new VelocityHelper();
87-
private final Rect mRect = new Rect();
8887
private final Rect mOverflowInset = new Rect();
8988

9089
private boolean mActivelyScrolling;
@@ -145,6 +144,7 @@ public ReactHorizontalScrollView(Context context, @Nullable FpsListener fpsListe
145144

146145
setOnHierarchyChangeListener(this);
147146
setClipChildren(false);
147+
mReactBackgroundManager.setOverflow(ViewProps.SCROLL);
148148
}
149149

150150
public boolean getScrollEnabled() {
@@ -273,7 +273,7 @@ public void flashScrollIndicators() {
273273

274274
public void setOverflow(@Nullable String overflow) {
275275
mOverflow = overflow;
276-
invalidate();
276+
mReactBackgroundManager.setOverflow(overflow == null ? ViewProps.SCROLL : overflow);
277277
}
278278

279279
public void setMaintainVisibleContentPosition(
@@ -306,14 +306,8 @@ public Rect getOverflowInset() {
306306
}
307307

308308
@Override
309-
protected void onDraw(Canvas canvas) {
310-
if (DEBUG_MODE) {
311-
FLog.i(TAG, "onDraw[%d]", getId());
312-
}
313-
getDrawingRect(mRect);
314-
if (!ViewProps.VISIBLE.equals(mOverflow)) {
315-
canvas.clipRect(mRect);
316-
}
309+
public void onDraw(Canvas canvas) {
310+
mReactBackgroundManager.maybeClipToPaddingBox(canvas);
317311
super.onDraw(canvas);
318312
}
319313

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ public class ReactScrollView extends ScrollView
8383
private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper();
8484
private final @Nullable OverScroller mScroller;
8585
private final VelocityHelper mVelocityHelper = new VelocityHelper();
86-
private final Rect mRect = new Rect(); // for reuse to avoid allocation
8786
private final Rect mTempRect = new Rect();
8887
private final Rect mOverflowInset = new Rect();
8988

@@ -136,6 +135,7 @@ public ReactScrollView(Context context, @Nullable FpsListener fpsListener) {
136135
setOnHierarchyChangeListener(this);
137136
setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
138137
setClipChildren(false);
138+
mReactBackgroundManager.setOverflow(ViewProps.SCROLL);
139139

140140
ViewCompat.setAccessibilityDelegate(this, new ReactScrollViewAccessibilityDelegate());
141141
}
@@ -261,7 +261,7 @@ public void flashScrollIndicators() {
261261

262262
public void setOverflow(@Nullable String overflow) {
263263
mOverflow = overflow;
264-
invalidate();
264+
mReactBackgroundManager.setOverflow(overflow == null ? ViewProps.SCROLL : overflow);
265265
}
266266

267267
public void setMaintainVisibleContentPosition(
@@ -640,15 +640,16 @@ public void draw(Canvas canvas) {
640640
mEndBackground.draw(canvas);
641641
}
642642
}
643-
getDrawingRect(mRect);
644-
645-
if (!ViewProps.VISIBLE.equals(mOverflow)) {
646-
canvas.clipRect(mRect);
647-
}
648643

649644
super.draw(canvas);
650645
}
651646

647+
@Override
648+
public void onDraw(Canvas canvas) {
649+
mReactBackgroundManager.maybeClipToPaddingBox(canvas);
650+
super.onDraw(canvas);
651+
}
652+
652653
/**
653654
* This handles any sort of scrolling that may occur after a touch is finished. This may be
654655
* momentum scrolling (fling) or because you have pagingEnabled on the scroll view. Because we

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,8 @@ protected void onDraw(Canvas canvas) {
382382
setText(getSpanned());
383383
}
384384

385+
mReactBackgroundManager.maybeClipToPaddingBox(canvas);
386+
385387
super.onDraw(canvas);
386388
}
387389

@@ -741,4 +743,8 @@ private void applyTextAttributes() {
741743
super.setLetterSpacing(mLetterSpacing);
742744
}
743745
}
746+
747+
public void setOverflow(@Nullable String overflow) {
748+
mReactBackgroundManager.setOverflow(overflow);
749+
}
744750
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.facebook.react.uimanager.ReactStylesDiffMap;
2424
import com.facebook.react.uimanager.StateWrapper;
2525
import com.facebook.react.uimanager.ThemedReactContext;
26+
import com.facebook.react.uimanager.annotations.ReactProp;
2627
import com.facebook.react.views.text.internal.span.ReactClickableSpan;
2728
import com.facebook.react.views.text.internal.span.TextInlineImageSpan;
2829
import com.facebook.yoga.YogaMeasureMode;
@@ -220,4 +221,9 @@ public long measure(
220221
public void setPadding(ReactTextView view, int left, int top, int right, int bottom) {
221222
view.setPadding(left, top, right, bottom);
222223
}
224+
225+
@ReactProp(name = "overflow")
226+
public void setOverflow(ReactTextView view, @Nullable String overflow) {
227+
view.setOverflow(overflow);
228+
}
223229
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import static com.facebook.react.uimanager.UIManagerHelper.getReactContext;
1111

1212
import android.content.Context;
13+
import android.graphics.Canvas;
1314
import android.graphics.Color;
1415
import android.graphics.Paint;
1516
import android.graphics.Rect;
@@ -1226,6 +1227,16 @@ void setEventDispatcher(@Nullable EventDispatcher eventDispatcher) {
12261227
mEventDispatcher = eventDispatcher;
12271228
}
12281229

1230+
public void setOverflow(@Nullable String overflow) {
1231+
mReactBackgroundManager.setOverflow(overflow);
1232+
}
1233+
1234+
@Override
1235+
public void onDraw(Canvas canvas) {
1236+
mReactBackgroundManager.maybeClipToPaddingBox(canvas);
1237+
super.onDraw(canvas);
1238+
}
1239+
12291240
/**
12301241
* This class will redirect *TextChanged calls to the listeners only in the case where the text is
12311242
* changed by the user, and not explicitly set by JS.

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,11 @@ public void setBorderColor(ReactEditText view, int index, Integer color) {
10381038
view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent);
10391039
}
10401040

1041+
@ReactProp(name = "overflow")
1042+
public void setOverflow(ReactEditText view, @Nullable String overflow) {
1043+
view.setOverflow(overflow);
1044+
}
1045+
10411046
@Override
10421047
protected void onAfterUpdateTransaction(ReactEditText view) {
10431048
super.onAfterUpdateTransaction(view);

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,30 @@
77

88
package com.facebook.react.views.view;
99

10+
import android.graphics.Canvas;
1011
import android.graphics.Color;
12+
import android.graphics.Path;
13+
import android.graphics.Rect;
14+
import android.graphics.RectF;
1115
import android.graphics.drawable.Drawable;
1216
import android.graphics.drawable.LayerDrawable;
1317
import android.view.View;
1418
import androidx.annotation.Nullable;
1519
import androidx.core.view.ViewCompat;
20+
import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable;
1621

1722
/** Class that manages the background for views and borders. */
1823
public class ReactViewBackgroundManager {
24+
private static enum Overflow {
25+
VISIBLE,
26+
HIDDEN,
27+
SCROLL,
28+
}
1929

20-
private @Nullable ReactViewBackgroundDrawable mReactBackgroundDrawable;
30+
private @Nullable CSSBackgroundDrawable mCSSBackgroundDrawable;
2131
private View mView;
2232
private int mColor = Color.TRANSPARENT;
33+
private Overflow mOverflow = Overflow.VISIBLE;
2334

2435
public ReactViewBackgroundManager(View view) {
2536
mView = view;
@@ -28,29 +39,29 @@ public ReactViewBackgroundManager(View view) {
2839
public void cleanup() {
2940
ViewCompat.setBackground(mView, null);
3041
mView = null;
31-
mReactBackgroundDrawable = null;
42+
mCSSBackgroundDrawable = null;
3243
}
3344

34-
private ReactViewBackgroundDrawable getOrCreateReactViewBackground() {
35-
if (mReactBackgroundDrawable == null) {
36-
mReactBackgroundDrawable = new ReactViewBackgroundDrawable(mView.getContext());
45+
private CSSBackgroundDrawable getOrCreateReactViewBackground() {
46+
if (mCSSBackgroundDrawable == null) {
47+
mCSSBackgroundDrawable = new CSSBackgroundDrawable(mView.getContext());
3748
Drawable backgroundDrawable = mView.getBackground();
3849
ViewCompat.setBackground(
3950
mView, null); // required so that drawable callback is cleared before we add the
4051
// drawable back as a part of LayerDrawable
4152
if (backgroundDrawable == null) {
42-
ViewCompat.setBackground(mView, mReactBackgroundDrawable);
53+
ViewCompat.setBackground(mView, mCSSBackgroundDrawable);
4354
} else {
4455
LayerDrawable layerDrawable =
45-
new LayerDrawable(new Drawable[] {mReactBackgroundDrawable, backgroundDrawable});
56+
new LayerDrawable(new Drawable[] {mCSSBackgroundDrawable, backgroundDrawable});
4657
ViewCompat.setBackground(mView, layerDrawable);
4758
}
4859
}
49-
return mReactBackgroundDrawable;
60+
return mCSSBackgroundDrawable;
5061
}
5162

5263
public void setBackgroundColor(int color) {
53-
if (color == Color.TRANSPARENT && mReactBackgroundDrawable == null) {
64+
if (color == Color.TRANSPARENT && mCSSBackgroundDrawable == null) {
5465
// don't do anything, no need to allocate ReactBackgroundDrawable for transparent background
5566
} else {
5667
getOrCreateReactViewBackground().setColor(color);
@@ -84,4 +95,50 @@ public void setBorderRadius(float borderRadius, int position) {
8495
public void setBorderStyle(@Nullable String style) {
8596
getOrCreateReactViewBackground().setBorderStyle(style);
8697
}
98+
99+
public void setOverflow(@Nullable String overflow) {
100+
Overflow lastOverflow = mOverflow;
101+
102+
if ("hidden".equals(overflow)) {
103+
mOverflow = Overflow.HIDDEN;
104+
} else if ("scroll".equals(overflow)) {
105+
mOverflow = Overflow.SCROLL;
106+
} else {
107+
mOverflow = Overflow.VISIBLE;
108+
}
109+
110+
if (lastOverflow != mOverflow) {
111+
mView.invalidate();
112+
}
113+
}
114+
115+
/**
116+
* Sets the canvas clipping region to exclude any area below or outide of borders if "overflow" is
117+
* set to clip to padding box.
118+
*/
119+
public void maybeClipToPaddingBox(Canvas canvas) {
120+
if (mOverflow == Overflow.VISIBLE) {
121+
return;
122+
}
123+
124+
// The canvas may be scrolled, so we need to offset
125+
Rect drawingRect = new Rect();
126+
mView.getDrawingRect(drawingRect);
127+
128+
@Nullable CSSBackgroundDrawable cssBackground = mCSSBackgroundDrawable;
129+
if (cssBackground == null) {
130+
canvas.clipRect(drawingRect);
131+
return;
132+
}
133+
134+
@Nullable Path paddingBoxPath = cssBackground.getPaddingBoxPath();
135+
if (paddingBoxPath != null) {
136+
paddingBoxPath.offset(drawingRect.left, drawingRect.top);
137+
canvas.clipPath(paddingBoxPath);
138+
} else {
139+
RectF paddingBoxRect = cssBackground.getPaddingBoxRect();
140+
paddingBoxRect.offset(drawingRect.left, drawingRect.top);
141+
canvas.clipRect(paddingBoxRect);
142+
}
143+
}
87144
}

0 commit comments

Comments
 (0)