【稀饭】react native 实战系列教程之自定义原生UI组件

本文详细介绍了如何在React Native中自定义原生UI组件,以VideoView视频播放器为例,讲解了JAVA端组件实现、JS端组件实现、原生层向JS发送消息事件以及JS向原生层发送命令的过程,涵盖了组件开发的全流程。
摘要由CSDN通过智能技术生成

上一节,讲了关于RN的自定义原生模块,本节是关于自定义原生UI组件,学习完本节,你将了解到原生UI组件的开发流程,以及js如何向native发送命令和native如何向js发送事件。

原生UI组件之VideoView视频播放器开发

React Native并没有给我们提供VideoView这个组件,那我们要播放视频的话,有两种方法:一种是借助WebView,一种就是使用原生的播放器。这里我们就介绍下,如何使用原生VideoView,封装成一个组件,提供给JS使用。

实现JAVA端的组件

开发View组件,需要Manager和Package。

新建VideoViewManager类,并继承SimpleViewManager,SimpleViewManager类需要传入一个泛型,该泛型继承android的View,也就是说该泛型是要使用android 平台的哪个View就传入该View,比如,我要使用android的VideoView,这个泛型就传入VideoView。

public class VideoViewManager extends SimpleViewManager<VideoView>{
   

    @Override
    public String getName() {
  //组件名称
        return "VideoView";
    }

    @Override
    protected VideoView createViewInstance(ThemedReactContext reactContext) {
        VideoView video = new VideoView(reactContext);
        return video;
    }
}

getName返回组件名称(可以加前缀RCT),createViewInstance方法返回实例对象,可以在初始化对象时设置一些属性。

接着,我们需要让该组件提供视频的url地址。

我们可以通过@ReactProp(或@ReactPropGroup)注解来导出属性的设置方法。该方法有两个参数,第一个参数是泛型View的实例对象,第二个参数是要设置的属性值。方法的返回值类型必须为void,而且访问控制必须被声明为public。组件的每一个属性的设置都会调用JAVA层被对应ReactProp注解的方法。 如下给VideoView提供source的属性设置:

@ReactProp(name = "source")
public void setSource(RCTVideoView videoView,@Nullable String source){
    if(source != null){
        videoView.setVideoURI(Uri.parse(source));
        videoView.start();
    }
}

@ReactProp注解必须包含一个字符串类型的参数name。这个参数指定了对应属性在JavaScript端的名字。那么现在JS端可以这么设置source属性值

<VideoView source='http://qiubai-video.qiushibaike.com/A14EXG7JQ53PYURP.mp4'/>

但是在设置播放地址的时候,我们可能需要同时设置header(为什么不能像上面source一样来提供一个方法setHeader呢?思考一下),现在改造一下setSource方法

@ReactProp(name = "source")
public void setSource(VideoView videoView,@Nullable ReadableMap source){
    if(source != null){
        if (source.hasKey("url")) {
            String url = source.getString("url");
            FLog.e(VideoViewManager.class,"url = "+url);
            HashMap<String, String> headerMap = new HashMap<>();
            if (source.hasKey("headers")) {
                ReadableMap headers = source.getMap("headers");
                ReadableMapKeySetIterator iter = headers.keySetIterator();
                while (iter.hasNextKey()) {
                    String key = iter.nextKey();
                    String value = headers.getString(key);
                    FLog.e(VideoViewManager.class,key+" = "+value);
                    headerMap.put(key,value);
                }
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                videoView.setVideoURI(Uri.parse(url),headerMap);
            }else{
                try {
                    Method setVideoURIMethod = videoView.getClass().getMethod("setVideoURI", Uri.class, Map.class);
                    setVideoURIMethod.invoke(videoView, Uri.parse(url), headerMap);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            videoView.start();
        }
    }
}

setSource的第二个参数变为ReadableMap,这是一个键值对类型的,用于JS传递参数给JAVA。url必修要有,headers不一定有,现在JS端可能变这样:

<VideoView
    source={
        {
            url:'http://qiubai-video.qiushibaike.com/A14EXG7JQ53PYURP.mp4',
            headers:{
                'refer':'myRefer'
            }
        }
    }
/>

可以发现不同的参数类型,在JS端使用的个中差异。JavaScript所得知的属性类型会由方法的第二个参数的类型来自动决定。支持的类型有:boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap。

当前阶段VideoViewManager类的完整代码如下

public class VideoViewManager extends SimpleViewManager<VideoView>{
   

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

    @Override
    protected VideoView createViewInstance(ThemedReactContext reactContext) {
        VideoView video = new VideoView(reactContext);
        return video;
    }

    @Override
    public void onDropViewInstance(VideoView view) {
  //对象销毁时
        super.onDropViewInstance(view);
         view.stopPlayback();//停止播放
    }

    @ReactProp(name = "source")
    public void setSource(VideoView videoView,@Nullable ReadableMap source){
        if(source != null){
            if (source.hasKey("url")) {
                String url = source.getString("url");
                System.out.println("url = "+url);
                HashMap<String, String> headerMap = new HashMap<>();
                if (source.hasKey("headers")) {
                    ReadableMap headers = source.getMap("headers");
                    ReadableMapKeySetIterator iter = headers.keySetIterator();
                    while (iter.hasNextKey()) {
                        String key = iter.nextKey();
                        headerMap.put(key, headers.getString(key));
                    }
                }
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                    videoView.setVideoURI(Uri.parse(url),headerMap);
                }else{
                    try {
                        Method setVideoURIMethod = videoView.getClass().getMethod("setVideoURI", Uri.class, Map.class);
                        setVideoURIMethod.invoke(videoView, Uri.parse(url), headerMap);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                videoView.start();
            }
        }
    }
}

接着,我们需要和自定义模块一样,创建VideoViewPackage,并注册到ReactNativeHost

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

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

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

MainApplication.java

@Override
protected List<ReactPackage> getPackages() {
    return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new OrientationPackage(),
            new VideoViewPackage()
    );
}

好了,写完java端,现在需要在JS端调用它。

实现JS端的组件

在项目js/component文件夹下新建VideoView.js

import React,{ PropTypes }from 'react';
import {requireNativeComponent,View} from 'react-native';

var VideoView = {
    name:'VideoView',
    propTypes:{
        style: View.propTypes.style,
        source:PropTypes.shape({
            url:PropTypes.string,
            headers:PropTypes.object,
        }),
        ...View.propTypes,//包含默认的View的属性,如果没有这句会报‘has no propType for native prop’错误
    }
};
var RCTVideoView = requireNativeComponent('VideoView',VideoView);
module.exports = RCTVideoView;

首先和自定义模块导入的NativeModules不同,组件使用的模块是requireNativeComponent,接着我们需要给组件定义声明一些属性name(用于调试信息显示)、propTypes。

其中重要的是propTypes,它定义了该组件拥有哪些属性可以使用,对应到原生视图上。由于source是url、headers一组属性值构成的,所以使用PropTypes.shape来定义。

最后不要遗漏了 …View.propTypes 这句,它包含了默认View的属性,如果没有这句就会报错

requireNativeComponent通常接受两个参数,第一个参数是原生视图的名字(JAVA层VideoViewManager$getName的值),而第二个参数是一个描述组件接口的对象。最后通过module.exports导出提供给其他组件使用。

在VideoPlayScene.js中使用

import React, {Component} from 'react';
import {
    View,
    WebView,
    NativeModules,
} from 'react-native';
import VideoView from './component/VideoView';

export default class VideoPlayScene extends Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值