使用原生试图,在RN里是必不可少的一部分。如果有人在原生功能都做好了,直接拿来用或者微调一下试图部分就可以用也就不需要再另外造一个,一套轮子了。

步骤

官方文档非常详细了。直接引用如下:

1. 新建一个ViewManager子类
2. 实现createViewInstance方法
3. 通过注解@ReactProp或者@ReactPropGroup暴露视图的属性
4. 在createViewManagers方法里注册这个manager
5. 实现JS模块

和原生模块的开发基本一个流程。
但是,首先要有一个原生视图。是这样的:

public class FillingHoleView extends View {
    // ...

    public float getRadius()
    public void setRadius(float radius)

    public int getStrokeColor()
    public void setStrokeColor(int color)

    onDraw

    onMeasure

    // ...
}

就是一个Android的视图,画一个圈。可以通过setter控制圈的颜色半径

然后就开始按照上面的顺序开始添加代码。

ViewManager子类

public class FillingHoleViewManager extends SimpleViewManager<FillingHoleView> {
    public static final String REACT_CLASS = "FillingHoleView";
    ReactApplicationContext mCallerContext;

    public FillingHoleViewManager(ReactApplicationContext reactContext) {
        this.mCallerContext = reactContext;
    }

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

    @NonNull
    @Override
    protected FillingHoleView createViewInstance(@NonNull ThemedReactContext reactContext) {
        return new FillingHoleView(reactContext);
    }

    @ReactProp(name = "radius", defaultFloat = 50f)
    public void setRadius(FillingHoleView fillingHoleView, int radius) {
        fillingHoleView.setRadius(radius);
    }

    @ReactProp(name = "color", defaultInt = 1)
    public void setStrokeColor(FillingHoleView fillingHoleView, int color) {
        fillingHoleView.setStrokeColor(Color.RED);
    }
}

使用SimpleViewManager有一个好处,它默认提供了背景色、透明度和Flex布局的功能,还有一些accessbility的功能。所以继承了这个view manager就可以使用flex布局了。

实现view manager的时候需要提供模块名称,在JS也是通过模块名称来获取原生模块的。

createViewInstance方法返回出来原生视图。

需要暴露给JS的属性通过@ReactProp或者@ReactGroupProp注解修饰。第一个参数是你的原生视图,第二个是要修改的属性。name是必须的。这些属性里只有值类型的可以提供具体的默认值,引用类型的只能是默认为null。

注册View Manager

如果还没有package类的话需要新建一个:

public class MyAppPackage implements ReactPackage {
    @NonNull
    @Override
    public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new FillingEventHole(reactContext));

        return modules;
    }

    @NonNull
    @Override
    public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(
            new FillingHoleViewManager(reactContext)
        );
    }

继承ReactPackage实现其中的方法。主要有两个,一个是注册原生模块的,一个是注册原生视图的。之后这个package还需要添加到你的application类里。参考这里

在JS里使用你的原生视图

简单版本的可以参考官网:

// FillingHoleView.js

import { requireNativeComponent } from 'react-native';

/**
 * Composes `View`.
 *
 * - radius: number
 * - color: number
 * - width: number
 */
module.exports = requireNativeComponent('FillingHoleView');

更加React的实现方法,可以参考这里:

import React from 'react';
import { requireNativeComponent } from 'react-native';

const FillingNativeView = requireNativeComponent('FillingHoleView');

interface FillingHoleViewProps {
  radius: number;
  color: number; // 1, 2, 3
  width?: number;
}

const FillingHoleView: React.FC<FillingHoleViewProps> = props => {
  return <FillingNativeView {...props} />;
};

export { FillingHoleView };

requireNativeComponent返回的就是可以在其他React组件里使用的组件了。但是缺少的是关于接收各种属性和方法的强制说明。所以,在外满包一层(其实并没有增加实际的视图层级)可以在使用中自动提示可以接受的属性等。

还有事件可以接受,这里可以参考官网。后续补充这部分内容。

布局

本文的例子是画一个背景色和半径可调的圈。

这里主要讨论的是绘图逻辑,通过setter制定半径,然后得到视图的宽高值(padding考虑在内了)。这样,在视图中执行flex布局的时候会有一些奇怪的问题。

首先,React的单位和android的单位不是一回事。比如,本例中,在Android代码中设置宽度为50,在JS设置宽度为100在Android里拿到的是275(在不同分辨率下得到的值也是不一样的)。

所以在开发中,最好统一宽、高入口。比如在本文最好都从JS设置,然后从宽度里计算圆的半径。半径也可以在JS设置,但是在measure的时候宽度已经得到了,半径还没有,或者是例子中的默认值。只要是Android这里拿到的值和JS得到的就是不同的(除非分辨率合适)。

上面说的是单位的问题。现在还要说宽度的问题。

宽度必须要有,否则这个原生视图在flex的布局系统了是一个宽度、高度都是0的存在。如图:
image.png

绿色圆形就被当做宽、高都为0的存在了。具体代码:

<View style={styles.fillingNative}>
  <FillingHoleView radius={50} color={2} />
  <Text>2</Text>
</View>

fillingNative的样式为:

  fillingNative: {
    flex: 1,
    height: 160,
    flexDirection: 'row',
    justifyContent: 'flex-start',
    alignItems: 'center',
    backgroundColor: 'powderblue',
  },

在这个情况下绿色圆已经超出了边界,而且文字也直接显示在上面。反而看下面的黄色圆设置了宽度,只是出现了上面说的单位不同的问题,布局正常。

红色圆球的问题在于设置宽度和发高度的方式和宽高度都必须明确给出的问题。可以看到红色圆和白色视图在纵向布局上有问题。当明确给出高度之后问题解决。如图:
image.png

设置宽高,除了可以明确的把每一个作为一个prop传进去:

<FillingHoleView width={60} height={60} radius={30} color={3} />

也可以使用style的方式,只是在本例需要在props里写出有style这个成员,否则会lint提示error。

<FillingHoleView style={{ width: 100, height: 100 }} radius={100} color={1} />

最后

解决React Native的性能问题,或者复用原生代码的问题都需要用到原生视图的内容,当然也不会少了原生模块。

在开发的过程中除了步骤之外还要注意RN和原生视图的度量单位和宽高对flex布局的影响。

项目代码在这里


小红星闪啊闪
914 声望1.9k 粉丝

时不我待