Skip to content

Commit ac2a12f

Browse files
committed
Allow touches on clipped children when clipChildren==false
1 parent 95a5101 commit ac2a12f

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

ReactAndroid/src/main/java/com/facebook/react/views/view/ReactViewGroup.java

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import android.content.Context;
1414
import android.graphics.Canvas;
1515
import android.graphics.Color;
16+
import android.graphics.Matrix;
1617
import android.graphics.Path;
1718
import android.graphics.Rect;
1819
import android.graphics.RectF;
@@ -45,6 +46,7 @@
4546
import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
4647
import com.facebook.react.uimanager.ViewProps;
4748
import com.facebook.yoga.YogaConstants;
49+
import java.util.ArrayList;
4850

4951
/**
5052
* Backing for a React View. Has support for borders, but since borders aren't common, lazy
@@ -887,4 +889,146 @@ public void setBackfaceVisibilityDependantOpacity() {
887889

888890
setAlpha(0);
889891
}
892+
893+
//region Handling of clipped touches
894+
895+
private boolean hasChildWithZ() {
896+
int childCount = getChildCount();
897+
for (int i = 0; i < childCount; i++)
898+
if (getChildAt(i).getZ() != 0) return true;
899+
return false;
900+
}
901+
902+
private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
903+
final int childIndex;
904+
if (customOrder) {
905+
final int childIndex1 = getChildDrawingOrder(childrenCount, i);
906+
if (childIndex1 >= childrenCount) {
907+
throw new IndexOutOfBoundsException("getChildDrawingOrder() "
908+
+ "returned invalid index " + childIndex1
909+
+ " (child count is " + childrenCount + ")");
910+
}
911+
childIndex = childIndex1;
912+
} else {
913+
childIndex = i;
914+
}
915+
return childIndex;
916+
}
917+
918+
private ArrayList<View> mPreSortedChildren;
919+
920+
private ArrayList<View> buildOrderedChildList() {
921+
final int childrenCount = getChildCount();
922+
if (childrenCount <= 1 || !hasChildWithZ()) return null;
923+
924+
if (mPreSortedChildren == null) {
925+
mPreSortedChildren = new ArrayList<>(childrenCount);
926+
} else {
927+
// callers should clear, so clear shouldn't be necessary, but for safety...
928+
mPreSortedChildren.clear();
929+
mPreSortedChildren.ensureCapacity(childrenCount);
930+
}
931+
932+
final boolean customOrder = isChildrenDrawingOrderEnabled();
933+
for (int i = 0; i < childrenCount; i++) {
934+
// add next child (in child order) to end of list
935+
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
936+
final View nextChild = getChildAt(childIndex);
937+
final float currentZ = nextChild.getZ();
938+
939+
// insert ahead of any Views with greater Z
940+
int insertIndex = i;
941+
while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
942+
insertIndex--;
943+
}
944+
mPreSortedChildren.add(insertIndex, nextChild);
945+
}
946+
return mPreSortedChildren;
947+
}
948+
949+
private static boolean canViewReceivePointerEvents(@NonNull View child) {
950+
return child.getVisibility() == VISIBLE || child.getAnimation() != null;
951+
}
952+
953+
private Matrix mTempMatrix = null;
954+
955+
@Override
956+
public boolean dispatchTouchEvent(MotionEvent ev) {
957+
if (super.dispatchTouchEvent(ev))
958+
return true;
959+
960+
if (ev == null || !onFilterTouchEventForSecurity(ev) || getClipChildren())
961+
return false;
962+
963+
// Now what we're going to do, is take some of Android's
964+
// routines for traversing through child views,
965+
// and pass the events down *without* testing for isPointView, as we're expecting it to *not* be in the view.
966+
// super.dispatchTouchEvent() already tested for points inside the view,
967+
// so if we're doing the extra work here it's only to find a target for the touch.
968+
//
969+
// Also note, that the one who's actually clipping the children - is the grandparent, not the parent.
970+
// That's right. Android's layout system is deeply flawed.
971+
//
972+
// Note: A lot of stuff is not in here, like accessibility and stuff.
973+
// That's basically okay, as the grandparent is passing its touches to the
974+
// parent, which will do it's normal magic as usual, except it will now
975+
// work for the touches that are outside the clipping zone.
976+
977+
boolean handled = false;
978+
979+
final int childrenCount = getChildCount();
980+
981+
if (childrenCount > 0) {
982+
final ArrayList<View> preorderedList = buildOrderedChildList();
983+
984+
final boolean customOrder = preorderedList == null
985+
&& isChildrenDrawingOrderEnabled();
986+
987+
for (int i = childrenCount - 1; i >= 0; i--) {
988+
final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
989+
View child = preorderedList == null ? null : preorderedList.get(childIndex);
990+
if (child == null)
991+
child = getChildAt(childIndex);
992+
993+
if (!canViewReceivePointerEvents(child))
994+
continue;
995+
996+
final MotionEvent transformedEvent;
997+
if (child.getMatrix().isIdentity()) {
998+
final float offsetX = getScrollX() - child.getLeft();
999+
final float offsetY = getScrollY() - child.getTop();
1000+
ev.offsetLocation(offsetX, offsetY);
1001+
1002+
handled = child.dispatchTouchEvent(ev);
1003+
1004+
ev.offsetLocation(-offsetX, -offsetY);
1005+
} else {
1006+
transformedEvent = MotionEvent.obtain(ev);
1007+
final float offsetX = getScrollX() - child.getLeft();
1008+
final float offsetY = getScrollY() - child.getTop();
1009+
transformedEvent.offsetLocation(offsetX, offsetY);
1010+
if (!child.getMatrix().isIdentity()) {
1011+
if (mTempMatrix == null)
1012+
mTempMatrix = new Matrix();
1013+
child.getMatrix().invert(mTempMatrix);
1014+
transformedEvent.transform(mTempMatrix);
1015+
}
1016+
1017+
handled = child.dispatchTouchEvent(transformedEvent);
1018+
1019+
transformedEvent.recycle();
1020+
}
1021+
1022+
if (handled)
1023+
break;
1024+
}
1025+
1026+
if (preorderedList != null)
1027+
preorderedList.clear();
1028+
}
1029+
1030+
return handled;
1031+
}
1032+
1033+
//endregion
8901034
}

0 commit comments

Comments
 (0)