react naive封装原生UI(android篇)


文章目录

  • react naive封装原生UI(android篇)
  • 一、基本视图封装
  • 1.1、首先我们需要自定义view
  • 1.2、创建一个ViewManager的子类,实现createViewInstance方法
  • 1.3、把这个视图管理类注册到应用程序包的createViewManagers里
  • 1.4、将程序包注册到rn应用中
  • 1.5、js端调用自定义组件
  • 二、封装props属性
  • 2.1、原生部分
  • 2.2、js部分
  • 三、封装ref调用原生方法
  • 3.1、原生部分
  • 3.2、js部分
  • 四、封装props事件
  • 4.1、原生部分
  • 4.2、js部分


一、基本视图封装

这里支持封装原生view桥接到rn,支持封装viewgroup作为容器接受js组件到rn

1.1、首先我们需要自定义view

这里以普通的弧形切割自定义view作为示例,也可以封装RecycleView、SmartRefleshLayout等

public class RadianView extends FrameLayout {

    private float radiusLength = 1600;
    private float yOffset = 0;          //Y轴偏移量(用于处理margin、padding布局产生的Canvas偏移)

    public RadianView(Context context) {
        this(context, null);
    }

    public RadianView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RadianView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    }

    public void setView(View child) {
        this.addView(child);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        Path path = new Path();
        path.addCircle(canvas.getWidth() / 2, canvas.getHeight() - radiusLength + yOffset, radiusLength, Path.Direction.CW);
        canvas.clipPath(path);
        super.dispatchDraw(canvas);
    }

    public void setRadiusLength(float radiusLength) {
        this.radiusLength = radiusLength;
    }

    public void setyOffset(float yOffset) {
        this.yOffset = yOffset;
    }
}
1.2、创建一个ViewManager的子类,实现createViewInstance方法

这里addView是桥接viewGroup容器的方法,桥接view可以不使用。同时这里的props属性没有需要也可以不添加

public class RadianViewManager extends ViewGroupManager<RadianView> {

    @Override
    public String getName() {
        return "RadianView";
    }

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

    @Override
    public void addView(RadianView parent, View child, int index) {
        parent.setView(child);
    }

    @ReactProp(name = "radius")
    public void setRadius(RadianView radianView, float size) {
        if (radianView != null) {
            radianView.setRadiusLength(size);
        }
    }

    @ReactProp(name = "yOffset")
    public void setYOffset(RadianView radianView, float size) {
        if (radianView != null) {
            radianView.setyOffset(size);
        }
    }
}
1.3、把这个视图管理类注册到应用程序包的createViewManagers里

这部分代码基本都是写死的,按照这个模板写即可

public class RadianViewPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Arrays.<ViewManager>asList(new RadianViewManager());
    }
}
1.4、将程序包注册到rn应用中

这步骤也是固定操作,在rn0.60版本一下肯定要加这一步,rn0.60版本上也可以使用动态注册

  • 1.4.1、注册到MainApplication

android 原生列表页_封装

  • 配置到临时文件PackageList中

可以在项目根目录下创建文件 react-native.config.js, 然后禁止 async-storage autolink:

module.exports = {
  dependencies: {
    '@react-native-community/async-storage': {
      platforms: {
        android: null, // disable Android platform, other platforms will still autolink if provided
      },
    },
  },
};
module.exports = {
  dependency: {
    platforms: {
      ios: { project: "lib/ios/react-native-amap-geolocation.podspec" },
      android: { sourceDir: "lib/android" }
    }
  },
  dependencies: {
    "react-native-amap-geolocation": {
      root: __dirname,
      platforms: {
        ios: { podspecPath: __dirname + "/lib/ios/react-native-amap-geolocation.podspec" },
        android: {
          sourceDir: __dirname + "/lib/android",
          packageImportPath: "import cn.qiuxiang.react.geolocation.AMapGeolocationPackage;",
          packageInstance: "new AMapGeolocationPackage()"
        }
      }
    }
  }
};
1.5、js端调用自定义组件

通过const AudioWaveView = requireNativeComponent(‘AudioWaveView’);找到原生组件即可

二、封装props属性

2.1、原生部分

在原生的RadianViewManager管理类中添加props属性如下即可,全部代码也可参考上面 #1.2

@ReactProp(name = "radius")
    public void setRadius(RadianView radianView, float size) {
        if (radianView != null) {
            radianView.setRadiusLength(size);
        }
    }
2.2、js部分

js端直接使用props属性即可,项目实际使用代码片段如下

import React, {Component} from 'react'
import {
    View,
    Animated,
    ImageBackground, Easing, requireNativeComponent,
} from 'react-native'
import ScreenUtils from "../utils/ScreenUtils";
import PropTypes from 'prop-types'
import bg_top_rectangle from '@assets/res/bg/bg_top_rectangle.png';

const width = ScreenUtils.width;

const RadianView = requireNativeComponent('RadianView');

export default class TopWithBgView extends React.Component {
    //约束
    static propTypes = {
        title: PropTypes.string,
        radian: PropTypes.number,
        radius: PropTypes.number,
    };

