mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-04 22:56:32 +08:00
Summary: @public When Android dispatches `MotionEvent` to `ReactRootView`, it needs to find a correspoding react node that should receive it. To be able to do it, we need to store boundaries of every `FlatShadowNode` in `FlatViewGroup`. Then we can iterate over node boundaries and find one that contains the touch event coordinates. Reviewed By: sriramramani Differential Revision: D2694197
224 lines
6.7 KiB
Java
224 lines
6.7 KiB
Java
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
package com.facebook.react.flat;
|
|
|
|
import java.lang.ref.WeakReference;
|
|
|
|
import javax.annotation.Nullable;
|
|
|
|
import android.content.Context;
|
|
import android.graphics.Canvas;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.ViewParent;
|
|
|
|
import com.facebook.react.uimanager.ReactCompoundView;
|
|
|
|
/**
|
|
* A view that FlatShadowNode hierarchy maps to. Performs drawing by iterating over
|
|
* array of DrawCommands, executing them one by one.
|
|
*/
|
|
/* package */ final class FlatViewGroup extends ViewGroup implements ReactCompoundView {
|
|
/**
|
|
* Helper class that allows AttachDetachListener to invalidate the hosting View.
|
|
*/
|
|
static final class InvalidateCallback extends WeakReference<FlatViewGroup> {
|
|
|
|
private InvalidateCallback(FlatViewGroup view) {
|
|
super(view);
|
|
}
|
|
|
|
/**
|
|
* Propagates invalidate() call up to the hosting View (if it's still alive)
|
|
*/
|
|
public void invalidate() {
|
|
FlatViewGroup view = get();
|
|
if (view != null) {
|
|
view.invalidate();
|
|
}
|
|
}
|
|
}
|
|
|
|
private @Nullable InvalidateCallback mInvalidateCallback;
|
|
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
|
|
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
|
|
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
|
|
private int mDrawChildIndex = 0;
|
|
private boolean mIsAttached = false;
|
|
|
|
/* package */ FlatViewGroup(Context context) {
|
|
super(context);
|
|
}
|
|
|
|
@Override
|
|
protected void detachAllViewsFromParent() {
|
|
super.detachAllViewsFromParent();
|
|
}
|
|
|
|
@Override
|
|
public int reactTagForTouch(float touchX, float touchY) {
|
|
for (NodeRegion nodeRegion : mNodeRegions) {
|
|
if (nodeRegion.mLeft <= touchX && touchX < nodeRegion.mRight &&
|
|
nodeRegion.mTop <= touchY && touchY < nodeRegion.mBottom) {
|
|
return nodeRegion.mTag;
|
|
}
|
|
}
|
|
|
|
// no children found
|
|
return getId();
|
|
}
|
|
|
|
@Override
|
|
public void dispatchDraw(Canvas canvas) {
|
|
super.dispatchDraw(canvas);
|
|
|
|
for (DrawCommand drawCommand : mDrawCommands) {
|
|
drawCommand.draw(this, canvas);
|
|
}
|
|
|
|
if (mDrawChildIndex != getChildCount()) {
|
|
throw new RuntimeException(
|
|
"Did not draw all children: " + mDrawChildIndex + " / " + getChildCount());
|
|
}
|
|
mDrawChildIndex = 0;
|
|
}
|
|
|
|
@Override
|
|
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
|
|
// suppress
|
|
// no drawing -> no invalidate -> return false
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
|
// nothing to do here
|
|
}
|
|
|
|
@Override
|
|
protected void onAttachedToWindow() {
|
|
if (mIsAttached) {
|
|
// this is possible, unfortunately.
|
|
return;
|
|
}
|
|
|
|
mIsAttached = true;
|
|
|
|
super.onAttachedToWindow();
|
|
dispatchOnAttached(mAttachDetachListeners);
|
|
}
|
|
|
|
@Override
|
|
protected void onDetachedFromWindow() {
|
|
if (!mIsAttached) {
|
|
throw new RuntimeException("Double detach");
|
|
}
|
|
|
|
mIsAttached = false;
|
|
|
|
super.onDetachedFromWindow();
|
|
dispatchOnDetached(mAttachDetachListeners);
|
|
}
|
|
|
|
/* package */ void drawNextChild(Canvas canvas) {
|
|
View child = getChildAt(mDrawChildIndex);
|
|
super.drawChild(canvas, child, getDrawingTime());
|
|
|
|
++mDrawChildIndex;
|
|
}
|
|
|
|
/* package */ void mountDrawCommands(DrawCommand[] drawCommands) {
|
|
mDrawCommands = drawCommands;
|
|
invalidate();
|
|
}
|
|
|
|
/* package */ void mountAttachDetachListeners(AttachDetachListener[] listeners) {
|
|
if (mIsAttached) {
|
|
// Ordering of the following 2 statements is very important. While logically it makes sense to
|
|
// detach old listeners first, and only then attach new listeners, this is not very efficient,
|
|
// because a listener can be in both lists. In this case, it will be detached first and then
|
|
// re-attached immediately. This is undesirable for a couple of reasons:
|
|
// 1) performance. Detaching is slow because it may cancel an ongoing network request
|
|
// 2) it may cause flicker: an image that was already loaded may get unloaded.
|
|
//
|
|
// For this reason, we are attaching new listeners first. What this means is that listeners
|
|
// that are in both lists need to gracefully handle a secondary attach and detach events,
|
|
// (i.e. onAttach() being called when already attached, followed by a detach that should be
|
|
// ignored) turning them into no-ops. This will result in no performance loss and no flicker,
|
|
// because ongoing network requests don't get cancelled.
|
|
dispatchOnAttached(listeners);
|
|
dispatchOnDetached(mAttachDetachListeners);
|
|
}
|
|
mAttachDetachListeners = listeners;
|
|
}
|
|
|
|
/* package */ void mountNodeRegions(NodeRegion[] nodeRegions) {
|
|
mNodeRegions = nodeRegions;
|
|
}
|
|
|
|
/* package */ void mountViews(ViewResolver viewResolver, int[] viewsToAdd, int[] viewsToDetach) {
|
|
for (int viewToAdd : viewsToAdd) {
|
|
if (viewToAdd > 0) {
|
|
View view = ensureViewHasNoParent(viewResolver.getView(viewToAdd));
|
|
addView(view, -1, ensureLayoutParams(view.getLayoutParams()));
|
|
} else {
|
|
View view = ensureViewHasNoParent(viewResolver.getView(-viewToAdd));
|
|
attachViewToParent(view, -1, ensureLayoutParams(view.getLayoutParams()));
|
|
}
|
|
}
|
|
|
|
for (int viewToDetach : viewsToDetach) {
|
|
removeDetachedView(viewResolver.getView(viewToDetach), false);
|
|
}
|
|
}
|
|
|
|
private View ensureViewHasNoParent(View view) {
|
|
ViewParent oldParent = view.getParent();
|
|
if (oldParent != null) {
|
|
throw new RuntimeException(
|
|
"Cannot add view " + view + " to " + this + " while it has a parent " + oldParent);
|
|
}
|
|
|
|
return view;
|
|
}
|
|
|
|
private void dispatchOnAttached(AttachDetachListener[] listeners) {
|
|
int numListeners = listeners.length;
|
|
if (numListeners == 0) {
|
|
return;
|
|
}
|
|
|
|
InvalidateCallback callback = getInvalidateCallback();
|
|
for (AttachDetachListener listener : listeners) {
|
|
listener.onAttached(callback);
|
|
}
|
|
}
|
|
|
|
private InvalidateCallback getInvalidateCallback() {
|
|
if (mInvalidateCallback == null) {
|
|
mInvalidateCallback = new InvalidateCallback(this);
|
|
}
|
|
return mInvalidateCallback;
|
|
}
|
|
|
|
private static void dispatchOnDetached(AttachDetachListener[] listeners) {
|
|
for (AttachDetachListener listener : listeners) {
|
|
listener.onDetached();
|
|
}
|
|
}
|
|
|
|
private ViewGroup.LayoutParams ensureLayoutParams(ViewGroup.LayoutParams lp) {
|
|
if (checkLayoutParams(lp)) {
|
|
return lp;
|
|
}
|
|
return generateDefaultLayoutParams();
|
|
}
|
|
}
|