com.facebook.react.views.view.ReactViewManager.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.react.views.view.ReactViewManager.java

Source

/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package com.facebook.react.views.view;

import android.annotation.TargetApi;
import android.graphics.Rect;
import android.os.Build;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.PointerEvents;
import com.facebook.react.uimanager.Spacing;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewProps;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.yoga.YogaConstants;
import java.util.Locale;
import java.util.Map;

/** View manager for AndroidViews (plain React Views). */
@ReactModule(name = ReactViewManager.REACT_CLASS)
public class ReactViewManager extends ReactClippingViewManager<ReactViewGroup> {

    @VisibleForTesting
    public static final String REACT_CLASS = ViewProps.VIEW_CLASS_NAME;

    private static final int[] SPACING_TYPES = { Spacing.ALL, Spacing.LEFT, Spacing.RIGHT, Spacing.TOP,
            Spacing.BOTTOM, Spacing.START, Spacing.END, };
    private static final int CMD_HOTSPOT_UPDATE = 1;
    private static final int CMD_SET_PRESSED = 2;
    private static final String HOTSPOT_UPDATE_KEY = "hotspotUpdate";

    @ReactProp(name = "accessible")
    public void setAccessible(ReactViewGroup view, boolean accessible) {
        view.setFocusable(accessible);
    }

    @ReactProp(name = "hasTVPreferredFocus")
    public void setTVPreferredFocus(ReactViewGroup view, boolean hasTVPreferredFocus) {
        if (hasTVPreferredFocus) {
            view.setFocusable(true);
            view.setFocusableInTouchMode(true);
            view.requestFocus();
        }
    }

    @ReactProp(name = "nextFocusDown", defaultInt = View.NO_ID)
    public void nextFocusDown(ReactViewGroup view, int viewId) {
        view.setNextFocusDownId(viewId);
    }

    @ReactProp(name = "nextFocusForward", defaultInt = View.NO_ID)
    public void nextFocusForward(ReactViewGroup view, int viewId) {
        view.setNextFocusForwardId(viewId);
    }

    @ReactProp(name = "nextFocusLeft", defaultInt = View.NO_ID)
    public void nextFocusLeft(ReactViewGroup view, int viewId) {
        view.setNextFocusLeftId(viewId);
    }

    @ReactProp(name = "nextFocusRight", defaultInt = View.NO_ID)
    public void nextFocusRight(ReactViewGroup view, int viewId) {
        view.setNextFocusRightId(viewId);
    }

    @ReactProp(name = "nextFocusUp", defaultInt = View.NO_ID)
    public void nextFocusUp(ReactViewGroup view, int viewId) {
        view.setNextFocusUpId(viewId);
    }

    @ReactPropGroup(names = { ViewProps.BORDER_RADIUS, ViewProps.BORDER_TOP_LEFT_RADIUS,
            ViewProps.BORDER_TOP_RIGHT_RADIUS, ViewProps.BORDER_BOTTOM_RIGHT_RADIUS,
            ViewProps.BORDER_BOTTOM_LEFT_RADIUS, ViewProps.BORDER_TOP_START_RADIUS, ViewProps.BORDER_TOP_END_RADIUS,
            ViewProps.BORDER_BOTTOM_START_RADIUS,
            ViewProps.BORDER_BOTTOM_END_RADIUS, }, defaultFloat = YogaConstants.UNDEFINED)
    public void setBorderRadius(ReactViewGroup view, int index, float borderRadius) {
        if (!YogaConstants.isUndefined(borderRadius) && borderRadius < 0) {
            borderRadius = YogaConstants.UNDEFINED;
        }

        if (!YogaConstants.isUndefined(borderRadius)) {
            borderRadius = PixelUtil.toPixelFromDIP(borderRadius);
        }

        if (index == 0) {
            view.setBorderRadius(borderRadius);
        } else {
            view.setBorderRadius(borderRadius, index - 1);
        }
    }

    @ReactProp(name = "borderStyle")
    public void setBorderStyle(ReactViewGroup view, @Nullable String borderStyle) {
        view.setBorderStyle(borderStyle);
    }

    @ReactProp(name = "hitSlop")
    public void setHitSlop(final ReactViewGroup view, @Nullable ReadableMap hitSlop) {
        if (hitSlop == null) {
            view.setHitSlopRect(null);
        } else {
            view.setHitSlopRect(new Rect(
                    hitSlop.hasKey("left") ? (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("left")) : 0,
                    hitSlop.hasKey("top") ? (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("top")) : 0,
                    hitSlop.hasKey("right") ? (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("right")) : 0,
                    hitSlop.hasKey("bottom") ? (int) PixelUtil.toPixelFromDIP(hitSlop.getDouble("bottom")) : 0));
        }
    }

    @ReactProp(name = ViewProps.POINTER_EVENTS)
    public void setPointerEvents(ReactViewGroup view, @Nullable String pointerEventsStr) {
        if (pointerEventsStr == null) {
            view.setPointerEvents(PointerEvents.AUTO);
        } else {
            PointerEvents pointerEvents = PointerEvents
                    .valueOf(pointerEventsStr.toUpperCase(Locale.US).replace("-", "_"));
            view.setPointerEvents(pointerEvents);
        }
    }

