|
13 | 13 | import android.content.Context;
|
14 | 14 | import android.graphics.Canvas;
|
15 | 15 | import android.graphics.Color;
|
| 16 | +import android.graphics.Matrix; |
16 | 17 | import android.graphics.Path;
|
17 | 18 | import android.graphics.Rect;
|
18 | 19 | import android.graphics.RectF;
|
|
45 | 46 | import com.facebook.react.uimanager.ViewGroupDrawingOrderHelper;
|
46 | 47 | import com.facebook.react.uimanager.ViewProps;
|
47 | 48 | import com.facebook.yoga.YogaConstants;
|
| 49 | +import java.util.ArrayList; |
48 | 50 |
|
49 | 51 | /**
|
50 | 52 | * Backing for a React View. Has support for borders, but since borders aren't common, lazy
|
@@ -887,4 +889,146 @@ public void setBackfaceVisibilityDependantOpacity() {
|
887 | 889 |
|
888 | 890 | setAlpha(0);
|
889 | 891 | }
|
| 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 |
890 | 1034 | }
|
0 commit comments