Add a nativeID prop to allow native code to reference react managed views
Reviewed By: sahrens Differential Revision: D4786713 fbshipit-source-id: af9cef0737c010b429d52d00181c00bd81f13f5b
This commit is contained in:
parent
e154117f37
commit
909af08f24
|
@ -698,6 +698,7 @@ const TextInput = React.createClass({
|
|||
accessible={props.accessible}
|
||||
accessibilityLabel={props.accessibilityLabel}
|
||||
accessibilityTraits={props.accessibilityTraits}
|
||||
nativeID={this.props.nativeID}
|
||||
testID={props.testID}>
|
||||
{textContainer}
|
||||
</TouchableWithoutFeedback>
|
||||
|
@ -748,6 +749,7 @@ const TextInput = React.createClass({
|
|||
accessible={this.props.accessible}
|
||||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityComponentType={this.props.accessibilityComponentType}
|
||||
nativeID={this.props.nativeID}
|
||||
testID={this.props.testID}>
|
||||
{textContainer}
|
||||
</TouchableWithoutFeedback>
|
||||
|
|
|
@ -141,6 +141,7 @@ var TouchableBounce = React.createClass({
|
|||
accessibilityLabel={this.props.accessibilityLabel}
|
||||
accessibilityComponentType={this.props.accessibilityComponentType}
|
||||
accessibilityTraits={this.props.accessibilityTraits}
|
||||
nativeID={this.props.nativeID}
|
||||
testID={this.props.testID}
|
||||
hitSlop={this.props.hitSlop}
|
||||
onStartShouldSetResponder={this.touchableHandleStartShouldSetResponder}
|
||||
|
|
|
@ -22,7 +22,6 @@ var TimerMixin = require('react-timer-mixin');
|
|||
var Touchable = require('Touchable');
|
||||
var TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
var View = require('View');
|
||||
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
|
||||
var ensureComponentIsNative = require('ensureComponentIsNative');
|
||||
|
@ -267,6 +266,7 @@ var TouchableHighlight = React.createClass({
|
|||
onResponderMove={this.touchableHandleResponderMove}
|
||||
onResponderRelease={this.touchableHandleResponderRelease}
|
||||
onResponderTerminate={this.touchableHandleResponderTerminate}
|
||||
nativeID={this.props.nativeID}
|
||||
testID={this.props.testID}>
|
||||
{React.cloneElement(
|
||||
React.Children.only(this.props.children),
|
||||
|
|
|
@ -165,7 +165,7 @@ var TouchableOpacity = React.createClass({
|
|||
_opacityFocused: function() {
|
||||
this.setOpacityTo(this.props.focusedOpacity);
|
||||
},
|
||||
|
||||
|
||||
_getChildStyleOpacityWithDefault: function() {
|
||||
var childStyle = flattenStyle(this.props.style) || {};
|
||||
return childStyle.opacity == undefined ? 1 : childStyle.opacity;
|
||||
|
@ -179,6 +179,7 @@ var TouchableOpacity = React.createClass({
|
|||
accessibilityComponentType={this.props.accessibilityComponentType}
|
||||
accessibilityTraits={this.props.accessibilityTraits}
|
||||
style={[this.props.style, {opacity: this.state.anim}]}
|
||||
nativeID={this.props.nativeID}
|
||||
testID={this.props.testID}
|
||||
onLayout={this.props.onLayout}
|
||||
isTVSelectable={true}
|
||||
|
|
|
@ -176,6 +176,8 @@ const TouchableWithoutFeedback = React.createClass({
|
|||
accessibilityComponentType: this.props.accessibilityComponentType,
|
||||
accessibilityTraits: this.props.accessibilityTraits,
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
nativeID: this.props.nativeID,
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
testID: this.props.testID,
|
||||
onLayout: this.props.onLayout,
|
||||
hitSlop: this.props.hitSlop,
|
||||
|
|
|
@ -9,6 +9,7 @@ exports[`TouchableHighlight renders correctly 1`] = `
|
|||
hasTVPreferredFocus={undefined}
|
||||
hitSlop={undefined}
|
||||
isTVSelectable={true}
|
||||
nativeID={undefined}
|
||||
onLayout={undefined}
|
||||
onResponderGrant={[Function]}
|
||||
onResponderMove={[Function]}
|
||||
|
|
|
@ -23,6 +23,7 @@ ReactNativeViewAttributes.UIView = {
|
|||
accessibilityLiveRegion: true,
|
||||
accessibilityTraits: true,
|
||||
importantForAccessibility: true,
|
||||
nativeID: true,
|
||||
testID: true,
|
||||
renderToHardwareTextureAndroid: true,
|
||||
shouldRasterizeIOS: true,
|
||||
|
|
|
@ -173,6 +173,15 @@ module.exports = {
|
|||
*/
|
||||
testID: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Used to locate this view from native classes.
|
||||
*
|
||||
* > This disables the 'layout-only view removal' optimization for this view!
|
||||
*
|
||||
* @platform android
|
||||
*/
|
||||
nativeID: PropTypes.string,
|
||||
|
||||
/**
|
||||
* For most touch interactions, you'll simply want to wrap your component in
|
||||
* `TouchableHighlight` or `TouchableOpacity`. Check out `Touchable.js`,
|
||||
|
|
|
@ -21,9 +21,9 @@ const StyleSheetPropType = require('StyleSheetPropType');
|
|||
const TextStylePropTypes = require('TextStylePropTypes');
|
||||
const Touchable = require('Touchable');
|
||||
|
||||
const processColor = require('processColor');
|
||||
const createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
const mergeFast = require('mergeFast');
|
||||
const processColor = require('processColor');
|
||||
|
||||
const { PropTypes } = React;
|
||||
|
||||
|
@ -177,6 +177,11 @@ const Text = React.createClass({
|
|||
* Used to locate this view in end-to-end tests.
|
||||
*/
|
||||
testID: PropTypes.string,
|
||||
/**
|
||||
* Used to locate this view from native code.
|
||||
* @platform android
|
||||
*/
|
||||
nativeID: PropTypes.string,
|
||||
/**
|
||||
* Specifies whether fonts should scale to respect Text Size accessibility settings. The
|
||||
* default is `true`.
|
||||
|
|
|
@ -13,13 +13,14 @@ deps = [
|
|||
react_native_target("java/com/facebook/react/modules/appstate:appstate"),
|
||||
react_native_target("java/com/facebook/react/modules/core:core"),
|
||||
react_native_target("java/com/facebook/react/modules/datepicker:datepicker"),
|
||||
react_native_target("java/com/facebook/react/modules/deviceinfo:deviceinfo"),
|
||||
react_native_target("java/com/facebook/react/modules/share:share"),
|
||||
react_native_target("java/com/facebook/react/modules/systeminfo:systeminfo"),
|
||||
react_native_target("java/com/facebook/react/modules/deviceinfo:deviceinfo"),
|
||||
react_native_target("java/com/facebook/react/modules/timepicker:timepicker"),
|
||||
react_native_target("java/com/facebook/react/touch:touch"),
|
||||
react_native_target("java/com/facebook/react/uimanager:uimanager"),
|
||||
react_native_target("java/com/facebook/react/uimanager/annotations:annotations"),
|
||||
react_native_target("java/com/facebook/react/uimanager/util:util"),
|
||||
react_native_target("java/com/facebook/react/views/picker:picker"),
|
||||
react_native_target("java/com/facebook/react/views/progressbar:progressbar"),
|
||||
react_native_target("java/com/facebook/react/views/scroll:scroll"),
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (c) 2014-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.tests;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import com.facebook.react.testing.ReactAppInstrumentationTestCase;
|
||||
import com.facebook.react.uimanager.util.ReactFindViewUtil;
|
||||
|
||||
/**
|
||||
* Tests that the 'nativeID' property can be set on various views.
|
||||
* The 'nativeID' property is used to reference react managed views from native code.
|
||||
*/
|
||||
public class NativeIdTestCase extends ReactAppInstrumentationTestCase {
|
||||
|
||||
@Override
|
||||
protected String getReactApplicationKeyUnderTest() {
|
||||
return "NativeIdTestApp";
|
||||
}
|
||||
|
||||
private final List<String> viewTags = Arrays.asList(
|
||||
"Image",
|
||||
"Text",
|
||||
"TouchableBounce",
|
||||
"TouchableHighlight",
|
||||
"TouchableOpacity",
|
||||
"TouchableWithoutFeedback",
|
||||
"TextInput",
|
||||
"View");
|
||||
|
||||
public void testPropertyIsSetForViews() {
|
||||
for (String nativeId : viewTags) {
|
||||
View viewWithTag = ReactFindViewUtil.findViewByNativeId(
|
||||
getActivity().getRootView(),
|
||||
nativeId);
|
||||
assertNotNull(
|
||||
"View with nativeID " + nativeId + " was not found. Check NativeIdTestModule.js.",
|
||||
viewWithTag);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* Copyright (c) 2013-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.
|
||||
*
|
||||
* @providesModule NativeIdTestModule
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Image = require('Image');
|
||||
const React = require('React');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const Text = require('Text');
|
||||
const TextInput = require('TextInput');
|
||||
const TouchableBounce = require('TouchableBounce');
|
||||
const TouchableHighlight = require('TouchableHighlight');
|
||||
const TouchableOpacity = require('TouchableOpacity');
|
||||
const TouchableWithoutFeedback = require('TouchableWithoutFeedback');
|
||||
const View = require('View');
|
||||
|
||||
/**
|
||||
* All the views implemented on Android, each with the nativeID property set.
|
||||
* We test that:
|
||||
* - The app renders fine
|
||||
* - The nativeID property is passed to the native views
|
||||
*/
|
||||
class NativeIdTestApp extends React.Component {
|
||||
render() {
|
||||
const uri = 'data:image/gif;base64,' +
|
||||
'R0lGODdhMAAwAPAAAAAAAP///ywAAAAAMAAwAAAC8IyPqcvt3wCcDkiLc7C0qwyGHhSWpjQu5yqmCYsapy' +
|
||||
'uvUUlvONmOZtfzgFzByTB10QgxOR0TqBQejhRNzOfkVJ+5YiUqrXF5Y5lKh/DeuNcP5yLWGsEbtLiOSpa/' +
|
||||
'TPg7JpJHxyendzWTBfX0cxOnKPjgBzi4diinWGdkF8kjdfnycQZXZeYGejmJlZeGl9i2icVqaNVailT6F5' +
|
||||
'iJ90m6mvuTS4OK05M0vDk0Q4XUtwvKOzrcd3iq9uisF81M1OIcR7lEewwcLp7tuNNkM3uNna3F2JQFo97V' +
|
||||
'riy/Xl4/f1cf5VWzXyym7PHhhx4dbgYKAAA7';
|
||||
return (
|
||||
<View>
|
||||
<Image
|
||||
nativeID="Image"
|
||||
source={{uri: uri}}
|
||||
style={styles.base} />
|
||||
<Text nativeID="Text">text</Text>
|
||||
<TextInput nativeID="TextInput" value="Text input" />
|
||||
<TouchableBounce nativeID="TouchableBounce">
|
||||
<Text>TouchableBounce</Text>
|
||||
</TouchableBounce>
|
||||
<TouchableHighlight nativeID="TouchableHighlight">
|
||||
<Text>TouchableHighlight</Text>
|
||||
</TouchableHighlight>
|
||||
<TouchableOpacity nativeID="TouchableOpacity">
|
||||
<Text>TouchableOpacity</Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableWithoutFeedback nativeID="TouchableWithoutFeedback">
|
||||
<View>
|
||||
<Text>TouchableWithoutFeedback</Text>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
<View nativeID="View" />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
width: 150,
|
||||
height: 50,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
NativeIdTestApp: NativeIdTestApp,
|
||||
};
|
|
@ -69,6 +69,10 @@ var apps = [
|
|||
appKey: 'MultitouchHandlingTestAppModule',
|
||||
component: () => require('MultitouchHandlingTestAppModule')
|
||||
},
|
||||
{
|
||||
appKey: 'NativeIdTestApp',
|
||||
component: () => require('NativeIdTestModule').NativeIdTestApp
|
||||
},
|
||||
{
|
||||
appKey: 'PickerAndroidTestApp',
|
||||
component: () => require('PickerAndroidTestModule').PickerAndroidTestApp,
|
||||
|
|
|
@ -41,6 +41,7 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
|||
* 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();
|
||||
|
@ -92,6 +93,11 @@ public abstract class BaseViewManager<T extends View, C extends LayoutShadowNode
|
|||
view.setTag(testId);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_NATIVE_ID)
|
||||
public void setNativeId(T view, String nativeId) {
|
||||
view.setTag(R.id.view_tag_native_id, nativeId);
|
||||
}
|
||||
|
||||
@ReactProp(name = PROP_ACCESSIBILITY_LABEL)
|
||||
public void setAccessibilityLabel(T view, String accessibilityLabel) {
|
||||
view.setContentDescription(accessibilityLabel);
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
include_defs("//ReactAndroid/DEFS")
|
||||
|
||||
android_library(
|
||||
name = "util",
|
||||
srcs = glob(["*.java"]),
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
deps = [
|
||||
react_native_dep("third-party/java/jsr-305:jsr-305"),
|
||||
react_native_target("res:uimanager"),
|
||||
],
|
||||
)
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2004-present Facebook. All Rights Reserved.
|
||||
|
||||
package com.facebook.react.uimanager.util;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.facebook.react.R;
|
||||
|
||||
/**
|
||||
* Finds views in React Native view hierarchies
|
||||
*/
|
||||
public class ReactFindViewUtil {
|
||||
|
||||
/**
|
||||
* Finds a view that is tagged with {@param nativeId} as its `nativeID` prop
|
||||
*/
|
||||
public static @Nullable View findViewByNativeId(View view, String nativeId) {
|
||||
Object tag = view.getTag(R.id.view_tag_native_id);
|
||||
if (tag instanceof String && tag.equals(nativeId)) {
|
||||
return view;
|
||||
}
|
||||
|
||||
if (view instanceof ViewGroup) {
|
||||
ViewGroup viewGroup = (ViewGroup) view;
|
||||
for (int i = 0; i < viewGroup.getChildCount(); i++) {
|
||||
View v = findViewByNativeId(viewGroup.getChildAt(i), nativeId);
|
||||
if (v != null) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -2,4 +2,7 @@
|
|||
<resources>
|
||||
<!-- tag is used to store the testID tag -->
|
||||
<item type="id" name="react_test_id"/>
|
||||
|
||||
<!-- tag is used to store the nativeID tag -->
|
||||
<item type="id" name="view_tag_native_id"/>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue