diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index b4d2f96a04ffe6..7058755cfc3333 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6703,7 +6703,7 @@ public class com/facebook/react/views/scroll/ReactHorizontalScrollView : android public fun onChildViewAdded (Landroid/view/View;Landroid/view/View;)V public fun onChildViewRemoved (Landroid/view/View;Landroid/view/View;)V protected fun onDetachedFromWindow ()V - protected fun onDraw (Landroid/graphics/Canvas;)V + public fun onDraw (Landroid/graphics/Canvas;)V public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z protected fun onLayout (ZIIII)V public fun onLayoutChange (Landroid/view/View;IIIIIIII)V @@ -6832,6 +6832,7 @@ public class com/facebook/react/views/scroll/ReactScrollView : android/widget/Sc public fun onChildViewAdded (Landroid/view/View;Landroid/view/View;)V public fun onChildViewRemoved (Landroid/view/View;Landroid/view/View;)V protected fun onDetachedFromWindow ()V + public fun onDraw (Landroid/graphics/Canvas;)V public fun onInitializeAccessibilityNodeInfo (Landroid/view/accessibility/AccessibilityNodeInfo;)V public fun onInterceptTouchEvent (Landroid/view/MotionEvent;)Z protected fun onLayout (ZIIII)V @@ -7383,6 +7384,7 @@ public class com/facebook/react/views/text/ReactTextView : androidx/appcompat/wi public fun setMinimumFontSize (F)V public fun setNotifyOnInlineViewLayout (Z)V public fun setNumberOfLines (I)V + public fun setOverflow (Ljava/lang/String;)V public fun setSpanned (Landroid/text/Spannable;)V public fun setText (Lcom/facebook/react/views/text/ReactTextUpdate;)V public fun setTextIsSelectable (Z)V @@ -7408,6 +7410,7 @@ public class com/facebook/react/views/text/ReactTextViewManager : com/facebook/r protected fun onAfterUpdateTransaction (Lcom/facebook/react/views/text/ReactTextView;)V 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/text/ReactTextView;)Lcom/facebook/react/views/text/ReactTextView; + public fun setOverflow (Lcom/facebook/react/views/text/ReactTextView;Ljava/lang/String;)V public synthetic fun setPadding (Landroid/view/View;IIII)V public fun setPadding (Lcom/facebook/react/views/text/ReactTextView;IIII)V 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 public fun onAttachedToWindow ()V public fun onCreateInputConnection (Landroid/view/inputmethod/EditorInfo;)Landroid/view/inputmethod/InputConnection; public fun onDetachedFromWindow ()V + public fun onDraw (Landroid/graphics/Canvas;)V public fun onFinishTemporaryDetach ()V protected fun onFocusChanged (ZILandroid/graphics/Rect;)V public fun onKeyUp (ILandroid/view/KeyEvent;)Z @@ -7719,6 +7723,7 @@ public class com/facebook/react/views/textinput/ReactEditText : androidx/appcomp public fun setLineHeight (I)V public fun setMaxFontSizeMultiplier (F)V public fun setOnKeyPress (Z)V + public fun setOverflow (Ljava/lang/String;)V public fun setPlaceholder (Ljava/lang/String;)V public fun setReturnKeyType (Ljava/lang/String;)V public fun setScrollWatcher (Lcom/facebook/react/views/textinput/ScrollWatcher;)V @@ -7804,6 +7809,7 @@ public class com/facebook/react/views/textinput/ReactTextInputManager : com/face public fun setOnKeyPress (Lcom/facebook/react/views/textinput/ReactEditText;Z)V public fun setOnScroll (Lcom/facebook/react/views/textinput/ReactEditText;Z)V public fun setOnSelectionChange (Lcom/facebook/react/views/textinput/ReactEditText;Z)V + public fun setOverflow (Lcom/facebook/react/views/textinput/ReactEditText;Ljava/lang/String;)V public synthetic fun setPadding (Landroid/view/View;IIII)V public fun setPadding (Lcom/facebook/react/views/textinput/ReactEditText;IIII)V 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 { public fun cleanup ()V public fun getBackgroundColor ()I public fun getBorderColor (I)I + public fun maybeClipToPaddingBox (Landroid/graphics/Canvas;)V public fun setBackgroundColor (I)V public fun setBorderColor (IFF)V public fun setBorderRadius (F)V public fun setBorderRadius (FI)V public fun setBorderStyle (Ljava/lang/String;)V public fun setBorderWidth (IF)V + public fun setOverflow (Ljava/lang/String;)V } 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 { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java index 4ffe87f9d308cf..7d8853ba446cc4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.java @@ -84,7 +84,6 @@ public class ReactHorizontalScrollView extends HorizontalScrollView private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper(); private final @Nullable OverScroller mScroller; private final VelocityHelper mVelocityHelper = new VelocityHelper(); - private final Rect mRect = new Rect(); private final Rect mOverflowInset = new Rect(); private boolean mActivelyScrolling; @@ -145,6 +144,7 @@ public ReactHorizontalScrollView(Context context, @Nullable FpsListener fpsListe setOnHierarchyChangeListener(this); setClipChildren(false); + mReactBackgroundManager.setOverflow(ViewProps.SCROLL); } public boolean getScrollEnabled() { @@ -273,7 +273,7 @@ public void flashScrollIndicators() { public void setOverflow(@Nullable String overflow) { mOverflow = overflow; - invalidate(); + mReactBackgroundManager.setOverflow(overflow == null ? ViewProps.SCROLL : overflow); } public void setMaintainVisibleContentPosition( @@ -306,14 +306,8 @@ public Rect getOverflowInset() { } @Override - protected void onDraw(Canvas canvas) { - if (DEBUG_MODE) { - FLog.i(TAG, "onDraw[%d]", getId()); - } - getDrawingRect(mRect); - if (!ViewProps.VISIBLE.equals(mOverflow)) { - canvas.clipRect(mRect); - } + public void onDraw(Canvas canvas) { + mReactBackgroundManager.maybeClipToPaddingBox(canvas); super.onDraw(canvas); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java index e6698e3cbd24ac..56f486a23c61a0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java @@ -83,7 +83,6 @@ public class ReactScrollView extends ScrollView private final OnScrollDispatchHelper mOnScrollDispatchHelper = new OnScrollDispatchHelper(); private final @Nullable OverScroller mScroller; private final VelocityHelper mVelocityHelper = new VelocityHelper(); - private final Rect mRect = new Rect(); // for reuse to avoid allocation private final Rect mTempRect = new Rect(); private final Rect mOverflowInset = new Rect(); @@ -136,6 +135,7 @@ public ReactScrollView(Context context, @Nullable FpsListener fpsListener) { setOnHierarchyChangeListener(this); setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY); setClipChildren(false); + mReactBackgroundManager.setOverflow(ViewProps.SCROLL); ViewCompat.setAccessibilityDelegate(this, new ReactScrollViewAccessibilityDelegate()); } @@ -261,7 +261,7 @@ public void flashScrollIndicators() { public void setOverflow(@Nullable String overflow) { mOverflow = overflow; - invalidate(); + mReactBackgroundManager.setOverflow(overflow == null ? ViewProps.SCROLL : overflow); } public void setMaintainVisibleContentPosition( @@ -640,15 +640,16 @@ public void draw(Canvas canvas) { mEndBackground.draw(canvas); } } - getDrawingRect(mRect); - - if (!ViewProps.VISIBLE.equals(mOverflow)) { - canvas.clipRect(mRect); - } super.draw(canvas); } + @Override + public void onDraw(Canvas canvas) { + mReactBackgroundManager.maybeClipToPaddingBox(canvas); + super.onDraw(canvas); + } + /** * This handles any sort of scrolling that may occur after a touch is finished. This may be * momentum scrolling (fling) or because you have pagingEnabled on the scroll view. Because we diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index e0ae77db3c3515..424160192fc16b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -382,6 +382,8 @@ protected void onDraw(Canvas canvas) { setText(getSpanned()); } + mReactBackgroundManager.maybeClipToPaddingBox(canvas); + super.onDraw(canvas); } @@ -741,4 +743,8 @@ private void applyTextAttributes() { super.setLetterSpacing(mLetterSpacing); } } + + public void setOverflow(@Nullable String overflow) { + mReactBackgroundManager.setOverflow(overflow); + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java index fe4bc770ff82fb..8ca3155b31a674 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java @@ -23,6 +23,7 @@ import com.facebook.react.uimanager.ReactStylesDiffMap; import com.facebook.react.uimanager.StateWrapper; import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.views.text.internal.span.ReactClickableSpan; import com.facebook.react.views.text.internal.span.TextInlineImageSpan; import com.facebook.yoga.YogaMeasureMode; @@ -220,4 +221,9 @@ public long measure( public void setPadding(ReactTextView view, int left, int top, int right, int bottom) { view.setPadding(left, top, right, bottom); } + + @ReactProp(name = "overflow") + public void setOverflow(ReactTextView view, @Nullable String overflow) { + view.setOverflow(overflow); + } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java index 2a09434f26956b..014e0b489dd340 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.java @@ -10,6 +10,7 @@ import static com.facebook.react.uimanager.UIManagerHelper.getReactContext; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; @@ -1226,6 +1227,16 @@ void setEventDispatcher(@Nullable EventDispatcher eventDispatcher) { mEventDispatcher = eventDispatcher; } + public void setOverflow(@Nullable String overflow) { + mReactBackgroundManager.setOverflow(overflow); + } + + @Override + public void onDraw(Canvas canvas) { + mReactBackgroundManager.maybeClipToPaddingBox(canvas); + super.onDraw(canvas); + } + /** * This class will redirect *TextChanged calls to the listeners only in the case where the text is * changed by the user, and not explicitly set by JS. diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java index c5c97898ede3dc..2c225da1554890 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java @@ -1038,6 +1038,11 @@ public void setBorderColor(ReactEditText view, int index, Integer color) { view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent); } + @ReactProp(name = "overflow") + public void setOverflow(ReactEditText view, @Nullable String overflow) { + view.setOverflow(overflow); + } + @Override protected void onAfterUpdateTransaction(ReactEditText view) { super.onAfterUpdateTransaction(view); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java index 5c616597d502cd..80ed63af37e29d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewBackgroundManager.java @@ -7,19 +7,30 @@ package com.facebook.react.views.view; +import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.Path; +import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.view.View; import androidx.annotation.Nullable; import androidx.core.view.ViewCompat; +import com.facebook.react.uimanager.drawable.CSSBackgroundDrawable; /** Class that manages the background for views and borders. */ public class ReactViewBackgroundManager { + private static enum Overflow { + VISIBLE, + HIDDEN, + SCROLL, + } - private @Nullable ReactViewBackgroundDrawable mReactBackgroundDrawable; + private @Nullable CSSBackgroundDrawable mCSSBackgroundDrawable; private View mView; private int mColor = Color.TRANSPARENT; + private Overflow mOverflow = Overflow.VISIBLE; public ReactViewBackgroundManager(View view) { mView = view; @@ -28,29 +39,29 @@ public ReactViewBackgroundManager(View view) { public void cleanup() { ViewCompat.setBackground(mView, null); mView = null; - mReactBackgroundDrawable = null; + mCSSBackgroundDrawable = null; } - private ReactViewBackgroundDrawable getOrCreateReactViewBackground() { - if (mReactBackgroundDrawable == null) { - mReactBackgroundDrawable = new ReactViewBackgroundDrawable(mView.getContext()); + private CSSBackgroundDrawable getOrCreateReactViewBackground() { + if (mCSSBackgroundDrawable == null) { + mCSSBackgroundDrawable = new CSSBackgroundDrawable(mView.getContext()); Drawable backgroundDrawable = mView.getBackground(); ViewCompat.setBackground( mView, null); // required so that drawable callback is cleared before we add the // drawable back as a part of LayerDrawable if (backgroundDrawable == null) { - ViewCompat.setBackground(mView, mReactBackgroundDrawable); + ViewCompat.setBackground(mView, mCSSBackgroundDrawable); } else { LayerDrawable layerDrawable = - new LayerDrawable(new Drawable[] {mReactBackgroundDrawable, backgroundDrawable}); + new LayerDrawable(new Drawable[] {mCSSBackgroundDrawable, backgroundDrawable}); ViewCompat.setBackground(mView, layerDrawable); } } - return mReactBackgroundDrawable; + return mCSSBackgroundDrawable; } public void setBackgroundColor(int color) { - if (color == Color.TRANSPARENT && mReactBackgroundDrawable == null) { + if (color == Color.TRANSPARENT && mCSSBackgroundDrawable == null) { // don't do anything, no need to allocate ReactBackgroundDrawable for transparent background } else { getOrCreateReactViewBackground().setColor(color); @@ -84,4 +95,50 @@ public void setBorderRadius(float borderRadius, int position) { public void setBorderStyle(@Nullable String style) { getOrCreateReactViewBackground().setBorderStyle(style); } + + public void setOverflow(@Nullable String overflow) { + Overflow lastOverflow = mOverflow; + + if ("hidden".equals(overflow)) { + mOverflow = Overflow.HIDDEN; + } else if ("scroll".equals(overflow)) { + mOverflow = Overflow.SCROLL; + } else { + mOverflow = Overflow.VISIBLE; + } + + if (lastOverflow != mOverflow) { + mView.invalidate(); + } + } + + /** + * Sets the canvas clipping region to exclude any area below or outide of borders if "overflow" is + * set to clip to padding box. + */ + public void maybeClipToPaddingBox(Canvas canvas) { + if (mOverflow == Overflow.VISIBLE) { + return; + } + + // The canvas may be scrolled, so we need to offset + Rect drawingRect = new Rect(); + mView.getDrawingRect(drawingRect); + + @Nullable CSSBackgroundDrawable cssBackground = mCSSBackgroundDrawable; + if (cssBackground == null) { + canvas.clipRect(drawingRect); + return; + } + + @Nullable Path paddingBoxPath = cssBackground.getPaddingBoxPath(); + if (paddingBoxPath != null) { + paddingBoxPath.offset(drawingRect.left, drawingRect.top); + canvas.clipPath(paddingBoxPath); + } else { + RectF paddingBoxRect = cssBackground.getPaddingBoxRect(); + paddingBoxRect.offset(drawingRect.left, drawingRect.top); + canvas.clipRect(paddingBoxRect); + } + } }