    //默认参数
    static defaultProps = {
        title: '',
        radian: 25,                      //ios弧度默认值
        radius: 1600,                    //android半径默认值
    };

    constructor(props) {
        super(props);
        const {defaultHigh = 150} = props;
        this.state = {
            highValue: new Animated.Value(defaultHigh),
        };
        this.scale = 1600 * (width / 375)
    }

    _onLayout = (e) => {
        const {height} = e.nativeEvent.layout;
        this.state.highValue.setValue(height);
    };

    render() {
        const {radian, radius, style,} = this.props;
        return (
            <View style={[{width: ScreenUtils.width, alignItems: "center", overflow: 'hidden'}, style]}>
                <RadianView radian={radian} radius={radius}>
                    <ImageBackground source={bg_top_rectangle} resizeMode={'stretch'}
                                     style={{width: ScreenUtils.width}}>
                        {this.props.children}
                    </ImageBackground>
                </RadianView>
            </View>
        )
    }
}

三、封装ref调用原生方法

通过rn的ref直接调用原生组件方法,当然也可以通过Promise、Callback或者Emit调用,但是这样代码太混乱了。

3.1、原生部分

首先在ViewManager中重写getCommandsMap、receiveCommand方法来定义可以调用的方法命令。如下

public class AudioWaveViewManager extends ViewGroupManager<AudioWaveView> {
    public static final int COMMAND_START = 1;
    public static final int COMMAND_STOP = 2;

    @Override
    public String getName() {
        return "AudioWaveView";
    }

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

    @Nullable
    @Override
    public Map<String, Integer> getCommandsMap() {
        return MapBuilder.<String, Integer>builder()
                .put("start", COMMAND_START)
                .put("stop", COMMAND_STOP)
                .build();
    }

    @Override
    public void receiveCommand(@NonNull AudioWaveView audioWaveView, int commandId, @Nullable ReadableArray args) {
        super.receiveCommand(audioWaveView, commandId, args);
        switch (commandId) {
            case COMMAND_START:
                audioWaveView.start();
                break;
            case COMMAND_STOP:
                audioWaveView.stop();
                break;
        }
    }
}

同时android官方建议receiveCommand的commandId使用字符串

android 原生列表页_android_02

3.2、js部分

然后再js端可访问:
UIManager.[UI组件名].[Constants(静态值)/Commands(命令/方法)]

import React, { Component } from 'react';
import {
    requireNativeComponent,
    View,
    findNodeHandle,
    UIManager
} from 'react-native';
import PropTypes from 'prop-types';

const AudioWaveView = requireNativeComponent('AudioWaveView', AudioWaveComponent);

export default class AudioWaveComponent extends Component {
    static defaultProps = {}

    start = () => {
        UIManager.dispatchViewManagerCommand(
            findNodeHandle(this),
            UIManager.getViewManagerConfig('AudioWaveView').Commands.start,
            null
        )
    }

    stop = () => {
        UIManager.dispatchViewManagerCommand(
            findNodeHandle(this),
            UIManager.getViewManagerConfig('AudioWaveView').Commands.stop,
            null
        )
    }

    render() {
        return <AudioWaveView {...this.props} />
    }
}

AudioWaveComponent.name = 'AudioWaveView'
AudioWaveComponent.propTypes = {
    // start: PropTypes.string,
    ...View.propTypes // 包含默认的View的属性
}

四、封装props事件

android 原生列表页_android 原生列表页_03

如上图onRefresh所示,如果原生自定义view需要回调事件给js端,考虑怎样处理比较合适呢

4.1、原生部分

首先原生这边我们需要在ViewManager重写getExportedCustomDirectEventTypeConstants方法注册桥接到js的事件。

参数释意:

1、”javaEventNmae“是java端发送事件事件名称

2、”registrationName“字符串的值是固定的,不能修改

3、"onChange"字符串是定义在js端的回调方法

如下

@Nullable
    @Override
    public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
        return MapBuilder.<String, Object>builder()
                .put("javaEventName", MapBuilder.of("registrationName", "onChange"))
                .build();
    }

依然是原生这边,我们需要在自定义view中具体调用,如下

public void test(){
        //向js发送事件
        WritableMap event = Arguments.createMap();
        event.putString("test","123");//key用于js中的nativeEvent
        ReactContext reactContext = (ReactContext) getContext();
        reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
                getId(),//native层和js层两个视图会依据getId()而关联在一起
                "javaEventName",//事件名称
                event//事件携带的数据
        );
    }
4.2、js部分

最后在js端使用即可,注意这里需要取nativeEvent数据

<AudioWaveView ref={ref => this.audioWaveRef = ref}
                                   onChange={event=>{
                                       const {nativeEvent}=event;
                                       console.warn('语音收到onChange方法:'+JSON.stringify(nativeEvent))
                                   }}
                        style={{ width: width * 0.9, height: 42 }}
                        source={{ centerSeparate: 2 }} />