前言
最近因为实习接触了React Native,有个要求是RN需要调用Android的原生控件来使用,结果百度一查发现许多都是很早之前的教程了(RN的更新速度比较快),就连RN的中文论坛也不是最新的,我这里就在此记录一下,使用的版本为0.62.2。
封装VideoView
我们以封装Android的VideoView为例子,根据官网教程(不推荐看中文版)需要5个步骤。
- 1.创建一个ViewManager的子类。
- 2.实现
createViewInstance()
方法。 - 3.导出视图的属性设置器:使用@ReactProp(或@ReactPropGroup)注解。
- 4.把这个视图管理类注册到应用程序包的createViewManagers里。
- 5.实现 JavaScript模块。
首先在创建完RN项目,在Android工程下用Android Studio打开来进行开发,新建一个ReactVideoManager类来继承SimpleViewManager。
public class ReactVideoManager extends SimpleViewManager<MyPlay> {
@NonNull
@Override
public String getName() {
return "MyVideoView";
}
@NonNull
@Override
protected MyPlay createViewInstance(@NonNull ThemedReactContext reactContext) {
return new MyPlay(reactContext);
}
}
这里需要重写两个方法,getName()
是返回这个自定义控件的名字来给RN调用,而createViewInstance()
返回的是要封装的控件,这个继承类也需要一个填入一个泛型,就是你要封装的组件,这个MyPlay就是我自己封装的VideoView,可以拿TextView来进行尝试。
接下来就是要导出属性给RN进行调用了,比如说视频播放器的加载地址都是放在RN那里去进行自由的调用,而不是在Android原生里面写死,我们在ReactVideoManager类下面写上以下代码:
/**
* 视频加载url
*
* @param videoView
* @param url
*/
@ReactProp(name = "videoUrl")
public void setUrl(MyPlay videoView, String url) {
videoView.setVideoPath(url);
videoView.start();
}
这个必须返回值为空而且是public权限的,然后添加上ReactProp注解,这个注解里面的name代表着RN可以调用这个名字来进行放入一个String类型的值,注意第一个参数必须也是createViewInstance和泛型创建和相同的参数。然后接下来我们在创建一个ReactVideoPackage类继承ReactPackage来把这个ReactVideoManager注册进去。
public class ReactVideoPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Arrays.asList(
new ReactVideoManager()
);
}
}
createNativeModules()
是注册模块的,createViewManagers()
是注册管理器的,如果想要注册多个模块就在List里面多个添加就行,没有要注册的要写成Collections.emptyList()。接下来就是在RN里面进行封装这个视频组件了,创建一个Video.js,代码如下:
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
requireNativeComponent, View, UIManager,
findNodeHandle,
} from 'react-native';
var RCT_VIDEO_REF = 'MyVideoView';
class VideoView extends Component {
constructor(props) {
super(props);
}
render() {
return <RCTVideoView
{...this.props}
ref={RCT_VIDEO_REF}
/>;
};
}
VideoView.propTypes = {
videoUrl: PropTypes.string,
...View.propTypes,
};
var RCTVideoView = requireNativeComponent('MyVideoView', VideoView);
module.exports = VideoView;
我们在VideoView.propTypes可以将属性videoUrl进行导出给外界调用,类型是string,当然也有其他对应的属性。
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
最后我们在需要调用的地方调用就完毕了。
import React, {Component} from 'react';
import {Text, View, TouchableOpacity, StyleSheet, Image} from 'react-native';
import MyVideView from './Video';
export default class HelloWorldApp extends Component {
render() {
return (
<View style={styles.container}>
<MyVideView
ref={(video) => {
this.video = video;
}}
videoUrl={'https://wyhouse.oss-cn-beijing.aliyuncs.com/test/V00604-153602.mp4'}
style={{height: 200, width: 380, background: 'transparent'}}/>
<View style={{height: 90, flexDirection: 'row', justifyContent: 'flex-start'}}>
<TouchableOpacity style={{marginLeft: 10}} onPress={this.onPressPauseOrStart.bind(this)}>
<Text>测试按钮</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'space-between',
},
});
Android向RN发送事件
封装完成了,通过以上方法,就可以导出各种设置属性来给RN使用,但是我要获取到一些事件该怎么办呢,比如视频长度,进度条之类的东西该怎么办呢。我们可以通过RN的一系列方法来实现,首相让VideoView实现MediaPlayer.OnPreparedListener监听,然后输入以下代码:
@Override
public void onPrepared(MediaPlayer mp) {
int duration = mp.getDuration();
//向js发送事件
WritableMap event = Arguments.createMap();
event.putInt("duration", duration);
ReactContext reactContext = (ReactContext) getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(), //绑定id
"topChange", //注意这里是发给RN的事件名称,对应RN上面的onchange事件
event //要传的值,类似Bundle
);
}
然后在Video.js添加onchange事件
_onChange(event) {
if (!this.props.onPrepared) {
return;
}
this.props.onPrepared(event.nativeEvent.duration);//这里获取到传过来的值,就是对应的key
}
render() {
return <RCTVideoView
{...this.props}
ref={RCT_VIDEO_REF}
onChange={this._onChange.bind(this)}
/>;
};
VideoView.propTypes = {
videoUrl: PropTypes.string,
onPrepared: PropTypes.func,
...View.propTypes,
};
var RCTVideoView = requireNativeComponent('MyVideoView', VideoView, {
nativeOnly: {onChange: true},
});//onChange属性不想暴露给外面的话,就是通过nativeOnly来设置
module.exports = VideoView;
最后进行调用:
//视频时长
_onPrepared(duration) {
console.log('视频时长 JS duration =' + duration);
}
<MyVideView
ref={(video) => {
this.video = video;
}}
videoUrl={'https://wyhouse.oss-cn-beijing.aliyuncs.com/test/V00604-153602.mp4'}
onPrepared={this._onPrepared}
style={{height: 200, width: 380, background: 'transparent'}}/>
接着你就可以在Android studio Logcat中看到RN输出的信息了。
但是如果还有其他的事件怎么办,不可能都只能用onchange事件吧,这里我们就可以自定义事件,只需要重写ReactVideoManager下的getExportedCustomBubblingEventTypeConstants()
方法就好,比如说实时获取进度条。
/**
* 新的自定义事件
*
* @return
*/
@Nullable
@Override
public Map<String, Object> getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.<String, Object>builder().put(
"onProgress", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onProgress"))).build();
}
phasedRegistrationNames是规定的字段不能改,这是按照最新文档写出来的,网上找的都是老版而且不生效(都是踩出来的坑啊)接下来也是一样的,比如我通过Handler和Runnable来配合获取实时进度条:
@Override
public void run() {
int progress = videoView.getCurrentPosition();
WritableMap event = Arguments.createMap();
event.putInt("progress", progress);
dispatchEvent("onProgress", event);
if (videoView.isPlaying()) {
videoSeekBar.setProgress(videoView.getCurrentPosition());
}
mHandler.postDelayed(this, 1000);
}
/**
* 传递RN层事件
*
* @param eventName
* @param eventMap
*/
public void dispatchEvent(String eventName, WritableMap eventMap) {
ReactContext reactContext = (ReactContext) getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),
eventName,
eventMap
);
}
//然后在RN层写入事件
_onProgress(event) {
if (!this.props.onProgress) {
return;
}
this.props.onProgress(event.nativeEvent.progress);
}
render() {
return <RCTVideoView
{...this.props}
ref={RCT_VIDEO_REF}
onChange={this._onChange.bind(this)}
onProgress={this._onProgress.bind(this)}
/>;
};
VideoView.propTypes = {
videoUrl: PropTypes.string,
onPrepared: PropTypes.func,
onProgress: PropTypes.func,
...View.propTypes,
};
//最后调用这个方法就完事了
onProgress={this._onProgress}
//时长进度更新
_onProgress(progress) {
console.log('JS time = ' + progress);
}