/**
* 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 javax.annotation.Nullable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.SoftAssertions;
import com.facebook.react.touch.OnInterceptTouchEventListener;
import com.facebook.react.touch.ReactHitSlopView;
import com.facebook.react.touch.ReactInterceptingViewGroup;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.ReactCompoundViewGroup;
import com.facebook.react.uimanager.ReactPointerEventsView;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.views.image.ImageLoadEvent;
import com.facebook.react.uimanager.ReactClippingViewGroup;
/**
* A view that the {@link FlatShadowNode} hierarchy maps to. Can mount and draw native views as
* well as draw commands. We reuse some of Android's ViewGroup logic, but in Nodes we try to
* minimize the amount of shadow nodes that map to native children, so we have a lot of logic
* specific to draw commands.
*
* In a very simple case with no Android children, the FlatViewGroup will receive:
*
* flatViewGroup.mountDrawCommands(...);
* flatViewGroup.dispatchDraw(...);
*
* The draw commands are mounted, then draw iterates through and draws them one by one.
*
* In a simple case where there are native children:
*
* flatViewGroup.mountDrawCommands(...);
* flatViewGroup.detachAllViewsFromParent(...);
* flatViewGroup.mountViews(...);
* flatViewGroup.dispatchDraw(...);
*
* Draw commands are mounted, with a draw view command for each mounted view. As an optimization
* we then detach all views from the FlatViewGroup, then allow mountViews to selectively reattach
* and add views in order. We do this as adding a single view is a O(n) operation (On average you
* have to move all the views in the array to the right one position), as is dropping and re-adding
* all views (One pass to clear the array and one pass to re-attach detached children and add new
* children).
*
* FlatViewGroups also have arrays of node regions, which are little more than a rects that
* represents a touch target. Native views contain their own touch logic, but not all react tags
* map to native views. We use node regions to find touch targets among commands as well as nodes
* which map to native views.
*
* In the case of clipping, much of the underlying logic for is handled by
* {@link DrawCommandManager}. This lets us separate logic, while also allowing us to save on
* memory for data structures only used in clipping. In a case of a clipping FlatViewGroup which
* is scrolling:
*
* flatViewGroup.setRemoveClippedSubviews(true);
* flatViewGroup.mountClippingDrawCommands(...);
* flatViewGroup.detachAllViewsFromParent(...);
* flatViewGroup.mountViews(...);
* flatViewGroup.updateClippingRect(...);
* flatViewGroup.dispatchDraw(...);
* flatViewGroup.updateClippingRect(...);
* flatViewGroup.dispatchDraw(...);
* flatViewGroup.updateClippingRect(...);
* flatViewGroup.dispatchDraw(...);
*
* Setting remove clipped subviews creates a {@link DrawCommandManager} to handle clipping, which
* allows the rest of the methods to simply call in to draw command manager to handle the clipping
* logic.
*/
/* package */ final class FlatViewGroup extends ViewGroup
implements ReactInterceptingViewGroup, ReactClippingViewGroup,
ReactCompoundViewGroup, ReactHitSlopView, ReactPointerEventsView, FlatMeasuredViewGroup {
/**
* Helper class that allows our AttachDetachListeners to invalidate the hosting View. When a
* listener gets an attach it is passed an invalidate callback for the FlatViewGroup it is being
* attached to.
*/
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();
}
}
/**
* Propogates image load events to javascript if the hosting view is still alive.
*
* @param reactTag The view id.
* @param imageLoadEvent The event type.
*/
public void dispatchImageLoadEvent(int reactTag, int imageLoadEvent) {
FlatViewGroup view = get();
if (view == null) {
return;
}
ReactContext reactContext = ((ReactContext) view.getContext());
UIManagerModule uiManagerModule = reactContext.getNativeModule(UIManagerModule.class);
uiManagerModule.getEventDispatcher().dispatchEvent(
new ImageLoadEvent(reactTag, imageLoadEvent));
}
}
// Draws the name of the draw commands at the bottom right corner of it's bounds.
private static final boolean DEBUG_DRAW_TEXT = false;
// Draws colored rectangles over known performance issues.
/* package */ static final boolean DEBUG_HIGHLIGHT_PERFORMANCE_ISSUES = false;
// Force layout bounds drawing. This can also be enabled by turning on layout bounds in Android.
private static final boolean DEBUG_DRAW = DEBUG_DRAW_TEXT || DEBUG_HIGHLIGHT_PERFORMANCE_ISSUES;
// Resources for debug drawing.
private boolean mAndroidDebugDraw;
private static Paint sDebugTextPaint;
private static Paint sDebugTextBackgroundPaint;
private static Paint sDebugRectPaint;
private static Paint sDebugCornerPaint;
private static Rect sDebugRect;
private static final ArrayList<FlatViewGroup> LAYOUT_REQUESTS = new ArrayList<>();
private static final Rect VIEW_BOUNDS = new Rect();
// An invalidate callback singleton for this FlatViewGroup.
private @Nullable InvalidateCallback mInvalidateCallback;
private DrawCommand[] mDrawCommands = DrawCommand.EMPTY_ARRAY;
private AttachDetachListener[] mAttachDetachListeners = AttachDetachListener.EMPTY_ARRAY;
private NodeRegion[] mNodeRegions = NodeRegion.EMPTY_ARRAY;
// The index of the next native child to draw. This is used in dispatchDraw to check that we are
// actually drawing all of our attached children, then is reset to 0.
private int mDrawChildIndex = 0;
private boolean mIsAttached = false;
private boolean mIsLayoutRequested = false;
private boolean mNeedsOffscreenAlphaCompositing = false;
private Drawable mHotspot;
private PointerEvents mPointerEvents = PointerEvents.AUTO;
private long mLastTouchDownTime;
private @Nullable OnInterceptTouchEventListener mOnInterceptTouchEventListener;
private static final SparseArray<View> EMPTY_DETACHED_VIEWS = new SparseArray<>(0);
// Provides clipping, drawing and node region finding logic if subview clipping is enabled.
private @Nullable DrawCommandManager mDrawCommandManager;
private @Nullable Rect mHitSlopRect;
/* package */ FlatViewGroup(Context context) {
super(context);
setClipChildren(false);
}
@Override
protected void detachAllViewsFromParent() {
super.detachAllViewsFromParent();
}
@Override
@SuppressLint("MissingSuperCall")
public void requestLayout() {
if (mIsLayoutReque