mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Fix overflowInset to take into account hitSlop
Summary: Previously, the touch area could never extend past the parent view bounds. Thus, hitSlop would not have any effect if it extended beyond any ancestor view's view bounds. In this diff, we apply hitSlop when calculating overflowInset. Specifically, we make sure to union the hit slop areas of all children when calculating the contentFrame. overflowInset is then used for hit testing (as in [TouchTargetHelper.java](https://www.internalfb.com/code/fbsource/[b0f630bb24271d8ed543e98ff144590290e19805]/xplat/js/react-native-github/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java?lines=195)). A risk is that other optimizations that may potentially rely on overflowInset beyond hit testing (such as clipToBounds) may not be as efficient with a large hitSlop. Changelog: [General][Fixed] - Fix touch handling so that hitSlop can extend beyond parent view bounds. Reviewed By: javache Differential Revision: D43854774 fbshipit-source-id: 160bef135b8487c28c4ada662577c35a7a36f484
This commit is contained in:
committed by
Facebook GitHub Bot
parent
f264fe12df
commit
96659f8e83
@@ -10,6 +10,7 @@
|
||||
#include <react/debug/flags.h>
|
||||
#include <react/debug/react_native_assert.h>
|
||||
#include <react/renderer/components/view/ViewProps.h>
|
||||
#include <react/renderer/components/view/ViewShadowNode.h>
|
||||
#include <react/renderer/components/view/conversions.h>
|
||||
#include <react/renderer/core/LayoutConstraints.h>
|
||||
#include <react/renderer/core/LayoutContext.h>
|
||||
@@ -624,12 +625,19 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) {
|
||||
|
||||
auto layoutMetricsWithOverflowInset = childNode.getLayoutMetrics();
|
||||
if (layoutMetricsWithOverflowInset.displayType != DisplayType::None) {
|
||||
auto viewChildNode = traitCast<ViewShadowNode const *>(&childNode);
|
||||
auto hitSlop = viewChildNode != nullptr
|
||||
? viewChildNode->getConcreteProps().hitSlop
|
||||
: EdgeInsets{};
|
||||
|
||||
// The contentFrame should always union with existing child node layout +
|
||||
// overflowInset. The transform may in a deferred animation and not
|
||||
// applied yet.
|
||||
contentFrame.unionInPlace(insetBy(
|
||||
layoutMetricsWithOverflowInset.frame,
|
||||
layoutMetricsWithOverflowInset.overflowInset));
|
||||
contentFrame.unionInPlace(
|
||||
outsetBy(layoutMetricsWithOverflowInset.frame, hitSlop));
|
||||
|
||||
auto childTransform = childNode.getTransform();
|
||||
if (childTransform != Transform::Identity()) {
|
||||
@@ -639,6 +647,8 @@ void YogaLayoutableShadowNode::layout(LayoutContext layoutContext) {
|
||||
contentFrame.unionInPlace(insetBy(
|
||||
layoutMetricsWithOverflowInset.frame * childTransform,
|
||||
layoutMetricsWithOverflowInset.overflowInset * childTransform));
|
||||
contentFrame.unionInPlace(outsetBy(
|
||||
layoutMetricsWithOverflowInset.frame * childTransform, hitSlop));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,14 @@ namespace facebook::react {
|
||||
// ***********************│ │************************************
|
||||
// ***********************└──────────┘************************************
|
||||
|
||||
enum TestCase { AS_IS, CLIPPING, TRANSFORM_SCALE, TRANSFORM_TRANSLATE };
|
||||
enum TestCase {
|
||||
AS_IS,
|
||||
CLIPPING,
|
||||
HIT_SLOP,
|
||||
HIT_SLOP_TRANSFORM_TRANSLATE,
|
||||
TRANSFORM_SCALE,
|
||||
TRANSFORM_TRANSLATE,
|
||||
};
|
||||
|
||||
class LayoutTest : public ::testing::Test {
|
||||
protected:
|
||||
@@ -105,9 +112,14 @@ class LayoutTest : public ::testing::Test {
|
||||
props.transform = props.transform * Transform::Scale(2, 2, 1);
|
||||
}
|
||||
|
||||
if (testCase == TRANSFORM_TRANSLATE) {
|
||||
if (testCase == TRANSFORM_TRANSLATE || testCase == HIT_SLOP_TRANSFORM_TRANSLATE) {
|
||||
props.transform = props.transform * Transform::Translate(10, 10, 0);
|
||||
}
|
||||
|
||||
if (testCase == HIT_SLOP || testCase == HIT_SLOP_TRANSFORM_TRANSLATE) {
|
||||
props.hitSlop = EdgeInsets{50, 50, 50, 50};
|
||||
}
|
||||
|
||||
return sharedProps;
|
||||
})
|
||||
.children({
|
||||
@@ -358,4 +370,78 @@ TEST_F(LayoutTest, overflowInsetTransformScaleTest) {
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
|
||||
}
|
||||
|
||||
TEST_F(LayoutTest, overflowInsetHitSlopTest) {
|
||||
initialize(HIT_SLOP);
|
||||
|
||||
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
|
||||
|
||||
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
|
||||
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
|
||||
|
||||
// Change on parent node
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.top, -40);
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.right, -80);
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -100);
|
||||
|
||||
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
|
||||
|
||||
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
|
||||
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
|
||||
|
||||
// No change on self node
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
|
||||
|
||||
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
|
||||
|
||||
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
|
||||
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
|
||||
|
||||
// No change on child node
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
|
||||
}
|
||||
|
||||
TEST_F(LayoutTest, overflowInsetHitSlopTransformTranslateTest) {
|
||||
initialize(HIT_SLOP_TRANSFORM_TRANSLATE);
|
||||
|
||||
auto layoutMetricsA = viewShadowNodeA_->getLayoutMetrics();
|
||||
|
||||
EXPECT_EQ(layoutMetricsA.frame.size.width, 50);
|
||||
EXPECT_EQ(layoutMetricsA.frame.size.height, 50);
|
||||
|
||||
// Change on parent node
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.left, -50);
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.top, -40);
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.right, -90);
|
||||
EXPECT_EQ(layoutMetricsA.overflowInset.bottom, -110);
|
||||
|
||||
auto layoutMetricsAB = viewShadowNodeAB_->getLayoutMetrics();
|
||||
|
||||
EXPECT_EQ(layoutMetricsAB.frame.size.width, 30);
|
||||
EXPECT_EQ(layoutMetricsAB.frame.size.height, 90);
|
||||
|
||||
// No change on self node
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.left, -60);
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.top, -40);
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.right, -90);
|
||||
EXPECT_EQ(layoutMetricsAB.overflowInset.bottom, 0);
|
||||
|
||||
auto layoutMetricsABC = viewShadowNodeABC_->getLayoutMetrics();
|
||||
|
||||
EXPECT_EQ(layoutMetricsABC.frame.size.width, 110);
|
||||
EXPECT_EQ(layoutMetricsABC.frame.size.height, 20);
|
||||
|
||||
// No change on child node
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.left, 0);
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.top, -50);
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.right, 0);
|
||||
EXPECT_EQ(layoutMetricsABC.overflowInset.bottom, 0);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -83,6 +83,16 @@ inline Rect insetBy(Rect const &rect, EdgeInsets const &insets) noexcept {
|
||||
rect.size.height - insets.top - insets.bottom}};
|
||||
}
|
||||
|
||||
/*
|
||||
* Adjusts a rectangle by the given edge outsets.
|
||||
*/
|
||||
inline Rect outsetBy(Rect const &rect, EdgeInsets const &outsets) noexcept {
|
||||
return Rect{
|
||||
{rect.origin.x - outsets.left, rect.origin.y - outsets.top},
|
||||
{rect.size.width + outsets.left + outsets.right,
|
||||
rect.size.height + outsets.top + outsets.bottom}};
|
||||
}
|
||||
|
||||
} // namespace react
|
||||
} // namespace facebook
|
||||
|
||||
|
||||
Reference in New Issue
Block a user