Java tutorial
// 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.uimanager; import android.graphics.Color; import android.os.Build; import android.support.v4.view.ViewCompat; import android.view.View; import android.view.ViewParent; import com.facebook.react.R; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.uimanager.AccessibilityDelegateUtil.AccessibilityRole; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.util.ReactFindViewUtil; import java.util.Locale; /** * Base class that should be suitable for the majority of subclasses of {@link ViewManager}. * It provides support for base view properties such as backgroundColor, opacity, etc. */ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode> extends ViewManager<T, C> { private static final String PROP_BACKGROUND_COLOR = ViewProps.BACKGROUND_COLOR; private static final String PROP_TRANSFORM = "transform"; private static final String PROP_ELEVATION = "elevation"; private static final String PROP_Z_INDEX = "zIndex"; private static final String PROP_RENDER_TO_HARDWARE_TEXTURE = "renderToHardwareTextureAndroid"; private static final String PROP_ACCESSIBILITY_LABEL = "accessibilityLabel"; private static final String PROP_ACCESSIBILITY_COMPONENT_TYPE = "accessibilityComponentType"; private static final String PROP_ACCESSIBILITY_HINT = "accessibilityHint"; private static final String PROP_ACCESSIBILITY_LIVE_REGION = "accessibilityLiveRegion"; private static final String PROP_ACCESSIBILITY_ROLE = "accessibilityRole"; private static final String PROP_ACCESSIBILITY_STATES = "accessibilityStates"; private static final String PROP_IMPORTANT_FOR_ACCESSIBILITY = "importantForAccessibility"; // DEPRECATED private static final String PROP_ROTATION = "rotation"; private static final String PROP_SCALE_X = "scaleX"; private static final String PROP_SCALE_Y = "scaleY"; private static final String PROP_TRANSLATE_X = "translateX"; private static final String PROP_TRANSLATE_Y = "translateY"; private static final int PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX = 2; private static final float CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER = (float) Math.sqrt(5); /** * Used to locate views in end-to-end (UI) tests. */ public static final String PROP_TEST_ID = "testID"; public static final String PROP_NATIVE_ID = "nativeID"; private static MatrixMathHelper.MatrixDecompositionContext sMatrixDecompositionContext = new MatrixMathHelper.MatrixDecompositionContext(); private static double[] sTransformDecompositionArray = new double[16]; @ReactProp(name = PROP_BACKGROUND_COLOR, defaultInt = Color.TRANSPARENT, customType = "Color") public void setBackgroundColor(T view, int backgroundColor) { view.setBackgroundColor(backgroundColor); } @ReactProp(name = PROP_TRANSFORM) public void setTransform(T view, ReadableArray matrix) { if (matrix == null) { resetTransformProperty(view); } else { setTransformProperty(view, matrix); } } @ReactProp(name = ViewProps.OPACITY, defaultFloat = 1.f) public void setOpacity(T view, float opacity) { view.setAlpha(opacity); } @ReactProp(name = PROP_ELEVATION) public void setElevation(T view, float elevation) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { view.setElevation(PixelUtil.toPixelFromDIP(elevation)); } // Do nothing on API < 21 } @ReactProp(name = PROP_Z_INDEX) public void setZIndex(T view, float zIndex) { int integerZIndex = Math.round(zIndex); ViewGroupManager.setViewZIndex(view, integerZIndex); ViewParent parent = view.getParent(); if (parent != null && parent instanceof ReactZIndexedViewGroup) { ((ReactZIndexedViewGroup) parent).updateDrawingOrder(); } } @ReactProp(name = PROP_RENDER_TO_HARDWARE_TEXTURE) public void setRenderToHardwareTexture(T view, boolean useHWTexture) { view.setLayerType(useHWTexture ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE, null); } @ReactProp(name = PROP_TEST_ID) public void setTestId(T view, String testId) { view.setTag(R.id.react_test_id, testId); // temporarily set the tag and keyed tags to avoid end to end test regressions view.setTag(testId); } @ReactProp(name = PROP_NATIVE_ID) public void setNativeId(T view, String nativeId) { view.setTag(R.id.view_tag_native_id, nativeId); ReactFindViewUtil.notifyViewRendered(view); } @ReactProp(name = PROP_ACCESSIBILITY_LABEL) public void setAccessibilityLabel(T view, String accessibilityLabel) { view.setContentDescription(accessibilityLabel); } @ReactProp(name = PROP_ACCESSIBILITY_COMPONENT_TYPE) public void setAccessibilityComponentType(T view, String accessibilityComponentType) { AccessibilityHelper.updateAccessibilityComponentType(view, accessibilityComponentType); } @ReactProp(name = PROP_ACCESSIBILITY_HINT) public void setAccessibilityHint(T view, String accessibilityHint) { view.setTag(R.id.accessibility_hint, accessibilityHint); } @ReactProp(name = PROP_ACCESSIBILITY_ROLE) public void setAccessibilityRole(T view, String accessibilityRole) { if (accessibilityRole == null) { return; } view.setTag(R.id.accessibility_role, AccessibilityRole.fromValue(accessibilityRole)); } @ReactProp(name = PROP_ACCESSIBILITY_STATES) public void setViewStates(T view, ReadableArray accessibilityStates) { view.setSelected(false); view.setEnabled(true); if (accessibilityStates == null) { return; } for (int i = 0; i < accessibilityStates.size(); i++) { String state = accessibilityStates.getString(i); if (state.equals("selected")) { view.setSelected(true); } else if (state.equals("disabled")) { view.setEnabled(false); } } } @ReactProp(name = PROP_IMPORTANT_FOR_ACCESSIBILITY) public void setImportantForAccessibility(T view, String importantForAccessibility) { if (importantForAccessibility == null || importantForAccessibility.equals("auto")) { ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO); } else if (importantForAccessibility.equals("yes")) { ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); } else if (importantForAccessibility.equals("no")) { ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO); } else if (importantForAccessibility.equals("no-hide-descendants")) { ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } } @Deprecated @ReactProp(name = PROP_ROTATION) public void setRotation(T view, float rotation) { view.setRotation(rotation); } @Deprecated @ReactProp(name = PROP_SCALE_X, defaultFloat = 1f) public void setScaleX(T view, float scaleX) { view.setScaleX(scaleX); } @Deprecated @ReactProp(name = PROP_SCALE_Y, defaultFloat = 1f) public void setScaleY(T view, float scaleY) { view.setScaleY(scaleY); } @Deprecated @ReactProp(name = PROP_TRANSLATE_X, defaultFloat = 0f) public void setTranslateX(T view, float translateX) { view.setTranslationX(PixelUtil.toPixelFromDIP(translateX)); } @Deprecated @ReactProp(name = PROP_TRANSLATE_Y, defaultFloat = 0f) public void setTranslateY(T view, float translateY) { view.setTranslationY(PixelUtil.toPixelFromDIP(translateY)); } @ReactProp(name = PROP_ACCESSIBILITY_LIVE_REGION) public void setAccessibilityLiveRegion(T view, String liveRegion) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (liveRegion == null || liveRegion.equals("none")) { view.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_NONE); } else if (liveRegion.equals("polite")) { view.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE); } else if (liveRegion.equals("assertive")) { view.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE); } } } private static void setTransformProperty(View view, ReadableArray transforms) { TransformHelper.processTransform(transforms, sTransformDecompositionArray); MatrixMathHelper.decomposeMatrix(sTransformDecompositionArray, sMatrixDecompositionContext); view.setTranslationX(PixelUtil.toPixelFromDIP((float) sMatrixDecompositionContext.translation[0])); view.setTranslationY(PixelUtil.toPixelFromDIP((float) sMatrixDecompositionContext.translation[1])); view.setRotation((float) sMatrixDecompositionContext.rotationDegrees[2]); view.setRotationX((float) sMatrixDecompositionContext.rotationDegrees[0]); view.setRotationY((float) sMatrixDecompositionContext.rotationDegrees[1]); view.setScaleX((float) sMatrixDecompositionContext.scale[0]); view.setScaleY((float) sMatrixDecompositionContext.scale[1]); double[] perspectiveArray = sMatrixDecompositionContext.perspective; if (perspectiveArray.length > PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX) { float invertedCameraDistance = (float) perspectiveArray[PERSPECTIVE_ARRAY_INVERTED_CAMERA_DISTANCE_INDEX]; if (invertedCameraDistance == 0) { // Default camera distance, before scale multiplier (1280) invertedCameraDistance = 0.00078125f; } float cameraDistance = -1 / invertedCameraDistance; float scale = DisplayMetricsHolder.getScreenDisplayMetrics().density; // The following converts the matrix's perspective to a camera distance // such that the camera perspective looks the same on Android and iOS. // The native Android implementation removed the screen density from the // calculation, so squaring and a normalization value of // sqrt(5) produces an exact replica with iOS. // For more information, see https://github.com/facebook/react-native/pull/18302 float normalizedCameraDistance = scale * scale * cameraDistance * CAMERA_DISTANCE_NORMALIZATION_MULTIPLIER; view.setCameraDistance(normalizedCameraDistance); } } private static void resetTransformProperty(View view) { view.setTranslationX(PixelUtil.toPixelFromDIP(0)); view.setTranslationY(PixelUtil.toPixelFromDIP(0)); view.setRotation(0); view.setRotationX(0); view.setRotationY(0); view.setScaleX(1); view.setScaleY(1); view.setCameraDistance(0); } private void updateViewAccessibility(T view) { AccessibilityDelegateUtil.setDelegate(view); } @Override protected void onAfterUpdateTransaction(T view) { super.onAfterUpdateTransaction(view); updateViewAccessibility(view); } }