    @ReactProp(name = "nativeBackgroundAndroid")
    public void setNativeBackground(ReactViewGroup view, @Nullable ReadableMap bg) {
        view.setTranslucentBackgroundDrawable(
                bg == null ? null : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), bg));
    }

    @TargetApi(Build.VERSION_CODES.M)
    @ReactProp(name = "nativeForegroundAndroid")
    public void setNativeForeground(ReactViewGroup view, @Nullable ReadableMap fg) {
        view.setForeground(
                fg == null ? null : ReactDrawableHelper.createDrawableFromJSDescription(view.getContext(), fg));
    }

    @ReactProp(name = ViewProps.NEEDS_OFFSCREEN_ALPHA_COMPOSITING)
    public void setNeedsOffscreenAlphaCompositing(ReactViewGroup view, boolean needsOffscreenAlphaCompositing) {
        view.setNeedsOffscreenAlphaCompositing(needsOffscreenAlphaCompositing);
    }

    @ReactPropGroup(names = { ViewProps.BORDER_WIDTH, ViewProps.BORDER_LEFT_WIDTH, ViewProps.BORDER_RIGHT_WIDTH,
            ViewProps.BORDER_TOP_WIDTH, ViewProps.BORDER_BOTTOM_WIDTH, ViewProps.BORDER_START_WIDTH,
            ViewProps.BORDER_END_WIDTH, }, defaultFloat = YogaConstants.UNDEFINED)
    public void setBorderWidth(ReactViewGroup view, int index, float width) {
        if (!YogaConstants.isUndefined(width) && width < 0) {
            width = YogaConstants.UNDEFINED;
        }

        if (!YogaConstants.isUndefined(width)) {
            width = PixelUtil.toPixelFromDIP(width);
        }

        view.setBorderWidth(SPACING_TYPES[index], width);
    }

    @ReactPropGroup(names = { ViewProps.BORDER_COLOR, ViewProps.BORDER_LEFT_COLOR, ViewProps.BORDER_RIGHT_COLOR,
            ViewProps.BORDER_TOP_COLOR, ViewProps.BORDER_BOTTOM_COLOR, ViewProps.BORDER_START_COLOR,
            ViewProps.BORDER_END_COLOR }, customType = "Color")
    public void setBorderColor(ReactViewGroup view, int index, Integer color) {
        float rgbComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int) color & 0x00FFFFFF);
        float alphaComponent = color == null ? YogaConstants.UNDEFINED : (float) ((int) color >>> 24);
        view.setBorderColor(SPACING_TYPES[index], rgbComponent, alphaComponent);
    }

    @ReactProp(name = ViewProps.COLLAPSABLE)
    public void setCollapsable(ReactViewGroup view, boolean collapsable) {
        // no-op: it's here only so that "collapsable" property is exported to JS. The value is actually
        // handled in NativeViewHierarchyOptimizer
    }

    @ReactProp(name = "focusable")
    public void setFocusable(final ReactViewGroup view, boolean focusable) {
        if (focusable) {
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    final EventDispatcher mEventDispatcher = ((ReactContext) view.getContext())
                            .getNativeModule(UIManagerModule.class).getEventDispatcher();
                    mEventDispatcher.dispatchEvent(new ViewGroupClickEvent(view.getId()));
                }
            });

            // Clickable elements are focusable. On API 26, this is taken care by setClickable.
            // Explicitly calling setFocusable here for backward compatibility.
            view.setFocusable(true /*isFocusable*/);
        } else {
            view.setOnClickListener(null);
            view.setClickable(false);
            // Don't set view.setFocusable(false) because we might still want it to be focusable for
            // accessibility reasons
        }
    }

    @ReactProp(name = ViewProps.OVERFLOW)
    public void setOverflow(ReactViewGroup view, String overflow) {
        view.setOverflow(overflow);
    }

    @ReactProp(name = "backfaceVisibility")
    public void setBackfaceVisibility(ReactViewGroup view, String backfaceVisibility) {
        view.setBackfaceVisibility(backfaceVisibility);
    }

    @Override
    public void setOpacity(@NonNull ReactViewGroup view, float opacity) {
        view.setOpacityIfPossible(opacity);
    }

    @Override
    public void setTransform(@NonNull ReactViewGroup view, @Nullable ReadableArray matrix) {
        super.setTransform(view, matrix);
        view.setBackfaceVisibilityDependantOpacity();
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @Override
    public ReactViewGroup createViewInstance(ThemedReactContext context) {
        return new ReactViewGroup(context);
    }

    @Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.of(HOTSPOT_UPDATE_KEY, CMD_HOTSPOT_UPDATE, "setPressed", CMD_SET_PRESSED);
    }

    @Override
    public void receiveCommand(ReactViewGroup root, int commandId, @Nullable ReadableArray args) {
        switch (commandId) {
        case CMD_HOTSPOT_UPDATE: {
            handleHotspotUpdate(root, args);
            break;
        }
        case CMD_SET_PRESSED: {
            handleSetPressed(root, args);
            break;
        }
        }
    }

    @Override
    public void receiveCommand(ReactViewGroup root, String commandId, @Nullable ReadableArray args) {
        switch (commandId) {
        case HOTSPOT_UPDATE_KEY: {
            handleHotspotUpdate(root, args);
            break;
        }
        case "setPressed": {
            handleSetPressed(root, args);
            break;
        }
        }
    }

    private void handleSetPressed(ReactViewGroup root, @Nullable ReadableArray args) {
        if (args == null || args.size() != 1) {
            throw new JSApplicationIllegalArgumentException("Illegal number of arguments for 'setPressed' command");
        }
        root.setPressed(args.getBoolean(0));
    }

    private void handleHotspotUpdate(ReactViewGroup root, @Nullable ReadableArray args) {
        if (args == null || args.size() != 2) {
            throw new JSApplicationIllegalArgumentException(
                    "Illegal number of arguments for 'updateHotspot' command");
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            float x = PixelUtil.toPixelFromDIP(args.getDouble(0));
            float y = PixelUtil.toPixelFromDIP(args.getDouble(1));
            root.drawableHotspotChanged(x, y);
        }
    }
}