mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Support clipping to children everywhere using ReactViewBackgroundManager (#44734)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44734 Fixes https://github.com/facebook/react-native/issues/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
This commit is contained in:
committed by
Facebook GitHub Bot
parent
c9d7774a24
commit
bfb3b7008d
@@ -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 {
|
||||
|
||||
+4
-10
@@ -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 class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
|
||||
setOnHierarchyChangeListener(this);
|
||||
setClipChildren(false);
|
||||
mReactBackgroundManager.setOverflow(ViewProps.SCROLL);
|
||||
}
|
||||
|
||||
public boolean getScrollEnabled() {
|
||||
@@ -273,7 +273,7 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
|
||||
public void setOverflow(@Nullable String overflow) {
|
||||
mOverflow = overflow;
|
||||
invalidate();
|
||||
mReactBackgroundManager.setOverflow(overflow == null ? ViewProps.SCROLL : overflow);
|
||||
}
|
||||
|
||||
public void setMaintainVisibleContentPosition(
|
||||
@@ -306,14 +306,8 @@ public class ReactHorizontalScrollView extends HorizontalScrollView
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -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 class ReactScrollView extends ScrollView
|
||||
setOnHierarchyChangeListener(this);
|
||||
setScrollBarStyle(SCROLLBARS_OUTSIDE_OVERLAY);
|
||||
setClipChildren(false);
|
||||
mReactBackgroundManager.setOverflow(ViewProps.SCROLL);
|
||||
|
||||
ViewCompat.setAccessibilityDelegate(this, new ReactScrollViewAccessibilityDelegate());
|
||||
}
|
||||
@@ -261,7 +261,7 @@ public class ReactScrollView extends ScrollView
|
||||
|
||||
public void setOverflow(@Nullable String overflow) {
|
||||
mOverflow = overflow;
|
||||
invalidate();
|
||||
mReactBackgroundManager.setOverflow(overflow == null ? ViewProps.SCROLL : overflow);
|
||||
}
|
||||
|
||||
public void setMaintainVisibleContentPosition(
|
||||
@@ -640,15 +640,16 @@ public class ReactScrollView extends ScrollView
|
||||
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
|
||||
|
||||
+6
@@ -382,6 +382,8 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
|
||||
setText(getSpanned());
|
||||
}
|
||||
|
||||
mReactBackgroundManager.maybeClipToPaddingBox(canvas);
|
||||
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
|
||||
@@ -741,4 +743,8 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie
|
||||
super.setLetterSpacing(mLetterSpacing);
|
||||
}
|
||||
}
|
||||
|
||||
public void setOverflow(@Nullable String overflow) {
|
||||
mReactBackgroundManager.setOverflow(overflow);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -23,6 +23,7 @@ import com.facebook.react.uimanager.ReactAccessibilityDelegate;
|
||||
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 class ReactTextViewManager
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -10,6 +10,7 @@ package com.facebook.react.views.textinput;
|
||||
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 @@ public class ReactEditText extends AppCompatEditText {
|
||||
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.
|
||||
|
||||
+5
@@ -1038,6 +1038,11 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
|
||||
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);
|
||||
|
||||
+66
-9
@@ -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 class ReactViewBackgroundManager {
|
||||
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 class ReactViewBackgroundManager {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user