Android React Native 已经将几个常用的原生组件进行了封装,比如 ScrollView 和 TextInput,但是并不是所有系统的原始组件都被封装了,因此有的时候我们不得不自己动手封装一下,从而能够使用那些React Native没有为我们封装的原生组件,比如WebView,官方并没有提供Android端的实现,那么我们现在就动手封装一下WebView。
之前写过一篇文章Android React Native使用原生模块,而使用原生UI组件的方法和使用原生模块的方法十分类似。
首先,我需要继承SimpleViewManager这个泛型类,和原生模块类似,需要重写getName()方法,将UI组件名称暴露给javascript层,接着需要重写createViewInstance方法,在里面返回我们需要使用的原生UI组件的实例,这里就是WebView。然后就是暴露一些必要属性给javascript层,为了简单起见,我们这里只暴露两个属性,一个是url,一个是html,一旦javascript层设置了url,就会加载一个网页,而一旦设置了html,则会去加载这段html,而属性的暴露是使用注解,将注解设置在对应的set方法上,之后再set方法中处理UI的更新,比如一旦设置了url,在setUrl里面就要加载网页。最终我们的ViewManager就是这样子的
public class ReactWebViewManager extends SimpleViewManager {
public static final String REACT_CLASS = "RCTWebView";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected WebView createViewInstance(ThemedReactContext reactContext) {
WebView webView= new WebView(reactContext);
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
return webView;
}
@ReactProp(name = "url")
public void setUrl(WebView view,@Nullable String url) {
Log.e("TAG", "setUrl");
view.loadUrl(url);
}
@ReactProp(name = "html")
public void setHtml(WebView view,@Nullable String html) {
Log.e("TAG", "setHtml");
view.loadData(html, "text/html; charset=utf-8", "UTF-8");
}
}
和原生模块一样,原生UI组件也需要进行注册,实现ReactPackage接口,进行WebView的注册。
public class AppReactPackage implements ReactPackage {
@Override
public List createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();;
}
@Override
public List> createJSModules() {
return Collections.emptyList();
}
@Override
public List createViewManagers(ReactApplicationContext reactContext) {
return Arrays.asList(
new ReactWebViewManager());
}
}
将这个ReactPackage添加到ReactInstanceManager实例中去
.addPackage(new AppReactPackage())
然后在javascript层新建一个WebView.js文件。输入下面的内容
'use strict';
var { requireNativeComponent,PropTypes } = require('react-native');
var iface = {
name: 'WebView',
propTypes: {
url: PropTypes.string,
html: PropTypes.string,
},
};
module.exports = requireNativeComponent('RCTWebView', iface);
可以看到,我们只是在里面指定了属性的类型。
到目前为止,你已经可以使用这个WebView组件了。
var WebView=require('./WebView');
render: function() {
return (
"https://www.baidu.com" style={{width:200,height:400}}>
);
},
这里只是简单加载了一下百度首页,有一点需要特别注意,就是组件的宽度高度一定要设置,否则你会看不到这个组件。最终效果如下。
这还只是最基础的将原始UI组件显示出来,而更为常见的却是事件,比如我们需要在javascript层处理这个WebView的滚动事件,这时候又要怎么做呢。
这时候我们就需要继承WebView,重写对应的事件,然后将事件传递给javascript层了
public class RTCWebView extends WebView{
public RTCWebView(Context context) {
super(context);
}
public RTCWebView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public RTCWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
Log.e("TAG","onScrollChanged");
WritableMap event = Arguments.createMap();
event.putInt("ScrollX", l);
event.putInt("ScrollY", t);
ReactContext reactContext = (ReactContext)getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(), "topChange", event);
}
}
我们重写了滚动时回调的onScrollChanged方法,构造了一个WritableMap 对象,将ScrollX和ScrollY传入,然后调用reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(getId(), “topChange”, event);将事件发生到javascript层,注意topChange对应着javascript层的onChange方法,这个映射关系在UIManagerModuleConstants类中。
然后我们需要修改ReactWebViewManager 中的createViewInstance方法,在里面返回我们实现的子类,就像这样子
protected WebView createViewInstance(ThemedReactContext reactContext) {
WebView webView= new RTCWebView(reactContext);
webView.setWebViewClient(new WebViewClient(){
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
return webView;
}
而javascript层也需要进行一定程度的改造,最终的代码如下
'use strict';
var React = require('react-native');
var {
requireNativeComponent,
PropTypes
} = React;
class WebView extends React.Component {
constructor() {
super();
this._onChange = this._onChange.bind(this);
}
_onChange(event: Event) {
if (!this.props.onScrollChange) {
return;
}
this.props.onScrollChange({ScrollX:event.nativeEvent.ScrollX,ScrollY:event.nativeEvent.ScrollY});
}
render() {
return this.props} onChange={this._onChange} />;
}
}
WebView.propTypes = {
url: PropTypes.string,
html: PropTypes.string,
onScrollChange: PropTypes.func,
};
var RCTWebView = requireNativeComponent('RCTWebView', WebView,{
nativeOnly: {onChange: true}
});
module.exports = WebView
不要问我为什么是这样子的,因为官方文档上就是在这么写的,你只需要复制代码,进行修改即可,详见文档Native UI Components
这里需要注意的就是function.bind(this)的语法了,有兴趣的自己去网上搜,这块我也讲不清楚,毕竟没怎么学过javascript和React,怕误人子弟。在onChange函数中,我们进行判断,如果属性onScrollChange没有设置,就直接return,否则就调用设置的onScrollChange属性值(该值是一个函数类型),将Java层传入的两个参数传到该函数中去,{ScrollX:event.nativeEvent.ScrollX,ScrollY:event.nativeEvent.ScrollY}
然后我们来进行调用
var WebView=require('./WebView');
render: function() {
return (
"https://www.baidu.com" style={{width:200,height:400}}>
);
},
onWebViewScroll:function(event){
console.log(event);
},
这时候等待WebView加载处理,你再上下滑动,就会看到控制台的输出,如下
以上就是使用原生UI组件的全部流程,可以看出React Native官方已经为我们做了很好的封装,我们只需要编写少量的代码,就可以使用原生UI组件了。